@prism-llm-labs/mcp-proxy 0.1.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/dist/index.js ADDED
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PrismMcpProxy: () => PrismMcpProxy
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/proxy.ts
28
+ var import_server = require("@modelcontextprotocol/sdk/server/index.js");
29
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
30
+ var import_client = require("@modelcontextprotocol/sdk/client/index.js");
31
+ var import_stdio2 = require("@modelcontextprotocol/sdk/client/stdio.js");
32
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
33
+ var import_mcp_sdk = require("@prism-llm-labs/mcp-sdk");
34
+ var DEFAULT_REDACT_KEYS = ["password", "token", "key", "secret", "api_key", "authorization"];
35
+ function redactObject(obj, keys) {
36
+ if (typeof obj !== "object" || obj === null) return obj;
37
+ if (Array.isArray(obj)) return obj.map((v) => redactObject(v, keys));
38
+ const out = {};
39
+ for (const [k, v] of Object.entries(obj)) {
40
+ out[k] = keys.some((r) => k.toLowerCase().includes(r.toLowerCase())) ? "[REDACTED]" : redactObject(v, keys);
41
+ }
42
+ return out;
43
+ }
44
+ function safeJson(val, redactKeys, maxLen) {
45
+ try {
46
+ const s = JSON.stringify(redactObject(val, redactKeys));
47
+ return s.length <= maxLen ? s : s.slice(0, maxLen) + "\u2026";
48
+ } catch {
49
+ return "[unserializable]";
50
+ }
51
+ }
52
+ function orgFromKey(key) {
53
+ const parts = key.split("_");
54
+ return parts.length >= 4 ? parts[2] ?? "" : "";
55
+ }
56
+ function ts() {
57
+ return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 23);
58
+ }
59
+ var PrismMcpProxy = class {
60
+ constructor(targetCommand, targetArgs, options = {}) {
61
+ this.targetCommand = targetCommand;
62
+ this.targetArgs = targetArgs;
63
+ const key = options.prismKey ?? process.env["PRISM_API_KEY"] ?? "";
64
+ if (!key) {
65
+ process.stderr.write("[prism-proxy] PRISM_API_KEY not set \u2014 telemetry disabled\n");
66
+ }
67
+ this.opts = {
68
+ prismKey: key,
69
+ serverName: options.serverName ?? targetCommand.split(/[\\/]/).pop() ?? "mcp-server",
70
+ project: options.project ?? process.env["PRISM_PROJECT"] ?? "",
71
+ team: options.team ?? process.env["PRISM_TEAM"] ?? "",
72
+ environment: options.environment ?? process.env["PRISM_ENVIRONMENT"] ?? "production",
73
+ sessionId: options.sessionId ?? crypto.randomUUID(),
74
+ sessionBudgetUsd: options.sessionBudgetUsd,
75
+ maxToolCallsPerSession: options.maxToolCallsPerSession,
76
+ captureInputs: options.captureInputs ?? false,
77
+ captureOutputs: options.captureOutputs ?? false,
78
+ costOverrides: options.costOverrides ?? {}
79
+ };
80
+ this.redactKeys = options.redactKeys ?? DEFAULT_REDACT_KEYS;
81
+ this.tracker = new import_mcp_sdk.McpEventTracker(key, this.opts.serverName, options.ingestUrl);
82
+ this.budget = new import_mcp_sdk.SessionBudgetChecker(orgFromKey(key));
83
+ }
84
+ /**
85
+ * Start the proxy. Blocks until the AI client disconnects.
86
+ * Spawn the target server, connect both transports, then wait.
87
+ */
88
+ async run() {
89
+ const targetTransport = new import_stdio2.StdioClientTransport({
90
+ command: this.targetCommand,
91
+ args: this.targetArgs,
92
+ stderr: "pipe"
93
+ // don't let target's stderr pollute our stdout
94
+ });
95
+ const targetClient = new import_client.Client(
96
+ { name: "prism-proxy-client", version: "1.0.0" },
97
+ { capabilities: {} }
98
+ );
99
+ await targetClient.connect(targetTransport);
100
+ const caps = targetClient.getServerCapabilities() ?? {};
101
+ const hasTools = !!caps.tools;
102
+ const hasResources = !!caps.resources;
103
+ const hasPrompts = !!caps.prompts;
104
+ const proxyCaps = {};
105
+ if (hasTools) proxyCaps.tools = {};
106
+ if (hasResources) proxyCaps.resources = { listChanged: false, subscribe: false };
107
+ if (hasPrompts) proxyCaps.prompts = { listChanged: false };
108
+ const proxyServer = new import_server.Server(
109
+ { name: this.opts.serverName, version: "1.0.0" },
110
+ { capabilities: proxyCaps }
111
+ );
112
+ if (hasTools) {
113
+ proxyServer.setRequestHandler(
114
+ import_types.ListToolsRequestSchema,
115
+ () => targetClient.listTools()
116
+ );
117
+ proxyServer.setRequestHandler(
118
+ import_types.CallToolRequestSchema,
119
+ (req) => this._handleToolCall(req.params.name, req.params.arguments ?? {}, targetClient)
120
+ );
121
+ }
122
+ if (hasResources) {
123
+ proxyServer.setRequestHandler(
124
+ import_types.ListResourcesRequestSchema,
125
+ () => targetClient.listResources()
126
+ );
127
+ proxyServer.setRequestHandler(
128
+ import_types.ListResourceTemplatesRequestSchema,
129
+ async () => {
130
+ try {
131
+ return await targetClient.listResourceTemplates();
132
+ } catch {
133
+ return { resourceTemplates: [] };
134
+ }
135
+ }
136
+ );
137
+ proxyServer.setRequestHandler(
138
+ import_types.ReadResourceRequestSchema,
139
+ (req) => this._handleResourceRead(req.params.uri, targetClient)
140
+ );
141
+ }
142
+ if (hasPrompts) {
143
+ proxyServer.setRequestHandler(
144
+ import_types.ListPromptsRequestSchema,
145
+ () => targetClient.listPrompts()
146
+ );
147
+ proxyServer.setRequestHandler(
148
+ import_types.GetPromptRequestSchema,
149
+ (req) => this._handlePromptGet(
150
+ req.params.name,
151
+ req.params.arguments,
152
+ targetClient
153
+ )
154
+ );
155
+ }
156
+ const proxyTransport = new import_stdio.StdioServerTransport();
157
+ await proxyServer.connect(proxyTransport);
158
+ await new Promise((resolve) => {
159
+ proxyTransport.onclose = resolve;
160
+ });
161
+ try {
162
+ await targetClient.close();
163
+ } catch {
164
+ }
165
+ }
166
+ // ── Private: tool call intercept ──────────────────────────────────────────
167
+ async _handleToolCall(name, args, target) {
168
+ await this._checkBudget();
169
+ const estimatedCost = (0, import_mcp_sdk.lookupToolCost)(name, this.opts.costOverrides);
170
+ const start = Date.now();
171
+ const eventTags = {};
172
+ if (this.opts.captureInputs) {
173
+ eventTags["tool_input"] = safeJson(args, this.redactKeys, 1e3);
174
+ }
175
+ let status = "ok";
176
+ let errorMsg = "";
177
+ let result;
178
+ try {
179
+ result = await target.callTool({ name, arguments: args });
180
+ } catch (err) {
181
+ status = "error";
182
+ errorMsg = err instanceof Error ? err.message : String(err);
183
+ this._ship("tool", name, Date.now() - start, estimatedCost, "estimated", status, errorMsg, eventTags);
184
+ throw err;
185
+ }
186
+ const latencyMs = Date.now() - start;
187
+ if (result.isError) {
188
+ status = "error";
189
+ const content = result.content;
190
+ const errBlock = Array.isArray(content) ? content.find((c) => c?.type === "text") : null;
191
+ errorMsg = errBlock?.text ?? "Tool returned error";
192
+ }
193
+ if (this.opts.captureOutputs) {
194
+ eventTags["tool_output"] = safeJson(result.content, this.redactKeys, 1e3);
195
+ }
196
+ this._ship("tool", name, latencyMs, estimatedCost, "estimated", status, errorMsg, eventTags);
197
+ return result;
198
+ }
199
+ // ── Private: resource read intercept ─────────────────────────────────────
200
+ async _handleResourceRead(uri, target) {
201
+ await this._checkBudget();
202
+ const start = Date.now();
203
+ let status = "ok";
204
+ let errorMsg = "";
205
+ let result;
206
+ try {
207
+ result = await target.readResource({ uri });
208
+ } catch (err) {
209
+ status = "error";
210
+ errorMsg = err instanceof Error ? err.message : String(err);
211
+ this._ship("resource", uri, Date.now() - start, 0, "estimated", status, errorMsg, {});
212
+ throw err;
213
+ }
214
+ this._ship("resource", uri, Date.now() - start, 0, "estimated", status, errorMsg, {});
215
+ return result;
216
+ }
217
+ // ── Private: prompt get intercept ────────────────────────────────────────
218
+ async _handlePromptGet(name, args, target) {
219
+ await this._checkBudget();
220
+ const start = Date.now();
221
+ let status = "ok";
222
+ let errorMsg = "";
223
+ let result;
224
+ try {
225
+ result = await target.getPrompt({ name, arguments: args });
226
+ } catch (err) {
227
+ status = "error";
228
+ errorMsg = err instanceof Error ? err.message : String(err);
229
+ this._ship("prompt", name, Date.now() - start, 0, "estimated", status, errorMsg, {});
230
+ throw err;
231
+ }
232
+ this._ship("prompt", name, Date.now() - start, 0, "estimated", status, errorMsg, {});
233
+ return result;
234
+ }
235
+ // ── Private: shared helpers ───────────────────────────────────────────────
236
+ async _checkBudget() {
237
+ await this.budget.checkOrThrow(
238
+ this.opts.sessionId,
239
+ this.opts.sessionBudgetUsd,
240
+ this.opts.maxToolCallsPerSession
241
+ );
242
+ }
243
+ _ship(primitiveType, toolName, latencyMs, costUsd, costStatus, status, errorMessage, tags) {
244
+ this.tracker.capture({
245
+ timestamp: ts(),
246
+ session_id: this.opts.sessionId,
247
+ project_id: this.opts.project,
248
+ team_id: this.opts.team,
249
+ user_id: "",
250
+ environment: this.opts.environment,
251
+ tool_name: toolName,
252
+ downstream_resource: "",
253
+ execution_latency_ms: latencyMs,
254
+ tool_cost_usd: costUsd,
255
+ cost_status: costStatus,
256
+ status,
257
+ error_message: errorMessage,
258
+ llm_request_id: "",
259
+ primitive_type: primitiveType,
260
+ tags
261
+ }).catch(() => {
262
+ });
263
+ }
264
+ };
265
+ // Annotate the CommonJS export names for ESM import in node:
266
+ 0 && (module.exports = {
267
+ PrismMcpProxy
268
+ });
269
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/proxy.ts"],"sourcesContent":["export { PrismMcpProxy } from \"./proxy.js\";\nexport type { ProxyOptions } from \"./types.js\";\n","/**\n * PrismMcpProxy — transparent process-level MCP proxy.\n *\n * Architecture:\n * AI Client (Claude Desktop, Cline, etc.)\n * ↕ MCP / stdio\n * PrismMcpProxy ← this package\n * ↕ MCP / stdio\n * Target MCP server (any server, unmodified)\n *\n * The proxy:\n * 1. Spawns the target as a child process via StdioClientTransport\n * 2. Discovers what capabilities the target declares (tools / resources / prompts)\n * 3. Creates a proxy Server that re-advertises those same capabilities\n * 4. Intercepts every tool call, resource read, and prompt get:\n * – checks session budget + loop limits (pre-call)\n * – forwards the request to the target\n * – measures wall-clock latency\n * – ships a fire-and-forget McpEvent to /api/mcp/ingest\n * – returns the target's response unchanged\n * 5. Connects the proxy Server to the caller via StdioServerTransport\n */\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type ServerCapabilities,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { McpEventTracker, SessionBudgetChecker, lookupToolCost } from \"@prism-llm-labs/mcp-sdk\";\nimport type { McpPrimitiveType } from \"@prism-llm-labs/mcp-sdk\";\nimport type { ProxyOptions } from \"./types.js\";\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst DEFAULT_REDACT_KEYS = [\"password\", \"token\", \"key\", \"secret\", \"api_key\", \"authorization\"];\n\nfunction redactObject(obj: unknown, keys: string[]): unknown {\n if (typeof obj !== \"object\" || obj === null) return obj;\n if (Array.isArray(obj)) return obj.map((v) => redactObject(v, keys));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n out[k] = keys.some((r) => k.toLowerCase().includes(r.toLowerCase()))\n ? \"[REDACTED]\"\n : redactObject(v, keys);\n }\n return out;\n}\n\nfunction safeJson(val: unknown, redactKeys: string[], maxLen: number): string {\n try {\n const s = JSON.stringify(redactObject(val, redactKeys));\n return s.length <= maxLen ? s : s.slice(0, maxLen) + \"…\";\n } catch {\n return \"[unserializable]\";\n }\n}\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nfunction ts(): string {\n return new Date().toISOString().replace(\"T\", \" \").slice(0, 23);\n}\n\n// ── Core class ────────────────────────────────────────────────────────────────\n\nexport class PrismMcpProxy {\n private readonly opts: {\n prismKey: string;\n serverName: string;\n project: string;\n team: string;\n environment: string;\n sessionId: string;\n sessionBudgetUsd?: number;\n maxToolCallsPerSession?: number;\n captureInputs: boolean;\n captureOutputs: boolean;\n costOverrides: Record<string, number>;\n };\n private readonly tracker: McpEventTracker;\n private readonly budget: SessionBudgetChecker;\n private readonly redactKeys: string[];\n\n constructor(\n private readonly targetCommand: string,\n private readonly targetArgs: string[],\n options: ProxyOptions = {},\n ) {\n const key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n if (!key) {\n process.stderr.write(\"[prism-proxy] PRISM_API_KEY not set — telemetry disabled\\n\");\n }\n\n this.opts = {\n prismKey: key,\n serverName: options.serverName ?? targetCommand.split(/[\\\\/]/).pop() ?? \"mcp-server\",\n project: options.project ?? process.env[\"PRISM_PROJECT\"] ?? \"\",\n team: options.team ?? process.env[\"PRISM_TEAM\"] ?? \"\",\n environment: options.environment ?? process.env[\"PRISM_ENVIRONMENT\"] ?? \"production\",\n sessionId: options.sessionId ?? crypto.randomUUID(),\n sessionBudgetUsd: options.sessionBudgetUsd,\n maxToolCallsPerSession: options.maxToolCallsPerSession,\n captureInputs: options.captureInputs ?? false,\n captureOutputs: options.captureOutputs ?? false,\n costOverrides: options.costOverrides ?? {},\n };\n\n this.redactKeys = options.redactKeys ?? DEFAULT_REDACT_KEYS;\n this.tracker = new McpEventTracker(key, this.opts.serverName, options.ingestUrl);\n this.budget = new SessionBudgetChecker(orgFromKey(key));\n }\n\n /**\n * Start the proxy. Blocks until the AI client disconnects.\n * Spawn the target server, connect both transports, then wait.\n */\n async run(): Promise<void> {\n // ── 1. Connect to target server ──────────────────────────────────────────\n const targetTransport = new StdioClientTransport({\n command: this.targetCommand,\n args: this.targetArgs,\n stderr: \"pipe\", // don't let target's stderr pollute our stdout\n });\n\n const targetClient = new Client(\n { name: \"prism-proxy-client\", version: \"1.0.0\" },\n { capabilities: {} },\n );\n\n await targetClient.connect(targetTransport);\n\n const caps: ServerCapabilities = targetClient.getServerCapabilities() ?? {};\n const hasTools = !!caps.tools;\n const hasResources = !!caps.resources;\n const hasPrompts = !!caps.prompts;\n\n // ── 2. Build proxy server ────────────────────────────────────────────────\n const proxyCaps: ServerCapabilities = {};\n if (hasTools) proxyCaps.tools = {};\n if (hasResources) proxyCaps.resources = { listChanged: false, subscribe: false };\n if (hasPrompts) proxyCaps.prompts = { listChanged: false };\n\n const proxyServer = new Server(\n { name: this.opts.serverName, version: \"1.0.0\" },\n { capabilities: proxyCaps },\n );\n\n // ── 3. Register handlers ─────────────────────────────────────────────────\n\n if (hasTools) {\n proxyServer.setRequestHandler(\n ListToolsRequestSchema,\n () => targetClient.listTools(),\n );\n\n proxyServer.setRequestHandler(\n CallToolRequestSchema,\n (req) => this._handleToolCall(req.params.name, req.params.arguments ?? {}, targetClient),\n );\n }\n\n if (hasResources) {\n proxyServer.setRequestHandler(\n ListResourcesRequestSchema,\n () => targetClient.listResources(),\n );\n\n // Resource templates — not all servers support this; ignore if unavailable\n proxyServer.setRequestHandler(\n ListResourceTemplatesRequestSchema,\n async () => {\n try {\n return await targetClient.listResourceTemplates();\n } catch {\n return { resourceTemplates: [] };\n }\n },\n );\n\n proxyServer.setRequestHandler(\n ReadResourceRequestSchema,\n (req) => this._handleResourceRead(req.params.uri, targetClient),\n );\n }\n\n if (hasPrompts) {\n proxyServer.setRequestHandler(\n ListPromptsRequestSchema,\n () => targetClient.listPrompts(),\n );\n\n proxyServer.setRequestHandler(\n GetPromptRequestSchema,\n (req) => this._handlePromptGet(\n req.params.name,\n req.params.arguments as Record<string, string> | undefined,\n targetClient,\n ),\n );\n }\n\n // ── 4. Connect proxy to the AI client via stdio ──────────────────────────\n const proxyTransport = new StdioServerTransport();\n await proxyServer.connect(proxyTransport);\n\n // ── 5. Wait for disconnect ───────────────────────────────────────────────\n await new Promise<void>((resolve) => {\n proxyTransport.onclose = resolve;\n });\n\n try { await targetClient.close(); } catch { /* ignore */ }\n }\n\n // ── Private: tool call intercept ──────────────────────────────────────────\n\n private async _handleToolCall(\n name: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n args: Record<string, any>,\n target: Client,\n ) {\n await this._checkBudget();\n\n const estimatedCost = lookupToolCost(name, this.opts.costOverrides);\n const start = Date.now();\n const eventTags: Record<string, string> = {};\n\n if (this.opts.captureInputs) {\n eventTags[\"tool_input\"] = safeJson(args, this.redactKeys, 1000);\n }\n\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n\n let result: Awaited<ReturnType<Client[\"callTool\"]>>;\n try {\n result = await target.callTool({ name, arguments: args });\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n this._ship(\"tool\", name, Date.now() - start, estimatedCost, \"estimated\", status, errorMsg, eventTags);\n throw err;\n }\n\n const latencyMs = Date.now() - start;\n\n // MCP tool errors are returned in the result (not thrown) with isError=true\n if (result.isError) {\n status = \"error\";\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content = result.content as any[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errBlock = Array.isArray(content) ? content.find((c: any) => c?.type === \"text\") : null;\n errorMsg = (errBlock as { text?: string } | null)?.text ?? \"Tool returned error\";\n }\n\n if (this.opts.captureOutputs) {\n eventTags[\"tool_output\"] = safeJson(result.content, this.redactKeys, 1000);\n }\n\n this._ship(\"tool\", name, latencyMs, estimatedCost, \"estimated\", status, errorMsg, eventTags);\n return result;\n }\n\n // ── Private: resource read intercept ─────────────────────────────────────\n\n private async _handleResourceRead(uri: string, target: Client) {\n await this._checkBudget();\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n\n let result: Awaited<ReturnType<Client[\"readResource\"]>>;\n try {\n result = await target.readResource({ uri });\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n this._ship(\"resource\", uri, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n throw err;\n }\n\n this._ship(\"resource\", uri, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n return result;\n }\n\n // ── Private: prompt get intercept ────────────────────────────────────────\n\n private async _handlePromptGet(\n name: string,\n args: Record<string, string> | undefined,\n target: Client,\n ) {\n await this._checkBudget();\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n\n let result: Awaited<ReturnType<Client[\"getPrompt\"]>>;\n try {\n result = await target.getPrompt({ name, arguments: args });\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n this._ship(\"prompt\", name, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n throw err;\n }\n\n this._ship(\"prompt\", name, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n return result;\n }\n\n // ── Private: shared helpers ───────────────────────────────────────────────\n\n private async _checkBudget(): Promise<void> {\n await this.budget.checkOrThrow(\n this.opts.sessionId,\n this.opts.sessionBudgetUsd,\n this.opts.maxToolCallsPerSession,\n );\n }\n\n private _ship(\n primitiveType: McpPrimitiveType,\n toolName: string,\n latencyMs: number,\n costUsd: number,\n costStatus: \"estimated\" | \"actual\",\n status: \"ok\" | \"error\" | \"timeout\",\n errorMessage: string,\n tags: Record<string, string>,\n ): void {\n this.tracker.capture({\n timestamp: ts(),\n session_id: this.opts.sessionId,\n project_id: this.opts.project,\n team_id: this.opts.team,\n user_id: \"\",\n environment: this.opts.environment,\n tool_name: toolName,\n downstream_resource: \"\",\n execution_latency_ms: latencyMs,\n tool_cost_usd: costUsd,\n cost_status: costStatus,\n status,\n error_message: errorMessage,\n llm_request_id: \"\",\n primitive_type: primitiveType,\n tags,\n }).catch(() => {});\n }\n}\n"],"mappings":"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","names":["import_stdio"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,250 @@
1
+ // src/proxy.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ GetPromptRequestSchema,
9
+ ListPromptsRequestSchema,
10
+ ListResourcesRequestSchema,
11
+ ListResourceTemplatesRequestSchema,
12
+ ListToolsRequestSchema,
13
+ ReadResourceRequestSchema
14
+ } from "@modelcontextprotocol/sdk/types.js";
15
+ import { McpEventTracker, SessionBudgetChecker, lookupToolCost } from "@prism-llm-labs/mcp-sdk";
16
+ var DEFAULT_REDACT_KEYS = ["password", "token", "key", "secret", "api_key", "authorization"];
17
+ function redactObject(obj, keys) {
18
+ if (typeof obj !== "object" || obj === null) return obj;
19
+ if (Array.isArray(obj)) return obj.map((v) => redactObject(v, keys));
20
+ const out = {};
21
+ for (const [k, v] of Object.entries(obj)) {
22
+ out[k] = keys.some((r) => k.toLowerCase().includes(r.toLowerCase())) ? "[REDACTED]" : redactObject(v, keys);
23
+ }
24
+ return out;
25
+ }
26
+ function safeJson(val, redactKeys, maxLen) {
27
+ try {
28
+ const s = JSON.stringify(redactObject(val, redactKeys));
29
+ return s.length <= maxLen ? s : s.slice(0, maxLen) + "\u2026";
30
+ } catch {
31
+ return "[unserializable]";
32
+ }
33
+ }
34
+ function orgFromKey(key) {
35
+ const parts = key.split("_");
36
+ return parts.length >= 4 ? parts[2] ?? "" : "";
37
+ }
38
+ function ts() {
39
+ return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 23);
40
+ }
41
+ var PrismMcpProxy = class {
42
+ constructor(targetCommand, targetArgs, options = {}) {
43
+ this.targetCommand = targetCommand;
44
+ this.targetArgs = targetArgs;
45
+ const key = options.prismKey ?? process.env["PRISM_API_KEY"] ?? "";
46
+ if (!key) {
47
+ process.stderr.write("[prism-proxy] PRISM_API_KEY not set \u2014 telemetry disabled\n");
48
+ }
49
+ this.opts = {
50
+ prismKey: key,
51
+ serverName: options.serverName ?? targetCommand.split(/[\\/]/).pop() ?? "mcp-server",
52
+ project: options.project ?? process.env["PRISM_PROJECT"] ?? "",
53
+ team: options.team ?? process.env["PRISM_TEAM"] ?? "",
54
+ environment: options.environment ?? process.env["PRISM_ENVIRONMENT"] ?? "production",
55
+ sessionId: options.sessionId ?? crypto.randomUUID(),
56
+ sessionBudgetUsd: options.sessionBudgetUsd,
57
+ maxToolCallsPerSession: options.maxToolCallsPerSession,
58
+ captureInputs: options.captureInputs ?? false,
59
+ captureOutputs: options.captureOutputs ?? false,
60
+ costOverrides: options.costOverrides ?? {}
61
+ };
62
+ this.redactKeys = options.redactKeys ?? DEFAULT_REDACT_KEYS;
63
+ this.tracker = new McpEventTracker(key, this.opts.serverName, options.ingestUrl);
64
+ this.budget = new SessionBudgetChecker(orgFromKey(key));
65
+ }
66
+ /**
67
+ * Start the proxy. Blocks until the AI client disconnects.
68
+ * Spawn the target server, connect both transports, then wait.
69
+ */
70
+ async run() {
71
+ const targetTransport = new StdioClientTransport({
72
+ command: this.targetCommand,
73
+ args: this.targetArgs,
74
+ stderr: "pipe"
75
+ // don't let target's stderr pollute our stdout
76
+ });
77
+ const targetClient = new Client(
78
+ { name: "prism-proxy-client", version: "1.0.0" },
79
+ { capabilities: {} }
80
+ );
81
+ await targetClient.connect(targetTransport);
82
+ const caps = targetClient.getServerCapabilities() ?? {};
83
+ const hasTools = !!caps.tools;
84
+ const hasResources = !!caps.resources;
85
+ const hasPrompts = !!caps.prompts;
86
+ const proxyCaps = {};
87
+ if (hasTools) proxyCaps.tools = {};
88
+ if (hasResources) proxyCaps.resources = { listChanged: false, subscribe: false };
89
+ if (hasPrompts) proxyCaps.prompts = { listChanged: false };
90
+ const proxyServer = new Server(
91
+ { name: this.opts.serverName, version: "1.0.0" },
92
+ { capabilities: proxyCaps }
93
+ );
94
+ if (hasTools) {
95
+ proxyServer.setRequestHandler(
96
+ ListToolsRequestSchema,
97
+ () => targetClient.listTools()
98
+ );
99
+ proxyServer.setRequestHandler(
100
+ CallToolRequestSchema,
101
+ (req) => this._handleToolCall(req.params.name, req.params.arguments ?? {}, targetClient)
102
+ );
103
+ }
104
+ if (hasResources) {
105
+ proxyServer.setRequestHandler(
106
+ ListResourcesRequestSchema,
107
+ () => targetClient.listResources()
108
+ );
109
+ proxyServer.setRequestHandler(
110
+ ListResourceTemplatesRequestSchema,
111
+ async () => {
112
+ try {
113
+ return await targetClient.listResourceTemplates();
114
+ } catch {
115
+ return { resourceTemplates: [] };
116
+ }
117
+ }
118
+ );
119
+ proxyServer.setRequestHandler(
120
+ ReadResourceRequestSchema,
121
+ (req) => this._handleResourceRead(req.params.uri, targetClient)
122
+ );
123
+ }
124
+ if (hasPrompts) {
125
+ proxyServer.setRequestHandler(
126
+ ListPromptsRequestSchema,
127
+ () => targetClient.listPrompts()
128
+ );
129
+ proxyServer.setRequestHandler(
130
+ GetPromptRequestSchema,
131
+ (req) => this._handlePromptGet(
132
+ req.params.name,
133
+ req.params.arguments,
134
+ targetClient
135
+ )
136
+ );
137
+ }
138
+ const proxyTransport = new StdioServerTransport();
139
+ await proxyServer.connect(proxyTransport);
140
+ await new Promise((resolve) => {
141
+ proxyTransport.onclose = resolve;
142
+ });
143
+ try {
144
+ await targetClient.close();
145
+ } catch {
146
+ }
147
+ }
148
+ // ── Private: tool call intercept ──────────────────────────────────────────
149
+ async _handleToolCall(name, args, target) {
150
+ await this._checkBudget();
151
+ const estimatedCost = lookupToolCost(name, this.opts.costOverrides);
152
+ const start = Date.now();
153
+ const eventTags = {};
154
+ if (this.opts.captureInputs) {
155
+ eventTags["tool_input"] = safeJson(args, this.redactKeys, 1e3);
156
+ }
157
+ let status = "ok";
158
+ let errorMsg = "";
159
+ let result;
160
+ try {
161
+ result = await target.callTool({ name, arguments: args });
162
+ } catch (err) {
163
+ status = "error";
164
+ errorMsg = err instanceof Error ? err.message : String(err);
165
+ this._ship("tool", name, Date.now() - start, estimatedCost, "estimated", status, errorMsg, eventTags);
166
+ throw err;
167
+ }
168
+ const latencyMs = Date.now() - start;
169
+ if (result.isError) {
170
+ status = "error";
171
+ const content = result.content;
172
+ const errBlock = Array.isArray(content) ? content.find((c) => c?.type === "text") : null;
173
+ errorMsg = errBlock?.text ?? "Tool returned error";
174
+ }
175
+ if (this.opts.captureOutputs) {
176
+ eventTags["tool_output"] = safeJson(result.content, this.redactKeys, 1e3);
177
+ }
178
+ this._ship("tool", name, latencyMs, estimatedCost, "estimated", status, errorMsg, eventTags);
179
+ return result;
180
+ }
181
+ // ── Private: resource read intercept ─────────────────────────────────────
182
+ async _handleResourceRead(uri, target) {
183
+ await this._checkBudget();
184
+ const start = Date.now();
185
+ let status = "ok";
186
+ let errorMsg = "";
187
+ let result;
188
+ try {
189
+ result = await target.readResource({ uri });
190
+ } catch (err) {
191
+ status = "error";
192
+ errorMsg = err instanceof Error ? err.message : String(err);
193
+ this._ship("resource", uri, Date.now() - start, 0, "estimated", status, errorMsg, {});
194
+ throw err;
195
+ }
196
+ this._ship("resource", uri, Date.now() - start, 0, "estimated", status, errorMsg, {});
197
+ return result;
198
+ }
199
+ // ── Private: prompt get intercept ────────────────────────────────────────
200
+ async _handlePromptGet(name, args, target) {
201
+ await this._checkBudget();
202
+ const start = Date.now();
203
+ let status = "ok";
204
+ let errorMsg = "";
205
+ let result;
206
+ try {
207
+ result = await target.getPrompt({ name, arguments: args });
208
+ } catch (err) {
209
+ status = "error";
210
+ errorMsg = err instanceof Error ? err.message : String(err);
211
+ this._ship("prompt", name, Date.now() - start, 0, "estimated", status, errorMsg, {});
212
+ throw err;
213
+ }
214
+ this._ship("prompt", name, Date.now() - start, 0, "estimated", status, errorMsg, {});
215
+ return result;
216
+ }
217
+ // ── Private: shared helpers ───────────────────────────────────────────────
218
+ async _checkBudget() {
219
+ await this.budget.checkOrThrow(
220
+ this.opts.sessionId,
221
+ this.opts.sessionBudgetUsd,
222
+ this.opts.maxToolCallsPerSession
223
+ );
224
+ }
225
+ _ship(primitiveType, toolName, latencyMs, costUsd, costStatus, status, errorMessage, tags) {
226
+ this.tracker.capture({
227
+ timestamp: ts(),
228
+ session_id: this.opts.sessionId,
229
+ project_id: this.opts.project,
230
+ team_id: this.opts.team,
231
+ user_id: "",
232
+ environment: this.opts.environment,
233
+ tool_name: toolName,
234
+ downstream_resource: "",
235
+ execution_latency_ms: latencyMs,
236
+ tool_cost_usd: costUsd,
237
+ cost_status: costStatus,
238
+ status,
239
+ error_message: errorMessage,
240
+ llm_request_id: "",
241
+ primitive_type: primitiveType,
242
+ tags
243
+ }).catch(() => {
244
+ });
245
+ }
246
+ };
247
+ export {
248
+ PrismMcpProxy
249
+ };
250
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["/**\n * PrismMcpProxy — transparent process-level MCP proxy.\n *\n * Architecture:\n * AI Client (Claude Desktop, Cline, etc.)\n * ↕ MCP / stdio\n * PrismMcpProxy ← this package\n * ↕ MCP / stdio\n * Target MCP server (any server, unmodified)\n *\n * The proxy:\n * 1. Spawns the target as a child process via StdioClientTransport\n * 2. Discovers what capabilities the target declares (tools / resources / prompts)\n * 3. Creates a proxy Server that re-advertises those same capabilities\n * 4. Intercepts every tool call, resource read, and prompt get:\n * – checks session budget + loop limits (pre-call)\n * – forwards the request to the target\n * – measures wall-clock latency\n * – ships a fire-and-forget McpEvent to /api/mcp/ingest\n * – returns the target's response unchanged\n * 5. Connects the proxy Server to the caller via StdioServerTransport\n */\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type ServerCapabilities,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { McpEventTracker, SessionBudgetChecker, lookupToolCost } from \"@prism-llm-labs/mcp-sdk\";\nimport type { McpPrimitiveType } from \"@prism-llm-labs/mcp-sdk\";\nimport type { ProxyOptions } from \"./types.js\";\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst DEFAULT_REDACT_KEYS = [\"password\", \"token\", \"key\", \"secret\", \"api_key\", \"authorization\"];\n\nfunction redactObject(obj: unknown, keys: string[]): unknown {\n if (typeof obj !== \"object\" || obj === null) return obj;\n if (Array.isArray(obj)) return obj.map((v) => redactObject(v, keys));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n out[k] = keys.some((r) => k.toLowerCase().includes(r.toLowerCase()))\n ? \"[REDACTED]\"\n : redactObject(v, keys);\n }\n return out;\n}\n\nfunction safeJson(val: unknown, redactKeys: string[], maxLen: number): string {\n try {\n const s = JSON.stringify(redactObject(val, redactKeys));\n return s.length <= maxLen ? s : s.slice(0, maxLen) + \"…\";\n } catch {\n return \"[unserializable]\";\n }\n}\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nfunction ts(): string {\n return new Date().toISOString().replace(\"T\", \" \").slice(0, 23);\n}\n\n// ── Core class ────────────────────────────────────────────────────────────────\n\nexport class PrismMcpProxy {\n private readonly opts: {\n prismKey: string;\n serverName: string;\n project: string;\n team: string;\n environment: string;\n sessionId: string;\n sessionBudgetUsd?: number;\n maxToolCallsPerSession?: number;\n captureInputs: boolean;\n captureOutputs: boolean;\n costOverrides: Record<string, number>;\n };\n private readonly tracker: McpEventTracker;\n private readonly budget: SessionBudgetChecker;\n private readonly redactKeys: string[];\n\n constructor(\n private readonly targetCommand: string,\n private readonly targetArgs: string[],\n options: ProxyOptions = {},\n ) {\n const key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n if (!key) {\n process.stderr.write(\"[prism-proxy] PRISM_API_KEY not set — telemetry disabled\\n\");\n }\n\n this.opts = {\n prismKey: key,\n serverName: options.serverName ?? targetCommand.split(/[\\\\/]/).pop() ?? \"mcp-server\",\n project: options.project ?? process.env[\"PRISM_PROJECT\"] ?? \"\",\n team: options.team ?? process.env[\"PRISM_TEAM\"] ?? \"\",\n environment: options.environment ?? process.env[\"PRISM_ENVIRONMENT\"] ?? \"production\",\n sessionId: options.sessionId ?? crypto.randomUUID(),\n sessionBudgetUsd: options.sessionBudgetUsd,\n maxToolCallsPerSession: options.maxToolCallsPerSession,\n captureInputs: options.captureInputs ?? false,\n captureOutputs: options.captureOutputs ?? false,\n costOverrides: options.costOverrides ?? {},\n };\n\n this.redactKeys = options.redactKeys ?? DEFAULT_REDACT_KEYS;\n this.tracker = new McpEventTracker(key, this.opts.serverName, options.ingestUrl);\n this.budget = new SessionBudgetChecker(orgFromKey(key));\n }\n\n /**\n * Start the proxy. Blocks until the AI client disconnects.\n * Spawn the target server, connect both transports, then wait.\n */\n async run(): Promise<void> {\n // ── 1. Connect to target server ──────────────────────────────────────────\n const targetTransport = new StdioClientTransport({\n command: this.targetCommand,\n args: this.targetArgs,\n stderr: \"pipe\", // don't let target's stderr pollute our stdout\n });\n\n const targetClient = new Client(\n { name: \"prism-proxy-client\", version: \"1.0.0\" },\n { capabilities: {} },\n );\n\n await targetClient.connect(targetTransport);\n\n const caps: ServerCapabilities = targetClient.getServerCapabilities() ?? {};\n const hasTools = !!caps.tools;\n const hasResources = !!caps.resources;\n const hasPrompts = !!caps.prompts;\n\n // ── 2. Build proxy server ────────────────────────────────────────────────\n const proxyCaps: ServerCapabilities = {};\n if (hasTools) proxyCaps.tools = {};\n if (hasResources) proxyCaps.resources = { listChanged: false, subscribe: false };\n if (hasPrompts) proxyCaps.prompts = { listChanged: false };\n\n const proxyServer = new Server(\n { name: this.opts.serverName, version: \"1.0.0\" },\n { capabilities: proxyCaps },\n );\n\n // ── 3. Register handlers ─────────────────────────────────────────────────\n\n if (hasTools) {\n proxyServer.setRequestHandler(\n ListToolsRequestSchema,\n () => targetClient.listTools(),\n );\n\n proxyServer.setRequestHandler(\n CallToolRequestSchema,\n (req) => this._handleToolCall(req.params.name, req.params.arguments ?? {}, targetClient),\n );\n }\n\n if (hasResources) {\n proxyServer.setRequestHandler(\n ListResourcesRequestSchema,\n () => targetClient.listResources(),\n );\n\n // Resource templates — not all servers support this; ignore if unavailable\n proxyServer.setRequestHandler(\n ListResourceTemplatesRequestSchema,\n async () => {\n try {\n return await targetClient.listResourceTemplates();\n } catch {\n return { resourceTemplates: [] };\n }\n },\n );\n\n proxyServer.setRequestHandler(\n ReadResourceRequestSchema,\n (req) => this._handleResourceRead(req.params.uri, targetClient),\n );\n }\n\n if (hasPrompts) {\n proxyServer.setRequestHandler(\n ListPromptsRequestSchema,\n () => targetClient.listPrompts(),\n );\n\n proxyServer.setRequestHandler(\n GetPromptRequestSchema,\n (req) => this._handlePromptGet(\n req.params.name,\n req.params.arguments as Record<string, string> | undefined,\n targetClient,\n ),\n );\n }\n\n // ── 4. Connect proxy to the AI client via stdio ──────────────────────────\n const proxyTransport = new StdioServerTransport();\n await proxyServer.connect(proxyTransport);\n\n // ── 5. Wait for disconnect ───────────────────────────────────────────────\n await new Promise<void>((resolve) => {\n proxyTransport.onclose = resolve;\n });\n\n try { await targetClient.close(); } catch { /* ignore */ }\n }\n\n // ── Private: tool call intercept ──────────────────────────────────────────\n\n private async _handleToolCall(\n name: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n args: Record<string, any>,\n target: Client,\n ) {\n await this._checkBudget();\n\n const estimatedCost = lookupToolCost(name, this.opts.costOverrides);\n const start = Date.now();\n const eventTags: Record<string, string> = {};\n\n if (this.opts.captureInputs) {\n eventTags[\"tool_input\"] = safeJson(args, this.redactKeys, 1000);\n }\n\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n\n let result: Awaited<ReturnType<Client[\"callTool\"]>>;\n try {\n result = await target.callTool({ name, arguments: args });\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n this._ship(\"tool\", name, Date.now() - start, estimatedCost, \"estimated\", status, errorMsg, eventTags);\n throw err;\n }\n\n const latencyMs = Date.now() - start;\n\n // MCP tool errors are returned in the result (not thrown) with isError=true\n if (result.isError) {\n status = \"error\";\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content = result.content as any[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errBlock = Array.isArray(content) ? content.find((c: any) => c?.type === \"text\") : null;\n errorMsg = (errBlock as { text?: string } | null)?.text ?? \"Tool returned error\";\n }\n\n if (this.opts.captureOutputs) {\n eventTags[\"tool_output\"] = safeJson(result.content, this.redactKeys, 1000);\n }\n\n this._ship(\"tool\", name, latencyMs, estimatedCost, \"estimated\", status, errorMsg, eventTags);\n return result;\n }\n\n // ── Private: resource read intercept ─────────────────────────────────────\n\n private async _handleResourceRead(uri: string, target: Client) {\n await this._checkBudget();\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n\n let result: Awaited<ReturnType<Client[\"readResource\"]>>;\n try {\n result = await target.readResource({ uri });\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n this._ship(\"resource\", uri, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n throw err;\n }\n\n this._ship(\"resource\", uri, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n return result;\n }\n\n // ── Private: prompt get intercept ────────────────────────────────────────\n\n private async _handlePromptGet(\n name: string,\n args: Record<string, string> | undefined,\n target: Client,\n ) {\n await this._checkBudget();\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n\n let result: Awaited<ReturnType<Client[\"getPrompt\"]>>;\n try {\n result = await target.getPrompt({ name, arguments: args });\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n this._ship(\"prompt\", name, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n throw err;\n }\n\n this._ship(\"prompt\", name, Date.now() - start, 0, \"estimated\", status, errorMsg, {});\n return result;\n }\n\n // ── Private: shared helpers ───────────────────────────────────────────────\n\n private async _checkBudget(): Promise<void> {\n await this.budget.checkOrThrow(\n this.opts.sessionId,\n this.opts.sessionBudgetUsd,\n this.opts.maxToolCallsPerSession,\n );\n }\n\n private _ship(\n primitiveType: McpPrimitiveType,\n toolName: string,\n latencyMs: number,\n costUsd: number,\n costStatus: \"estimated\" | \"actual\",\n status: \"ok\" | \"error\" | \"timeout\",\n errorMessage: string,\n tags: Record<string, string>,\n ): void {\n this.tracker.capture({\n timestamp: ts(),\n session_id: this.opts.sessionId,\n project_id: this.opts.project,\n team_id: this.opts.team,\n user_id: \"\",\n environment: this.opts.environment,\n tool_name: toolName,\n downstream_resource: \"\",\n execution_latency_ms: latencyMs,\n tool_cost_usd: costUsd,\n cost_status: costStatus,\n status,\n error_message: errorMessage,\n llm_request_id: \"\",\n primitive_type: primitiveType,\n tags,\n }).catch(() => {});\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@prism-llm-labs/mcp-proxy",
3
+ "version": "0.1.0",
4
+ "description": "Transparent MCP proxy for Prism LLM observability — wraps any MCP server with zero code changes",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "mcp-proxy": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "@prism-llm-labs/mcp-sdk": "0.3.0"
20
+ },
21
+ "peerDependencies": {
22
+ "@modelcontextprotocol/sdk": ">=1.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.0.0",
26
+ "@types/node": "^20",
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5",
29
+ "vitest": "^1.0.0"
30
+ },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "dev": "tsup --watch",
34
+ "test": "vitest run",
35
+ "lint": "tsc --noEmit"
36
+ }
37
+ }