@prism-llm-labs/mcp-sdk 0.2.0 → 0.3.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.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +154 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +154 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/pricing.ts +170 -21
- package/src/prism-mcp.ts +27 -2
package/dist/index.d.mts
CHANGED
|
@@ -90,6 +90,8 @@ declare class PrismToolCallLimitError extends Error {
|
|
|
90
90
|
declare class WrapContext {
|
|
91
91
|
/** @internal - read by _wrap() after fn() completes */
|
|
92
92
|
_actualCostUsd: number | null;
|
|
93
|
+
/** @internal */
|
|
94
|
+
_downstreamResource: string | null;
|
|
93
95
|
/**
|
|
94
96
|
* Override the catalog-estimated tool cost with the real billing figure.
|
|
95
97
|
* Call this inside your tool handler when you have access to actual cost data
|
|
@@ -104,6 +106,26 @@ declare class WrapContext {
|
|
|
104
106
|
* });
|
|
105
107
|
*/
|
|
106
108
|
reportActualCost(usd: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* Set the downstream resource identifier for this call.
|
|
111
|
+
* Use this when the resource name is resolved inside the handler (e.g. after
|
|
112
|
+
* looking up which Pinecone index to query).
|
|
113
|
+
*
|
|
114
|
+
* Convention:
|
|
115
|
+
* - Pinecone: "pinecone:<index-name>" e.g. "pinecone:product-embeddings"
|
|
116
|
+
* - Qdrant: "qdrant:<collection>" e.g. "qdrant:support-docs"
|
|
117
|
+
* - Generic: just the provider name e.g. "weaviate", "redis"
|
|
118
|
+
*
|
|
119
|
+
* Overrides the `downstreamResource` option passed to wrapToolCall().
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* await prismMcp.wrapToolCall("vector_search", async (ctx) => {
|
|
123
|
+
* const index = await resolveIndex(query);
|
|
124
|
+
* ctx.setDownstreamResource(`pinecone:${index}`);
|
|
125
|
+
* return pinecone.query({ index, vector });
|
|
126
|
+
* });
|
|
127
|
+
*/
|
|
128
|
+
setDownstreamResource(resource: string): void;
|
|
107
129
|
}
|
|
108
130
|
declare class PrismMCP {
|
|
109
131
|
private readonly tracker;
|
|
@@ -300,6 +322,19 @@ declare class SessionBudgetChecker {
|
|
|
300
322
|
/**
|
|
301
323
|
* Built-in tool cost estimates (USD per call).
|
|
302
324
|
* Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.
|
|
325
|
+
*
|
|
326
|
+
* Pricing basis:
|
|
327
|
+
* - Filesystem / Git: trivial I/O; estimated at $0.01/1K ops
|
|
328
|
+
* - GitHub API: free tier 5 000 req/hr; charged via Actions minutes at ~$0.0003/req
|
|
329
|
+
* - Slack / email: negligible API cost; estimated at overhead cost
|
|
330
|
+
* - Databases: typical query cost on managed DB (Neon/RDS) per operation
|
|
331
|
+
* - HTTP / browser: outbound network + optional headless browser compute
|
|
332
|
+
* - AWS: official published rates (Lambda $0.0000002/req, S3 $0.004/10K GET)
|
|
333
|
+
* - Search APIs: published per-query pricing
|
|
334
|
+
* - Code execution: E2B/code_interpreter published rates
|
|
335
|
+
*
|
|
336
|
+
* Prefix wildcards ("github_*") match any tool whose name starts with that prefix.
|
|
337
|
+
* lookupToolCost() tries exact → longest-prefix → 0.
|
|
303
338
|
*/
|
|
304
339
|
/**
|
|
305
340
|
* Look up estimated cost for a tool name.
|
package/dist/index.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ declare class PrismToolCallLimitError extends Error {
|
|
|
90
90
|
declare class WrapContext {
|
|
91
91
|
/** @internal - read by _wrap() after fn() completes */
|
|
92
92
|
_actualCostUsd: number | null;
|
|
93
|
+
/** @internal */
|
|
94
|
+
_downstreamResource: string | null;
|
|
93
95
|
/**
|
|
94
96
|
* Override the catalog-estimated tool cost with the real billing figure.
|
|
95
97
|
* Call this inside your tool handler when you have access to actual cost data
|
|
@@ -104,6 +106,26 @@ declare class WrapContext {
|
|
|
104
106
|
* });
|
|
105
107
|
*/
|
|
106
108
|
reportActualCost(usd: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* Set the downstream resource identifier for this call.
|
|
111
|
+
* Use this when the resource name is resolved inside the handler (e.g. after
|
|
112
|
+
* looking up which Pinecone index to query).
|
|
113
|
+
*
|
|
114
|
+
* Convention:
|
|
115
|
+
* - Pinecone: "pinecone:<index-name>" e.g. "pinecone:product-embeddings"
|
|
116
|
+
* - Qdrant: "qdrant:<collection>" e.g. "qdrant:support-docs"
|
|
117
|
+
* - Generic: just the provider name e.g. "weaviate", "redis"
|
|
118
|
+
*
|
|
119
|
+
* Overrides the `downstreamResource` option passed to wrapToolCall().
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* await prismMcp.wrapToolCall("vector_search", async (ctx) => {
|
|
123
|
+
* const index = await resolveIndex(query);
|
|
124
|
+
* ctx.setDownstreamResource(`pinecone:${index}`);
|
|
125
|
+
* return pinecone.query({ index, vector });
|
|
126
|
+
* });
|
|
127
|
+
*/
|
|
128
|
+
setDownstreamResource(resource: string): void;
|
|
107
129
|
}
|
|
108
130
|
declare class PrismMCP {
|
|
109
131
|
private readonly tracker;
|
|
@@ -300,6 +322,19 @@ declare class SessionBudgetChecker {
|
|
|
300
322
|
/**
|
|
301
323
|
* Built-in tool cost estimates (USD per call).
|
|
302
324
|
* Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.
|
|
325
|
+
*
|
|
326
|
+
* Pricing basis:
|
|
327
|
+
* - Filesystem / Git: trivial I/O; estimated at $0.01/1K ops
|
|
328
|
+
* - GitHub API: free tier 5 000 req/hr; charged via Actions minutes at ~$0.0003/req
|
|
329
|
+
* - Slack / email: negligible API cost; estimated at overhead cost
|
|
330
|
+
* - Databases: typical query cost on managed DB (Neon/RDS) per operation
|
|
331
|
+
* - HTTP / browser: outbound network + optional headless browser compute
|
|
332
|
+
* - AWS: official published rates (Lambda $0.0000002/req, S3 $0.004/10K GET)
|
|
333
|
+
* - Search APIs: published per-query pricing
|
|
334
|
+
* - Code execution: E2B/code_interpreter published rates
|
|
335
|
+
*
|
|
336
|
+
* Prefix wildcards ("github_*") match any tool whose name starts with that prefix.
|
|
337
|
+
* lookupToolCost() tries exact → longest-prefix → 0.
|
|
303
338
|
*/
|
|
304
339
|
/**
|
|
305
340
|
* Look up estimated cost for a tool name.
|
package/dist/index.js
CHANGED
|
@@ -129,27 +129,150 @@ var SessionBudgetChecker = class {
|
|
|
129
129
|
|
|
130
130
|
// src/pricing.ts
|
|
131
131
|
var BUILT_IN = {
|
|
132
|
-
//
|
|
132
|
+
// ── Filesystem & local I/O ─────────────────────────────────────────────────
|
|
133
|
+
read_file: 1e-5,
|
|
134
|
+
write_file: 5e-5,
|
|
135
|
+
create_file: 5e-5,
|
|
136
|
+
delete_file: 2e-5,
|
|
137
|
+
list_directory: 1e-5,
|
|
138
|
+
move_file: 2e-5,
|
|
139
|
+
copy_file: 2e-5,
|
|
140
|
+
search_files: 3e-5,
|
|
141
|
+
get_file_info: 5e-6,
|
|
142
|
+
read_multiple_files: 3e-5,
|
|
143
|
+
// ── Git operations ─────────────────────────────────────────────────────────
|
|
144
|
+
git_status: 5e-6,
|
|
145
|
+
git_diff: 1e-5,
|
|
146
|
+
git_commit: 5e-5,
|
|
147
|
+
git_push: 2e-4,
|
|
148
|
+
git_pull: 1e-4,
|
|
149
|
+
git_clone: 5e-4,
|
|
150
|
+
git_log: 1e-5,
|
|
151
|
+
git_branch: 5e-6,
|
|
152
|
+
git_checkout: 2e-5,
|
|
153
|
+
git_merge: 1e-4,
|
|
154
|
+
// ── GitHub API ────────────────────────────────────────────────────────────
|
|
155
|
+
create_issue: 3e-4,
|
|
156
|
+
update_issue: 3e-4,
|
|
157
|
+
close_issue: 2e-4,
|
|
158
|
+
list_issues: 1e-4,
|
|
159
|
+
get_issue: 1e-4,
|
|
160
|
+
create_pull_request: 3e-4,
|
|
161
|
+
merge_pull_request: 5e-4,
|
|
162
|
+
list_pull_requests: 1e-4,
|
|
163
|
+
search_code: 1e-3,
|
|
164
|
+
search_repositories: 5e-4,
|
|
165
|
+
get_file_contents: 1e-4,
|
|
166
|
+
create_or_update_file: 3e-4,
|
|
167
|
+
fork_repository: 5e-4,
|
|
168
|
+
create_repository: 5e-4,
|
|
169
|
+
push_files: 3e-4,
|
|
170
|
+
"github_*": 2e-4,
|
|
171
|
+
// wildcard fallback for any github_ prefixed tool
|
|
172
|
+
// ── Communication — Slack ─────────────────────────────────────────────────
|
|
173
|
+
slack_post_message: 1e-4,
|
|
174
|
+
slack_reply_to_thread: 1e-4,
|
|
175
|
+
slack_add_reaction: 5e-5,
|
|
176
|
+
slack_get_channel_info: 5e-5,
|
|
177
|
+
slack_list_channels: 8e-5,
|
|
178
|
+
slack_get_users: 1e-4,
|
|
179
|
+
slack_upload_file: 2e-4,
|
|
180
|
+
"slack_*": 8e-5,
|
|
181
|
+
// ── Communication — email / other ─────────────────────────────────────────
|
|
182
|
+
send_email: 1e-3,
|
|
183
|
+
send_slack_message: 1e-4,
|
|
184
|
+
// ── Databases ─────────────────────────────────────────────────────────────
|
|
185
|
+
postgres_query: 5e-4,
|
|
186
|
+
postgres_execute: 1e-3,
|
|
187
|
+
postgres_insert: 1e-3,
|
|
188
|
+
postgres_update: 1e-3,
|
|
189
|
+
postgres_delete: 1e-3,
|
|
190
|
+
"postgres_*": 5e-4,
|
|
191
|
+
mysql_query: 5e-4,
|
|
192
|
+
"mysql_*": 5e-4,
|
|
193
|
+
mongodb_find: 2e-4,
|
|
194
|
+
mongodb_insert: 5e-4,
|
|
195
|
+
"mongodb_*": 3e-4,
|
|
196
|
+
redis_get: 5e-5,
|
|
197
|
+
redis_set: 1e-4,
|
|
198
|
+
"redis_*": 8e-5,
|
|
199
|
+
sqlite_query: 5e-5,
|
|
200
|
+
// ── HTTP / REST ───────────────────────────────────────────────────────────
|
|
201
|
+
http_get: 2e-4,
|
|
202
|
+
http_post: 3e-4,
|
|
203
|
+
http_put: 3e-4,
|
|
204
|
+
http_delete: 2e-4,
|
|
205
|
+
http_fetch: 2e-4,
|
|
206
|
+
fetch_url: 2e-4,
|
|
207
|
+
make_api_request: 3e-4,
|
|
208
|
+
"http_*": 2e-4,
|
|
209
|
+
// ── Browser / web automation ──────────────────────────────────────────────
|
|
210
|
+
browser_navigate: 2e-3,
|
|
211
|
+
browser_screenshot: 3e-3,
|
|
212
|
+
browser_click: 1e-3,
|
|
213
|
+
browser_type: 1e-3,
|
|
214
|
+
browser_scroll: 5e-4,
|
|
215
|
+
browser_get_text: 1e-3,
|
|
216
|
+
browser_fill_form: 2e-3,
|
|
217
|
+
browser_wait: 5e-4,
|
|
218
|
+
puppeteer_navigate: 2e-3,
|
|
219
|
+
playwright_goto: 2e-3,
|
|
220
|
+
"browser_*": 2e-3,
|
|
221
|
+
"puppeteer_*": 2e-3,
|
|
222
|
+
"playwright_*": 2e-3,
|
|
223
|
+
// ── Project management — Jira ─────────────────────────────────────────────
|
|
224
|
+
create_jira_issue: 3e-4,
|
|
225
|
+
update_jira_issue: 3e-4,
|
|
226
|
+
search_jira_issues: 2e-4,
|
|
227
|
+
get_jira_issue: 1e-4,
|
|
228
|
+
add_jira_comment: 2e-4,
|
|
229
|
+
"jira_*": 2e-4,
|
|
230
|
+
// ── Project management — Linear / Notion / Asana ─────────────────────────
|
|
231
|
+
create_linear_issue: 3e-4,
|
|
232
|
+
"linear_*": 2e-4,
|
|
233
|
+
notion_create_page: 3e-4,
|
|
234
|
+
notion_update_page: 3e-4,
|
|
235
|
+
notion_search: 2e-4,
|
|
236
|
+
"notion_*": 2e-4,
|
|
237
|
+
create_asana_task: 3e-4,
|
|
238
|
+
"asana_*": 2e-4,
|
|
239
|
+
// ── Vector DBs ────────────────────────────────────────────────────────────
|
|
133
240
|
pinecone_query: 1e-6,
|
|
134
241
|
pinecone_upsert: 2e-6,
|
|
135
242
|
weaviate_query: 8e-7,
|
|
136
243
|
qdrant_search: 5e-7,
|
|
137
|
-
|
|
244
|
+
chroma_query: 5e-7,
|
|
245
|
+
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
138
246
|
lambda_invoke: 2e-7,
|
|
139
247
|
s3_get_object: 4e-7,
|
|
140
248
|
s3_put_object: 5e-6,
|
|
249
|
+
s3_list_objects: 5e-6,
|
|
141
250
|
dynamodb_get_item: 25e-8,
|
|
142
251
|
dynamodb_put_item: 125e-8,
|
|
143
|
-
|
|
252
|
+
dynamodb_query: 125e-8,
|
|
253
|
+
sqs_send_message: 4e-7,
|
|
254
|
+
sns_publish: 5e-7,
|
|
255
|
+
"aws_*": 1e-6,
|
|
256
|
+
// ── Search / web ──────────────────────────────────────────────────────────
|
|
144
257
|
brave_search: 3e-6,
|
|
145
258
|
serper_search: 1e-6,
|
|
146
259
|
tavily_search: 2e-6,
|
|
147
260
|
exa_search: 5e-6,
|
|
148
|
-
|
|
261
|
+
google_search: 5e-6,
|
|
262
|
+
bing_search: 3e-6,
|
|
263
|
+
duckduckgo_search: 1e-6,
|
|
264
|
+
web_search: 2e-6,
|
|
265
|
+
// ── Code execution ────────────────────────────────────────────────────────
|
|
149
266
|
e2b_run_code: 14e-6,
|
|
150
267
|
code_interpreter: 14e-6,
|
|
151
|
-
|
|
152
|
-
|
|
268
|
+
bash_execute: 1e-3,
|
|
269
|
+
run_command: 1e-3,
|
|
270
|
+
run_terminal_command: 1e-3,
|
|
271
|
+
docker_run: 5e-3,
|
|
272
|
+
docker_exec: 2e-3,
|
|
273
|
+
// ── AI / embeddings ───────────────────────────────────────────────────────
|
|
274
|
+
generate_embedding: 1e-5,
|
|
275
|
+
openai_embedding: 1e-5
|
|
153
276
|
};
|
|
154
277
|
function lookupToolCost(toolName, overrides = {}) {
|
|
155
278
|
const all = { ...BUILT_IN, ...overrides };
|
|
@@ -203,6 +326,8 @@ var WrapContext = class {
|
|
|
203
326
|
constructor() {
|
|
204
327
|
/** @internal - read by _wrap() after fn() completes */
|
|
205
328
|
this._actualCostUsd = null;
|
|
329
|
+
/** @internal */
|
|
330
|
+
this._downstreamResource = null;
|
|
206
331
|
}
|
|
207
332
|
/**
|
|
208
333
|
* Override the catalog-estimated tool cost with the real billing figure.
|
|
@@ -220,6 +345,28 @@ var WrapContext = class {
|
|
|
220
345
|
reportActualCost(usd) {
|
|
221
346
|
this._actualCostUsd = usd;
|
|
222
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Set the downstream resource identifier for this call.
|
|
350
|
+
* Use this when the resource name is resolved inside the handler (e.g. after
|
|
351
|
+
* looking up which Pinecone index to query).
|
|
352
|
+
*
|
|
353
|
+
* Convention:
|
|
354
|
+
* - Pinecone: "pinecone:<index-name>" e.g. "pinecone:product-embeddings"
|
|
355
|
+
* - Qdrant: "qdrant:<collection>" e.g. "qdrant:support-docs"
|
|
356
|
+
* - Generic: just the provider name e.g. "weaviate", "redis"
|
|
357
|
+
*
|
|
358
|
+
* Overrides the `downstreamResource` option passed to wrapToolCall().
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* await prismMcp.wrapToolCall("vector_search", async (ctx) => {
|
|
362
|
+
* const index = await resolveIndex(query);
|
|
363
|
+
* ctx.setDownstreamResource(`pinecone:${index}`);
|
|
364
|
+
* return pinecone.query({ index, vector });
|
|
365
|
+
* });
|
|
366
|
+
*/
|
|
367
|
+
setDownstreamResource(resource) {
|
|
368
|
+
this._downstreamResource = resource;
|
|
369
|
+
}
|
|
223
370
|
};
|
|
224
371
|
var PrismMCP = class {
|
|
225
372
|
constructor(options = {}) {
|
|
@@ -274,7 +421,7 @@ var PrismMCP = class {
|
|
|
274
421
|
user_id: "",
|
|
275
422
|
environment: this.opts.environment,
|
|
276
423
|
tool_name: name,
|
|
277
|
-
downstream_resource: extra.downstreamResource ?? "",
|
|
424
|
+
downstream_resource: ctx._downstreamResource ?? extra.downstreamResource ?? "",
|
|
278
425
|
execution_latency_ms: latencyMs,
|
|
279
426
|
tool_cost_usd: actualCostUsd ?? estimatedCost,
|
|
280
427
|
cost_status: actualCostUsd != null ? "actual" : "estimated",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/tracker.ts","../src/types.ts","../src/budget.ts","../src/pricing.ts","../src/prism-mcp.ts","../src/session.ts"],"sourcesContent":["export { PrismMCP, WrapContext } from \"./prism-mcp\";\nexport { PrismSession } from \"./session\";\nexport type { PrismSessionOptions, PerServerOptions } from \"./session\";\nexport { McpEventTracker } from \"./tracker\";\nexport { SessionBudgetChecker } from \"./budget\";\nexport { lookupToolCost } from \"./pricing\";\nexport type {\n McpEvent,\n McpPrimitiveType,\n PrismMcpOptions,\n} from \"./types\";\nexport {\n PrismSessionBudgetExceededError,\n PrismToolCallLimitError,\n} from \"./types\";\n","import type { McpEvent } from \"./types\";\n\nfunction defaultIngestUrl(): string {\n const appUrl = (\n process.env[\"PRISM_APP_URL\"] ??\n process.env[\"NEXT_PUBLIC_APP_URL\"] ??\n \"https://useprism.dev\"\n ).replace(/\\/$/, \"\");\n return `${appUrl}/api/mcp/ingest`;\n}\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nexport class McpEventTracker {\n private readonly key: string;\n private readonly ingestUrl: string;\n private readonly serverName: string;\n private readonly orgId: string;\n\n constructor(key: string, serverName: string, ingestUrl?: string) {\n this.key = key;\n this.ingestUrl = ingestUrl ?? defaultIngestUrl();\n this.serverName = serverName;\n this.orgId = orgFromKey(key);\n }\n\n async capture(event: Omit<McpEvent, \"event_id\" | \"org_id\" | \"mcp_server_name\">): Promise<void> {\n try {\n const full: McpEvent = {\n ...event,\n event_id: crypto.randomUUID(),\n org_id: this.orgId,\n mcp_server_name: this.serverName,\n };\n\n const res = await fetch(this.ingestUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.key}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ events: [full] }),\n });\n\n if (!res.ok && res.status !== 422) {\n // Silently ignore — observability must never break the agent\n console.warn(`[prism-mcp] Ingest returned ${res.status}`);\n }\n } catch {\n // Never propagate\n }\n }\n}\n","export type McpPrimitiveType = \"tool\" | \"resource\" | \"prompt\" | \"sampling\";\n\nexport interface McpEvent {\n event_id: string;\n timestamp: string;\n session_id: string;\n org_id: string;\n project_id: string;\n team_id: string;\n user_id: string;\n environment: string;\n mcp_server_name: string;\n /** tool name, resource URI, prompt name, or model hint for sampling */\n tool_name: string;\n downstream_resource: string;\n execution_latency_ms: number;\n tool_cost_usd: number;\n status: \"ok\" | \"error\" | \"timeout\";\n error_message: string;\n llm_request_id: string;\n primitive_type: McpPrimitiveType;\n /**\n * Whether tool_cost_usd is an estimate from the built-in catalog (\"estimated\")\n * or a real figure provided by the tool via ctx.reportActualCost() (\"actual\").\n */\n cost_status: \"estimated\" | \"actual\";\n tags: Record<string, string>;\n}\n\nexport interface PrismMcpOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /** Explicit session ID. Auto-generated UUID if omitted. */\n sessionId?: string;\n /** MCP server name shown in the dashboard */\n serverName?: string;\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n /**\n * Session budget in USD. Tool/resource/prompt calls are blocked when the\n * combined session cost exceeds this value.\n */\n sessionBudgetUsd?: number;\n /**\n * Maximum MCP primitive calls per session. Blocks further calls when exceeded.\n * Loop detection guard — default unlimited.\n */\n maxToolCallsPerSession?: number;\n /**\n * Log call arguments into tags['tool_input'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureInputs?: boolean;\n /**\n * Log call results into tags['tool_output'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureOutputs?: boolean;\n /**\n * Keys to redact from captured inputs/outputs.\n * Default: ['password', 'token', 'key', 'secret', 'api_key', 'authorization']\n */\n redactKeys?: string[];\n}\n\nexport class PrismSessionBudgetExceededError extends Error {\n constructor(sessionId: string, budgetUsd: number) {\n super(\n `[prism-mcp] Session budget of $${budgetUsd} exceeded for session ${sessionId}. ` +\n `Tool call blocked to prevent runaway agent costs.`,\n );\n this.name = \"PrismSessionBudgetExceededError\";\n }\n}\n\nexport class PrismToolCallLimitError extends Error {\n constructor(sessionId: string, limit: number) {\n super(\n `[prism-mcp] Tool call limit of ${limit} reached for session ${sessionId}. ` +\n `Possible agent loop detected — tool call blocked.`,\n );\n this.name = \"PrismToolCallLimitError\";\n }\n}\n","/**\n * Session budget circuit breaker.\n * Checks Redis session cost + tool-call counters before each tool invocation.\n * If the UPSTASH env vars are absent, checks are skipped (graceful degradation).\n */\n\nimport {\n PrismSessionBudgetExceededError,\n PrismToolCallLimitError,\n} from \"./types\";\n\nasync function redisGet(url: string, token: string, key: string): Promise<number> {\n const res = await fetch(`${url}/get/${encodeURIComponent(key)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return 0;\n const json = await res.json() as { result: string | null };\n return parseFloat(json.result ?? \"0\") || 0;\n}\n\nexport class SessionBudgetChecker {\n private readonly url: string | null;\n private readonly token: string | null;\n private readonly orgId: string;\n\n constructor(orgId: string) {\n this.url = process.env[\"UPSTASH_REDIS_REST_URL\"] ?? null;\n this.token = process.env[\"UPSTASH_REDIS_REST_TOKEN\"] ?? null;\n this.orgId = orgId;\n }\n\n /**\n * Throws PrismSessionBudgetExceededError or PrismToolCallLimitError\n * if the session has exceeded its configured limits.\n */\n async checkOrThrow(\n sessionId: string,\n sessionBudgetUsd?: number,\n maxToolCallsPerSession?: number,\n ): Promise<void> {\n if (!this.url || !this.token) return; // No Redis configured — skip\n\n if (sessionBudgetUsd != null && sessionBudgetUsd > 0) {\n const costKey = `session:${this.orgId}:${sessionId}:cost`;\n const current = await redisGet(this.url, this.token, costKey);\n if (current >= sessionBudgetUsd) {\n throw new PrismSessionBudgetExceededError(sessionId, sessionBudgetUsd);\n }\n }\n\n if (maxToolCallsPerSession != null && maxToolCallsPerSession > 0) {\n const toolKey = `session:${this.orgId}:${sessionId}:tool_calls`;\n const count = await redisGet(this.url, this.token, toolKey);\n if (count >= maxToolCallsPerSession) {\n throw new PrismToolCallLimitError(sessionId, maxToolCallsPerSession);\n }\n }\n }\n}\n","/**\n * Built-in tool cost estimates (USD per call).\n * Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.\n */\n\nconst BUILT_IN: Record<string, number> = {\n // Vector DBs\n pinecone_query: 0.000001,\n pinecone_upsert: 0.000002,\n weaviate_query: 0.0000008,\n qdrant_search: 0.0000005,\n // AWS\n lambda_invoke: 0.0000002,\n s3_get_object: 0.0000004,\n s3_put_object: 0.000005,\n dynamodb_get_item: 0.00000025,\n dynamodb_put_item: 0.00000125,\n // Search / web\n brave_search: 0.000003,\n serper_search: 0.000001,\n tavily_search: 0.000002,\n exa_search: 0.000005,\n // Code execution\n e2b_run_code: 0.000014,\n code_interpreter: 0.000014,\n // Communication\n send_email: 0.000001,\n};\n\n/**\n * Look up estimated cost for a tool name.\n * Exact match, then prefix match (\"pinecone_*\"), then 0.\n */\nexport function lookupToolCost(\n toolName: string,\n overrides: Record<string, number> = {},\n): number {\n const all = { ...BUILT_IN, ...overrides };\n\n // Exact\n if (toolName in all) return all[toolName]!;\n\n // Prefix: find the longest matching prefix key ending in \"_*\" or \":*\"\n const prefix = Object.keys(all)\n .filter((k) => k.endsWith(\"*\") && toolName.startsWith(k.slice(0, -1)))\n .sort((a, b) => b.length - a.length)[0];\n\n return prefix ? (all[prefix] ?? 0) : 0;\n}\n","/**\n * PrismMCP — instruments any MCP Server with full observability:\n * - tools/call → wrapToolCall() / patchHandler()\n * - resources/read → wrapResourceRead() / patchResourceHandler()\n * - prompts/get → wrapPromptGet() / patchPromptHandler()\n * - sampling/createMessage → wrapSamplingHandler()\n *\n * Features:\n * - Session budget circuit breaker (throws before execution if over budget)\n * - Tool loop detection (throws if max_tool_calls_per_session exceeded)\n * - Opt-in I/O capture (captureInputs / captureOutputs)\n * - Streaming tool latency: correctly measures time-to-stream-end, not first-chunk\n */\n\nimport type { McpPrimitiveType, PrismMcpOptions } from \"./types\";\nimport { McpEventTracker } from \"./tracker\";\nimport { SessionBudgetChecker } from \"./budget\";\nimport { lookupToolCost } from \"./pricing\";\n\n// ── Private helpers ────────────────────────────────────────────────────────────\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nconst DEFAULT_REDACT_KEYS = [\"password\", \"token\", \"key\", \"secret\", \"api_key\", \"authorization\"];\n\nfunction redactObject(\n obj: unknown,\n redactKeys: string[],\n): unknown {\n if (typeof obj !== \"object\" || obj === null) return obj;\n if (Array.isArray(obj)) return obj.map((v) => redactObject(v, redactKeys));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n out[k] = redactKeys.some((r) => k.toLowerCase().includes(r.toLowerCase()))\n ? \"[REDACTED]\"\n : redactObject(v, redactKeys);\n }\n return out;\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : s.slice(0, max) + \"…\";\n}\n\nfunction safeJson(val: unknown, redactKeys: string[], maxLen: number): string {\n try {\n return truncate(JSON.stringify(redactObject(val, redactKeys)), maxLen);\n } catch {\n return \"[unserializable]\";\n }\n}\n\n// ── Streaming proxy (Epic 6) ───────────────────────────────────────────────────\n\nfunction isAsyncIterable(val: unknown): val is AsyncIterable<unknown> {\n return val != null &&\n typeof (val as AsyncIterable<unknown>)[Symbol.asyncIterator] === \"function\";\n}\n\n/**\n * Wraps an async iterable so that `onEnd` is called with the total elapsed ms\n * when the stream is fully consumed or throws. All values are forwarded unchanged.\n */\nasync function* proxyAsyncIterable<T>(\n source: AsyncIterable<T>,\n start: number,\n onEnd: (latencyMs: number, threw: boolean) => void,\n): AsyncGenerator<T> {\n let threw = false;\n try {\n for await (const value of source) {\n yield value;\n }\n } catch (err) {\n threw = true;\n throw err;\n } finally {\n onEnd(Date.now() - start, threw);\n }\n}\n\n// ── WrapContext — passed to the fn() callback for actual cost self-reporting ──\n\nexport class WrapContext {\n /** @internal - read by _wrap() after fn() completes */\n _actualCostUsd: number | null = null;\n\n /**\n * Override the catalog-estimated tool cost with the real billing figure.\n * Call this inside your tool handler when you have access to actual cost data\n * (e.g. from AWS SDK response metadata, a billing header, or a usage API).\n *\n * @example\n * await prismMcp.wrapToolCall(\"invoke_lambda\", async (ctx) => {\n * const res = await lambda.invoke({ FunctionName: \"fn\", Payload: payload });\n * const billedMs = res.$metadata.httpHeaders?.[\"x-amz-billed-duration-ms\"] ?? \"0\";\n * ctx.reportActualCost(parseInt(billedMs) * 0.000016667 / 1000);\n * return res;\n * });\n */\n reportActualCost(usd: number): void {\n this._actualCostUsd = usd;\n }\n}\n\n// ── Main class ────────────────────────────────────────────────────────────────\n\nexport class PrismMCP {\n private readonly tracker: McpEventTracker;\n private readonly budget: SessionBudgetChecker;\n private readonly redactKeys: string[];\n private readonly opts: Required<Pick<PrismMcpOptions,\n \"project\" | \"team\" | \"environment\" | \"sessionId\" | \"serverName\" |\n \"captureInputs\" | \"captureOutputs\">> &\n Pick<PrismMcpOptions, \"sessionBudgetUsd\" | \"maxToolCallsPerSession\">;\n\n constructor(options: PrismMcpOptions = {}) {\n const key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n if (!key) {\n console.warn(\"[prism-mcp] PRISM_API_KEY not set — MCP observability disabled.\");\n }\n\n this.opts = {\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 serverName: options.serverName ?? \"mcp-server\",\n sessionBudgetUsd: options.sessionBudgetUsd,\n maxToolCallsPerSession: options.maxToolCallsPerSession,\n captureInputs: options.captureInputs ?? false,\n captureOutputs: options.captureOutputs ?? false,\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 // ── Core primitive wrapper ─────────────────────────────────────────────────\n\n /**\n * Internal wrapper used by all four MCP primitives.\n * Handles: budget check, I/O capture, streaming proxy, fire-and-forget telemetry,\n * and actual cost self-reporting via ctx.reportActualCost().\n */\n private async _wrap<T>(\n primitiveType: McpPrimitiveType,\n name: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n // 1. Pre-call budget/loop guard\n await this.budget.checkOrThrow(\n this.opts.sessionId,\n this.opts.sessionBudgetUsd,\n this.opts.maxToolCallsPerSession,\n );\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n let result: T;\n const ctx = new WrapContext();\n\n // Build tags — I/O capture applied here\n const eventTags: Record<string, string> = { ...extra.tags };\n if (this.opts.captureInputs && extra.inputs != null) {\n eventTags[\"tool_input\"] = safeJson(extra.inputs, this.redactKeys, 1000);\n }\n\n // Cost lookup — overridden by ctx.reportActualCost() if called during fn()\n const estimatedCost = primitiveType === \"tool\" ? lookupToolCost(name) : 0;\n\n const fireEvent = (latencyMs: number, finalStatus: \"ok\" | \"error\" | \"timeout\") => {\n const actualCostUsd = ctx._actualCostUsd;\n this.tracker.capture({\n timestamp: new Date().toISOString().replace(\"T\", \" \").slice(0, 23),\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: name,\n downstream_resource: extra.downstreamResource ?? \"\",\n execution_latency_ms: latencyMs,\n tool_cost_usd: actualCostUsd ?? estimatedCost,\n cost_status: actualCostUsd != null ? \"actual\" : \"estimated\",\n status: finalStatus,\n error_message: errorMsg,\n llm_request_id: extra.llmRequestId ?? \"\",\n primitive_type: primitiveType,\n tags: eventTags,\n }).catch(() => {});\n };\n\n try {\n result = await fn(ctx);\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n fireEvent(Date.now() - start, status);\n throw err;\n }\n\n // Capture output if requested (non-streaming path)\n if (this.opts.captureOutputs && result != null && !isAsyncIterable(result)) {\n eventTags[\"tool_output\"] = safeJson(result, this.redactKeys, 1000);\n }\n\n // Streaming path — defer telemetry until stream is exhausted\n if (isAsyncIterable(result)) {\n return proxyAsyncIterable(\n result as AsyncIterable<unknown>,\n start,\n (latencyMs, threw) => {\n fireEvent(latencyMs, threw ? \"error\" : \"ok\");\n },\n ) as unknown as T;\n }\n\n // Non-streaming path — fire immediately\n fireEvent(Date.now() - start, status);\n return result;\n }\n\n // ── Public API: tools ──────────────────────────────────────────────────────\n\n /**\n * Wrap a tools/call execution with Prism instrumentation.\n */\n async wrapToolCall<T>(\n toolName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n /** Raw tool arguments — captured if captureInputs: true */\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"tool\", toolName, fn, extra);\n }\n\n /**\n * Drop-in patch for MCP SDK's CallToolRequestSchema handler.\n * Automatically passes req.params.arguments as inputs when captureInputs: true.\n * ctx is available for reportActualCost() inside the handler.\n */\n patchHandler<TReq extends { params: { name: string; arguments?: Record<string, unknown> } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedHandler(req: TReq): Promise<TRes> {\n return self.wrapToolCall(\n req.params.name,\n (ctx) => handler(req, ctx),\n { inputs: req.params.arguments },\n );\n };\n }\n\n // ── Public API: resources ──────────────────────────────────────────────────\n\n /**\n * Wrap a resources/read execution with Prism instrumentation.\n *\n * @param resourceUri - The resource URI being read (e.g. \"file:///path/to/file\")\n */\n async wrapResourceRead<T>(\n resourceUri: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"resource\", resourceUri, fn, extra);\n }\n\n patchResourceHandler<TReq extends { params: { uri: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedResourceHandler(req: TReq): Promise<TRes> {\n return self.wrapResourceRead(req.params.uri, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: prompts ────────────────────────────────────────────────────\n\n async wrapPromptGet<T>(\n promptName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"prompt\", promptName, fn, extra);\n }\n\n patchPromptHandler<TReq extends { params: { name: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedPromptHandler(req: TReq): Promise<TRes> {\n return self.wrapPromptGet(req.params.name, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: sampling ───────────────────────────────────────────────────\n\n /**\n * Wrap the MCP client's sampling/createMessage callback so LLM calls\n * requested by the MCP server appear in the session timeline.\n *\n * Usage with @modelcontextprotocol/sdk:\n * client.setRequestHandler(\n * CreateMessageRequestSchema,\n * prismMcp.wrapSamplingHandler(async (req) => {\n * const res = await openai.chat.completions.create({ ... });\n * return { role: \"assistant\", content: [{ type: \"text\", text: res.choices[0].message.content }] };\n * })\n * );\n */\n wrapSamplingHandler<\n TReq extends { params?: { modelPreferences?: { hints?: Array<{ name?: string }> } } },\n TRes,\n >(\n handler: (req: TReq) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedSamplingHandler(req: TReq): Promise<TRes> {\n // Use model hint as the \"tool_name\" for display in the session timeline\n const modelHint =\n req.params?.modelPreferences?.hints?.[0]?.name ?? \"sampling\";\n return self._wrap(\"sampling\", modelHint, (_ctx) => handler(req));\n };\n }\n\n /** The session_id assigned to this instance (useful for logging). */\n get sessionId(): string { return this.opts.sessionId; }\n}\n","/**\n * PrismSession — shared session context for multi-server agent runs.\n *\n * Creates a single session_id that is automatically threaded through:\n * - Multiple PrismMCP server instances (files, database, search, etc.)\n * - LLM SDK clients (OpenAI, Anthropic) via toLLMOptions()\n *\n * This ensures every LLM call and every tool/resource/prompt call in one\n * agent run appears under the same session in /dashboard/sessions/[id].\n *\n * Usage:\n * const session = new PrismSession({\n * prismKey: process.env.PRISM_API_KEY,\n * project: \"customer-support\",\n * sessionBudgetUsd: 2.00, // hard cap for the whole agent run\n * });\n *\n * // MCP servers — all share session.sessionId automatically\n * const filesMcp = session.createServer({ serverName: \"files\" });\n * const dbMcp = session.createServer({ serverName: \"database\" });\n * const searchMcp = session.createServer({ serverName: \"search\" });\n *\n * // LLM SDK client — same session_id\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n\nimport { PrismMCP } from \"./prism-mcp\";\nimport type { PrismMcpOptions } from \"./types\";\n\nexport interface PrismSessionOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /**\n * Explicit session ID. Auto-generated UUID if omitted.\n * Pass an explicit ID when the session ID is determined by an external\n * orchestrator (e.g. a LangGraph run ID, a task queue job ID).\n */\n sessionId?: string;\n /** Hard cost cap across ALL servers in this session */\n sessionBudgetUsd?: number;\n /** Hard tool-call cap across ALL servers in this session */\n maxToolCallsPerSession?: number;\n /** Opt-in I/O capture for all servers created from this session */\n captureInputs?: boolean;\n captureOutputs?: boolean;\n redactKeys?: string[];\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n}\n\n/** Options for individual servers within a session */\nexport type PerServerOptions = Omit<\n PrismMcpOptions,\n \"prismKey\" | \"project\" | \"team\" | \"environment\" | \"sessionId\" |\n \"sessionBudgetUsd\" | \"maxToolCallsPerSession\" | \"captureInputs\" |\n \"captureOutputs\" | \"redactKeys\" | \"ingestUrl\"\n>;\n\nexport class PrismSession {\n readonly sessionId: string;\n\n private readonly key: string;\n private readonly project: string;\n private readonly team: string;\n private readonly environment: string;\n private readonly sharedOpts: PrismSessionOptions;\n\n constructor(options: PrismSessionOptions = {}) {\n this.key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n this.project = options.project ?? process.env[\"PRISM_PROJECT\"] ?? \"\";\n this.team = options.team ?? process.env[\"PRISM_TEAM\"] ?? \"\";\n this.environment = options.environment ?? process.env[\"PRISM_ENVIRONMENT\"] ?? \"production\";\n this.sessionId = options.sessionId ?? crypto.randomUUID();\n this.sharedOpts = options;\n\n if (!this.key) {\n console.warn(\"[prism-mcp] PrismSession: PRISM_API_KEY not set — observability disabled.\");\n }\n }\n\n /**\n * Create a PrismMCP instance bound to this session.\n * All servers created from the same session share the same session_id.\n */\n createServer(perServer: PerServerOptions = {}): PrismMCP {\n return new PrismMCP({\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n sessionBudgetUsd: this.sharedOpts.sessionBudgetUsd,\n maxToolCallsPerSession: this.sharedOpts.maxToolCallsPerSession,\n captureInputs: this.sharedOpts.captureInputs,\n captureOutputs: this.sharedOpts.captureOutputs,\n redactKeys: this.sharedOpts.redactKeys,\n ingestUrl: this.sharedOpts.ingestUrl,\n // per-server overrides\n serverName: perServer.serverName,\n });\n }\n\n /**\n * Returns options to pass to the Prism LLM SDK clients (@prism-llm-labs/sdk)\n * so that LLM completions share this session's session_id.\n *\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n toLLMOptions(): {\n prismKey: string;\n project: string;\n team: string;\n environment: string;\n sessionId: string;\n } {\n return {\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n };\n }\n}\n"],"mappings":"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","names":["orgFromKey"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tracker.ts","../src/types.ts","../src/budget.ts","../src/pricing.ts","../src/prism-mcp.ts","../src/session.ts"],"sourcesContent":["export { PrismMCP, WrapContext } from \"./prism-mcp\";\nexport { PrismSession } from \"./session\";\nexport type { PrismSessionOptions, PerServerOptions } from \"./session\";\nexport { McpEventTracker } from \"./tracker\";\nexport { SessionBudgetChecker } from \"./budget\";\nexport { lookupToolCost } from \"./pricing\";\nexport type {\n McpEvent,\n McpPrimitiveType,\n PrismMcpOptions,\n} from \"./types\";\nexport {\n PrismSessionBudgetExceededError,\n PrismToolCallLimitError,\n} from \"./types\";\n","import type { McpEvent } from \"./types\";\n\nfunction defaultIngestUrl(): string {\n const appUrl = (\n process.env[\"PRISM_APP_URL\"] ??\n process.env[\"NEXT_PUBLIC_APP_URL\"] ??\n \"https://useprism.dev\"\n ).replace(/\\/$/, \"\");\n return `${appUrl}/api/mcp/ingest`;\n}\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nexport class McpEventTracker {\n private readonly key: string;\n private readonly ingestUrl: string;\n private readonly serverName: string;\n private readonly orgId: string;\n\n constructor(key: string, serverName: string, ingestUrl?: string) {\n this.key = key;\n this.ingestUrl = ingestUrl ?? defaultIngestUrl();\n this.serverName = serverName;\n this.orgId = orgFromKey(key);\n }\n\n async capture(event: Omit<McpEvent, \"event_id\" | \"org_id\" | \"mcp_server_name\">): Promise<void> {\n try {\n const full: McpEvent = {\n ...event,\n event_id: crypto.randomUUID(),\n org_id: this.orgId,\n mcp_server_name: this.serverName,\n };\n\n const res = await fetch(this.ingestUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.key}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ events: [full] }),\n });\n\n if (!res.ok && res.status !== 422) {\n // Silently ignore — observability must never break the agent\n console.warn(`[prism-mcp] Ingest returned ${res.status}`);\n }\n } catch {\n // Never propagate\n }\n }\n}\n","export type McpPrimitiveType = \"tool\" | \"resource\" | \"prompt\" | \"sampling\";\n\nexport interface McpEvent {\n event_id: string;\n timestamp: string;\n session_id: string;\n org_id: string;\n project_id: string;\n team_id: string;\n user_id: string;\n environment: string;\n mcp_server_name: string;\n /** tool name, resource URI, prompt name, or model hint for sampling */\n tool_name: string;\n downstream_resource: string;\n execution_latency_ms: number;\n tool_cost_usd: number;\n status: \"ok\" | \"error\" | \"timeout\";\n error_message: string;\n llm_request_id: string;\n primitive_type: McpPrimitiveType;\n /**\n * Whether tool_cost_usd is an estimate from the built-in catalog (\"estimated\")\n * or a real figure provided by the tool via ctx.reportActualCost() (\"actual\").\n */\n cost_status: \"estimated\" | \"actual\";\n tags: Record<string, string>;\n}\n\nexport interface PrismMcpOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /** Explicit session ID. Auto-generated UUID if omitted. */\n sessionId?: string;\n /** MCP server name shown in the dashboard */\n serverName?: string;\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n /**\n * Session budget in USD. Tool/resource/prompt calls are blocked when the\n * combined session cost exceeds this value.\n */\n sessionBudgetUsd?: number;\n /**\n * Maximum MCP primitive calls per session. Blocks further calls when exceeded.\n * Loop detection guard — default unlimited.\n */\n maxToolCallsPerSession?: number;\n /**\n * Log call arguments into tags['tool_input'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureInputs?: boolean;\n /**\n * Log call results into tags['tool_output'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureOutputs?: boolean;\n /**\n * Keys to redact from captured inputs/outputs.\n * Default: ['password', 'token', 'key', 'secret', 'api_key', 'authorization']\n */\n redactKeys?: string[];\n}\n\nexport class PrismSessionBudgetExceededError extends Error {\n constructor(sessionId: string, budgetUsd: number) {\n super(\n `[prism-mcp] Session budget of $${budgetUsd} exceeded for session ${sessionId}. ` +\n `Tool call blocked to prevent runaway agent costs.`,\n );\n this.name = \"PrismSessionBudgetExceededError\";\n }\n}\n\nexport class PrismToolCallLimitError extends Error {\n constructor(sessionId: string, limit: number) {\n super(\n `[prism-mcp] Tool call limit of ${limit} reached for session ${sessionId}. ` +\n `Possible agent loop detected — tool call blocked.`,\n );\n this.name = \"PrismToolCallLimitError\";\n }\n}\n","/**\n * Session budget circuit breaker.\n * Checks Redis session cost + tool-call counters before each tool invocation.\n * If the UPSTASH env vars are absent, checks are skipped (graceful degradation).\n */\n\nimport {\n PrismSessionBudgetExceededError,\n PrismToolCallLimitError,\n} from \"./types\";\n\nasync function redisGet(url: string, token: string, key: string): Promise<number> {\n const res = await fetch(`${url}/get/${encodeURIComponent(key)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return 0;\n const json = await res.json() as { result: string | null };\n return parseFloat(json.result ?? \"0\") || 0;\n}\n\nexport class SessionBudgetChecker {\n private readonly url: string | null;\n private readonly token: string | null;\n private readonly orgId: string;\n\n constructor(orgId: string) {\n this.url = process.env[\"UPSTASH_REDIS_REST_URL\"] ?? null;\n this.token = process.env[\"UPSTASH_REDIS_REST_TOKEN\"] ?? null;\n this.orgId = orgId;\n }\n\n /**\n * Throws PrismSessionBudgetExceededError or PrismToolCallLimitError\n * if the session has exceeded its configured limits.\n */\n async checkOrThrow(\n sessionId: string,\n sessionBudgetUsd?: number,\n maxToolCallsPerSession?: number,\n ): Promise<void> {\n if (!this.url || !this.token) return; // No Redis configured — skip\n\n if (sessionBudgetUsd != null && sessionBudgetUsd > 0) {\n const costKey = `session:${this.orgId}:${sessionId}:cost`;\n const current = await redisGet(this.url, this.token, costKey);\n if (current >= sessionBudgetUsd) {\n throw new PrismSessionBudgetExceededError(sessionId, sessionBudgetUsd);\n }\n }\n\n if (maxToolCallsPerSession != null && maxToolCallsPerSession > 0) {\n const toolKey = `session:${this.orgId}:${sessionId}:tool_calls`;\n const count = await redisGet(this.url, this.token, toolKey);\n if (count >= maxToolCallsPerSession) {\n throw new PrismToolCallLimitError(sessionId, maxToolCallsPerSession);\n }\n }\n }\n}\n","/**\n * Built-in tool cost estimates (USD per call).\n * Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.\n *\n * Pricing basis:\n * - Filesystem / Git: trivial I/O; estimated at $0.01/1K ops\n * - GitHub API: free tier 5 000 req/hr; charged via Actions minutes at ~$0.0003/req\n * - Slack / email: negligible API cost; estimated at overhead cost\n * - Databases: typical query cost on managed DB (Neon/RDS) per operation\n * - HTTP / browser: outbound network + optional headless browser compute\n * - AWS: official published rates (Lambda $0.0000002/req, S3 $0.004/10K GET)\n * - Search APIs: published per-query pricing\n * - Code execution: E2B/code_interpreter published rates\n *\n * Prefix wildcards (\"github_*\") match any tool whose name starts with that prefix.\n * lookupToolCost() tries exact → longest-prefix → 0.\n */\n\nconst BUILT_IN: Record<string, number> = {\n // ── Filesystem & local I/O ─────────────────────────────────────────────────\n read_file: 0.00001,\n write_file: 0.00005,\n create_file: 0.00005,\n delete_file: 0.00002,\n list_directory: 0.00001,\n move_file: 0.00002,\n copy_file: 0.00002,\n search_files: 0.00003,\n get_file_info: 0.000005,\n read_multiple_files: 0.00003,\n\n // ── Git operations ─────────────────────────────────────────────────────────\n git_status: 0.000005,\n git_diff: 0.00001,\n git_commit: 0.00005,\n git_push: 0.0002,\n git_pull: 0.0001,\n git_clone: 0.0005,\n git_log: 0.00001,\n git_branch: 0.000005,\n git_checkout: 0.00002,\n git_merge: 0.0001,\n\n // ── GitHub API ────────────────────────────────────────────────────────────\n create_issue: 0.0003,\n update_issue: 0.0003,\n close_issue: 0.0002,\n list_issues: 0.0001,\n get_issue: 0.0001,\n create_pull_request: 0.0003,\n merge_pull_request: 0.0005,\n list_pull_requests: 0.0001,\n search_code: 0.001,\n search_repositories: 0.0005,\n get_file_contents: 0.0001,\n create_or_update_file: 0.0003,\n fork_repository: 0.0005,\n create_repository: 0.0005,\n push_files: 0.0003,\n \"github_*\": 0.0002, // wildcard fallback for any github_ prefixed tool\n\n // ── Communication — Slack ─────────────────────────────────────────────────\n slack_post_message: 0.0001,\n slack_reply_to_thread: 0.0001,\n slack_add_reaction: 0.00005,\n slack_get_channel_info: 0.00005,\n slack_list_channels: 0.00008,\n slack_get_users: 0.0001,\n slack_upload_file: 0.0002,\n \"slack_*\": 0.00008,\n\n // ── Communication — email / other ─────────────────────────────────────────\n send_email: 0.001,\n send_slack_message: 0.0001,\n\n // ── Databases ─────────────────────────────────────────────────────────────\n postgres_query: 0.0005,\n postgres_execute: 0.001,\n postgres_insert: 0.001,\n postgres_update: 0.001,\n postgres_delete: 0.001,\n \"postgres_*\": 0.0005,\n mysql_query: 0.0005,\n \"mysql_*\": 0.0005,\n mongodb_find: 0.0002,\n mongodb_insert: 0.0005,\n \"mongodb_*\": 0.0003,\n redis_get: 0.00005,\n redis_set: 0.0001,\n \"redis_*\": 0.00008,\n sqlite_query: 0.00005,\n\n // ── HTTP / REST ───────────────────────────────────────────────────────────\n http_get: 0.0002,\n http_post: 0.0003,\n http_put: 0.0003,\n http_delete: 0.0002,\n http_fetch: 0.0002,\n fetch_url: 0.0002,\n make_api_request: 0.0003,\n \"http_*\": 0.0002,\n\n // ── Browser / web automation ──────────────────────────────────────────────\n browser_navigate: 0.002,\n browser_screenshot: 0.003,\n browser_click: 0.001,\n browser_type: 0.001,\n browser_scroll: 0.0005,\n browser_get_text: 0.001,\n browser_fill_form: 0.002,\n browser_wait: 0.0005,\n puppeteer_navigate: 0.002,\n playwright_goto: 0.002,\n \"browser_*\": 0.002,\n \"puppeteer_*\": 0.002,\n \"playwright_*\": 0.002,\n\n // ── Project management — Jira ─────────────────────────────────────────────\n create_jira_issue: 0.0003,\n update_jira_issue: 0.0003,\n search_jira_issues: 0.0002,\n get_jira_issue: 0.0001,\n add_jira_comment: 0.0002,\n \"jira_*\": 0.0002,\n\n // ── Project management — Linear / Notion / Asana ─────────────────────────\n create_linear_issue: 0.0003,\n \"linear_*\": 0.0002,\n notion_create_page: 0.0003,\n notion_update_page: 0.0003,\n notion_search: 0.0002,\n \"notion_*\": 0.0002,\n create_asana_task: 0.0003,\n \"asana_*\": 0.0002,\n\n // ── Vector DBs ────────────────────────────────────────────────────────────\n pinecone_query: 0.000001,\n pinecone_upsert: 0.000002,\n weaviate_query: 0.0000008,\n qdrant_search: 0.0000005,\n chroma_query: 0.0000005,\n\n // ── AWS ───────────────────────────────────────────────────────────────────\n lambda_invoke: 0.0000002,\n s3_get_object: 0.0000004,\n s3_put_object: 0.000005,\n s3_list_objects: 0.000005,\n dynamodb_get_item: 0.00000025,\n dynamodb_put_item: 0.00000125,\n dynamodb_query: 0.00000125,\n sqs_send_message: 0.00000040,\n sns_publish: 0.00000050,\n \"aws_*\": 0.000001,\n\n // ── Search / web ──────────────────────────────────────────────────────────\n brave_search: 0.000003,\n serper_search: 0.000001,\n tavily_search: 0.000002,\n exa_search: 0.000005,\n google_search: 0.000005,\n bing_search: 0.000003,\n duckduckgo_search: 0.000001,\n web_search: 0.000002,\n\n // ── Code execution ────────────────────────────────────────────────────────\n e2b_run_code: 0.000014,\n code_interpreter: 0.000014,\n bash_execute: 0.001,\n run_command: 0.001,\n run_terminal_command: 0.001,\n docker_run: 0.005,\n docker_exec: 0.002,\n\n // ── AI / embeddings ───────────────────────────────────────────────────────\n generate_embedding: 0.00001,\n openai_embedding: 0.00001,\n};\n\n/**\n * Look up estimated cost for a tool name.\n * Exact match, then prefix match (\"pinecone_*\"), then 0.\n */\nexport function lookupToolCost(\n toolName: string,\n overrides: Record<string, number> = {},\n): number {\n const all = { ...BUILT_IN, ...overrides };\n\n // Exact\n if (toolName in all) return all[toolName]!;\n\n // Prefix: find the longest matching prefix key ending in \"_*\" or \":*\"\n const prefix = Object.keys(all)\n .filter((k) => k.endsWith(\"*\") && toolName.startsWith(k.slice(0, -1)))\n .sort((a, b) => b.length - a.length)[0];\n\n return prefix ? (all[prefix] ?? 0) : 0;\n}\n","/**\n * PrismMCP — instruments any MCP Server with full observability:\n * - tools/call → wrapToolCall() / patchHandler()\n * - resources/read → wrapResourceRead() / patchResourceHandler()\n * - prompts/get → wrapPromptGet() / patchPromptHandler()\n * - sampling/createMessage → wrapSamplingHandler()\n *\n * Features:\n * - Session budget circuit breaker (throws before execution if over budget)\n * - Tool loop detection (throws if max_tool_calls_per_session exceeded)\n * - Opt-in I/O capture (captureInputs / captureOutputs)\n * - Streaming tool latency: correctly measures time-to-stream-end, not first-chunk\n */\n\nimport type { McpPrimitiveType, PrismMcpOptions } from \"./types\";\nimport { McpEventTracker } from \"./tracker\";\nimport { SessionBudgetChecker } from \"./budget\";\nimport { lookupToolCost } from \"./pricing\";\n\n// ── Private helpers ────────────────────────────────────────────────────────────\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nconst DEFAULT_REDACT_KEYS = [\"password\", \"token\", \"key\", \"secret\", \"api_key\", \"authorization\"];\n\nfunction redactObject(\n obj: unknown,\n redactKeys: string[],\n): unknown {\n if (typeof obj !== \"object\" || obj === null) return obj;\n if (Array.isArray(obj)) return obj.map((v) => redactObject(v, redactKeys));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n out[k] = redactKeys.some((r) => k.toLowerCase().includes(r.toLowerCase()))\n ? \"[REDACTED]\"\n : redactObject(v, redactKeys);\n }\n return out;\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : s.slice(0, max) + \"…\";\n}\n\nfunction safeJson(val: unknown, redactKeys: string[], maxLen: number): string {\n try {\n return truncate(JSON.stringify(redactObject(val, redactKeys)), maxLen);\n } catch {\n return \"[unserializable]\";\n }\n}\n\n// ── Streaming proxy (Epic 6) ───────────────────────────────────────────────────\n\nfunction isAsyncIterable(val: unknown): val is AsyncIterable<unknown> {\n return val != null &&\n typeof (val as AsyncIterable<unknown>)[Symbol.asyncIterator] === \"function\";\n}\n\n/**\n * Wraps an async iterable so that `onEnd` is called with the total elapsed ms\n * when the stream is fully consumed or throws. All values are forwarded unchanged.\n */\nasync function* proxyAsyncIterable<T>(\n source: AsyncIterable<T>,\n start: number,\n onEnd: (latencyMs: number, threw: boolean) => void,\n): AsyncGenerator<T> {\n let threw = false;\n try {\n for await (const value of source) {\n yield value;\n }\n } catch (err) {\n threw = true;\n throw err;\n } finally {\n onEnd(Date.now() - start, threw);\n }\n}\n\n// ── WrapContext — passed to the fn() callback for actual cost self-reporting ──\n\nexport class WrapContext {\n /** @internal - read by _wrap() after fn() completes */\n _actualCostUsd: number | null = null;\n /** @internal */\n _downstreamResource: string | null = null;\n\n /**\n * Override the catalog-estimated tool cost with the real billing figure.\n * Call this inside your tool handler when you have access to actual cost data\n * (e.g. from AWS SDK response metadata, a billing header, or a usage API).\n *\n * @example\n * await prismMcp.wrapToolCall(\"invoke_lambda\", async (ctx) => {\n * const res = await lambda.invoke({ FunctionName: \"fn\", Payload: payload });\n * const billedMs = res.$metadata.httpHeaders?.[\"x-amz-billed-duration-ms\"] ?? \"0\";\n * ctx.reportActualCost(parseInt(billedMs) * 0.000016667 / 1000);\n * return res;\n * });\n */\n reportActualCost(usd: number): void {\n this._actualCostUsd = usd;\n }\n\n /**\n * Set the downstream resource identifier for this call.\n * Use this when the resource name is resolved inside the handler (e.g. after\n * looking up which Pinecone index to query).\n *\n * Convention:\n * - Pinecone: \"pinecone:<index-name>\" e.g. \"pinecone:product-embeddings\"\n * - Qdrant: \"qdrant:<collection>\" e.g. \"qdrant:support-docs\"\n * - Generic: just the provider name e.g. \"weaviate\", \"redis\"\n *\n * Overrides the `downstreamResource` option passed to wrapToolCall().\n *\n * @example\n * await prismMcp.wrapToolCall(\"vector_search\", async (ctx) => {\n * const index = await resolveIndex(query);\n * ctx.setDownstreamResource(`pinecone:${index}`);\n * return pinecone.query({ index, vector });\n * });\n */\n setDownstreamResource(resource: string): void {\n this._downstreamResource = resource;\n }\n}\n\n// ── Main class ────────────────────────────────────────────────────────────────\n\nexport class PrismMCP {\n private readonly tracker: McpEventTracker;\n private readonly budget: SessionBudgetChecker;\n private readonly redactKeys: string[];\n private readonly opts: Required<Pick<PrismMcpOptions,\n \"project\" | \"team\" | \"environment\" | \"sessionId\" | \"serverName\" |\n \"captureInputs\" | \"captureOutputs\">> &\n Pick<PrismMcpOptions, \"sessionBudgetUsd\" | \"maxToolCallsPerSession\">;\n\n constructor(options: PrismMcpOptions = {}) {\n const key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n if (!key) {\n console.warn(\"[prism-mcp] PRISM_API_KEY not set — MCP observability disabled.\");\n }\n\n this.opts = {\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 serverName: options.serverName ?? \"mcp-server\",\n sessionBudgetUsd: options.sessionBudgetUsd,\n maxToolCallsPerSession: options.maxToolCallsPerSession,\n captureInputs: options.captureInputs ?? false,\n captureOutputs: options.captureOutputs ?? false,\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 // ── Core primitive wrapper ─────────────────────────────────────────────────\n\n /**\n * Internal wrapper used by all four MCP primitives.\n * Handles: budget check, I/O capture, streaming proxy, fire-and-forget telemetry,\n * and actual cost self-reporting via ctx.reportActualCost().\n */\n private async _wrap<T>(\n primitiveType: McpPrimitiveType,\n name: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n // 1. Pre-call budget/loop guard\n await this.budget.checkOrThrow(\n this.opts.sessionId,\n this.opts.sessionBudgetUsd,\n this.opts.maxToolCallsPerSession,\n );\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n let result: T;\n const ctx = new WrapContext();\n\n // Build tags — I/O capture applied here\n const eventTags: Record<string, string> = { ...extra.tags };\n if (this.opts.captureInputs && extra.inputs != null) {\n eventTags[\"tool_input\"] = safeJson(extra.inputs, this.redactKeys, 1000);\n }\n\n // Cost lookup — overridden by ctx.reportActualCost() if called during fn()\n const estimatedCost = primitiveType === \"tool\" ? lookupToolCost(name) : 0;\n\n const fireEvent = (latencyMs: number, finalStatus: \"ok\" | \"error\" | \"timeout\") => {\n const actualCostUsd = ctx._actualCostUsd;\n this.tracker.capture({\n timestamp: new Date().toISOString().replace(\"T\", \" \").slice(0, 23),\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: name,\n downstream_resource: ctx._downstreamResource ?? extra.downstreamResource ?? \"\",\n execution_latency_ms: latencyMs,\n tool_cost_usd: actualCostUsd ?? estimatedCost,\n cost_status: actualCostUsd != null ? \"actual\" : \"estimated\",\n status: finalStatus,\n error_message: errorMsg,\n llm_request_id: extra.llmRequestId ?? \"\",\n primitive_type: primitiveType,\n tags: eventTags,\n }).catch(() => {});\n };\n\n try {\n result = await fn(ctx);\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n fireEvent(Date.now() - start, status);\n throw err;\n }\n\n // Capture output if requested (non-streaming path)\n if (this.opts.captureOutputs && result != null && !isAsyncIterable(result)) {\n eventTags[\"tool_output\"] = safeJson(result, this.redactKeys, 1000);\n }\n\n // Streaming path — defer telemetry until stream is exhausted\n if (isAsyncIterable(result)) {\n return proxyAsyncIterable(\n result as AsyncIterable<unknown>,\n start,\n (latencyMs, threw) => {\n fireEvent(latencyMs, threw ? \"error\" : \"ok\");\n },\n ) as unknown as T;\n }\n\n // Non-streaming path — fire immediately\n fireEvent(Date.now() - start, status);\n return result;\n }\n\n // ── Public API: tools ──────────────────────────────────────────────────────\n\n /**\n * Wrap a tools/call execution with Prism instrumentation.\n */\n async wrapToolCall<T>(\n toolName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n /** Raw tool arguments — captured if captureInputs: true */\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"tool\", toolName, fn, extra);\n }\n\n /**\n * Drop-in patch for MCP SDK's CallToolRequestSchema handler.\n * Automatically passes req.params.arguments as inputs when captureInputs: true.\n * ctx is available for reportActualCost() inside the handler.\n */\n patchHandler<TReq extends { params: { name: string; arguments?: Record<string, unknown> } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedHandler(req: TReq): Promise<TRes> {\n return self.wrapToolCall(\n req.params.name,\n (ctx) => handler(req, ctx),\n { inputs: req.params.arguments },\n );\n };\n }\n\n // ── Public API: resources ──────────────────────────────────────────────────\n\n /**\n * Wrap a resources/read execution with Prism instrumentation.\n *\n * @param resourceUri - The resource URI being read (e.g. \"file:///path/to/file\")\n */\n async wrapResourceRead<T>(\n resourceUri: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"resource\", resourceUri, fn, extra);\n }\n\n patchResourceHandler<TReq extends { params: { uri: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedResourceHandler(req: TReq): Promise<TRes> {\n return self.wrapResourceRead(req.params.uri, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: prompts ────────────────────────────────────────────────────\n\n async wrapPromptGet<T>(\n promptName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"prompt\", promptName, fn, extra);\n }\n\n patchPromptHandler<TReq extends { params: { name: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedPromptHandler(req: TReq): Promise<TRes> {\n return self.wrapPromptGet(req.params.name, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: sampling ───────────────────────────────────────────────────\n\n /**\n * Wrap the MCP client's sampling/createMessage callback so LLM calls\n * requested by the MCP server appear in the session timeline.\n *\n * Usage with @modelcontextprotocol/sdk:\n * client.setRequestHandler(\n * CreateMessageRequestSchema,\n * prismMcp.wrapSamplingHandler(async (req) => {\n * const res = await openai.chat.completions.create({ ... });\n * return { role: \"assistant\", content: [{ type: \"text\", text: res.choices[0].message.content }] };\n * })\n * );\n */\n wrapSamplingHandler<\n TReq extends { params?: { modelPreferences?: { hints?: Array<{ name?: string }> } } },\n TRes,\n >(\n handler: (req: TReq) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedSamplingHandler(req: TReq): Promise<TRes> {\n // Use model hint as the \"tool_name\" for display in the session timeline\n const modelHint =\n req.params?.modelPreferences?.hints?.[0]?.name ?? \"sampling\";\n return self._wrap(\"sampling\", modelHint, (_ctx) => handler(req));\n };\n }\n\n /** The session_id assigned to this instance (useful for logging). */\n get sessionId(): string { return this.opts.sessionId; }\n}\n","/**\n * PrismSession — shared session context for multi-server agent runs.\n *\n * Creates a single session_id that is automatically threaded through:\n * - Multiple PrismMCP server instances (files, database, search, etc.)\n * - LLM SDK clients (OpenAI, Anthropic) via toLLMOptions()\n *\n * This ensures every LLM call and every tool/resource/prompt call in one\n * agent run appears under the same session in /dashboard/sessions/[id].\n *\n * Usage:\n * const session = new PrismSession({\n * prismKey: process.env.PRISM_API_KEY,\n * project: \"customer-support\",\n * sessionBudgetUsd: 2.00, // hard cap for the whole agent run\n * });\n *\n * // MCP servers — all share session.sessionId automatically\n * const filesMcp = session.createServer({ serverName: \"files\" });\n * const dbMcp = session.createServer({ serverName: \"database\" });\n * const searchMcp = session.createServer({ serverName: \"search\" });\n *\n * // LLM SDK client — same session_id\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n\nimport { PrismMCP } from \"./prism-mcp\";\nimport type { PrismMcpOptions } from \"./types\";\n\nexport interface PrismSessionOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /**\n * Explicit session ID. Auto-generated UUID if omitted.\n * Pass an explicit ID when the session ID is determined by an external\n * orchestrator (e.g. a LangGraph run ID, a task queue job ID).\n */\n sessionId?: string;\n /** Hard cost cap across ALL servers in this session */\n sessionBudgetUsd?: number;\n /** Hard tool-call cap across ALL servers in this session */\n maxToolCallsPerSession?: number;\n /** Opt-in I/O capture for all servers created from this session */\n captureInputs?: boolean;\n captureOutputs?: boolean;\n redactKeys?: string[];\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n}\n\n/** Options for individual servers within a session */\nexport type PerServerOptions = Omit<\n PrismMcpOptions,\n \"prismKey\" | \"project\" | \"team\" | \"environment\" | \"sessionId\" |\n \"sessionBudgetUsd\" | \"maxToolCallsPerSession\" | \"captureInputs\" |\n \"captureOutputs\" | \"redactKeys\" | \"ingestUrl\"\n>;\n\nexport class PrismSession {\n readonly sessionId: string;\n\n private readonly key: string;\n private readonly project: string;\n private readonly team: string;\n private readonly environment: string;\n private readonly sharedOpts: PrismSessionOptions;\n\n constructor(options: PrismSessionOptions = {}) {\n this.key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n this.project = options.project ?? process.env[\"PRISM_PROJECT\"] ?? \"\";\n this.team = options.team ?? process.env[\"PRISM_TEAM\"] ?? \"\";\n this.environment = options.environment ?? process.env[\"PRISM_ENVIRONMENT\"] ?? \"production\";\n this.sessionId = options.sessionId ?? crypto.randomUUID();\n this.sharedOpts = options;\n\n if (!this.key) {\n console.warn(\"[prism-mcp] PrismSession: PRISM_API_KEY not set — observability disabled.\");\n }\n }\n\n /**\n * Create a PrismMCP instance bound to this session.\n * All servers created from the same session share the same session_id.\n */\n createServer(perServer: PerServerOptions = {}): PrismMCP {\n return new PrismMCP({\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n sessionBudgetUsd: this.sharedOpts.sessionBudgetUsd,\n maxToolCallsPerSession: this.sharedOpts.maxToolCallsPerSession,\n captureInputs: this.sharedOpts.captureInputs,\n captureOutputs: this.sharedOpts.captureOutputs,\n redactKeys: this.sharedOpts.redactKeys,\n ingestUrl: this.sharedOpts.ingestUrl,\n // per-server overrides\n serverName: perServer.serverName,\n });\n }\n\n /**\n * Returns options to pass to the Prism LLM SDK clients (@prism-llm-labs/sdk)\n * so that LLM completions share this session's session_id.\n *\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n toLLMOptions(): {\n prismKey: string;\n project: string;\n team: string;\n environment: string;\n sessionId: string;\n } {\n return {\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n };\n }\n}\n"],"mappings":"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","names":["orgFromKey"]}
|
package/dist/index.mjs
CHANGED
|
@@ -96,27 +96,150 @@ var SessionBudgetChecker = class {
|
|
|
96
96
|
|
|
97
97
|
// src/pricing.ts
|
|
98
98
|
var BUILT_IN = {
|
|
99
|
-
//
|
|
99
|
+
// ── Filesystem & local I/O ─────────────────────────────────────────────────
|
|
100
|
+
read_file: 1e-5,
|
|
101
|
+
write_file: 5e-5,
|
|
102
|
+
create_file: 5e-5,
|
|
103
|
+
delete_file: 2e-5,
|
|
104
|
+
list_directory: 1e-5,
|
|
105
|
+
move_file: 2e-5,
|
|
106
|
+
copy_file: 2e-5,
|
|
107
|
+
search_files: 3e-5,
|
|
108
|
+
get_file_info: 5e-6,
|
|
109
|
+
read_multiple_files: 3e-5,
|
|
110
|
+
// ── Git operations ─────────────────────────────────────────────────────────
|
|
111
|
+
git_status: 5e-6,
|
|
112
|
+
git_diff: 1e-5,
|
|
113
|
+
git_commit: 5e-5,
|
|
114
|
+
git_push: 2e-4,
|
|
115
|
+
git_pull: 1e-4,
|
|
116
|
+
git_clone: 5e-4,
|
|
117
|
+
git_log: 1e-5,
|
|
118
|
+
git_branch: 5e-6,
|
|
119
|
+
git_checkout: 2e-5,
|
|
120
|
+
git_merge: 1e-4,
|
|
121
|
+
// ── GitHub API ────────────────────────────────────────────────────────────
|
|
122
|
+
create_issue: 3e-4,
|
|
123
|
+
update_issue: 3e-4,
|
|
124
|
+
close_issue: 2e-4,
|
|
125
|
+
list_issues: 1e-4,
|
|
126
|
+
get_issue: 1e-4,
|
|
127
|
+
create_pull_request: 3e-4,
|
|
128
|
+
merge_pull_request: 5e-4,
|
|
129
|
+
list_pull_requests: 1e-4,
|
|
130
|
+
search_code: 1e-3,
|
|
131
|
+
search_repositories: 5e-4,
|
|
132
|
+
get_file_contents: 1e-4,
|
|
133
|
+
create_or_update_file: 3e-4,
|
|
134
|
+
fork_repository: 5e-4,
|
|
135
|
+
create_repository: 5e-4,
|
|
136
|
+
push_files: 3e-4,
|
|
137
|
+
"github_*": 2e-4,
|
|
138
|
+
// wildcard fallback for any github_ prefixed tool
|
|
139
|
+
// ── Communication — Slack ─────────────────────────────────────────────────
|
|
140
|
+
slack_post_message: 1e-4,
|
|
141
|
+
slack_reply_to_thread: 1e-4,
|
|
142
|
+
slack_add_reaction: 5e-5,
|
|
143
|
+
slack_get_channel_info: 5e-5,
|
|
144
|
+
slack_list_channels: 8e-5,
|
|
145
|
+
slack_get_users: 1e-4,
|
|
146
|
+
slack_upload_file: 2e-4,
|
|
147
|
+
"slack_*": 8e-5,
|
|
148
|
+
// ── Communication — email / other ─────────────────────────────────────────
|
|
149
|
+
send_email: 1e-3,
|
|
150
|
+
send_slack_message: 1e-4,
|
|
151
|
+
// ── Databases ─────────────────────────────────────────────────────────────
|
|
152
|
+
postgres_query: 5e-4,
|
|
153
|
+
postgres_execute: 1e-3,
|
|
154
|
+
postgres_insert: 1e-3,
|
|
155
|
+
postgres_update: 1e-3,
|
|
156
|
+
postgres_delete: 1e-3,
|
|
157
|
+
"postgres_*": 5e-4,
|
|
158
|
+
mysql_query: 5e-4,
|
|
159
|
+
"mysql_*": 5e-4,
|
|
160
|
+
mongodb_find: 2e-4,
|
|
161
|
+
mongodb_insert: 5e-4,
|
|
162
|
+
"mongodb_*": 3e-4,
|
|
163
|
+
redis_get: 5e-5,
|
|
164
|
+
redis_set: 1e-4,
|
|
165
|
+
"redis_*": 8e-5,
|
|
166
|
+
sqlite_query: 5e-5,
|
|
167
|
+
// ── HTTP / REST ───────────────────────────────────────────────────────────
|
|
168
|
+
http_get: 2e-4,
|
|
169
|
+
http_post: 3e-4,
|
|
170
|
+
http_put: 3e-4,
|
|
171
|
+
http_delete: 2e-4,
|
|
172
|
+
http_fetch: 2e-4,
|
|
173
|
+
fetch_url: 2e-4,
|
|
174
|
+
make_api_request: 3e-4,
|
|
175
|
+
"http_*": 2e-4,
|
|
176
|
+
// ── Browser / web automation ──────────────────────────────────────────────
|
|
177
|
+
browser_navigate: 2e-3,
|
|
178
|
+
browser_screenshot: 3e-3,
|
|
179
|
+
browser_click: 1e-3,
|
|
180
|
+
browser_type: 1e-3,
|
|
181
|
+
browser_scroll: 5e-4,
|
|
182
|
+
browser_get_text: 1e-3,
|
|
183
|
+
browser_fill_form: 2e-3,
|
|
184
|
+
browser_wait: 5e-4,
|
|
185
|
+
puppeteer_navigate: 2e-3,
|
|
186
|
+
playwright_goto: 2e-3,
|
|
187
|
+
"browser_*": 2e-3,
|
|
188
|
+
"puppeteer_*": 2e-3,
|
|
189
|
+
"playwright_*": 2e-3,
|
|
190
|
+
// ── Project management — Jira ─────────────────────────────────────────────
|
|
191
|
+
create_jira_issue: 3e-4,
|
|
192
|
+
update_jira_issue: 3e-4,
|
|
193
|
+
search_jira_issues: 2e-4,
|
|
194
|
+
get_jira_issue: 1e-4,
|
|
195
|
+
add_jira_comment: 2e-4,
|
|
196
|
+
"jira_*": 2e-4,
|
|
197
|
+
// ── Project management — Linear / Notion / Asana ─────────────────────────
|
|
198
|
+
create_linear_issue: 3e-4,
|
|
199
|
+
"linear_*": 2e-4,
|
|
200
|
+
notion_create_page: 3e-4,
|
|
201
|
+
notion_update_page: 3e-4,
|
|
202
|
+
notion_search: 2e-4,
|
|
203
|
+
"notion_*": 2e-4,
|
|
204
|
+
create_asana_task: 3e-4,
|
|
205
|
+
"asana_*": 2e-4,
|
|
206
|
+
// ── Vector DBs ────────────────────────────────────────────────────────────
|
|
100
207
|
pinecone_query: 1e-6,
|
|
101
208
|
pinecone_upsert: 2e-6,
|
|
102
209
|
weaviate_query: 8e-7,
|
|
103
210
|
qdrant_search: 5e-7,
|
|
104
|
-
|
|
211
|
+
chroma_query: 5e-7,
|
|
212
|
+
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
105
213
|
lambda_invoke: 2e-7,
|
|
106
214
|
s3_get_object: 4e-7,
|
|
107
215
|
s3_put_object: 5e-6,
|
|
216
|
+
s3_list_objects: 5e-6,
|
|
108
217
|
dynamodb_get_item: 25e-8,
|
|
109
218
|
dynamodb_put_item: 125e-8,
|
|
110
|
-
|
|
219
|
+
dynamodb_query: 125e-8,
|
|
220
|
+
sqs_send_message: 4e-7,
|
|
221
|
+
sns_publish: 5e-7,
|
|
222
|
+
"aws_*": 1e-6,
|
|
223
|
+
// ── Search / web ──────────────────────────────────────────────────────────
|
|
111
224
|
brave_search: 3e-6,
|
|
112
225
|
serper_search: 1e-6,
|
|
113
226
|
tavily_search: 2e-6,
|
|
114
227
|
exa_search: 5e-6,
|
|
115
|
-
|
|
228
|
+
google_search: 5e-6,
|
|
229
|
+
bing_search: 3e-6,
|
|
230
|
+
duckduckgo_search: 1e-6,
|
|
231
|
+
web_search: 2e-6,
|
|
232
|
+
// ── Code execution ────────────────────────────────────────────────────────
|
|
116
233
|
e2b_run_code: 14e-6,
|
|
117
234
|
code_interpreter: 14e-6,
|
|
118
|
-
|
|
119
|
-
|
|
235
|
+
bash_execute: 1e-3,
|
|
236
|
+
run_command: 1e-3,
|
|
237
|
+
run_terminal_command: 1e-3,
|
|
238
|
+
docker_run: 5e-3,
|
|
239
|
+
docker_exec: 2e-3,
|
|
240
|
+
// ── AI / embeddings ───────────────────────────────────────────────────────
|
|
241
|
+
generate_embedding: 1e-5,
|
|
242
|
+
openai_embedding: 1e-5
|
|
120
243
|
};
|
|
121
244
|
function lookupToolCost(toolName, overrides = {}) {
|
|
122
245
|
const all = { ...BUILT_IN, ...overrides };
|
|
@@ -170,6 +293,8 @@ var WrapContext = class {
|
|
|
170
293
|
constructor() {
|
|
171
294
|
/** @internal - read by _wrap() after fn() completes */
|
|
172
295
|
this._actualCostUsd = null;
|
|
296
|
+
/** @internal */
|
|
297
|
+
this._downstreamResource = null;
|
|
173
298
|
}
|
|
174
299
|
/**
|
|
175
300
|
* Override the catalog-estimated tool cost with the real billing figure.
|
|
@@ -187,6 +312,28 @@ var WrapContext = class {
|
|
|
187
312
|
reportActualCost(usd) {
|
|
188
313
|
this._actualCostUsd = usd;
|
|
189
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Set the downstream resource identifier for this call.
|
|
317
|
+
* Use this when the resource name is resolved inside the handler (e.g. after
|
|
318
|
+
* looking up which Pinecone index to query).
|
|
319
|
+
*
|
|
320
|
+
* Convention:
|
|
321
|
+
* - Pinecone: "pinecone:<index-name>" e.g. "pinecone:product-embeddings"
|
|
322
|
+
* - Qdrant: "qdrant:<collection>" e.g. "qdrant:support-docs"
|
|
323
|
+
* - Generic: just the provider name e.g. "weaviate", "redis"
|
|
324
|
+
*
|
|
325
|
+
* Overrides the `downstreamResource` option passed to wrapToolCall().
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* await prismMcp.wrapToolCall("vector_search", async (ctx) => {
|
|
329
|
+
* const index = await resolveIndex(query);
|
|
330
|
+
* ctx.setDownstreamResource(`pinecone:${index}`);
|
|
331
|
+
* return pinecone.query({ index, vector });
|
|
332
|
+
* });
|
|
333
|
+
*/
|
|
334
|
+
setDownstreamResource(resource) {
|
|
335
|
+
this._downstreamResource = resource;
|
|
336
|
+
}
|
|
190
337
|
};
|
|
191
338
|
var PrismMCP = class {
|
|
192
339
|
constructor(options = {}) {
|
|
@@ -241,7 +388,7 @@ var PrismMCP = class {
|
|
|
241
388
|
user_id: "",
|
|
242
389
|
environment: this.opts.environment,
|
|
243
390
|
tool_name: name,
|
|
244
|
-
downstream_resource: extra.downstreamResource ?? "",
|
|
391
|
+
downstream_resource: ctx._downstreamResource ?? extra.downstreamResource ?? "",
|
|
245
392
|
execution_latency_ms: latencyMs,
|
|
246
393
|
tool_cost_usd: actualCostUsd ?? estimatedCost,
|
|
247
394
|
cost_status: actualCostUsd != null ? "actual" : "estimated",
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tracker.ts","../src/types.ts","../src/budget.ts","../src/pricing.ts","../src/prism-mcp.ts","../src/session.ts"],"sourcesContent":["import type { McpEvent } from \"./types\";\n\nfunction defaultIngestUrl(): string {\n const appUrl = (\n process.env[\"PRISM_APP_URL\"] ??\n process.env[\"NEXT_PUBLIC_APP_URL\"] ??\n \"https://useprism.dev\"\n ).replace(/\\/$/, \"\");\n return `${appUrl}/api/mcp/ingest`;\n}\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nexport class McpEventTracker {\n private readonly key: string;\n private readonly ingestUrl: string;\n private readonly serverName: string;\n private readonly orgId: string;\n\n constructor(key: string, serverName: string, ingestUrl?: string) {\n this.key = key;\n this.ingestUrl = ingestUrl ?? defaultIngestUrl();\n this.serverName = serverName;\n this.orgId = orgFromKey(key);\n }\n\n async capture(event: Omit<McpEvent, \"event_id\" | \"org_id\" | \"mcp_server_name\">): Promise<void> {\n try {\n const full: McpEvent = {\n ...event,\n event_id: crypto.randomUUID(),\n org_id: this.orgId,\n mcp_server_name: this.serverName,\n };\n\n const res = await fetch(this.ingestUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.key}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ events: [full] }),\n });\n\n if (!res.ok && res.status !== 422) {\n // Silently ignore — observability must never break the agent\n console.warn(`[prism-mcp] Ingest returned ${res.status}`);\n }\n } catch {\n // Never propagate\n }\n }\n}\n","export type McpPrimitiveType = \"tool\" | \"resource\" | \"prompt\" | \"sampling\";\n\nexport interface McpEvent {\n event_id: string;\n timestamp: string;\n session_id: string;\n org_id: string;\n project_id: string;\n team_id: string;\n user_id: string;\n environment: string;\n mcp_server_name: string;\n /** tool name, resource URI, prompt name, or model hint for sampling */\n tool_name: string;\n downstream_resource: string;\n execution_latency_ms: number;\n tool_cost_usd: number;\n status: \"ok\" | \"error\" | \"timeout\";\n error_message: string;\n llm_request_id: string;\n primitive_type: McpPrimitiveType;\n /**\n * Whether tool_cost_usd is an estimate from the built-in catalog (\"estimated\")\n * or a real figure provided by the tool via ctx.reportActualCost() (\"actual\").\n */\n cost_status: \"estimated\" | \"actual\";\n tags: Record<string, string>;\n}\n\nexport interface PrismMcpOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /** Explicit session ID. Auto-generated UUID if omitted. */\n sessionId?: string;\n /** MCP server name shown in the dashboard */\n serverName?: string;\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n /**\n * Session budget in USD. Tool/resource/prompt calls are blocked when the\n * combined session cost exceeds this value.\n */\n sessionBudgetUsd?: number;\n /**\n * Maximum MCP primitive calls per session. Blocks further calls when exceeded.\n * Loop detection guard — default unlimited.\n */\n maxToolCallsPerSession?: number;\n /**\n * Log call arguments into tags['tool_input'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureInputs?: boolean;\n /**\n * Log call results into tags['tool_output'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureOutputs?: boolean;\n /**\n * Keys to redact from captured inputs/outputs.\n * Default: ['password', 'token', 'key', 'secret', 'api_key', 'authorization']\n */\n redactKeys?: string[];\n}\n\nexport class PrismSessionBudgetExceededError extends Error {\n constructor(sessionId: string, budgetUsd: number) {\n super(\n `[prism-mcp] Session budget of $${budgetUsd} exceeded for session ${sessionId}. ` +\n `Tool call blocked to prevent runaway agent costs.`,\n );\n this.name = \"PrismSessionBudgetExceededError\";\n }\n}\n\nexport class PrismToolCallLimitError extends Error {\n constructor(sessionId: string, limit: number) {\n super(\n `[prism-mcp] Tool call limit of ${limit} reached for session ${sessionId}. ` +\n `Possible agent loop detected — tool call blocked.`,\n );\n this.name = \"PrismToolCallLimitError\";\n }\n}\n","/**\n * Session budget circuit breaker.\n * Checks Redis session cost + tool-call counters before each tool invocation.\n * If the UPSTASH env vars are absent, checks are skipped (graceful degradation).\n */\n\nimport {\n PrismSessionBudgetExceededError,\n PrismToolCallLimitError,\n} from \"./types\";\n\nasync function redisGet(url: string, token: string, key: string): Promise<number> {\n const res = await fetch(`${url}/get/${encodeURIComponent(key)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return 0;\n const json = await res.json() as { result: string | null };\n return parseFloat(json.result ?? \"0\") || 0;\n}\n\nexport class SessionBudgetChecker {\n private readonly url: string | null;\n private readonly token: string | null;\n private readonly orgId: string;\n\n constructor(orgId: string) {\n this.url = process.env[\"UPSTASH_REDIS_REST_URL\"] ?? null;\n this.token = process.env[\"UPSTASH_REDIS_REST_TOKEN\"] ?? null;\n this.orgId = orgId;\n }\n\n /**\n * Throws PrismSessionBudgetExceededError or PrismToolCallLimitError\n * if the session has exceeded its configured limits.\n */\n async checkOrThrow(\n sessionId: string,\n sessionBudgetUsd?: number,\n maxToolCallsPerSession?: number,\n ): Promise<void> {\n if (!this.url || !this.token) return; // No Redis configured — skip\n\n if (sessionBudgetUsd != null && sessionBudgetUsd > 0) {\n const costKey = `session:${this.orgId}:${sessionId}:cost`;\n const current = await redisGet(this.url, this.token, costKey);\n if (current >= sessionBudgetUsd) {\n throw new PrismSessionBudgetExceededError(sessionId, sessionBudgetUsd);\n }\n }\n\n if (maxToolCallsPerSession != null && maxToolCallsPerSession > 0) {\n const toolKey = `session:${this.orgId}:${sessionId}:tool_calls`;\n const count = await redisGet(this.url, this.token, toolKey);\n if (count >= maxToolCallsPerSession) {\n throw new PrismToolCallLimitError(sessionId, maxToolCallsPerSession);\n }\n }\n }\n}\n","/**\n * Built-in tool cost estimates (USD per call).\n * Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.\n */\n\nconst BUILT_IN: Record<string, number> = {\n // Vector DBs\n pinecone_query: 0.000001,\n pinecone_upsert: 0.000002,\n weaviate_query: 0.0000008,\n qdrant_search: 0.0000005,\n // AWS\n lambda_invoke: 0.0000002,\n s3_get_object: 0.0000004,\n s3_put_object: 0.000005,\n dynamodb_get_item: 0.00000025,\n dynamodb_put_item: 0.00000125,\n // Search / web\n brave_search: 0.000003,\n serper_search: 0.000001,\n tavily_search: 0.000002,\n exa_search: 0.000005,\n // Code execution\n e2b_run_code: 0.000014,\n code_interpreter: 0.000014,\n // Communication\n send_email: 0.000001,\n};\n\n/**\n * Look up estimated cost for a tool name.\n * Exact match, then prefix match (\"pinecone_*\"), then 0.\n */\nexport function lookupToolCost(\n toolName: string,\n overrides: Record<string, number> = {},\n): number {\n const all = { ...BUILT_IN, ...overrides };\n\n // Exact\n if (toolName in all) return all[toolName]!;\n\n // Prefix: find the longest matching prefix key ending in \"_*\" or \":*\"\n const prefix = Object.keys(all)\n .filter((k) => k.endsWith(\"*\") && toolName.startsWith(k.slice(0, -1)))\n .sort((a, b) => b.length - a.length)[0];\n\n return prefix ? (all[prefix] ?? 0) : 0;\n}\n","/**\n * PrismMCP — instruments any MCP Server with full observability:\n * - tools/call → wrapToolCall() / patchHandler()\n * - resources/read → wrapResourceRead() / patchResourceHandler()\n * - prompts/get → wrapPromptGet() / patchPromptHandler()\n * - sampling/createMessage → wrapSamplingHandler()\n *\n * Features:\n * - Session budget circuit breaker (throws before execution if over budget)\n * - Tool loop detection (throws if max_tool_calls_per_session exceeded)\n * - Opt-in I/O capture (captureInputs / captureOutputs)\n * - Streaming tool latency: correctly measures time-to-stream-end, not first-chunk\n */\n\nimport type { McpPrimitiveType, PrismMcpOptions } from \"./types\";\nimport { McpEventTracker } from \"./tracker\";\nimport { SessionBudgetChecker } from \"./budget\";\nimport { lookupToolCost } from \"./pricing\";\n\n// ── Private helpers ────────────────────────────────────────────────────────────\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nconst DEFAULT_REDACT_KEYS = [\"password\", \"token\", \"key\", \"secret\", \"api_key\", \"authorization\"];\n\nfunction redactObject(\n obj: unknown,\n redactKeys: string[],\n): unknown {\n if (typeof obj !== \"object\" || obj === null) return obj;\n if (Array.isArray(obj)) return obj.map((v) => redactObject(v, redactKeys));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n out[k] = redactKeys.some((r) => k.toLowerCase().includes(r.toLowerCase()))\n ? \"[REDACTED]\"\n : redactObject(v, redactKeys);\n }\n return out;\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : s.slice(0, max) + \"…\";\n}\n\nfunction safeJson(val: unknown, redactKeys: string[], maxLen: number): string {\n try {\n return truncate(JSON.stringify(redactObject(val, redactKeys)), maxLen);\n } catch {\n return \"[unserializable]\";\n }\n}\n\n// ── Streaming proxy (Epic 6) ───────────────────────────────────────────────────\n\nfunction isAsyncIterable(val: unknown): val is AsyncIterable<unknown> {\n return val != null &&\n typeof (val as AsyncIterable<unknown>)[Symbol.asyncIterator] === \"function\";\n}\n\n/**\n * Wraps an async iterable so that `onEnd` is called with the total elapsed ms\n * when the stream is fully consumed or throws. All values are forwarded unchanged.\n */\nasync function* proxyAsyncIterable<T>(\n source: AsyncIterable<T>,\n start: number,\n onEnd: (latencyMs: number, threw: boolean) => void,\n): AsyncGenerator<T> {\n let threw = false;\n try {\n for await (const value of source) {\n yield value;\n }\n } catch (err) {\n threw = true;\n throw err;\n } finally {\n onEnd(Date.now() - start, threw);\n }\n}\n\n// ── WrapContext — passed to the fn() callback for actual cost self-reporting ──\n\nexport class WrapContext {\n /** @internal - read by _wrap() after fn() completes */\n _actualCostUsd: number | null = null;\n\n /**\n * Override the catalog-estimated tool cost with the real billing figure.\n * Call this inside your tool handler when you have access to actual cost data\n * (e.g. from AWS SDK response metadata, a billing header, or a usage API).\n *\n * @example\n * await prismMcp.wrapToolCall(\"invoke_lambda\", async (ctx) => {\n * const res = await lambda.invoke({ FunctionName: \"fn\", Payload: payload });\n * const billedMs = res.$metadata.httpHeaders?.[\"x-amz-billed-duration-ms\"] ?? \"0\";\n * ctx.reportActualCost(parseInt(billedMs) * 0.000016667 / 1000);\n * return res;\n * });\n */\n reportActualCost(usd: number): void {\n this._actualCostUsd = usd;\n }\n}\n\n// ── Main class ────────────────────────────────────────────────────────────────\n\nexport class PrismMCP {\n private readonly tracker: McpEventTracker;\n private readonly budget: SessionBudgetChecker;\n private readonly redactKeys: string[];\n private readonly opts: Required<Pick<PrismMcpOptions,\n \"project\" | \"team\" | \"environment\" | \"sessionId\" | \"serverName\" |\n \"captureInputs\" | \"captureOutputs\">> &\n Pick<PrismMcpOptions, \"sessionBudgetUsd\" | \"maxToolCallsPerSession\">;\n\n constructor(options: PrismMcpOptions = {}) {\n const key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n if (!key) {\n console.warn(\"[prism-mcp] PRISM_API_KEY not set — MCP observability disabled.\");\n }\n\n this.opts = {\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 serverName: options.serverName ?? \"mcp-server\",\n sessionBudgetUsd: options.sessionBudgetUsd,\n maxToolCallsPerSession: options.maxToolCallsPerSession,\n captureInputs: options.captureInputs ?? false,\n captureOutputs: options.captureOutputs ?? false,\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 // ── Core primitive wrapper ─────────────────────────────────────────────────\n\n /**\n * Internal wrapper used by all four MCP primitives.\n * Handles: budget check, I/O capture, streaming proxy, fire-and-forget telemetry,\n * and actual cost self-reporting via ctx.reportActualCost().\n */\n private async _wrap<T>(\n primitiveType: McpPrimitiveType,\n name: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n // 1. Pre-call budget/loop guard\n await this.budget.checkOrThrow(\n this.opts.sessionId,\n this.opts.sessionBudgetUsd,\n this.opts.maxToolCallsPerSession,\n );\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n let result: T;\n const ctx = new WrapContext();\n\n // Build tags — I/O capture applied here\n const eventTags: Record<string, string> = { ...extra.tags };\n if (this.opts.captureInputs && extra.inputs != null) {\n eventTags[\"tool_input\"] = safeJson(extra.inputs, this.redactKeys, 1000);\n }\n\n // Cost lookup — overridden by ctx.reportActualCost() if called during fn()\n const estimatedCost = primitiveType === \"tool\" ? lookupToolCost(name) : 0;\n\n const fireEvent = (latencyMs: number, finalStatus: \"ok\" | \"error\" | \"timeout\") => {\n const actualCostUsd = ctx._actualCostUsd;\n this.tracker.capture({\n timestamp: new Date().toISOString().replace(\"T\", \" \").slice(0, 23),\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: name,\n downstream_resource: extra.downstreamResource ?? \"\",\n execution_latency_ms: latencyMs,\n tool_cost_usd: actualCostUsd ?? estimatedCost,\n cost_status: actualCostUsd != null ? \"actual\" : \"estimated\",\n status: finalStatus,\n error_message: errorMsg,\n llm_request_id: extra.llmRequestId ?? \"\",\n primitive_type: primitiveType,\n tags: eventTags,\n }).catch(() => {});\n };\n\n try {\n result = await fn(ctx);\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n fireEvent(Date.now() - start, status);\n throw err;\n }\n\n // Capture output if requested (non-streaming path)\n if (this.opts.captureOutputs && result != null && !isAsyncIterable(result)) {\n eventTags[\"tool_output\"] = safeJson(result, this.redactKeys, 1000);\n }\n\n // Streaming path — defer telemetry until stream is exhausted\n if (isAsyncIterable(result)) {\n return proxyAsyncIterable(\n result as AsyncIterable<unknown>,\n start,\n (latencyMs, threw) => {\n fireEvent(latencyMs, threw ? \"error\" : \"ok\");\n },\n ) as unknown as T;\n }\n\n // Non-streaming path — fire immediately\n fireEvent(Date.now() - start, status);\n return result;\n }\n\n // ── Public API: tools ──────────────────────────────────────────────────────\n\n /**\n * Wrap a tools/call execution with Prism instrumentation.\n */\n async wrapToolCall<T>(\n toolName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n /** Raw tool arguments — captured if captureInputs: true */\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"tool\", toolName, fn, extra);\n }\n\n /**\n * Drop-in patch for MCP SDK's CallToolRequestSchema handler.\n * Automatically passes req.params.arguments as inputs when captureInputs: true.\n * ctx is available for reportActualCost() inside the handler.\n */\n patchHandler<TReq extends { params: { name: string; arguments?: Record<string, unknown> } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedHandler(req: TReq): Promise<TRes> {\n return self.wrapToolCall(\n req.params.name,\n (ctx) => handler(req, ctx),\n { inputs: req.params.arguments },\n );\n };\n }\n\n // ── Public API: resources ──────────────────────────────────────────────────\n\n /**\n * Wrap a resources/read execution with Prism instrumentation.\n *\n * @param resourceUri - The resource URI being read (e.g. \"file:///path/to/file\")\n */\n async wrapResourceRead<T>(\n resourceUri: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"resource\", resourceUri, fn, extra);\n }\n\n patchResourceHandler<TReq extends { params: { uri: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedResourceHandler(req: TReq): Promise<TRes> {\n return self.wrapResourceRead(req.params.uri, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: prompts ────────────────────────────────────────────────────\n\n async wrapPromptGet<T>(\n promptName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"prompt\", promptName, fn, extra);\n }\n\n patchPromptHandler<TReq extends { params: { name: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedPromptHandler(req: TReq): Promise<TRes> {\n return self.wrapPromptGet(req.params.name, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: sampling ───────────────────────────────────────────────────\n\n /**\n * Wrap the MCP client's sampling/createMessage callback so LLM calls\n * requested by the MCP server appear in the session timeline.\n *\n * Usage with @modelcontextprotocol/sdk:\n * client.setRequestHandler(\n * CreateMessageRequestSchema,\n * prismMcp.wrapSamplingHandler(async (req) => {\n * const res = await openai.chat.completions.create({ ... });\n * return { role: \"assistant\", content: [{ type: \"text\", text: res.choices[0].message.content }] };\n * })\n * );\n */\n wrapSamplingHandler<\n TReq extends { params?: { modelPreferences?: { hints?: Array<{ name?: string }> } } },\n TRes,\n >(\n handler: (req: TReq) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedSamplingHandler(req: TReq): Promise<TRes> {\n // Use model hint as the \"tool_name\" for display in the session timeline\n const modelHint =\n req.params?.modelPreferences?.hints?.[0]?.name ?? \"sampling\";\n return self._wrap(\"sampling\", modelHint, (_ctx) => handler(req));\n };\n }\n\n /** The session_id assigned to this instance (useful for logging). */\n get sessionId(): string { return this.opts.sessionId; }\n}\n","/**\n * PrismSession — shared session context for multi-server agent runs.\n *\n * Creates a single session_id that is automatically threaded through:\n * - Multiple PrismMCP server instances (files, database, search, etc.)\n * - LLM SDK clients (OpenAI, Anthropic) via toLLMOptions()\n *\n * This ensures every LLM call and every tool/resource/prompt call in one\n * agent run appears under the same session in /dashboard/sessions/[id].\n *\n * Usage:\n * const session = new PrismSession({\n * prismKey: process.env.PRISM_API_KEY,\n * project: \"customer-support\",\n * sessionBudgetUsd: 2.00, // hard cap for the whole agent run\n * });\n *\n * // MCP servers — all share session.sessionId automatically\n * const filesMcp = session.createServer({ serverName: \"files\" });\n * const dbMcp = session.createServer({ serverName: \"database\" });\n * const searchMcp = session.createServer({ serverName: \"search\" });\n *\n * // LLM SDK client — same session_id\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n\nimport { PrismMCP } from \"./prism-mcp\";\nimport type { PrismMcpOptions } from \"./types\";\n\nexport interface PrismSessionOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /**\n * Explicit session ID. Auto-generated UUID if omitted.\n * Pass an explicit ID when the session ID is determined by an external\n * orchestrator (e.g. a LangGraph run ID, a task queue job ID).\n */\n sessionId?: string;\n /** Hard cost cap across ALL servers in this session */\n sessionBudgetUsd?: number;\n /** Hard tool-call cap across ALL servers in this session */\n maxToolCallsPerSession?: number;\n /** Opt-in I/O capture for all servers created from this session */\n captureInputs?: boolean;\n captureOutputs?: boolean;\n redactKeys?: string[];\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n}\n\n/** Options for individual servers within a session */\nexport type PerServerOptions = Omit<\n PrismMcpOptions,\n \"prismKey\" | \"project\" | \"team\" | \"environment\" | \"sessionId\" |\n \"sessionBudgetUsd\" | \"maxToolCallsPerSession\" | \"captureInputs\" |\n \"captureOutputs\" | \"redactKeys\" | \"ingestUrl\"\n>;\n\nexport class PrismSession {\n readonly sessionId: string;\n\n private readonly key: string;\n private readonly project: string;\n private readonly team: string;\n private readonly environment: string;\n private readonly sharedOpts: PrismSessionOptions;\n\n constructor(options: PrismSessionOptions = {}) {\n this.key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n this.project = options.project ?? process.env[\"PRISM_PROJECT\"] ?? \"\";\n this.team = options.team ?? process.env[\"PRISM_TEAM\"] ?? \"\";\n this.environment = options.environment ?? process.env[\"PRISM_ENVIRONMENT\"] ?? \"production\";\n this.sessionId = options.sessionId ?? crypto.randomUUID();\n this.sharedOpts = options;\n\n if (!this.key) {\n console.warn(\"[prism-mcp] PrismSession: PRISM_API_KEY not set — observability disabled.\");\n }\n }\n\n /**\n * Create a PrismMCP instance bound to this session.\n * All servers created from the same session share the same session_id.\n */\n createServer(perServer: PerServerOptions = {}): PrismMCP {\n return new PrismMCP({\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n sessionBudgetUsd: this.sharedOpts.sessionBudgetUsd,\n maxToolCallsPerSession: this.sharedOpts.maxToolCallsPerSession,\n captureInputs: this.sharedOpts.captureInputs,\n captureOutputs: this.sharedOpts.captureOutputs,\n redactKeys: this.sharedOpts.redactKeys,\n ingestUrl: this.sharedOpts.ingestUrl,\n // per-server overrides\n serverName: perServer.serverName,\n });\n }\n\n /**\n * Returns options to pass to the Prism LLM SDK clients (@prism-llm-labs/sdk)\n * so that LLM completions share this session's session_id.\n *\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n toLLMOptions(): {\n prismKey: string;\n project: string;\n team: string;\n environment: string;\n sessionId: string;\n } {\n return {\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n };\n }\n}\n"],"mappings":"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","names":["orgFromKey"]}
|
|
1
|
+
{"version":3,"sources":["../src/tracker.ts","../src/types.ts","../src/budget.ts","../src/pricing.ts","../src/prism-mcp.ts","../src/session.ts"],"sourcesContent":["import type { McpEvent } from \"./types\";\n\nfunction defaultIngestUrl(): string {\n const appUrl = (\n process.env[\"PRISM_APP_URL\"] ??\n process.env[\"NEXT_PUBLIC_APP_URL\"] ??\n \"https://useprism.dev\"\n ).replace(/\\/$/, \"\");\n return `${appUrl}/api/mcp/ingest`;\n}\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nexport class McpEventTracker {\n private readonly key: string;\n private readonly ingestUrl: string;\n private readonly serverName: string;\n private readonly orgId: string;\n\n constructor(key: string, serverName: string, ingestUrl?: string) {\n this.key = key;\n this.ingestUrl = ingestUrl ?? defaultIngestUrl();\n this.serverName = serverName;\n this.orgId = orgFromKey(key);\n }\n\n async capture(event: Omit<McpEvent, \"event_id\" | \"org_id\" | \"mcp_server_name\">): Promise<void> {\n try {\n const full: McpEvent = {\n ...event,\n event_id: crypto.randomUUID(),\n org_id: this.orgId,\n mcp_server_name: this.serverName,\n };\n\n const res = await fetch(this.ingestUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.key}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ events: [full] }),\n });\n\n if (!res.ok && res.status !== 422) {\n // Silently ignore — observability must never break the agent\n console.warn(`[prism-mcp] Ingest returned ${res.status}`);\n }\n } catch {\n // Never propagate\n }\n }\n}\n","export type McpPrimitiveType = \"tool\" | \"resource\" | \"prompt\" | \"sampling\";\n\nexport interface McpEvent {\n event_id: string;\n timestamp: string;\n session_id: string;\n org_id: string;\n project_id: string;\n team_id: string;\n user_id: string;\n environment: string;\n mcp_server_name: string;\n /** tool name, resource URI, prompt name, or model hint for sampling */\n tool_name: string;\n downstream_resource: string;\n execution_latency_ms: number;\n tool_cost_usd: number;\n status: \"ok\" | \"error\" | \"timeout\";\n error_message: string;\n llm_request_id: string;\n primitive_type: McpPrimitiveType;\n /**\n * Whether tool_cost_usd is an estimate from the built-in catalog (\"estimated\")\n * or a real figure provided by the tool via ctx.reportActualCost() (\"actual\").\n */\n cost_status: \"estimated\" | \"actual\";\n tags: Record<string, string>;\n}\n\nexport interface PrismMcpOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /** Explicit session ID. Auto-generated UUID if omitted. */\n sessionId?: string;\n /** MCP server name shown in the dashboard */\n serverName?: string;\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n /**\n * Session budget in USD. Tool/resource/prompt calls are blocked when the\n * combined session cost exceeds this value.\n */\n sessionBudgetUsd?: number;\n /**\n * Maximum MCP primitive calls per session. Blocks further calls when exceeded.\n * Loop detection guard — default unlimited.\n */\n maxToolCallsPerSession?: number;\n /**\n * Log call arguments into tags['tool_input'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureInputs?: boolean;\n /**\n * Log call results into tags['tool_output'] (truncated to 1000 chars).\n * Opt-in only — disabled by default for privacy.\n */\n captureOutputs?: boolean;\n /**\n * Keys to redact from captured inputs/outputs.\n * Default: ['password', 'token', 'key', 'secret', 'api_key', 'authorization']\n */\n redactKeys?: string[];\n}\n\nexport class PrismSessionBudgetExceededError extends Error {\n constructor(sessionId: string, budgetUsd: number) {\n super(\n `[prism-mcp] Session budget of $${budgetUsd} exceeded for session ${sessionId}. ` +\n `Tool call blocked to prevent runaway agent costs.`,\n );\n this.name = \"PrismSessionBudgetExceededError\";\n }\n}\n\nexport class PrismToolCallLimitError extends Error {\n constructor(sessionId: string, limit: number) {\n super(\n `[prism-mcp] Tool call limit of ${limit} reached for session ${sessionId}. ` +\n `Possible agent loop detected — tool call blocked.`,\n );\n this.name = \"PrismToolCallLimitError\";\n }\n}\n","/**\n * Session budget circuit breaker.\n * Checks Redis session cost + tool-call counters before each tool invocation.\n * If the UPSTASH env vars are absent, checks are skipped (graceful degradation).\n */\n\nimport {\n PrismSessionBudgetExceededError,\n PrismToolCallLimitError,\n} from \"./types\";\n\nasync function redisGet(url: string, token: string, key: string): Promise<number> {\n const res = await fetch(`${url}/get/${encodeURIComponent(key)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return 0;\n const json = await res.json() as { result: string | null };\n return parseFloat(json.result ?? \"0\") || 0;\n}\n\nexport class SessionBudgetChecker {\n private readonly url: string | null;\n private readonly token: string | null;\n private readonly orgId: string;\n\n constructor(orgId: string) {\n this.url = process.env[\"UPSTASH_REDIS_REST_URL\"] ?? null;\n this.token = process.env[\"UPSTASH_REDIS_REST_TOKEN\"] ?? null;\n this.orgId = orgId;\n }\n\n /**\n * Throws PrismSessionBudgetExceededError or PrismToolCallLimitError\n * if the session has exceeded its configured limits.\n */\n async checkOrThrow(\n sessionId: string,\n sessionBudgetUsd?: number,\n maxToolCallsPerSession?: number,\n ): Promise<void> {\n if (!this.url || !this.token) return; // No Redis configured — skip\n\n if (sessionBudgetUsd != null && sessionBudgetUsd > 0) {\n const costKey = `session:${this.orgId}:${sessionId}:cost`;\n const current = await redisGet(this.url, this.token, costKey);\n if (current >= sessionBudgetUsd) {\n throw new PrismSessionBudgetExceededError(sessionId, sessionBudgetUsd);\n }\n }\n\n if (maxToolCallsPerSession != null && maxToolCallsPerSession > 0) {\n const toolKey = `session:${this.orgId}:${sessionId}:tool_calls`;\n const count = await redisGet(this.url, this.token, toolKey);\n if (count >= maxToolCallsPerSession) {\n throw new PrismToolCallLimitError(sessionId, maxToolCallsPerSession);\n }\n }\n }\n}\n","/**\n * Built-in tool cost estimates (USD per call).\n * Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.\n *\n * Pricing basis:\n * - Filesystem / Git: trivial I/O; estimated at $0.01/1K ops\n * - GitHub API: free tier 5 000 req/hr; charged via Actions minutes at ~$0.0003/req\n * - Slack / email: negligible API cost; estimated at overhead cost\n * - Databases: typical query cost on managed DB (Neon/RDS) per operation\n * - HTTP / browser: outbound network + optional headless browser compute\n * - AWS: official published rates (Lambda $0.0000002/req, S3 $0.004/10K GET)\n * - Search APIs: published per-query pricing\n * - Code execution: E2B/code_interpreter published rates\n *\n * Prefix wildcards (\"github_*\") match any tool whose name starts with that prefix.\n * lookupToolCost() tries exact → longest-prefix → 0.\n */\n\nconst BUILT_IN: Record<string, number> = {\n // ── Filesystem & local I/O ─────────────────────────────────────────────────\n read_file: 0.00001,\n write_file: 0.00005,\n create_file: 0.00005,\n delete_file: 0.00002,\n list_directory: 0.00001,\n move_file: 0.00002,\n copy_file: 0.00002,\n search_files: 0.00003,\n get_file_info: 0.000005,\n read_multiple_files: 0.00003,\n\n // ── Git operations ─────────────────────────────────────────────────────────\n git_status: 0.000005,\n git_diff: 0.00001,\n git_commit: 0.00005,\n git_push: 0.0002,\n git_pull: 0.0001,\n git_clone: 0.0005,\n git_log: 0.00001,\n git_branch: 0.000005,\n git_checkout: 0.00002,\n git_merge: 0.0001,\n\n // ── GitHub API ────────────────────────────────────────────────────────────\n create_issue: 0.0003,\n update_issue: 0.0003,\n close_issue: 0.0002,\n list_issues: 0.0001,\n get_issue: 0.0001,\n create_pull_request: 0.0003,\n merge_pull_request: 0.0005,\n list_pull_requests: 0.0001,\n search_code: 0.001,\n search_repositories: 0.0005,\n get_file_contents: 0.0001,\n create_or_update_file: 0.0003,\n fork_repository: 0.0005,\n create_repository: 0.0005,\n push_files: 0.0003,\n \"github_*\": 0.0002, // wildcard fallback for any github_ prefixed tool\n\n // ── Communication — Slack ─────────────────────────────────────────────────\n slack_post_message: 0.0001,\n slack_reply_to_thread: 0.0001,\n slack_add_reaction: 0.00005,\n slack_get_channel_info: 0.00005,\n slack_list_channels: 0.00008,\n slack_get_users: 0.0001,\n slack_upload_file: 0.0002,\n \"slack_*\": 0.00008,\n\n // ── Communication — email / other ─────────────────────────────────────────\n send_email: 0.001,\n send_slack_message: 0.0001,\n\n // ── Databases ─────────────────────────────────────────────────────────────\n postgres_query: 0.0005,\n postgres_execute: 0.001,\n postgres_insert: 0.001,\n postgres_update: 0.001,\n postgres_delete: 0.001,\n \"postgres_*\": 0.0005,\n mysql_query: 0.0005,\n \"mysql_*\": 0.0005,\n mongodb_find: 0.0002,\n mongodb_insert: 0.0005,\n \"mongodb_*\": 0.0003,\n redis_get: 0.00005,\n redis_set: 0.0001,\n \"redis_*\": 0.00008,\n sqlite_query: 0.00005,\n\n // ── HTTP / REST ───────────────────────────────────────────────────────────\n http_get: 0.0002,\n http_post: 0.0003,\n http_put: 0.0003,\n http_delete: 0.0002,\n http_fetch: 0.0002,\n fetch_url: 0.0002,\n make_api_request: 0.0003,\n \"http_*\": 0.0002,\n\n // ── Browser / web automation ──────────────────────────────────────────────\n browser_navigate: 0.002,\n browser_screenshot: 0.003,\n browser_click: 0.001,\n browser_type: 0.001,\n browser_scroll: 0.0005,\n browser_get_text: 0.001,\n browser_fill_form: 0.002,\n browser_wait: 0.0005,\n puppeteer_navigate: 0.002,\n playwright_goto: 0.002,\n \"browser_*\": 0.002,\n \"puppeteer_*\": 0.002,\n \"playwright_*\": 0.002,\n\n // ── Project management — Jira ─────────────────────────────────────────────\n create_jira_issue: 0.0003,\n update_jira_issue: 0.0003,\n search_jira_issues: 0.0002,\n get_jira_issue: 0.0001,\n add_jira_comment: 0.0002,\n \"jira_*\": 0.0002,\n\n // ── Project management — Linear / Notion / Asana ─────────────────────────\n create_linear_issue: 0.0003,\n \"linear_*\": 0.0002,\n notion_create_page: 0.0003,\n notion_update_page: 0.0003,\n notion_search: 0.0002,\n \"notion_*\": 0.0002,\n create_asana_task: 0.0003,\n \"asana_*\": 0.0002,\n\n // ── Vector DBs ────────────────────────────────────────────────────────────\n pinecone_query: 0.000001,\n pinecone_upsert: 0.000002,\n weaviate_query: 0.0000008,\n qdrant_search: 0.0000005,\n chroma_query: 0.0000005,\n\n // ── AWS ───────────────────────────────────────────────────────────────────\n lambda_invoke: 0.0000002,\n s3_get_object: 0.0000004,\n s3_put_object: 0.000005,\n s3_list_objects: 0.000005,\n dynamodb_get_item: 0.00000025,\n dynamodb_put_item: 0.00000125,\n dynamodb_query: 0.00000125,\n sqs_send_message: 0.00000040,\n sns_publish: 0.00000050,\n \"aws_*\": 0.000001,\n\n // ── Search / web ──────────────────────────────────────────────────────────\n brave_search: 0.000003,\n serper_search: 0.000001,\n tavily_search: 0.000002,\n exa_search: 0.000005,\n google_search: 0.000005,\n bing_search: 0.000003,\n duckduckgo_search: 0.000001,\n web_search: 0.000002,\n\n // ── Code execution ────────────────────────────────────────────────────────\n e2b_run_code: 0.000014,\n code_interpreter: 0.000014,\n bash_execute: 0.001,\n run_command: 0.001,\n run_terminal_command: 0.001,\n docker_run: 0.005,\n docker_exec: 0.002,\n\n // ── AI / embeddings ───────────────────────────────────────────────────────\n generate_embedding: 0.00001,\n openai_embedding: 0.00001,\n};\n\n/**\n * Look up estimated cost for a tool name.\n * Exact match, then prefix match (\"pinecone_*\"), then 0.\n */\nexport function lookupToolCost(\n toolName: string,\n overrides: Record<string, number> = {},\n): number {\n const all = { ...BUILT_IN, ...overrides };\n\n // Exact\n if (toolName in all) return all[toolName]!;\n\n // Prefix: find the longest matching prefix key ending in \"_*\" or \":*\"\n const prefix = Object.keys(all)\n .filter((k) => k.endsWith(\"*\") && toolName.startsWith(k.slice(0, -1)))\n .sort((a, b) => b.length - a.length)[0];\n\n return prefix ? (all[prefix] ?? 0) : 0;\n}\n","/**\n * PrismMCP — instruments any MCP Server with full observability:\n * - tools/call → wrapToolCall() / patchHandler()\n * - resources/read → wrapResourceRead() / patchResourceHandler()\n * - prompts/get → wrapPromptGet() / patchPromptHandler()\n * - sampling/createMessage → wrapSamplingHandler()\n *\n * Features:\n * - Session budget circuit breaker (throws before execution if over budget)\n * - Tool loop detection (throws if max_tool_calls_per_session exceeded)\n * - Opt-in I/O capture (captureInputs / captureOutputs)\n * - Streaming tool latency: correctly measures time-to-stream-end, not first-chunk\n */\n\nimport type { McpPrimitiveType, PrismMcpOptions } from \"./types\";\nimport { McpEventTracker } from \"./tracker\";\nimport { SessionBudgetChecker } from \"./budget\";\nimport { lookupToolCost } from \"./pricing\";\n\n// ── Private helpers ────────────────────────────────────────────────────────────\n\nfunction orgFromKey(key: string): string {\n const parts = key.split(\"_\");\n return parts.length >= 4 ? (parts[2] ?? \"\") : \"\";\n}\n\nconst DEFAULT_REDACT_KEYS = [\"password\", \"token\", \"key\", \"secret\", \"api_key\", \"authorization\"];\n\nfunction redactObject(\n obj: unknown,\n redactKeys: string[],\n): unknown {\n if (typeof obj !== \"object\" || obj === null) return obj;\n if (Array.isArray(obj)) return obj.map((v) => redactObject(v, redactKeys));\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n out[k] = redactKeys.some((r) => k.toLowerCase().includes(r.toLowerCase()))\n ? \"[REDACTED]\"\n : redactObject(v, redactKeys);\n }\n return out;\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : s.slice(0, max) + \"…\";\n}\n\nfunction safeJson(val: unknown, redactKeys: string[], maxLen: number): string {\n try {\n return truncate(JSON.stringify(redactObject(val, redactKeys)), maxLen);\n } catch {\n return \"[unserializable]\";\n }\n}\n\n// ── Streaming proxy (Epic 6) ───────────────────────────────────────────────────\n\nfunction isAsyncIterable(val: unknown): val is AsyncIterable<unknown> {\n return val != null &&\n typeof (val as AsyncIterable<unknown>)[Symbol.asyncIterator] === \"function\";\n}\n\n/**\n * Wraps an async iterable so that `onEnd` is called with the total elapsed ms\n * when the stream is fully consumed or throws. All values are forwarded unchanged.\n */\nasync function* proxyAsyncIterable<T>(\n source: AsyncIterable<T>,\n start: number,\n onEnd: (latencyMs: number, threw: boolean) => void,\n): AsyncGenerator<T> {\n let threw = false;\n try {\n for await (const value of source) {\n yield value;\n }\n } catch (err) {\n threw = true;\n throw err;\n } finally {\n onEnd(Date.now() - start, threw);\n }\n}\n\n// ── WrapContext — passed to the fn() callback for actual cost self-reporting ──\n\nexport class WrapContext {\n /** @internal - read by _wrap() after fn() completes */\n _actualCostUsd: number | null = null;\n /** @internal */\n _downstreamResource: string | null = null;\n\n /**\n * Override the catalog-estimated tool cost with the real billing figure.\n * Call this inside your tool handler when you have access to actual cost data\n * (e.g. from AWS SDK response metadata, a billing header, or a usage API).\n *\n * @example\n * await prismMcp.wrapToolCall(\"invoke_lambda\", async (ctx) => {\n * const res = await lambda.invoke({ FunctionName: \"fn\", Payload: payload });\n * const billedMs = res.$metadata.httpHeaders?.[\"x-amz-billed-duration-ms\"] ?? \"0\";\n * ctx.reportActualCost(parseInt(billedMs) * 0.000016667 / 1000);\n * return res;\n * });\n */\n reportActualCost(usd: number): void {\n this._actualCostUsd = usd;\n }\n\n /**\n * Set the downstream resource identifier for this call.\n * Use this when the resource name is resolved inside the handler (e.g. after\n * looking up which Pinecone index to query).\n *\n * Convention:\n * - Pinecone: \"pinecone:<index-name>\" e.g. \"pinecone:product-embeddings\"\n * - Qdrant: \"qdrant:<collection>\" e.g. \"qdrant:support-docs\"\n * - Generic: just the provider name e.g. \"weaviate\", \"redis\"\n *\n * Overrides the `downstreamResource` option passed to wrapToolCall().\n *\n * @example\n * await prismMcp.wrapToolCall(\"vector_search\", async (ctx) => {\n * const index = await resolveIndex(query);\n * ctx.setDownstreamResource(`pinecone:${index}`);\n * return pinecone.query({ index, vector });\n * });\n */\n setDownstreamResource(resource: string): void {\n this._downstreamResource = resource;\n }\n}\n\n// ── Main class ────────────────────────────────────────────────────────────────\n\nexport class PrismMCP {\n private readonly tracker: McpEventTracker;\n private readonly budget: SessionBudgetChecker;\n private readonly redactKeys: string[];\n private readonly opts: Required<Pick<PrismMcpOptions,\n \"project\" | \"team\" | \"environment\" | \"sessionId\" | \"serverName\" |\n \"captureInputs\" | \"captureOutputs\">> &\n Pick<PrismMcpOptions, \"sessionBudgetUsd\" | \"maxToolCallsPerSession\">;\n\n constructor(options: PrismMcpOptions = {}) {\n const key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n if (!key) {\n console.warn(\"[prism-mcp] PRISM_API_KEY not set — MCP observability disabled.\");\n }\n\n this.opts = {\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 serverName: options.serverName ?? \"mcp-server\",\n sessionBudgetUsd: options.sessionBudgetUsd,\n maxToolCallsPerSession: options.maxToolCallsPerSession,\n captureInputs: options.captureInputs ?? false,\n captureOutputs: options.captureOutputs ?? false,\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 // ── Core primitive wrapper ─────────────────────────────────────────────────\n\n /**\n * Internal wrapper used by all four MCP primitives.\n * Handles: budget check, I/O capture, streaming proxy, fire-and-forget telemetry,\n * and actual cost self-reporting via ctx.reportActualCost().\n */\n private async _wrap<T>(\n primitiveType: McpPrimitiveType,\n name: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n // 1. Pre-call budget/loop guard\n await this.budget.checkOrThrow(\n this.opts.sessionId,\n this.opts.sessionBudgetUsd,\n this.opts.maxToolCallsPerSession,\n );\n\n const start = Date.now();\n let status: \"ok\" | \"error\" | \"timeout\" = \"ok\";\n let errorMsg = \"\";\n let result: T;\n const ctx = new WrapContext();\n\n // Build tags — I/O capture applied here\n const eventTags: Record<string, string> = { ...extra.tags };\n if (this.opts.captureInputs && extra.inputs != null) {\n eventTags[\"tool_input\"] = safeJson(extra.inputs, this.redactKeys, 1000);\n }\n\n // Cost lookup — overridden by ctx.reportActualCost() if called during fn()\n const estimatedCost = primitiveType === \"tool\" ? lookupToolCost(name) : 0;\n\n const fireEvent = (latencyMs: number, finalStatus: \"ok\" | \"error\" | \"timeout\") => {\n const actualCostUsd = ctx._actualCostUsd;\n this.tracker.capture({\n timestamp: new Date().toISOString().replace(\"T\", \" \").slice(0, 23),\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: name,\n downstream_resource: ctx._downstreamResource ?? extra.downstreamResource ?? \"\",\n execution_latency_ms: latencyMs,\n tool_cost_usd: actualCostUsd ?? estimatedCost,\n cost_status: actualCostUsd != null ? \"actual\" : \"estimated\",\n status: finalStatus,\n error_message: errorMsg,\n llm_request_id: extra.llmRequestId ?? \"\",\n primitive_type: primitiveType,\n tags: eventTags,\n }).catch(() => {});\n };\n\n try {\n result = await fn(ctx);\n } catch (err) {\n status = \"error\";\n errorMsg = err instanceof Error ? err.message : String(err);\n fireEvent(Date.now() - start, status);\n throw err;\n }\n\n // Capture output if requested (non-streaming path)\n if (this.opts.captureOutputs && result != null && !isAsyncIterable(result)) {\n eventTags[\"tool_output\"] = safeJson(result, this.redactKeys, 1000);\n }\n\n // Streaming path — defer telemetry until stream is exhausted\n if (isAsyncIterable(result)) {\n return proxyAsyncIterable(\n result as AsyncIterable<unknown>,\n start,\n (latencyMs, threw) => {\n fireEvent(latencyMs, threw ? \"error\" : \"ok\");\n },\n ) as unknown as T;\n }\n\n // Non-streaming path — fire immediately\n fireEvent(Date.now() - start, status);\n return result;\n }\n\n // ── Public API: tools ──────────────────────────────────────────────────────\n\n /**\n * Wrap a tools/call execution with Prism instrumentation.\n */\n async wrapToolCall<T>(\n toolName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n downstreamResource?: string;\n /** Raw tool arguments — captured if captureInputs: true */\n inputs?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"tool\", toolName, fn, extra);\n }\n\n /**\n * Drop-in patch for MCP SDK's CallToolRequestSchema handler.\n * Automatically passes req.params.arguments as inputs when captureInputs: true.\n * ctx is available for reportActualCost() inside the handler.\n */\n patchHandler<TReq extends { params: { name: string; arguments?: Record<string, unknown> } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedHandler(req: TReq): Promise<TRes> {\n return self.wrapToolCall(\n req.params.name,\n (ctx) => handler(req, ctx),\n { inputs: req.params.arguments },\n );\n };\n }\n\n // ── Public API: resources ──────────────────────────────────────────────────\n\n /**\n * Wrap a resources/read execution with Prism instrumentation.\n *\n * @param resourceUri - The resource URI being read (e.g. \"file:///path/to/file\")\n */\n async wrapResourceRead<T>(\n resourceUri: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"resource\", resourceUri, fn, extra);\n }\n\n patchResourceHandler<TReq extends { params: { uri: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedResourceHandler(req: TReq): Promise<TRes> {\n return self.wrapResourceRead(req.params.uri, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: prompts ────────────────────────────────────────────────────\n\n async wrapPromptGet<T>(\n promptName: string,\n fn: (ctx: WrapContext) => Promise<T>,\n extra: {\n llmRequestId?: string;\n tags?: Record<string, string>;\n } = {},\n ): Promise<T> {\n return this._wrap(\"prompt\", promptName, fn, extra);\n }\n\n patchPromptHandler<TReq extends { params: { name: string } }, TRes>(\n handler: (req: TReq, ctx: WrapContext) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedPromptHandler(req: TReq): Promise<TRes> {\n return self.wrapPromptGet(req.params.name, (ctx) => handler(req, ctx));\n };\n }\n\n // ── Public API: sampling ───────────────────────────────────────────────────\n\n /**\n * Wrap the MCP client's sampling/createMessage callback so LLM calls\n * requested by the MCP server appear in the session timeline.\n *\n * Usage with @modelcontextprotocol/sdk:\n * client.setRequestHandler(\n * CreateMessageRequestSchema,\n * prismMcp.wrapSamplingHandler(async (req) => {\n * const res = await openai.chat.completions.create({ ... });\n * return { role: \"assistant\", content: [{ type: \"text\", text: res.choices[0].message.content }] };\n * })\n * );\n */\n wrapSamplingHandler<\n TReq extends { params?: { modelPreferences?: { hints?: Array<{ name?: string }> } } },\n TRes,\n >(\n handler: (req: TReq) => Promise<TRes>,\n ): (req: TReq) => Promise<TRes> {\n const self = this;\n return async function patchedSamplingHandler(req: TReq): Promise<TRes> {\n // Use model hint as the \"tool_name\" for display in the session timeline\n const modelHint =\n req.params?.modelPreferences?.hints?.[0]?.name ?? \"sampling\";\n return self._wrap(\"sampling\", modelHint, (_ctx) => handler(req));\n };\n }\n\n /** The session_id assigned to this instance (useful for logging). */\n get sessionId(): string { return this.opts.sessionId; }\n}\n","/**\n * PrismSession — shared session context for multi-server agent runs.\n *\n * Creates a single session_id that is automatically threaded through:\n * - Multiple PrismMCP server instances (files, database, search, etc.)\n * - LLM SDK clients (OpenAI, Anthropic) via toLLMOptions()\n *\n * This ensures every LLM call and every tool/resource/prompt call in one\n * agent run appears under the same session in /dashboard/sessions/[id].\n *\n * Usage:\n * const session = new PrismSession({\n * prismKey: process.env.PRISM_API_KEY,\n * project: \"customer-support\",\n * sessionBudgetUsd: 2.00, // hard cap for the whole agent run\n * });\n *\n * // MCP servers — all share session.sessionId automatically\n * const filesMcp = session.createServer({ serverName: \"files\" });\n * const dbMcp = session.createServer({ serverName: \"database\" });\n * const searchMcp = session.createServer({ serverName: \"search\" });\n *\n * // LLM SDK client — same session_id\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n\nimport { PrismMCP } from \"./prism-mcp\";\nimport type { PrismMcpOptions } from \"./types\";\n\nexport interface PrismSessionOptions {\n /** Prism API key — or set PRISM_API_KEY env var */\n prismKey?: string;\n /** Project ID for cost attribution */\n project?: string;\n /** Team attribution tag */\n team?: string;\n /** \"production\" | \"staging\" | \"development\" */\n environment?: string;\n /**\n * Explicit session ID. Auto-generated UUID if omitted.\n * Pass an explicit ID when the session ID is determined by an external\n * orchestrator (e.g. a LangGraph run ID, a task queue job ID).\n */\n sessionId?: string;\n /** Hard cost cap across ALL servers in this session */\n sessionBudgetUsd?: number;\n /** Hard tool-call cap across ALL servers in this session */\n maxToolCallsPerSession?: number;\n /** Opt-in I/O capture for all servers created from this session */\n captureInputs?: boolean;\n captureOutputs?: boolean;\n redactKeys?: string[];\n /** Override ingest URL (for testing) */\n ingestUrl?: string;\n}\n\n/** Options for individual servers within a session */\nexport type PerServerOptions = Omit<\n PrismMcpOptions,\n \"prismKey\" | \"project\" | \"team\" | \"environment\" | \"sessionId\" |\n \"sessionBudgetUsd\" | \"maxToolCallsPerSession\" | \"captureInputs\" |\n \"captureOutputs\" | \"redactKeys\" | \"ingestUrl\"\n>;\n\nexport class PrismSession {\n readonly sessionId: string;\n\n private readonly key: string;\n private readonly project: string;\n private readonly team: string;\n private readonly environment: string;\n private readonly sharedOpts: PrismSessionOptions;\n\n constructor(options: PrismSessionOptions = {}) {\n this.key = options.prismKey ?? process.env[\"PRISM_API_KEY\"] ?? \"\";\n this.project = options.project ?? process.env[\"PRISM_PROJECT\"] ?? \"\";\n this.team = options.team ?? process.env[\"PRISM_TEAM\"] ?? \"\";\n this.environment = options.environment ?? process.env[\"PRISM_ENVIRONMENT\"] ?? \"production\";\n this.sessionId = options.sessionId ?? crypto.randomUUID();\n this.sharedOpts = options;\n\n if (!this.key) {\n console.warn(\"[prism-mcp] PrismSession: PRISM_API_KEY not set — observability disabled.\");\n }\n }\n\n /**\n * Create a PrismMCP instance bound to this session.\n * All servers created from the same session share the same session_id.\n */\n createServer(perServer: PerServerOptions = {}): PrismMCP {\n return new PrismMCP({\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n sessionBudgetUsd: this.sharedOpts.sessionBudgetUsd,\n maxToolCallsPerSession: this.sharedOpts.maxToolCallsPerSession,\n captureInputs: this.sharedOpts.captureInputs,\n captureOutputs: this.sharedOpts.captureOutputs,\n redactKeys: this.sharedOpts.redactKeys,\n ingestUrl: this.sharedOpts.ingestUrl,\n // per-server overrides\n serverName: perServer.serverName,\n });\n }\n\n /**\n * Returns options to pass to the Prism LLM SDK clients (@prism-llm-labs/sdk)\n * so that LLM completions share this session's session_id.\n *\n * import { OpenAI } from \"@prism-llm-labs/sdk\";\n * const openai = new OpenAI(session.toLLMOptions());\n */\n toLLMOptions(): {\n prismKey: string;\n project: string;\n team: string;\n environment: string;\n sessionId: string;\n } {\n return {\n prismKey: this.key,\n project: this.project,\n team: this.team,\n environment: this.environment,\n sessionId: this.sessionId,\n };\n }\n}\n"],"mappings":"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","names":["orgFromKey"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prism-llm-labs/mcp-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP middleware for Prism LLM observability — intercepts tool calls, tracks costs, enforces session budgets",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
package/src/pricing.ts
CHANGED
|
@@ -1,30 +1,179 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in tool cost estimates (USD per call).
|
|
3
3
|
* Mirrors apps/web/lib/pricing/tool-catalog.ts — keep in sync.
|
|
4
|
+
*
|
|
5
|
+
* Pricing basis:
|
|
6
|
+
* - Filesystem / Git: trivial I/O; estimated at $0.01/1K ops
|
|
7
|
+
* - GitHub API: free tier 5 000 req/hr; charged via Actions minutes at ~$0.0003/req
|
|
8
|
+
* - Slack / email: negligible API cost; estimated at overhead cost
|
|
9
|
+
* - Databases: typical query cost on managed DB (Neon/RDS) per operation
|
|
10
|
+
* - HTTP / browser: outbound network + optional headless browser compute
|
|
11
|
+
* - AWS: official published rates (Lambda $0.0000002/req, S3 $0.004/10K GET)
|
|
12
|
+
* - Search APIs: published per-query pricing
|
|
13
|
+
* - Code execution: E2B/code_interpreter published rates
|
|
14
|
+
*
|
|
15
|
+
* Prefix wildcards ("github_*") match any tool whose name starts with that prefix.
|
|
16
|
+
* lookupToolCost() tries exact → longest-prefix → 0.
|
|
4
17
|
*/
|
|
5
18
|
|
|
6
19
|
const BUILT_IN: Record<string, number> = {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
// ── Filesystem & local I/O ─────────────────────────────────────────────────
|
|
21
|
+
read_file: 0.00001,
|
|
22
|
+
write_file: 0.00005,
|
|
23
|
+
create_file: 0.00005,
|
|
24
|
+
delete_file: 0.00002,
|
|
25
|
+
list_directory: 0.00001,
|
|
26
|
+
move_file: 0.00002,
|
|
27
|
+
copy_file: 0.00002,
|
|
28
|
+
search_files: 0.00003,
|
|
29
|
+
get_file_info: 0.000005,
|
|
30
|
+
read_multiple_files: 0.00003,
|
|
31
|
+
|
|
32
|
+
// ── Git operations ─────────────────────────────────────────────────────────
|
|
33
|
+
git_status: 0.000005,
|
|
34
|
+
git_diff: 0.00001,
|
|
35
|
+
git_commit: 0.00005,
|
|
36
|
+
git_push: 0.0002,
|
|
37
|
+
git_pull: 0.0001,
|
|
38
|
+
git_clone: 0.0005,
|
|
39
|
+
git_log: 0.00001,
|
|
40
|
+
git_branch: 0.000005,
|
|
41
|
+
git_checkout: 0.00002,
|
|
42
|
+
git_merge: 0.0001,
|
|
43
|
+
|
|
44
|
+
// ── GitHub API ────────────────────────────────────────────────────────────
|
|
45
|
+
create_issue: 0.0003,
|
|
46
|
+
update_issue: 0.0003,
|
|
47
|
+
close_issue: 0.0002,
|
|
48
|
+
list_issues: 0.0001,
|
|
49
|
+
get_issue: 0.0001,
|
|
50
|
+
create_pull_request: 0.0003,
|
|
51
|
+
merge_pull_request: 0.0005,
|
|
52
|
+
list_pull_requests: 0.0001,
|
|
53
|
+
search_code: 0.001,
|
|
54
|
+
search_repositories: 0.0005,
|
|
55
|
+
get_file_contents: 0.0001,
|
|
56
|
+
create_or_update_file: 0.0003,
|
|
57
|
+
fork_repository: 0.0005,
|
|
58
|
+
create_repository: 0.0005,
|
|
59
|
+
push_files: 0.0003,
|
|
60
|
+
"github_*": 0.0002, // wildcard fallback for any github_ prefixed tool
|
|
61
|
+
|
|
62
|
+
// ── Communication — Slack ─────────────────────────────────────────────────
|
|
63
|
+
slack_post_message: 0.0001,
|
|
64
|
+
slack_reply_to_thread: 0.0001,
|
|
65
|
+
slack_add_reaction: 0.00005,
|
|
66
|
+
slack_get_channel_info: 0.00005,
|
|
67
|
+
slack_list_channels: 0.00008,
|
|
68
|
+
slack_get_users: 0.0001,
|
|
69
|
+
slack_upload_file: 0.0002,
|
|
70
|
+
"slack_*": 0.00008,
|
|
71
|
+
|
|
72
|
+
// ── Communication — email / other ─────────────────────────────────────────
|
|
73
|
+
send_email: 0.001,
|
|
74
|
+
send_slack_message: 0.0001,
|
|
75
|
+
|
|
76
|
+
// ── Databases ─────────────────────────────────────────────────────────────
|
|
77
|
+
postgres_query: 0.0005,
|
|
78
|
+
postgres_execute: 0.001,
|
|
79
|
+
postgres_insert: 0.001,
|
|
80
|
+
postgres_update: 0.001,
|
|
81
|
+
postgres_delete: 0.001,
|
|
82
|
+
"postgres_*": 0.0005,
|
|
83
|
+
mysql_query: 0.0005,
|
|
84
|
+
"mysql_*": 0.0005,
|
|
85
|
+
mongodb_find: 0.0002,
|
|
86
|
+
mongodb_insert: 0.0005,
|
|
87
|
+
"mongodb_*": 0.0003,
|
|
88
|
+
redis_get: 0.00005,
|
|
89
|
+
redis_set: 0.0001,
|
|
90
|
+
"redis_*": 0.00008,
|
|
91
|
+
sqlite_query: 0.00005,
|
|
92
|
+
|
|
93
|
+
// ── HTTP / REST ───────────────────────────────────────────────────────────
|
|
94
|
+
http_get: 0.0002,
|
|
95
|
+
http_post: 0.0003,
|
|
96
|
+
http_put: 0.0003,
|
|
97
|
+
http_delete: 0.0002,
|
|
98
|
+
http_fetch: 0.0002,
|
|
99
|
+
fetch_url: 0.0002,
|
|
100
|
+
make_api_request: 0.0003,
|
|
101
|
+
"http_*": 0.0002,
|
|
102
|
+
|
|
103
|
+
// ── Browser / web automation ──────────────────────────────────────────────
|
|
104
|
+
browser_navigate: 0.002,
|
|
105
|
+
browser_screenshot: 0.003,
|
|
106
|
+
browser_click: 0.001,
|
|
107
|
+
browser_type: 0.001,
|
|
108
|
+
browser_scroll: 0.0005,
|
|
109
|
+
browser_get_text: 0.001,
|
|
110
|
+
browser_fill_form: 0.002,
|
|
111
|
+
browser_wait: 0.0005,
|
|
112
|
+
puppeteer_navigate: 0.002,
|
|
113
|
+
playwright_goto: 0.002,
|
|
114
|
+
"browser_*": 0.002,
|
|
115
|
+
"puppeteer_*": 0.002,
|
|
116
|
+
"playwright_*": 0.002,
|
|
117
|
+
|
|
118
|
+
// ── Project management — Jira ─────────────────────────────────────────────
|
|
119
|
+
create_jira_issue: 0.0003,
|
|
120
|
+
update_jira_issue: 0.0003,
|
|
121
|
+
search_jira_issues: 0.0002,
|
|
122
|
+
get_jira_issue: 0.0001,
|
|
123
|
+
add_jira_comment: 0.0002,
|
|
124
|
+
"jira_*": 0.0002,
|
|
125
|
+
|
|
126
|
+
// ── Project management — Linear / Notion / Asana ─────────────────────────
|
|
127
|
+
create_linear_issue: 0.0003,
|
|
128
|
+
"linear_*": 0.0002,
|
|
129
|
+
notion_create_page: 0.0003,
|
|
130
|
+
notion_update_page: 0.0003,
|
|
131
|
+
notion_search: 0.0002,
|
|
132
|
+
"notion_*": 0.0002,
|
|
133
|
+
create_asana_task: 0.0003,
|
|
134
|
+
"asana_*": 0.0002,
|
|
135
|
+
|
|
136
|
+
// ── Vector DBs ────────────────────────────────────────────────────────────
|
|
137
|
+
pinecone_query: 0.000001,
|
|
138
|
+
pinecone_upsert: 0.000002,
|
|
139
|
+
weaviate_query: 0.0000008,
|
|
140
|
+
qdrant_search: 0.0000005,
|
|
141
|
+
chroma_query: 0.0000005,
|
|
142
|
+
|
|
143
|
+
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
144
|
+
lambda_invoke: 0.0000002,
|
|
145
|
+
s3_get_object: 0.0000004,
|
|
146
|
+
s3_put_object: 0.000005,
|
|
147
|
+
s3_list_objects: 0.000005,
|
|
148
|
+
dynamodb_get_item: 0.00000025,
|
|
149
|
+
dynamodb_put_item: 0.00000125,
|
|
150
|
+
dynamodb_query: 0.00000125,
|
|
151
|
+
sqs_send_message: 0.00000040,
|
|
152
|
+
sns_publish: 0.00000050,
|
|
153
|
+
"aws_*": 0.000001,
|
|
154
|
+
|
|
155
|
+
// ── Search / web ──────────────────────────────────────────────────────────
|
|
156
|
+
brave_search: 0.000003,
|
|
157
|
+
serper_search: 0.000001,
|
|
158
|
+
tavily_search: 0.000002,
|
|
159
|
+
exa_search: 0.000005,
|
|
160
|
+
google_search: 0.000005,
|
|
161
|
+
bing_search: 0.000003,
|
|
162
|
+
duckduckgo_search: 0.000001,
|
|
163
|
+
web_search: 0.000002,
|
|
164
|
+
|
|
165
|
+
// ── Code execution ────────────────────────────────────────────────────────
|
|
166
|
+
e2b_run_code: 0.000014,
|
|
167
|
+
code_interpreter: 0.000014,
|
|
168
|
+
bash_execute: 0.001,
|
|
169
|
+
run_command: 0.001,
|
|
170
|
+
run_terminal_command: 0.001,
|
|
171
|
+
docker_run: 0.005,
|
|
172
|
+
docker_exec: 0.002,
|
|
173
|
+
|
|
174
|
+
// ── AI / embeddings ───────────────────────────────────────────────────────
|
|
175
|
+
generate_embedding: 0.00001,
|
|
176
|
+
openai_embedding: 0.00001,
|
|
28
177
|
};
|
|
29
178
|
|
|
30
179
|
/**
|
package/src/prism-mcp.ts
CHANGED
|
@@ -86,7 +86,9 @@ async function* proxyAsyncIterable<T>(
|
|
|
86
86
|
|
|
87
87
|
export class WrapContext {
|
|
88
88
|
/** @internal - read by _wrap() after fn() completes */
|
|
89
|
-
_actualCostUsd:
|
|
89
|
+
_actualCostUsd: number | null = null;
|
|
90
|
+
/** @internal */
|
|
91
|
+
_downstreamResource: string | null = null;
|
|
90
92
|
|
|
91
93
|
/**
|
|
92
94
|
* Override the catalog-estimated tool cost with the real billing figure.
|
|
@@ -104,6 +106,29 @@ export class WrapContext {
|
|
|
104
106
|
reportActualCost(usd: number): void {
|
|
105
107
|
this._actualCostUsd = usd;
|
|
106
108
|
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Set the downstream resource identifier for this call.
|
|
112
|
+
* Use this when the resource name is resolved inside the handler (e.g. after
|
|
113
|
+
* looking up which Pinecone index to query).
|
|
114
|
+
*
|
|
115
|
+
* Convention:
|
|
116
|
+
* - Pinecone: "pinecone:<index-name>" e.g. "pinecone:product-embeddings"
|
|
117
|
+
* - Qdrant: "qdrant:<collection>" e.g. "qdrant:support-docs"
|
|
118
|
+
* - Generic: just the provider name e.g. "weaviate", "redis"
|
|
119
|
+
*
|
|
120
|
+
* Overrides the `downstreamResource` option passed to wrapToolCall().
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* await prismMcp.wrapToolCall("vector_search", async (ctx) => {
|
|
124
|
+
* const index = await resolveIndex(query);
|
|
125
|
+
* ctx.setDownstreamResource(`pinecone:${index}`);
|
|
126
|
+
* return pinecone.query({ index, vector });
|
|
127
|
+
* });
|
|
128
|
+
*/
|
|
129
|
+
setDownstreamResource(resource: string): void {
|
|
130
|
+
this._downstreamResource = resource;
|
|
131
|
+
}
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
// ── Main class ────────────────────────────────────────────────────────────────
|
|
@@ -189,7 +214,7 @@ export class PrismMCP {
|
|
|
189
214
|
user_id: "",
|
|
190
215
|
environment: this.opts.environment,
|
|
191
216
|
tool_name: name,
|
|
192
|
-
downstream_resource: extra.downstreamResource ?? "",
|
|
217
|
+
downstream_resource: ctx._downstreamResource ?? extra.downstreamResource ?? "",
|
|
193
218
|
execution_latency_ms: latencyMs,
|
|
194
219
|
tool_cost_usd: actualCostUsd ?? estimatedCost,
|
|
195
220
|
cost_status: actualCostUsd != null ? "actual" : "estimated",
|