@agentwares/agentguard 0.1.6 → 0.1.9

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +223 -16
  3. package/dist/audit-2YCMHMCP.js +17 -0
  4. package/dist/audit-2YCMHMCP.js.map +1 -0
  5. package/dist/chunk-3O6KWT3E.js +345 -0
  6. package/dist/chunk-3O6KWT3E.js.map +1 -0
  7. package/dist/chunk-5WRI5ZAA.js +31 -0
  8. package/dist/chunk-5WRI5ZAA.js.map +1 -0
  9. package/dist/chunk-6S7LYVOC.js +1033 -0
  10. package/dist/chunk-6S7LYVOC.js.map +1 -0
  11. package/dist/chunk-6TUQD666.js +828 -0
  12. package/dist/chunk-6TUQD666.js.map +1 -0
  13. package/dist/chunk-BKXXTWIS.js +119 -0
  14. package/dist/chunk-BKXXTWIS.js.map +1 -0
  15. package/dist/chunk-D66U3PVB.js +529 -0
  16. package/dist/chunk-D66U3PVB.js.map +1 -0
  17. package/dist/chunk-HPYZ3JDX.js +206 -0
  18. package/dist/chunk-HPYZ3JDX.js.map +1 -0
  19. package/dist/chunk-KRKZGAJ3.js +769 -0
  20. package/dist/chunk-KRKZGAJ3.js.map +1 -0
  21. package/dist/chunk-SUXQTT3X.js +1268 -0
  22. package/dist/chunk-SUXQTT3X.js.map +1 -0
  23. package/dist/chunk-TW5KJREH.js +190 -0
  24. package/dist/chunk-TW5KJREH.js.map +1 -0
  25. package/dist/chunk-VQWQ5TOM.js +113 -0
  26. package/dist/chunk-VQWQ5TOM.js.map +1 -0
  27. package/dist/chunk-X4M7YWQA.js +270 -0
  28. package/dist/chunk-X4M7YWQA.js.map +1 -0
  29. package/dist/chunk-ZSLWPKXI.js +1144 -0
  30. package/dist/chunk-ZSLWPKXI.js.map +1 -0
  31. package/dist/cli.js +8 -4725
  32. package/dist/cli.js.map +1 -1
  33. package/dist/fixtures/crm-server.js +6 -1272
  34. package/dist/fixtures/crm-server.js.map +1 -1
  35. package/dist/fixtures/demo-agent.js +4 -115
  36. package/dist/fixtures/demo-agent.js.map +1 -1
  37. package/dist/index.d.ts +76 -10
  38. package/dist/index.js +246 -5087
  39. package/dist/index.js.map +1 -1
  40. package/dist/init-CWROGV5I.js +15 -0
  41. package/dist/init-CWROGV5I.js.map +1 -0
  42. package/dist/proxy-3Z5YEZ4R.js +154 -0
  43. package/dist/proxy-3Z5YEZ4R.js.map +1 -0
  44. package/dist/tools-3LLDG3XQ.js +15 -0
  45. package/dist/tools-3LLDG3XQ.js.map +1 -0
  46. package/llms.txt +4 -0
  47. package/package.json +9 -3
  48. package/server.json +2 -2
@@ -1,1277 +1,11 @@
1
1
  #!/usr/bin/env node
2
-
3
- // ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
4
- import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
5
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
6
2
  import {
7
- CreateMessageResultSchema,
8
- ElicitResultSchema,
9
- SubscribeRequestSchema,
10
- UnsubscribeRequestSchema
11
- } from "@modelcontextprotocol/sdk/types.js";
12
- import { z } from "zod";
13
-
14
- // ../../packages/x402/dist/index.js
15
- var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
16
- var USDC_DECIMALS = 6;
17
- var USDC_EIP712_DOMAIN = { name: "USD Coin", version: "2" };
18
- function toAtomicUnits(amountUsd, decimals = USDC_DECIMALS) {
19
- const trimmed = amountUsd.trim();
20
- if (!/^\d+(\.\d+)?$/.test(trimmed)) throw new Error(`not a decimal amount: ${amountUsd}`);
21
- const [whole = "0", fraction = ""] = trimmed.split(".");
22
- if (fraction.length > decimals) {
23
- throw new Error(`${amountUsd} has more than ${decimals} decimal places`);
24
- }
25
- return `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
26
- }
27
- var DEFAULT_X402_NETWORK = "eip155:8453";
28
- var X402_HEADER = "X-Payment-Requirements";
29
- var X402_REQUIRED_HEADER = "PAYMENT-REQUIRED";
30
- function toAmount(priceUsd) {
31
- if (!Number.isFinite(priceUsd) || priceUsd < 0)
32
- throw new Error("priceUsd must be a non-negative number");
33
- return priceUsd.toFixed(6).replace(/0+$/, "").replace(/\.$/, ".0");
34
- }
35
- function buildPaymentRequired(opts) {
36
- const amountUsd = toAmount(opts.priceUsd);
37
- const creditsPayTo = opts.creditsPayTo ?? opts.selfServe?.keysUrl;
38
- const accepts = [];
39
- if (opts.payTo) {
40
- accepts.push({
41
- rail: "x402",
42
- scheme: "exact",
43
- amountUsd,
44
- amount: toAtomicUnits(amountUsd, opts.assetDecimals),
45
- resource: opts.resource,
46
- description: opts.description,
47
- mimeType: "application/json",
48
- network: opts.network ?? DEFAULT_X402_NETWORK,
49
- asset: opts.asset ?? USDC_BASE,
50
- extra: opts.assetDomain ?? USDC_EIP712_DOMAIN,
51
- payTo: opts.payTo,
52
- maxTimeoutSeconds: 300,
53
- ...opts.bazaar ? { bazaar: opts.bazaar } : {}
54
- });
55
- }
56
- if (creditsPayTo) {
57
- accepts.push({
58
- rail: "prepaid_credits",
59
- amountUsd,
60
- resource: opts.resource,
61
- description: opts.description,
62
- currency: "usd",
63
- payTo: creditsPayTo,
64
- maxTimeoutSeconds: 300,
65
- // funding a balance ends at a hosted Stripe Checkout page, which an agent cannot fill in
66
- humanRequired: true
67
- });
68
- }
69
- const live = opts.live === true;
70
- const selfServe = opts.selfServe;
71
- const x402Rail = accepts.find((a) => a.rail === "x402" && a.payTo);
72
- const usdcFix = x402Rail ? `Pay in USDC with no account and no human: take the \`accepts\` entry with \`rail: "x402"\`, sign an EIP-3009 transferWithAuthorization for ${x402Rail.amountUsd} USDC to ${x402Rail.payTo} on ${x402Rail.network}, and retry with it base64 in the X-PAYMENT header. The facilitator submits the transfer, so you need USDC and no gas. ` : "";
73
- const sampleFix = opts.sampleMode ? "Free first: retry with sample=true \u2014 no key, no payment. " : "";
74
- const cardFix = selfServe ? `Or pay by card: POST ${selfServe.keysUrl} (no auth, no signup) returns a key and a Stripe Checkout URL. Funding that key needs a person at a browser; if you are running unattended, surface the URL to your operator rather than retrying, or use the USDC rail above. Once funded, send Authorization: Bearer <key>, plus Idempotency-Key so a repeated call is charged once.` : "";
75
- const liveFix = usdcFix || cardFix ? `${sampleFix}${usdcFix}${cardFix}`.trim() : "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance.";
76
- const body = {
77
- code: "PAYMENT_REQUIRED",
78
- cause: live ? `This call costs $${amountUsd}. No valid payment or credit was presented.` : `This call costs $${amountUsd}. Pay-per-call (x402 / Stripe MPP) is coming soon; use an API key with prepaid credits.`,
79
- fix: live ? liveFix : `Create an API key${opts.signupUrl ? ` at ${opts.signupUrl}` : ""} and retry with Authorization: Bearer <key>. Try the free sample mode first.`,
80
- retryable: true,
81
- priceUsd: opts.priceUsd,
82
- accepts,
83
- comingSoon: !live,
84
- alternatives: [
85
- ...creditsPayTo ? ["api_key_credits"] : [],
86
- ...opts.sampleMode ? ["sample_mode"] : []
87
- ],
88
- ...selfServe ? { selfServe } : {},
89
- ...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
90
- };
91
- const encoded = JSON.stringify({
92
- x402Version: 2,
93
- accepts,
94
- resource: opts.resource,
95
- ...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
96
- });
97
- const headers = {
98
- "Content-Type": "application/json",
99
- "WWW-Authenticate": `Payment realm="agentwares", methods="x402 prepaid_credits", price="${amountUsd} USD"`,
100
- [X402_HEADER]: encoded,
101
- [X402_REQUIRED_HEADER]: btoa(encoded),
102
- "Cache-Control": "no-store"
103
- };
104
- return { status: 402, headers, body };
105
- }
106
- var StubVerifier = class {
107
- async verify(request, requirement) {
108
- const hasHeader = request.headers.has("PAYMENT") || request.headers.has("X-PAYMENT");
109
- return {
110
- ok: false,
111
- rail: requirement.rail,
112
- reason: hasHeader ? "payments are not live yet (phase 2: wallet). Use an API key with credits." : "no payment header present"
113
- };
114
- }
115
- };
116
-
117
- // ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
118
- import { z as z2 } from "zod";
119
- import { z as z3 } from "zod";
120
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
121
- import {
122
- CallToolRequestSchema,
123
- ErrorCode,
124
- ListToolsRequestSchema,
125
- McpError
126
- } from "@modelcontextprotocol/sdk/types.js";
127
- import { z as z4 } from "zod";
128
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
129
- var ERROR_CODES = [
130
- "INVALID_INPUT",
131
- "NOT_FOUND",
132
- "UNAUTHORIZED",
133
- "PAYMENT_REQUIRED",
134
- "RATE_LIMITED",
135
- "UPSTREAM_ERROR",
136
- "INTERNAL"
137
- ];
138
- var DEFAULT_RETRYABLE = {
139
- INVALID_INPUT: false,
140
- NOT_FOUND: false,
141
- UNAUTHORIZED: false,
142
- PAYMENT_REQUIRED: true,
143
- RATE_LIMITED: true,
144
- UPSTREAM_ERROR: true,
145
- INTERNAL: false
146
- };
147
- var DEFAULT_HTTP_STATUS = {
148
- INVALID_INPUT: 400,
149
- NOT_FOUND: 404,
150
- UNAUTHORIZED: 401,
151
- PAYMENT_REQUIRED: 402,
152
- RATE_LIMITED: 429,
153
- UPSTREAM_ERROR: 502,
154
- INTERNAL: 500
155
- };
156
- var INTERNAL_FIX = "retry; if it persists, report the request id";
157
- function isStandardCode(code) {
158
- return ERROR_CODES.includes(code);
159
- }
160
- var McpToolError = class extends Error {
161
- code;
162
- cause;
163
- fix;
164
- retryable;
165
- details;
166
- httpStatus;
167
- extra;
168
- constructor(init) {
169
- super(`${init.code}: ${init.cause}`);
170
- this.name = "McpToolError";
171
- this.code = init.code;
172
- this.cause = init.cause;
173
- this.fix = init.fix;
174
- this.retryable = init.retryable ?? (isStandardCode(init.code) ? DEFAULT_RETRYABLE[init.code] : false);
175
- this.details = init.details;
176
- this.httpStatus = init.httpStatus ?? (isStandardCode(init.code) ? DEFAULT_HTTP_STATUS[init.code] : 500);
177
- this.extra = init.extra;
178
- }
179
- /** The body agents see: `{ code, cause, fix, retryable, details?, ...extra }`. */
180
- toJSON() {
181
- const body = {
182
- ...this.extra,
183
- code: this.code,
184
- cause: this.cause,
185
- fix: this.fix,
186
- retryable: this.retryable
187
- };
188
- if (this.details !== void 0) body.details = this.details;
189
- return body;
190
- }
191
- };
192
- function toolError(codeOrInit, cause, fix, extra) {
193
- if (typeof codeOrInit === "object") return new McpToolError(codeOrInit);
194
- return new McpToolError({ code: codeOrInit, cause: cause ?? "", fix: fix ?? "", ...extra });
195
- }
196
- function describeUnknown(err) {
197
- if (err instanceof Error) return err.message || err.name;
198
- if (typeof err === "string") return err;
199
- try {
200
- return JSON.stringify(err);
201
- } catch {
202
- return String(err);
203
- }
204
- }
205
- function normalizeError(err) {
206
- if (err instanceof McpToolError) return err;
207
- return new McpToolError({
208
- code: "INTERNAL",
209
- cause: describeUnknown(err),
210
- fix: INTERNAL_FIX,
211
- retryable: false
212
- });
213
- }
214
- function errorResult(err, opts = {}) {
215
- const error = normalizeError(err);
216
- const body = error.toJSON();
217
- const meta = { httpStatus: error.httpStatus };
218
- if (opts.requestId !== void 0) meta.requestId = opts.requestId;
219
- Object.assign(meta, opts.meta);
220
- return {
221
- isError: true,
222
- content: [{ type: "text", text: JSON.stringify(body) }],
223
- structuredContent: body,
224
- _meta: meta
225
- };
226
- }
227
- var TOOL_NAME_PATTERN = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
228
- var MIN_NAMESPACE_LENGTH = 3;
229
- var MIN_DESCRIPTION_LENGTH = 20;
230
- function isValidToolName(name) {
231
- if (!TOOL_NAME_PATTERN.test(name)) return false;
232
- const namespace = name.split("_")[0] ?? "";
233
- return namespace.length >= MIN_NAMESPACE_LENGTH;
234
- }
235
- function assertToolName(name) {
236
- if (!isValidToolName(name)) {
237
- throw new Error(
238
- `tool name must be namespaced: <namespace>_<verb>_<object>, got ${JSON.stringify(name)}`
239
- );
240
- }
241
- }
242
- function isPlainObject(value) {
243
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
244
- const proto = Object.getPrototypeOf(value);
245
- return proto === Object.prototype || proto === null;
246
- }
247
- function isCallToolResult(value) {
248
- if (!isPlainObject(value) || !Array.isArray(value.content)) return false;
249
- return value.content.every(
250
- (block) => isPlainObject(block) && typeof block.type === "string"
251
- );
252
- }
253
- function toCallToolResult(value) {
254
- if (isCallToolResult(value)) return value;
255
- const text = typeof value === "string" ? value : JSON.stringify(value ?? null, null, 2);
256
- const result = { content: [{ type: "text", text }] };
257
- if (isPlainObject(value)) result.structuredContent = value;
258
- return result;
259
- }
260
- function issuePath(issue) {
261
- return issue.path.map(String).join(".") || "(root)";
262
- }
263
- function invalidInputError(issues) {
264
- const fields = [...new Set(issues.map(issuePath))];
265
- const summary = issues.map((issue) => `${issuePath(issue)}: ${issue.message}`).join("; ");
266
- return toolError({
267
- code: "INVALID_INPUT",
268
- cause: `invalid input for ${fields.join(", ")} \u2014 ${summary}`,
269
- fix: `fix ${fields.join(", ")} to match the tool's inputSchema (see details.issues), then retry`,
270
- retryable: false,
271
- details: {
272
- fields,
273
- issues: issues.map((issue) => ({
274
- path: issue.path.map(String),
275
- code: issue.code,
276
- message: issue.message
277
- }))
278
- }
279
- });
280
- }
281
- function defineTool(opts) {
282
- assertToolName(opts.name);
283
- if (typeof opts.description !== "string" || opts.description.trim().length < MIN_DESCRIPTION_LENGTH) {
284
- throw new Error(
285
- `tool ${opts.name}: description must be at least ${MIN_DESCRIPTION_LENGTH} characters \u2014 say what it does, when to use it and what it returns`
286
- );
287
- }
288
- const { name, title, description, input, output, annotations } = opts;
289
- const handler = opts.handler.bind(opts);
290
- const tool = {
291
- name,
292
- title,
293
- description,
294
- input,
295
- output,
296
- annotations,
297
- handler,
298
- async invoke(rawInput, ctx) {
299
- const fail = (err) => {
300
- const result = errorResult(err, { requestId: ctx.requestId });
301
- if (output) delete result.structuredContent;
302
- return result;
303
- };
304
- const parsed = input.safeParse(rawInput ?? {});
305
- if (!parsed.success) return fail(invalidInputError(parsed.error.issues));
306
- try {
307
- const value = await handler(parsed.data, ctx);
308
- if (output && !isCallToolResult(value)) {
309
- const checked = output.safeParse(value);
310
- if (!checked.success) {
311
- throw toolError({
312
- code: "INTERNAL",
313
- cause: `tool ${name} returned a value that does not match its output schema \u2014 ${checked.error.issues.map((issue) => `${issuePath(issue)}: ${issue.message}`).join("; ")}`,
314
- fix: "this is a bug in the tool, not in your call; report it with the request id",
315
- retryable: false,
316
- details: { issues: checked.error.issues }
317
- });
318
- }
319
- return toCallToolResult(checked.data);
320
- }
321
- return toCallToolResult(value);
322
- } catch (err) {
323
- return fail(err);
324
- }
325
- }
326
- };
327
- return tool;
328
- }
329
- var PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
330
- var WAV_BASE64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
331
- var STATIC_TEXT_URI = "test://static-text";
332
- var STATIC_TEXT = "This is a static text resource for testing.";
333
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
334
- function embeddedResource() {
335
- return {
336
- type: "resource",
337
- resource: { uri: STATIC_TEXT_URI, mimeType: "text/plain", text: STATIC_TEXT }
338
- };
339
- }
340
- function requireExtra(ctx) {
341
- if (!ctx.extra) {
342
- throw toolError("INTERNAL", "no client connection in this context", "call this tool over MCP");
343
- }
344
- return ctx.extra;
345
- }
346
- async function elicit(ctx, message, requestedSchema) {
347
- const result = await requireExtra(ctx).sendRequest(
348
- { method: "elicitation/create", params: { message, requestedSchema } },
349
- ElicitResultSchema
350
- );
351
- return `Elicitation completed: action=${result.action}, content=${JSON.stringify(result.content ?? {})}`;
352
- }
353
- var conformanceTools = [
354
- defineTool({
355
- name: "test_simple_text",
356
- description: "Conformance fixture: returns a fixed simple text response.",
357
- input: z.object({}),
358
- handler: () => "This is a simple text response for testing."
359
- }),
360
- defineTool({
361
- name: "test_error_handling",
362
- description: "Conformance fixture: always returns an error result (isError: true).",
363
- input: z.object({}),
364
- handler: () => {
365
- throw toolError({
366
- code: "UPSTREAM_ERROR",
367
- cause: "This tool intentionally returns an error for testing",
368
- fix: "nothing to fix; this fixture always fails",
369
- retryable: false
370
- });
371
- }
372
- }),
373
- defineTool({
374
- name: "test_image_content",
375
- description: "Conformance fixture: returns a 1x1 PNG image content block.",
376
- input: z.object({}),
377
- handler: () => ({ content: [{ type: "image", data: PNG_BASE64, mimeType: "image/png" }] })
378
- }),
379
- defineTool({
380
- name: "test_audio_content",
381
- description: "Conformance fixture: returns an empty WAV audio content block.",
382
- input: z.object({}),
383
- handler: () => ({ content: [{ type: "audio", data: WAV_BASE64, mimeType: "audio/wav" }] })
384
- }),
385
- defineTool({
386
- name: "test_embedded_resource",
387
- description: "Conformance fixture: returns an embedded text resource content block.",
388
- input: z.object({}),
389
- handler: () => ({ content: [embeddedResource()] })
390
- }),
391
- defineTool({
392
- name: "test_multiple_content_types",
393
- description: "Conformance fixture: returns text, image and resource content blocks together.",
394
- input: z.object({}),
395
- handler: () => ({
396
- content: [
397
- { type: "text", text: "Mixed content response" },
398
- { type: "image", data: PNG_BASE64, mimeType: "image/png" },
399
- embeddedResource()
400
- ]
401
- })
402
- }),
403
- defineTool({
404
- name: "test_tool_with_logging",
405
- description: "Conformance fixture: emits three info log notifications while running.",
406
- input: z.object({}),
407
- handler: async (_input, ctx) => {
408
- await ctx.log?.("info", "Tool execution started");
409
- await sleep(50);
410
- await ctx.log?.("info", "Tool processing data");
411
- await sleep(50);
412
- await ctx.log?.("info", "Tool execution completed");
413
- return "Tool execution completed with logging";
414
- }
415
- }),
416
- defineTool({
417
- name: "test_tool_with_progress",
418
- description: "Conformance fixture: reports progress 0/100, 50/100, 100/100 while running.",
419
- input: z.object({}),
420
- handler: async (_input, ctx) => {
421
- await ctx.progress?.(0, 100, "starting");
422
- await sleep(50);
423
- await ctx.progress?.(50, 100, "halfway");
424
- await sleep(50);
425
- await ctx.progress?.(100, 100, "done");
426
- return "Tool execution completed with progress";
427
- }
428
- }),
429
- defineTool({
430
- name: "test_sampling",
431
- description: "Conformance fixture: asks the client to sample a completion and returns it.",
432
- input: z.object({ prompt: z.string().describe("Prompt to send to the client's model") }),
433
- handler: async ({ prompt }, ctx) => {
434
- const result = await requireExtra(ctx).sendRequest(
435
- {
436
- method: "sampling/createMessage",
437
- params: {
438
- messages: [{ role: "user", content: { type: "text", text: prompt } }],
439
- maxTokens: 100
440
- }
441
- },
442
- CreateMessageResultSchema
443
- );
444
- const content = Array.isArray(result.content) ? result.content[0] : result.content;
445
- const text = content && content.type === "text" ? content.text : JSON.stringify(content);
446
- return `Sampling completed (${result.model}): ${text}`;
447
- }
448
- }),
449
- defineTool({
450
- name: "test_elicitation",
451
- description: "Conformance fixture: asks the client for user input and reports the outcome.",
452
- input: z.object({ message: z.string().describe("Message shown to the user") }),
453
- handler: ({ message }, ctx) => elicit(ctx, message, {
454
- type: "object",
455
- properties: {
456
- name: { type: "string", title: "Name", description: "Your name" },
457
- email: { type: "string", title: "Email", format: "email" }
458
- },
459
- required: ["name"]
460
- })
461
- }),
462
- defineTool({
463
- name: "test_elicitation_sep1034_defaults",
464
- description: "Conformance fixture: elicitation schema with defaults for every primitive type.",
465
- input: z.object({}),
466
- handler: (_input, ctx) => elicit(ctx, "Please confirm your details", {
467
- type: "object",
468
- properties: {
469
- name: { type: "string", title: "Name", default: "John Doe" },
470
- age: { type: "integer", title: "Age", default: 30 },
471
- score: { type: "number", title: "Score", default: 95.5 },
472
- status: {
473
- type: "string",
474
- title: "Status",
475
- enum: ["active", "inactive", "pending"],
476
- default: "active"
477
- },
478
- verified: { type: "boolean", title: "Verified", default: true }
479
- }
480
- })
481
- }),
482
- defineTool({
483
- name: "test_elicitation_sep1330_enums",
484
- description: "Conformance fixture: elicitation schema with titled, untitled and multi-select enums.",
485
- input: z.object({}),
486
- handler: (_input, ctx) => elicit(ctx, "Pick some options", {
487
- type: "object",
488
- properties: {
489
- untitledSingle: {
490
- type: "string",
491
- title: "Untitled single",
492
- enum: ["red", "green", "blue"]
493
- },
494
- titledSingle: {
495
- type: "string",
496
- title: "Titled single",
497
- oneOf: [
498
- { const: "red", title: "Red" },
499
- { const: "green", title: "Green" },
500
- { const: "blue", title: "Blue" }
501
- ]
502
- },
503
- legacyEnum: {
504
- type: "string",
505
- title: "Legacy titled",
506
- enum: ["red", "green", "blue"],
507
- enumNames: ["Red", "Green", "Blue"]
508
- },
509
- untitledMulti: {
510
- type: "array",
511
- title: "Untitled multi",
512
- items: { type: "string", enum: ["red", "green", "blue"] }
513
- },
514
- titledMulti: {
515
- type: "array",
516
- title: "Titled multi",
517
- items: {
518
- anyOf: [
519
- { const: "red", title: "Red" },
520
- { const: "green", title: "Green" },
521
- { const: "blue", title: "Blue" }
522
- ]
523
- }
524
- }
525
- }
526
- })
527
- }),
528
- defineTool({
529
- name: "json_schema_2020_12_tool",
530
- description: "Conformance fixture: input schema with a $defs reference to an address object.",
531
- input: z.object({
532
- name: z.string(),
533
- address: z.object({ street: z.string(), city: z.string() }).meta({ id: "address" })
534
- }),
535
- handler: (input) => input
536
- })
537
- ];
538
- function paymentRequiredError(body, reason) {
539
- const { code, cause, fix, retryable, ...rest } = body;
540
- return toolError({
541
- code,
542
- cause,
543
- fix,
544
- retryable,
545
- httpStatus: 402,
546
- details: reason !== void 0 ? { reason } : void 0,
547
- extra: rest
548
- });
549
- }
550
- function formatUsd(priceUsd) {
551
- return priceUsd.toFixed(6).replace(/0+$/, "").replace(/\.$/, ".00").replace(/\.(\d)$/, ".$10");
552
- }
553
- function withPayment(tool, opts) {
554
- if (!Number.isFinite(opts.priceUsd) || opts.priceUsd < 0) {
555
- throw new Error(`withPayment(${tool.name}): priceUsd must be a non-negative number`);
556
- }
557
- const sampleField = opts.sampleField ?? "sample";
558
- if (sampleField in tool.input.shape) {
559
- throw new Error(
560
- `withPayment(${tool.name}): input already has a field named ${JSON.stringify(sampleField)}; pass sampleField to rename it`
561
- );
562
- }
563
- const verifier = opts.verifier ?? new StubVerifier();
564
- const liveRail = opts.verifier !== void 0;
565
- const input = tool.input.extend({
566
- [sampleField]: z2.boolean().default(false).describe(
567
- "Set true to return an example response at no charge (sample mode). Default false."
568
- )
569
- });
570
- const description = `${tool.description.trim()} Paid: $${formatUsd(opts.priceUsd)} per call; without credit you get a PAYMENT_REQUIRED result. Set ${sampleField}=true for a free example response.`;
571
- return defineTool({
572
- name: tool.name,
573
- title: tool.title,
574
- description,
575
- input,
576
- output: tool.output,
577
- annotations: tool.annotations,
578
- async handler(raw, ctx) {
579
- const record = { ...raw };
580
- const sample = record[sampleField] === true;
581
- delete record[sampleField];
582
- if (sample) return tool.handler(record, { ...ctx, sample: true });
583
- let reason;
584
- if (opts.authorize) {
585
- const auth = await opts.authorize(ctx);
586
- if (auth.ok) return tool.handler(record, { ...ctx, userId: auth.userId });
587
- reason = auth.reason;
588
- }
589
- const resource = ctx.resourceUrl ?? tool.name;
590
- const required = buildPaymentRequired({
591
- resource,
592
- priceUsd: opts.priceUsd,
593
- live: opts.live,
594
- description: opts.description,
595
- payTo: opts.payTo,
596
- creditsPayTo: opts.creditsPayTo,
597
- signupUrl: opts.signupUrl,
598
- selfServe: opts.selfServe,
599
- // withPayment always adds a free `sample` flag to the tool's input schema
600
- sampleMode: true,
601
- ...opts.bazaar ? { bazaar: opts.bazaar } : {}
602
- });
603
- const advertised = required.body.accepts.find((a) => a.rail === "prepaid_credits") ?? required.body.accepts[0];
604
- const requirement = advertised ?? {
605
- rail: "prepaid_credits",
606
- amountUsd: String(required.body.priceUsd),
607
- resource,
608
- ...opts.description ? { description: opts.description } : {}
609
- };
610
- const verified = await verifier.verify(
611
- new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
612
- requirement
613
- );
614
- if (verified.ok) {
615
- let result;
616
- try {
617
- result = await tool.handler(record, ctx);
618
- } catch (error) {
619
- await opts.onPaidFailure?.(ctx, verified, error);
620
- throw error;
621
- }
622
- const settled = await verifier.settle?.(verified);
623
- if (settled && !settled.ok) {
624
- opts.onSettlementFailure?.(ctx, verified, settled.reason);
625
- }
626
- return result;
627
- }
628
- reason = liveRail ? verified.reason ?? reason : reason ?? verified.reason;
629
- throw paymentRequiredError(required.body, reason);
630
- }
631
- });
632
- }
633
- var exampleEcho = defineTool({
634
- name: "example_echo",
635
- title: "Echo",
636
- description: "Echo a message back unchanged. Use it to check the connection and to see the result shape mcp-kit tools return (JSON text plus structuredContent).",
637
- input: z3.object({ message: z3.string().describe("Text to echo back") }),
638
- output: z3.object({ message: z3.string() }),
639
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
640
- handler: ({ message }) => ({ message })
641
- });
642
- var exampleAdd = defineTool({
643
- name: "example_add",
644
- title: "Add",
645
- description: "Add two numbers and return their sum as { sum }. Demonstrates typed input and output schemas; send non-numbers to see an INVALID_INPUT error result.",
646
- input: z3.object({
647
- a: z3.number().describe("First addend"),
648
- b: z3.number().describe("Second addend")
649
- }),
650
- output: z3.object({ sum: z3.number() }),
651
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
652
- handler: ({ a, b }) => ({ sum: a + b })
653
- });
654
- var examplePaidLookup = withPayment(
655
- defineTool({
656
- name: "example_paid_lookup",
657
- title: "Paid lookup",
658
- description: "Look up a record by query and return { query, result, sample } (fake data). Demonstrates a paid tool: without an entitlement the call returns a PAYMENT_REQUIRED result with x402 and MPP payment options.",
659
- input: z3.object({ query: z3.string().min(1).describe("What to look up") }),
660
- output: z3.object({ query: z3.string(), result: z3.string(), sample: z3.boolean() }),
661
- annotations: { readOnlyHint: true, idempotentHint: true },
662
- handler: ({ query }, ctx) => ({
663
- query,
664
- result: ctx.sample ? `SAMPLE: example record for "${query}"` : `record for "${query}"`,
665
- sample: ctx.sample === true
666
- })
667
- }),
668
- { priceUsd: 0.05, description: "One record lookup" }
669
- );
670
- function isZodObject(schema) {
671
- return schema instanceof z4.ZodObject;
672
- }
673
- function contextFromExtra(extra) {
674
- const headers = new Headers();
675
- const raw = extra.requestInfo?.headers;
676
- if (raw) {
677
- for (const [key, value] of Object.entries(raw)) {
678
- if (Array.isArray(value)) for (const item of value) headers.append(key, item);
679
- else if (typeof value === "string") headers.append(key, value);
680
- }
681
- }
682
- const progressToken = extra._meta?.progressToken;
683
- return {
684
- headers,
685
- requestId: String(extra.requestId),
686
- signal: extra.signal,
687
- sessionId: extra.sessionId,
688
- meta: extra._meta,
689
- extra,
690
- log: async (level, data) => {
691
- await extra.sendNotification({ method: "notifications/message", params: { level, data } });
692
- },
693
- progress: async (progress, total, message) => {
694
- if (progressToken === void 0) return;
695
- await extra.sendNotification({
696
- method: "notifications/progress",
697
- params: { progressToken, progress, total, message }
698
- });
699
- }
700
- };
701
- }
702
- function createMcpServer(opts) {
703
- const byName = /* @__PURE__ */ new Map();
704
- for (const tool of opts.tools) {
705
- if (byName.has(tool.name)) throw new Error(`duplicate tool name: ${tool.name}`);
706
- byName.set(tool.name, tool);
707
- }
708
- const server = new McpServer(
709
- { name: opts.name, version: opts.version, title: opts.title, websiteUrl: opts.websiteUrl },
710
- { instructions: opts.instructions, capabilities: { logging: {}, ...opts.capabilities } }
711
- );
712
- const registered = /* @__PURE__ */ new Map();
713
- for (const tool of byName.values()) {
714
- const config = {
715
- title: tool.title,
716
- description: tool.description,
717
- inputSchema: tool.input,
718
- annotations: tool.annotations
719
- };
720
- if (isZodObject(tool.output)) config.outputSchema = tool.output;
721
- registered.set(
722
- tool.name,
723
- server.registerTool(tool.name, config, () => {
724
- throw new Error("unreachable: mcp-kit dispatches tools/call itself");
725
- })
726
- );
727
- }
728
- if (byName.size > 0) {
729
- server.server.setRequestHandler(ListToolsRequestSchema, async () => {
730
- const enabled = [...byName.values()].filter(
731
- (tool) => registered.get(tool.name)?.enabled !== false
732
- );
733
- return {
734
- tools: listToolManifest(enabled).map((entry) => ({
735
- ...entry,
736
- inputSchema: entry.inputSchema,
737
- outputSchema: entry.outputSchema
738
- }))
739
- };
740
- });
741
- server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
742
- const name = request.params.name;
743
- const tool = byName.get(name);
744
- const entry = registered.get(name);
745
- if (!tool || !entry) throw new McpError(ErrorCode.InvalidParams, `Tool ${name} not found`);
746
- if (!entry.enabled) throw new McpError(ErrorCode.InvalidParams, `Tool ${name} disabled`);
747
- return tool.invoke(request.params.arguments ?? {}, contextFromExtra(extra));
748
- });
749
- }
750
- return server;
751
- }
752
- function toJsonSchema(schema, io = "input") {
753
- const json = z4.toJSONSchema(schema, { io, target: "draft-2020-12" });
754
- if (isZodObject(schema) && schema._zod.def.catchall === void 0 && json.additionalProperties === void 0) {
755
- json.additionalProperties = false;
756
- }
757
- return json;
758
- }
759
- function listToolManifest(tools) {
760
- return tools.map((tool) => {
761
- const entry = {
762
- name: tool.name,
763
- description: tool.description,
764
- inputSchema: toJsonSchema(tool.input, "input")
765
- };
766
- if (tool.title !== void 0) entry.title = tool.title;
767
- if (isZodObject(tool.output)) entry.outputSchema = toJsonSchema(tool.output, "output");
768
- if (tool.annotations !== void 0) entry.annotations = tool.annotations;
769
- return entry;
770
- });
771
- }
772
- var DEFAULT_CORS = {
773
- origin: "*",
774
- methods: "GET, POST, DELETE, OPTIONS",
775
- headers: "Content-Type, Accept, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, PAYMENT, X-PAYMENT",
776
- exposeHeaders: "Mcp-Session-Id, Mcp-Protocol-Version, WWW-Authenticate, X-Payment-Requirements, PAYMENT-REQUIRED",
777
- maxAge: 86400
778
- };
779
- var DEFAULT_SESSION_TTL_MS = 30 * 6e4;
780
- function corsHeaders(cors) {
781
- if (!cors) return {};
782
- const c = { ...DEFAULT_CORS, ...typeof cors === "object" ? cors : {} };
783
- return {
784
- "Access-Control-Allow-Origin": c.origin,
785
- "Access-Control-Allow-Methods": c.methods,
786
- "Access-Control-Allow-Headers": c.headers,
787
- "Access-Control-Expose-Headers": c.exposeHeaders,
788
- "Access-Control-Max-Age": String(c.maxAge)
789
- };
790
- }
791
- function jsonResponse(body, status, headers = {}) {
792
- return new Response(JSON.stringify(body), {
793
- status,
794
- headers: { "Content-Type": "application/json", "Cache-Control": "no-store", ...headers }
795
- });
796
- }
797
- function methodNotAllowed(method, allow, cors) {
798
- return jsonResponse(
799
- {
800
- code: "METHOD_NOT_ALLOWED",
801
- cause: `${method} is not supported here without a session`,
802
- fix: "send JSON-RPC over POST with `Accept: application/json, text/event-stream` (start with `initialize`)",
803
- retryable: false
804
- },
805
- 405,
806
- { Allow: allow, ...cors }
807
- );
808
- }
809
- function splitOptions(opts, transport) {
810
- if (typeof opts === "function") return { factory: opts, transport };
811
- const {
812
- cors,
813
- jsonResponse: jsonResponse2,
814
- sessions,
815
- allowedHosts,
816
- allowedOrigins,
817
- enableDnsRebindingProtection,
818
- ...server
819
- } = opts;
820
- return {
821
- factory: () => createMcpServer(server),
822
- transport: {
823
- cors,
824
- jsonResponse: jsonResponse2,
825
- sessions,
826
- allowedHosts,
827
- allowedOrigins,
828
- enableDnsRebindingProtection,
829
- ...transport
830
- }
831
- };
832
- }
833
- function withCors(response, cors) {
834
- const keys = Object.keys(cors);
835
- if (keys.length === 0) return response;
836
- const headers = new Headers(response.headers);
837
- for (const key of keys) headers.set(key, cors[key] ?? "");
838
- return new Response(response.body, {
839
- status: response.status,
840
- statusText: response.statusText,
841
- headers
842
- });
843
- }
844
- function errorResponse(err, cors) {
845
- const error = normalizeError(err);
846
- return jsonResponse(error.toJSON(), error.httpStatus, cors);
847
- }
848
- function createStatelessHandler({ factory, cors, transportBase }) {
849
- return async (request) => {
850
- if (request.method !== "POST") return methodNotAllowed(request.method, "POST, OPTIONS", cors);
851
- const server = factory();
852
- const transport = new WebStandardStreamableHTTPServerTransport({
853
- ...transportBase,
854
- sessionIdGenerator: void 0
855
- });
856
- let closed = false;
857
- const cleanup = async () => {
858
- if (closed) return;
859
- closed = true;
860
- try {
861
- await server.close();
862
- } catch {
863
- }
864
- };
865
- try {
866
- await server.connect(transport);
867
- const response = withCors(await transport.handleRequest(request), cors);
868
- if (!response.body) {
869
- await cleanup();
870
- return response;
871
- }
872
- request.signal?.addEventListener("abort", () => void cleanup(), { once: true });
873
- const body = response.body.pipeThrough(
874
- new TransformStream({ flush: () => cleanup() })
875
- );
876
- return new Response(body, {
877
- status: response.status,
878
- statusText: response.statusText,
879
- headers: response.headers
880
- });
881
- } catch (err) {
882
- await cleanup();
883
- return errorResponse(err, cors);
884
- }
885
- };
886
- }
887
- function createStatefulHandler({ factory, cors, transportBase }, options) {
888
- const ttlMs = options.ttlMs ?? DEFAULT_SESSION_TTL_MS;
889
- const sessions = /* @__PURE__ */ new Map();
890
- const drop = (id) => {
891
- const session = sessions.get(id);
892
- if (!session) return;
893
- sessions.delete(id);
894
- session.server.close().catch(() => void 0);
895
- };
896
- const sweep = () => {
897
- const now = Date.now();
898
- for (const [id, session] of sessions) if (now - session.lastSeen > ttlMs) drop(id);
899
- };
900
- return async (request) => {
901
- const sessionId = request.headers.get("mcp-session-id");
902
- if (sessionId) {
903
- const session = sessions.get(sessionId);
904
- if (!session) {
905
- return jsonResponse(
906
- {
907
- code: "NOT_FOUND",
908
- cause: "unknown or expired Mcp-Session-Id",
909
- fix: "send `initialize` again without an Mcp-Session-Id header and use the id from the response",
910
- retryable: true
911
- },
912
- 404,
913
- cors
914
- );
915
- }
916
- session.lastSeen = Date.now();
917
- try {
918
- return withCors(await session.transport.handleRequest(request), cors);
919
- } catch (err) {
920
- return errorResponse(err, cors);
921
- }
922
- }
923
- if (request.method !== "POST") return methodNotAllowed(request.method, "POST, OPTIONS", cors);
924
- sweep();
925
- const server = factory();
926
- const transport = new WebStandardStreamableHTTPServerTransport({
927
- ...transportBase,
928
- sessionIdGenerator: () => crypto.randomUUID(),
929
- onsessioninitialized: (id) => {
930
- sessions.set(id, { server, transport, lastSeen: Date.now() });
931
- },
932
- onsessionclosed: (id) => {
933
- sessions.delete(id);
934
- }
935
- });
936
- try {
937
- await server.connect(transport);
938
- const previousOnClose = transport.onclose;
939
- transport.onclose = () => {
940
- previousOnClose?.();
941
- if (transport.sessionId) sessions.delete(transport.sessionId);
942
- };
943
- const response = withCors(await transport.handleRequest(request), cors);
944
- if (!transport.sessionId) server.close().catch(() => void 0);
945
- return response;
946
- } catch (err) {
947
- server.close().catch(() => void 0);
948
- return errorResponse(err, cors);
949
- }
950
- };
951
- }
952
- function createHttpHandler(opts, transportOptions = {}) {
953
- const { factory, transport: t } = splitOptions(opts, transportOptions);
954
- const cors = corsHeaders(t.cors);
955
- const sessions = t.sessions ? typeof t.sessions === "object" ? t.sessions : {} : void 0;
956
- const context = {
957
- factory,
958
- cors,
959
- transportBase: {
960
- enableJsonResponse: t.jsonResponse ?? sessions === void 0,
961
- allowedHosts: t.allowedHosts,
962
- allowedOrigins: t.allowedOrigins,
963
- enableDnsRebindingProtection: t.enableDnsRebindingProtection
964
- }
965
- };
966
- const handler = sessions ? createStatefulHandler(context, sessions) : createStatelessHandler(context);
967
- const allow = sessions ? "GET, POST, DELETE, OPTIONS" : "POST, OPTIONS";
968
- return async (request) => {
969
- if (request.method === "OPTIONS") {
970
- return new Response(null, { status: 204, headers: { Allow: allow, ...cors } });
971
- }
972
- return handler(request);
973
- };
974
- }
975
- function localhostHosts(port) {
976
- const hosts = ["localhost", "127.0.0.1", "[::1]"];
977
- return [...hosts, ...hosts.map((h) => `${h}:${port}`)];
978
- }
979
- function handleHealth(info) {
980
- const tools = typeof info.tools === "number" ? info.tools : info.tools.length;
981
- return jsonResponse({ ok: true, name: info.name, version: info.version, tools }, 200);
982
- }
983
- async function serveNodeHttp(opts) {
984
- const http = await import("http");
985
- const host = opts.host ?? "127.0.0.1";
986
- const path = opts.path ?? "/mcp";
987
- const server = http.createServer((req, res) => {
988
- void (async () => {
989
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
990
- let response;
991
- if (url.pathname === path) {
992
- const controller = new AbortController();
993
- res.on("close", () => {
994
- if (!res.writableFinished) controller.abort();
995
- });
996
- const headers = new Headers();
997
- for (const [key, value] of Object.entries(req.headers)) {
998
- if (Array.isArray(value)) for (const item of value) headers.append(key, item);
999
- else if (typeof value === "string") headers.set(key, value);
1000
- }
1001
- const method = req.method ?? "GET";
1002
- let body;
1003
- if (method !== "GET" && method !== "HEAD") {
1004
- const chunks = [];
1005
- for await (const chunk of req) chunks.push(chunk);
1006
- body = new Uint8Array(Buffer.concat(chunks));
1007
- }
1008
- response = await opts.handler(
1009
- new Request(url, { method, headers, body, signal: controller.signal })
1010
- );
1011
- } else if (url.pathname === "/health" && opts.health) {
1012
- response = opts.health();
1013
- } else {
1014
- response = new Response(
1015
- JSON.stringify({
1016
- code: "NOT_FOUND",
1017
- cause: `no route for ${url.pathname}`,
1018
- fix: `POST JSON-RPC to ${path}`,
1019
- retryable: false
1020
- }),
1021
- { status: 404, headers: { "Content-Type": "application/json" } }
1022
- );
1023
- }
1024
- const outHeaders = {};
1025
- response.headers.forEach((value, key) => {
1026
- outHeaders[key] = value;
1027
- });
1028
- res.writeHead(response.status, outHeaders);
1029
- if (!response.body) {
1030
- res.end();
1031
- return;
1032
- }
1033
- const reader = response.body.getReader();
1034
- try {
1035
- for (; ; ) {
1036
- const { done, value } = await reader.read();
1037
- if (done) break;
1038
- res.write(value);
1039
- }
1040
- } catch {
1041
- }
1042
- res.end();
1043
- })().catch((err) => {
1044
- if (!res.headersSent) res.writeHead(500, { "Content-Type": "application/json" });
1045
- res.end(
1046
- JSON.stringify({
1047
- code: "INTERNAL",
1048
- cause: err instanceof Error ? err.message : String(err),
1049
- fix: "retry; if it persists, report the request id",
1050
- retryable: false
1051
- })
1052
- );
1053
- });
1054
- });
1055
- await new Promise((resolve, reject) => {
1056
- server.once("error", reject);
1057
- server.listen(opts.port ?? 8765, host, () => resolve());
1058
- });
1059
- const address = server.address();
1060
- const port = typeof address === "object" && address ? address.port : opts.port ?? 8765;
1061
- return {
1062
- port,
1063
- url: `http://${host}:${port}${path}`,
1064
- close: () => new Promise((resolve, reject) => {
1065
- server.closeAllConnections();
1066
- server.close((err) => err ? reject(err) : resolve());
1067
- })
1068
- };
1069
- }
1070
-
1071
- // ../../packages/mcp-kit/dist/index.js
1072
- import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
1073
- async function serveStdio(serverOrOpts) {
1074
- const server = serverOrOpts instanceof McpServer2 ? serverOrOpts : createMcpServer(serverOrOpts);
1075
- const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
1076
- const transport = new StdioServerTransport();
1077
- await server.connect(transport);
1078
- await new Promise((resolve) => {
1079
- const previous = transport.onclose;
1080
- transport.onclose = () => {
1081
- previous?.();
1082
- resolve();
1083
- };
1084
- });
1085
- }
1086
-
1087
- // src/fixtures/crm-server.ts
1088
- import { z as z5 } from "zod";
1089
- function seedState() {
1090
- const contacts = /* @__PURE__ */ new Map();
1091
- for (const [id, name, email] of [
1092
- ["c_1", "Ada Lovelace", "ada@example.com"],
1093
- ["c_2", "Grace Hopper", "grace@example.com"],
1094
- ["c_3", "Linus Torvalds", "linus@example.com"]
1095
- ]) {
1096
- contacts.set(id, { id, name, email, updated_at: "2026-01-01T00:00:00.000Z" });
1097
- }
1098
- return { contacts, mutations: [], emails: [], charges: [] };
1099
- }
1100
- var counter = 0;
1101
- var nextId = (prefix) => `${prefix}_${(++counter).toString(36).padStart(4, "0")}`;
1102
- function crmTools(state) {
1103
- const contactSchema = z5.object({
1104
- id: z5.string(),
1105
- name: z5.string(),
1106
- email: z5.string(),
1107
- updated_at: z5.string()
1108
- });
1109
- return [
1110
- defineTool({
1111
- name: "crm_list_contacts",
1112
- description: "List every contact in the CRM. Read-only; returns id, name, email and updated_at.",
1113
- input: z5.object({ limit: z5.number().int().min(1).max(500).default(100) }),
1114
- output: z5.object({ contacts: z5.array(contactSchema), total: z5.number() }),
1115
- annotations: { readOnlyHint: true },
1116
- handler: ({ limit }) => ({
1117
- contacts: [...state.contacts.values()].slice(0, limit),
1118
- total: state.contacts.size
1119
- })
1120
- }),
1121
- defineTool({
1122
- name: "crm_get_contact",
1123
- description: "Fetch one contact by id. Read-only; NOT_FOUND when the id does not exist.",
1124
- input: z5.object({ id: z5.string() }),
1125
- output: contactSchema,
1126
- annotations: { readOnlyHint: true },
1127
- handler: ({ id }) => {
1128
- const c = state.contacts.get(id);
1129
- if (!c)
1130
- throw toolError(
1131
- "NOT_FOUND",
1132
- `no contact ${id}`,
1133
- "call crm_list_contacts and use an existing id"
1134
- );
1135
- return c;
1136
- }
1137
- }),
1138
- defineTool({
1139
- name: "crm_search",
1140
- description: "Search contacts by name or email substring. Returns matching contacts (no annotations on purpose).",
1141
- input: z5.object({ query: z5.string() }),
1142
- handler: ({ query }) => ({
1143
- contacts: [...state.contacts.values()].filter(
1144
- (c) => `${c.name} ${c.email}`.toLowerCase().includes(query.toLowerCase())
1145
- )
1146
- })
1147
- }),
1148
- defineTool({
1149
- name: "crm_create_contact",
1150
- description: "Create a contact. Returns the new id and created_at timestamp.",
1151
- input: z5.object({ name: z5.string(), email: z5.string() }),
1152
- output: z5.object({ id: z5.string(), created_at: z5.string() }),
1153
- annotations: { readOnlyHint: false, destructiveHint: false },
1154
- handler: ({ name, email }) => {
1155
- const id = nextId("c");
1156
- const now = (/* @__PURE__ */ new Date()).toISOString();
1157
- state.contacts.set(id, { id, name, email, updated_at: now });
1158
- state.mutations.push({ tool: "crm_create_contact", args: { name, email } });
1159
- return { id, created_at: now };
1160
- }
1161
- }),
1162
- defineTool({
1163
- name: "crm_update_contact",
1164
- description: "Update fields on a contact. Returns the id and updated_at; NOT_FOUND for unknown ids.",
1165
- input: z5.object({
1166
- id: z5.string(),
1167
- fields: z5.object({ name: z5.string().optional(), email: z5.string().optional() })
1168
- }),
1169
- output: z5.object({ id: z5.string(), updated_at: z5.string() }),
1170
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
1171
- handler: ({ id, fields }) => {
1172
- const c = state.contacts.get(id);
1173
- if (!c)
1174
- throw toolError(
1175
- "NOT_FOUND",
1176
- `no contact ${id}`,
1177
- "call crm_list_contacts and use an existing id"
1178
- );
1179
- Object.assign(c, fields, { updated_at: (/* @__PURE__ */ new Date()).toISOString() });
1180
- state.mutations.push({ tool: "crm_update_contact", args: { id, fields } });
1181
- return { id, updated_at: c.updated_at };
1182
- }
1183
- }),
1184
- defineTool({
1185
- name: "crm_delete_contact",
1186
- description: "Permanently delete a contact. Irreversible. Returns { deleted, id }.",
1187
- input: z5.object({ id: z5.string() }),
1188
- output: z5.object({ deleted: z5.boolean(), id: z5.string() }),
1189
- annotations: { readOnlyHint: false, destructiveHint: true },
1190
- handler: ({ id }) => {
1191
- const existed = state.contacts.delete(id);
1192
- state.mutations.push({ tool: "crm_delete_contact", args: { id } });
1193
- return { deleted: existed, id };
1194
- }
1195
- }),
1196
- defineTool({
1197
- name: "crm_send_email",
1198
- description: "Send an email to a contact through the CRM. Returns the message id and sent_at.",
1199
- input: z5.object({ to: z5.string(), subject: z5.string(), body: z5.string() }),
1200
- output: z5.object({ message_id: z5.string(), sent_at: z5.string() }),
1201
- handler: ({ to, subject }) => {
1202
- state.emails.push({ to, subject });
1203
- state.mutations.push({ tool: "crm_send_email", args: { to, subject } });
1204
- return { message_id: nextId("msg"), sent_at: (/* @__PURE__ */ new Date()).toISOString() };
1205
- }
1206
- }),
1207
- defineTool({
1208
- name: "crm_charge_card",
1209
- description: "Charge a customer's card on file (amount in cents). Returns the charge id and amount_usd.",
1210
- input: z5.object({
1211
- customer_id: z5.string(),
1212
- amount_cents: z5.number().int().positive(),
1213
- currency: z5.string().default("usd")
1214
- }),
1215
- output: z5.object({ charge_id: z5.string(), amount_usd: z5.number() }),
1216
- handler: ({ customer_id, amount_cents }) => {
1217
- const amount_usd = amount_cents / 100;
1218
- state.charges.push({ customer_id, amount_usd });
1219
- state.mutations.push({ tool: "crm_charge_card", args: { customer_id, amount_usd } });
1220
- return { charge_id: nextId("ch"), amount_usd };
1221
- }
1222
- }),
1223
- defineTool({
1224
- name: "crm_frobnicate",
1225
- description: "A tool with no annotations and no recognizable verb \u2014 exists to test `unknown` handling.",
1226
- input: z5.object({}),
1227
- handler: () => {
1228
- state.mutations.push({ tool: "crm_frobnicate", args: {} });
1229
- return { frobnicated: true };
1230
- }
1231
- }),
1232
- defineTool({
1233
- name: "crm_fail",
1234
- description: "Always returns a structured UPSTREAM_ERROR result, for error-path tests.",
1235
- input: z5.object({}),
1236
- annotations: { readOnlyHint: true },
1237
- handler: () => {
1238
- throw toolError("UPSTREAM_ERROR", "the CRM is having a bad day", "retry in a minute", {
1239
- retryable: true
1240
- });
1241
- }
1242
- })
1243
- ];
1244
- }
1245
- function createCrmMcpServer(state = seedState()) {
1246
- return createMcpServer({
1247
- name: "fake-crm",
1248
- version: "0.1.0",
1249
- instructions: "A fake CRM for agentguard tests. Nothing here is real.",
1250
- tools: crmTools(state)
1251
- });
1252
- }
1253
- var isMain = process.argv[1] !== void 0 && /crm-server\.[cm]?[jt]s$/.test(process.argv[1]);
1254
- if (isMain) {
1255
- const state = seedState();
1256
- if (process.argv.includes("--http")) {
1257
- const portArg = process.argv.find((a) => a.startsWith("--port="));
1258
- const port = portArg ? Number(portArg.slice("--port=".length)) : 0;
1259
- const handler = createHttpHandler(() => createCrmMcpServer(state), {
1260
- sessions: true,
1261
- jsonResponse: false,
1262
- allowedHosts: port ? localhostHosts(port) : void 0
1263
- });
1264
- const server = await serveNodeHttp({
1265
- handler,
1266
- port,
1267
- health: () => handleHealth({ name: "fake-crm", version: "0.1.0", tools: crmTools(state).length })
1268
- });
1269
- console.log(`fake-crm listening at ${server.url}`);
1270
- } else {
1271
- for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => process.exit(0));
1272
- await serveStdio(createCrmMcpServer(state));
1273
- }
1274
- }
3
+ createCrmMcpServer,
4
+ crmTools,
5
+ seedState
6
+ } from "../chunk-HPYZ3JDX.js";
7
+ import "../chunk-ZSLWPKXI.js";
8
+ import "../chunk-5WRI5ZAA.js";
1275
9
  export {
1276
10
  createCrmMcpServer,
1277
11
  crmTools,