@ackuity/inline-proxy 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Ackuity MCP Proxy - A security-focused Model Context Protocol proxy component for multi-agent AI systems.
2
+ This license applies solely to the MCP Proxy component of the Ackuity AI Security Platform. Other components of the platform may be subject to separate license terms.
3
+
4
+ MIT License
5
+
6
+ Copyright (c) 2026 Ackuity, Inc.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # Ackuity Inline MCP Proxy
2
+
3
+ A transparent MCP proxy that intercepts tool calls and evaluates them against the [Ackuity Inline Decision Engine](https://ackuity.ai) before they reach the MCP server. Destructive operations (DELETE, DROP, TRUNCATE) are blocked in real time.
4
+
5
+ ## How It Works
6
+
7
+ ```
8
+ MCP Client (LLM Agent)
9
+
10
+
11
+ ┌──────────────────────┐
12
+ │ Ackuity Inline │
13
+ │ MCP Proxy │──── tools/call ──▶ Inline Decision Engine
14
+ │ │◀─── allow/block ──┘
15
+ └──────────────────────┘
16
+ │ (if allowed)
17
+
18
+ MCP Server (e.g. DAB)
19
+ ```
20
+
21
+ The proxy sits between any MCP client and MCP server. When it sees a `tools/call` request, it sends the tool name and arguments to the Ackuity `protect_tools` API. If the decision is **block**, an error is returned to the client and the call never reaches the MCP server.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install
27
+ npm run build
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ Set these environment variables, or add them to a `.env` file in the project root:
33
+
34
+ | Variable | Required | Description | Default |
35
+ |--------------------|----------|--------------------------------------------------|------------------------------------------------------|
36
+ | `INLINE_API_TOKEN` | Yes | Bearer token for the Inline Decision Engine | — |
37
+ | `INLINE_URL` | Yes | Protect tools endpoint URL | — |
38
+ | `INLINE_AGENT_ID` | Yes | Agent ID registered with the Inline Engine | — |
39
+
40
+ ## Usage
41
+
42
+ The proxy supports three modes depending on how your MCP client and server communicate.
43
+
44
+ ### Mode 1: stdio
45
+
46
+ Client connects via stdin/stdout. Proxy spawns the MCP server as a child process.
47
+
48
+ ```bash
49
+ node dist/main.js --mode stdio --cmd "dab start --config dab-config.json"
50
+ ```
51
+
52
+ Use this when your MCP client (e.g. Claude Desktop, Cursor) expects to launch a command.
53
+
54
+ ### Mode 2: stdio-to-server
55
+
56
+ Client connects via stdin/stdout. Proxy forwards to an already-running HTTP MCP server.
57
+
58
+ ```bash
59
+ node dist/main.js --mode stdio-to-server --target http://localhost:5000/mcp
60
+ ```
61
+
62
+ Use this when your MCP server is already running on HTTP and your client expects stdio.
63
+
64
+ ### Mode 3: server
65
+
66
+ Proxy runs as an HTTP server. Both client and MCP server communicate over HTTP.
67
+
68
+ ```bash
69
+ node dist/main.js --mode server --target http://localhost:5000/mcp --port 7500
70
+ ```
71
+
72
+ Use this when your MCP client connects over HTTP (e.g. a web app or remote agent).
73
+
74
+ ## How to Use with Your Agent
75
+
76
+ ### Option 1: Spawn the proxy from your agent (stdio / stdio-to-server)
77
+
78
+ If your agent uses the MCP SDK, you can launch the proxy as a subprocess using `StdioClientTransport`. The proxy reads Ackuity credentials from environment variables, so pass them via `env`.
79
+
80
+ Replace `<ACKUITY-DIST-REF>` in the examples below depending on how you installed the proxy:
81
+
82
+ | Install method | `command` | `<ACKUITY-DIST-REF>` |
83
+ |---|---|---|
84
+ | Cloned and built locally | `node` | `path/to/ackuity-inline-proxy/dist/main.js` |
85
+ | npx from GitHub | `npx` | `github:Ackuity/ackuity-mcp-proxy` |
86
+ | Published to npm | `npx` | `ackuity-inline-proxy` |
87
+
88
+ **TypeScript / JavaScript**
89
+
90
+ ```typescript
91
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
92
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
93
+
94
+ const transport = new StdioClientTransport({
95
+ command: "npx", // or "node" if running locally
96
+ args: [
97
+ "<ACKUITY-DIST-REF>",
98
+ "--mode", "stdio-to-server",
99
+ "--target", "http://localhost:5000/mcp",
100
+ ],
101
+ env: {
102
+ ...process.env,
103
+ INLINE_API_TOKEN: "your_token_here",
104
+ INLINE_URL: "https://inline.ackuity.ai/api/v1/inline/protect_tools",
105
+ INLINE_AGENT_ID: "your_agent_id_here",
106
+ },
107
+ });
108
+
109
+ const client = new Client({ name: "my-agent", version: "1.0.0" });
110
+ await client.connect(transport);
111
+
112
+ // All tool calls now go through the Ackuity Inline Decision Engine
113
+ const tools = await client.listTools();
114
+ const result = await client.callTool({ name: "read_records", arguments: { entity: "Employees" } });
115
+ ```
116
+
117
+ > **Tip:** Use `--mode stdio` with `--cmd` instead if you want the proxy to also spawn the MCP server:
118
+ > ```typescript
119
+ > args: [
120
+ > "<ACKUITY-DIST-REF>",
121
+ > "--mode", "stdio",
122
+ > "--cmd", "npx @azure/data-api-builder start --config dab-config.json",
123
+ > ],
124
+ > ```
125
+
126
+ **Python**
127
+
128
+ ```python
129
+ from mcp.client.stdio import stdio_client, StdioServerParameters
130
+
131
+ server = StdioServerParameters(
132
+ command="npx", # or "node" if running locally
133
+ args=[
134
+ "<ACKUITY-DIST-REF>",
135
+ "--mode", "stdio-to-server",
136
+ "--target", "http://localhost:5000/mcp",
137
+ ],
138
+ env={
139
+ **os.environ,
140
+ "INLINE_API_TOKEN": "your_token_here",
141
+ "INLINE_URL": "https://inline.ackuity.ai/api/v1/inline/protect_tools",
142
+ "INLINE_AGENT_ID": "your_agent_id_here",
143
+ },
144
+ )
145
+
146
+ async with stdio_client(server) as (read_stream, write_stream):
147
+ async with ClientSession(read_stream, write_stream) as session:
148
+ await session.initialize()
149
+ result = await session.call_tool("read_records", {"entity": "Employees"})
150
+ ```
151
+
152
+ ### Option 2: Run the proxy as a Docker container (server mode)
153
+
154
+ When running in **server mode**, the proxy exposes an HTTP endpoint at `/mcp`. This is ideal for containerized deployments where your agent and MCP server are separate services.
155
+
156
+ **docker-compose.yml**
157
+
158
+ ```yaml
159
+ services:
160
+ mcp-server:
161
+ image: your-mcp-server-image
162
+ ports:
163
+ - "5000:5000"
164
+
165
+ ackuity-proxy:
166
+ image: node:22-slim
167
+ working_dir: /app
168
+ command: npx <ACKUITY-DIST-REF> --mode server --target http://mcp-server:5000/mcp --port 7500
169
+ ports:
170
+ - "7500:7500"
171
+ environment:
172
+ INLINE_API_TOKEN: ${INLINE_API_TOKEN}
173
+ INLINE_URL: ${INLINE_URL}
174
+ INLINE_AGENT_ID: ${INLINE_AGENT_ID}
175
+ depends_on:
176
+ - mcp-server
177
+
178
+ agent:
179
+ image: your-agent-image
180
+ environment:
181
+ MCP_ENDPOINT: http://ackuity-proxy:7500/mcp
182
+ depends_on:
183
+ - ackuity-proxy
184
+ ```
185
+
186
+ Your agent connects to `http://ackuity-proxy:7500/mcp` instead of directly to the MCP server. All tool calls are evaluated by the Ackuity Inline Decision Engine before reaching the MCP server.
187
+
188
+ **With a `.env` file**
189
+
190
+ ```env
191
+ INLINE_API_TOKEN=your_token_here
192
+ INLINE_URL=https://inline.ackuity.ai/api/v1/inline/protect_tools
193
+ INLINE_AGENT_ID=your_agent_id_here
194
+ ```
195
+
196
+ Then run:
197
+
198
+ ```bash
199
+ docker compose up
200
+ ```
201
+
202
+ ## Fail-Closed Behavior
203
+
204
+ If the Inline Decision Engine is unreachable or returns an error (e.g. invalid token), the proxy **blocks the tool call** rather than forwarding it. This prevents destructive operations from slipping through during outages.
205
+
206
+ ## Project Structure
207
+
208
+ ```
209
+ src/
210
+ ├── main.ts Entry point, arg parsing, mode selection
211
+ ├── common.ts Shared utilities (logging, arg parser, constants)
212
+ ├── inline_engine.ts Protect Tools API types, response manager, API call
213
+ └── mode.ts ProxyMode interface and implementations (Stdio, StdioToServer, Server)
214
+ ```
215
+
216
+ ## Logging
217
+
218
+ All MCP traffic is logged to `proxy_traffic.txt` in the working directory. Status messages are written to stderr with a `[PROXY]` prefix.
package/dist/common.js ADDED
@@ -0,0 +1,51 @@
1
+ import * as fs from "node:fs";
2
+ // --- Arg parsing ---
3
+ export class ProxyProcessArguments {
4
+ params = {};
5
+ constructor(args) {
6
+ args.forEach((arg) => {
7
+ const pArg = "--".concat(arg);
8
+ const index = process.argv.indexOf(pArg);
9
+ this.params[arg] = index === -1 ? undefined : process.argv[index + 1];
10
+ });
11
+ }
12
+ mode() {
13
+ return this.params.mode;
14
+ }
15
+ target() {
16
+ return this.params.target;
17
+ }
18
+ cmd() {
19
+ return this.params.cmd;
20
+ }
21
+ port() {
22
+ return this.params.port;
23
+ }
24
+ toolName() {
25
+ return this.params["tool-name"];
26
+ }
27
+ }
28
+ // --- Traffic logging ---
29
+ const logFile = fs.createWriteStream("proxy_traffic.txt", { flags: "a" });
30
+ export function logTraffic(direction, msg) {
31
+ const timestamp = new Date().toISOString();
32
+ const pretty = JSON.stringify(msg, null, 2);
33
+ logFile.write(`\n--- ${direction} ${timestamp} ---\n${pretty}\n`);
34
+ }
35
+ export function logToStderr(text) {
36
+ process.stderr.write(`[PROXY] ${text}\n`);
37
+ }
38
+ // --- Error formatting ---
39
+ export function formatError(err) {
40
+ if (!(err instanceof Error))
41
+ return String(err);
42
+ const cause = err.cause;
43
+ if (cause instanceof AggregateError) {
44
+ const reasons = cause.errors.map((e) => e.message).join("; ");
45
+ return `${err.message} (${reasons})`;
46
+ }
47
+ if (cause instanceof Error) {
48
+ return `${err.message} (${cause.message})`;
49
+ }
50
+ return cause ? `${err.message} (${cause})` : err.message;
51
+ }
package/dist/config.js ADDED
@@ -0,0 +1,7 @@
1
+ import { ServerMode, StdioMode, StdioToServerMode } from "./mode.js";
2
+ export const DEFAULT_SERVER_PORT = "7500";
3
+ export const modes = new Map([
4
+ ["stdio", StdioMode],
5
+ ["stdio-to-server", StdioToServerMode],
6
+ ["server", ServerMode],
7
+ ]);
@@ -0,0 +1,69 @@
1
+ // --- Protect Tools API types ---
2
+ export class ProtectToolsResponseManager {
3
+ request_id;
4
+ session_id;
5
+ request_time;
6
+ response_time;
7
+ status;
8
+ request_evaluation_ms;
9
+ result;
10
+ error;
11
+ error_description;
12
+ constructor(response) {
13
+ Object.assign(this, response);
14
+ }
15
+ isBlocked() {
16
+ return this.result?.decision === "block";
17
+ }
18
+ isAllowed() {
19
+ return this.result?.decision === "allow";
20
+ }
21
+ isError() {
22
+ return this.status === "error";
23
+ }
24
+ getReasons() {
25
+ return this.result?.reason ?? [];
26
+ }
27
+ }
28
+ // --- Protect Tools API call ---
29
+ if (!process.env.INLINE_URL) {
30
+ throw new Error("Missing required environment variable: INLINE_URL");
31
+ }
32
+ if (!process.env.INLINE_API_TOKEN) {
33
+ throw new Error("Missing required environment variable: INLINE_API_TOKEN");
34
+ }
35
+ if (!process.env.INLINE_AGENT_ID) {
36
+ throw new Error("Missing required environment variable: INLINE_AGENT_ID");
37
+ }
38
+ const PROTECT_TOOLS_URL = process.env.INLINE_URL;
39
+ const INLINE_API_TOKEN = process.env.INLINE_API_TOKEN;
40
+ const INLINE_AGENT_ID = process.env.INLINE_AGENT_ID;
41
+ let TOOL_NAME = "unknown";
42
+ export function setToolName(name) {
43
+ TOOL_NAME = name;
44
+ }
45
+ export async function callProtectTools(mcpMethod, actionName, toolArguments) {
46
+ const request = {
47
+ messages: [],
48
+ agent: { agent_name: TOOL_NAME, agent_id: INLINE_AGENT_ID },
49
+ tool_calls: [
50
+ {
51
+ tool_name: TOOL_NAME,
52
+ tool_type: "mcp",
53
+ method: mcpMethod,
54
+ action: actionName,
55
+ arguments: toolArguments,
56
+ },
57
+ ],
58
+ };
59
+ const res = await fetch(PROTECT_TOOLS_URL, {
60
+ method: "POST",
61
+ headers: {
62
+ Authorization: `Bearer ${INLINE_API_TOKEN}`,
63
+ "Content-Type": "application/json",
64
+ },
65
+ body: JSON.stringify(request),
66
+ });
67
+ const data = await res.json();
68
+ return new ProtectToolsResponseManager(data);
69
+ }
package/dist/main.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { logToStderr, ProxyProcessArguments } from "./common.js";
4
+ import { modes } from "./config.js";
5
+ import { setToolName } from "./inline_engine.js";
6
+ const programArgs = new ProxyProcessArguments([
7
+ "mode",
8
+ "target",
9
+ "cmd",
10
+ "port",
11
+ "tool-name",
12
+ ]);
13
+ const modeName = programArgs.mode();
14
+ const toolName = programArgs.toolName();
15
+ if (toolName) {
16
+ setToolName(toolName);
17
+ }
18
+ logToStderr(`mode: ${modeName}`);
19
+ const ModeClass = modeName ? modes.get(modeName) : undefined;
20
+ if (!ModeClass) {
21
+ logToStderr(`ERROR: --mode required (${[...modes.keys()].join(" | ")})`);
22
+ process.exit(1);
23
+ }
24
+ const mode = new ModeClass();
25
+ mode.init(programArgs);
26
+ mode.start();
package/dist/mode.js ADDED
@@ -0,0 +1,208 @@
1
+ import * as crypto from "node:crypto";
2
+ import { createServer, } from "node:http";
3
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
+ import { formatError, logToStderr, logTraffic, } from "./common.js";
8
+ import { DEFAULT_SERVER_PORT } from "./config.js";
9
+ import { callProtectTools } from "./inline_engine.js";
10
+ // --- Interception logic ---
11
+ async function inspectRequest(msg) {
12
+ logTraffic("to", msg);
13
+ if ("method" in msg) {
14
+ logToStderr(`to ${msg.method}`);
15
+ if (msg.method === "tools/call" && msg.params) {
16
+ const params = msg.params;
17
+ logToStderr(` tool: ${params.name}, args: ${JSON.stringify(params.arguments)}`);
18
+ try {
19
+ const decision = await callProtectTools(msg.method, params.name ?? "unknown", params.arguments);
20
+ if (decision.isError()) {
21
+ logToStderr(` protect_tools error: ${decision.error} - ${decision.error_description}`);
22
+ return null;
23
+ }
24
+ else if (decision.isBlocked()) {
25
+ logToStderr(` BLOCKED: ${decision.getReasons().join(", ")}`);
26
+ return null;
27
+ }
28
+ }
29
+ catch (err) {
30
+ logToStderr(` protect_tools call failed: ${formatError(err)}`);
31
+ return null;
32
+ }
33
+ }
34
+ }
35
+ return msg;
36
+ }
37
+ function inspectResponse(msg) {
38
+ logTraffic("from", msg);
39
+ const rpcMsg = msg;
40
+ if ("result" in msg) {
41
+ logToStderr(`from response id:${rpcMsg.id}`);
42
+ }
43
+ else if ("error" in msg) {
44
+ logToStderr(`from error id:${rpcMsg.id}: ${rpcMsg.error?.message}`);
45
+ }
46
+ return msg;
47
+ }
48
+ // --- Wire up two transports ---
49
+ async function startProxy(agentTransport, serverTransport, onClose) {
50
+ let closing = false;
51
+ const closeSession = (reason) => {
52
+ if (closing)
53
+ return;
54
+ closing = true;
55
+ logToStderr(reason);
56
+ agentTransport.close();
57
+ serverTransport.close();
58
+ onClose?.();
59
+ };
60
+ agentTransport.onmessage = async (msg) => {
61
+ const allowed = await inspectRequest(msg);
62
+ if (allowed) {
63
+ await serverTransport.send(allowed);
64
+ }
65
+ else {
66
+ if ("id" in msg && msg.id !== undefined) {
67
+ await agentTransport.send({
68
+ jsonrpc: "2.0",
69
+ id: msg.id,
70
+ error: { code: -32001, message: "Blocked by security policy" },
71
+ });
72
+ }
73
+ }
74
+ };
75
+ serverTransport.onmessage = async (msg) => {
76
+ const allowed = inspectResponse(msg);
77
+ if (allowed) {
78
+ await agentTransport.send(allowed);
79
+ }
80
+ };
81
+ agentTransport.onerror = (err) => logToStderr(`agent error: ${err}`);
82
+ serverTransport.onerror = (err) => logToStderr(`server error: ${err}`);
83
+ agentTransport.onclose = () => closeSession("agent closed");
84
+ serverTransport.onclose = () => closeSession("server closed");
85
+ await serverTransport.start();
86
+ logToStderr("server transport started");
87
+ await agentTransport.start();
88
+ logToStderr("agent transport started");
89
+ }
90
+ // --- Mode: stdio ---
91
+ export class StdioMode {
92
+ cmd;
93
+ init(args) {
94
+ const cmd = args.cmd();
95
+ if (!cmd) {
96
+ logToStderr("ERROR: --cmd required for stdio mode");
97
+ process.exit(1);
98
+ }
99
+ this.cmd = cmd;
100
+ logToStderr(`cmd: ${this.cmd}`);
101
+ }
102
+ start() {
103
+ const parts = this.cmd.split(" ");
104
+ const agentTransport = new StdioServerTransport();
105
+ const serverTransport = new StdioClientTransport({
106
+ command: parts[0],
107
+ args: parts.slice(1),
108
+ });
109
+ startProxy(agentTransport, serverTransport, () => process.exit(0));
110
+ }
111
+ }
112
+ // --- Mode: stdio-to-server ---
113
+ export class StdioToServerMode {
114
+ target;
115
+ init(args) {
116
+ const target = args.target();
117
+ if (!target) {
118
+ logToStderr("ERROR: --target required for stdio-to-server mode");
119
+ process.exit(1);
120
+ }
121
+ this.target = target;
122
+ logToStderr(`target: ${this.target}`);
123
+ }
124
+ start() {
125
+ const agentTransport = new StdioServerTransport();
126
+ const serverTransport = new StreamableHTTPClientTransport(new URL(this.target));
127
+ startProxy(agentTransport, serverTransport, () => process.exit(0));
128
+ }
129
+ }
130
+ // --- Mode: server ---
131
+ export class ServerMode {
132
+ target;
133
+ port;
134
+ init(args) {
135
+ const target = args.target();
136
+ if (!target) {
137
+ logToStderr("ERROR: --target required for server mode");
138
+ process.exit(1);
139
+ }
140
+ this.target = target;
141
+ this.port = args.port() ?? DEFAULT_SERVER_PORT;
142
+ logToStderr(`starting HTTP server on port ${this.port} to ${this.target}`);
143
+ }
144
+ start() {
145
+ const target = this.target;
146
+ const sessions = new Map();
147
+ const httpServer = createServer(async (req, res) => {
148
+ if (req.url !== "/mcp") {
149
+ res.writeHead(404);
150
+ res.end("Not found");
151
+ return;
152
+ }
153
+ const sessionId = req.headers["mcp-session-id"];
154
+ if (sessionId && sessions.has(sessionId)) {
155
+ const session = sessions.get(sessionId);
156
+ if (!session)
157
+ return;
158
+ const { agent } = session;
159
+ await agent.handleRequest(req, res);
160
+ }
161
+ else if (req.method === "POST") {
162
+ const agentTransport = new StreamableHTTPServerTransport({
163
+ sessionIdGenerator: () => crypto.randomUUID(),
164
+ });
165
+ const forwardHeaders = {};
166
+ const skipHeaders = [
167
+ "host",
168
+ "content-length",
169
+ "transfer-encoding",
170
+ "connection",
171
+ ];
172
+ for (const [key, val] of Object.entries(req.headers)) {
173
+ if (val && !skipHeaders.includes(key)) {
174
+ forwardHeaders[key] = Array.isArray(val) ? val.join(", ") : val;
175
+ }
176
+ }
177
+ const serverTransport = new StreamableHTTPClientTransport(new URL(target), {
178
+ requestInit: {
179
+ headers: forwardHeaders,
180
+ },
181
+ });
182
+ await startProxy(agentTransport, serverTransport, () => {
183
+ const sid = agentTransport.sessionId;
184
+ if (sid) {
185
+ sessions.delete(sid);
186
+ logToStderr(`session cleaned up: ${sid}`);
187
+ }
188
+ });
189
+ await agentTransport.handleRequest(req, res);
190
+ const newSessionId = agentTransport.sessionId;
191
+ if (newSessionId) {
192
+ sessions.set(newSessionId, {
193
+ agent: agentTransport,
194
+ server: serverTransport,
195
+ });
196
+ logToStderr(`new session: ${newSessionId}`);
197
+ }
198
+ }
199
+ else {
200
+ res.writeHead(405);
201
+ res.end("Method not allowed");
202
+ }
203
+ });
204
+ httpServer.listen(Number(this.port), () => {
205
+ logToStderr(`listening on http://localhost:${this.port}/mcp`);
206
+ });
207
+ }
208
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@ackuity/inline-proxy",
3
+ "version": "0.7.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "dist/main.js",
7
+ "bin": {
8
+ "ackuity-mcp-proxy": "dist/main.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc && npx biome check src/",
12
+ "prepare": "tsc",
13
+ "start": "node dist/main.js",
14
+ "lint": "npx biome check src/",
15
+ "lint:fix": "npx biome check --write src/"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.28.0",
19
+ "dotenv": "^16.4.7"
20
+ },
21
+ "devDependencies": {
22
+ "@biomejs/biome": "^2.4.9",
23
+ "@types/node": "^25.4.0",
24
+ "typescript": "^5.9.3"
25
+ }
26
+ }