@devlab.group/agent-commerce 2.1.0 → 2.2.1

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 (75) hide show
  1. package/README.md +35 -5
  2. package/dist/ap2.js +3 -3
  3. package/dist/{chunk-LZS5BL3W.js → chunk-2DAJAJI7.js} +23 -5
  4. package/dist/chunk-2DAJAJI7.js.map +1 -0
  5. package/dist/{chunk-UHMYRV3J.js → chunk-BNUD4RBN.js} +22 -7
  6. package/dist/chunk-BNUD4RBN.js.map +1 -0
  7. package/dist/{chunk-IFGRCXME.js → chunk-REBY2IEP.js} +24 -20
  8. package/dist/chunk-REBY2IEP.js.map +1 -0
  9. package/dist/{chunk-SY6GUYSQ.js → chunk-XYHDQ22D.js} +3 -3
  10. package/dist/{chunk-SY6GUYSQ.js.map → chunk-XYHDQ22D.js.map} +1 -1
  11. package/dist/cli/ap2-HLBIUSTK.js +678 -0
  12. package/dist/cli/ap2-HLBIUSTK.js.map +1 -0
  13. package/dist/cli/chunk-2LN5PO5I.js +1352 -0
  14. package/dist/cli/chunk-2LN5PO5I.js.map +1 -0
  15. package/dist/cli/chunk-3EQBCFZY.js +53 -0
  16. package/dist/cli/chunk-3EQBCFZY.js.map +1 -0
  17. package/dist/cli/chunk-3M6U7ILB.js +8 -0
  18. package/dist/cli/chunk-3M6U7ILB.js.map +1 -0
  19. package/dist/cli/chunk-3MGAJ5FX.js +70 -0
  20. package/dist/cli/chunk-3MGAJ5FX.js.map +1 -0
  21. package/dist/cli/chunk-5K4XEAZV.js +113 -0
  22. package/dist/cli/chunk-5K4XEAZV.js.map +1 -0
  23. package/dist/cli/chunk-AA3FIEAR.js +27 -0
  24. package/dist/cli/chunk-AA3FIEAR.js.map +1 -0
  25. package/dist/cli/chunk-FHCFR2GS.js +17 -0
  26. package/dist/cli/chunk-FHCFR2GS.js.map +1 -0
  27. package/dist/cli/chunk-ILUYEQSV.js +1744 -0
  28. package/dist/cli/chunk-ILUYEQSV.js.map +1 -0
  29. package/dist/cli/chunk-KTV52MCU.js +338 -0
  30. package/dist/cli/chunk-KTV52MCU.js.map +1 -0
  31. package/dist/cli/chunk-OSTCU6ZK.js +44 -0
  32. package/dist/cli/chunk-OSTCU6ZK.js.map +1 -0
  33. package/dist/cli/chunk-OZ4LAWBK.js +256 -0
  34. package/dist/cli/chunk-OZ4LAWBK.js.map +1 -0
  35. package/dist/cli/chunk-QQ3WY5L3.js +271 -0
  36. package/dist/cli/chunk-QQ3WY5L3.js.map +1 -0
  37. package/dist/cli/chunk-QY3K6PEQ.js +8 -0
  38. package/dist/cli/chunk-QY3K6PEQ.js.map +1 -0
  39. package/dist/cli/chunk-QZ6TOMX6.js +1413 -0
  40. package/dist/cli/chunk-QZ6TOMX6.js.map +1 -0
  41. package/dist/cli/chunk-SNM3OVCY.js +625 -0
  42. package/dist/cli/chunk-SNM3OVCY.js.map +1 -0
  43. package/dist/cli/chunk-SPAYKVUP.js +118 -0
  44. package/dist/cli/chunk-SPAYKVUP.js.map +1 -0
  45. package/dist/cli/chunk-TPC4EJLT.js +11 -0
  46. package/dist/cli/chunk-TPC4EJLT.js.map +1 -0
  47. package/dist/cli/config-VYAJGZXE.js +11 -0
  48. package/dist/cli/config-VYAJGZXE.js.map +1 -0
  49. package/dist/cli/index.js +150 -3627
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/cli/mcp-HFD7ZNCQ.js +559 -0
  52. package/dist/cli/mcp-HFD7ZNCQ.js.map +1 -0
  53. package/dist/cli/near-5AY3HWYF.js +58 -0
  54. package/dist/cli/near-5AY3HWYF.js.map +1 -0
  55. package/dist/cli/provider-JV5HCQGR.js +396 -0
  56. package/dist/cli/provider-JV5HCQGR.js.map +1 -0
  57. package/dist/cli/run-WV44B62Y.js +7301 -0
  58. package/dist/cli/run-WV44B62Y.js.map +1 -0
  59. package/dist/cli/svm-3MMYFPC7.js +33 -0
  60. package/dist/cli/svm-3MMYFPC7.js.map +1 -0
  61. package/dist/cli/x402-NADFV5CD.js +96 -0
  62. package/dist/cli/x402-NADFV5CD.js.map +1 -0
  63. package/dist/index.d.ts +12 -3
  64. package/dist/index.js +82 -11
  65. package/dist/index.js.map +1 -1
  66. package/dist/mcp.js +6 -16
  67. package/dist/mcp.js.map +1 -1
  68. package/dist/mpp.js +3 -3
  69. package/dist/{networks-olfflCI_.d.ts → networks-DTWRlj-K.d.ts} +2 -2
  70. package/dist/x402.d.ts +7 -2
  71. package/dist/x402.js +4 -4
  72. package/package.json +2 -2
  73. package/dist/chunk-IFGRCXME.js.map +0 -1
  74. package/dist/chunk-LZS5BL3W.js.map +0 -1
  75. package/dist/chunk-UHMYRV3J.js.map +0 -1
@@ -0,0 +1,559 @@
1
+ import { readCappedBody, x402ErrorCode, settlementFailure, settlementResponseFromDetails, x402McpPaymentSubmission, X402_MCP_PAYMENT_META_KEY, X402_MCP_PAYMENT_RESPONSE_META_KEY, settlementResponse, X402_MCP_ERROR_META_KEY } from './chunk-SPAYKVUP.js';
2
+ import { MCP_TOOL_NAME_PATTERN, MCP_MODERN_PROTOCOL_REVISION } from './chunk-TPC4EJLT.js';
3
+ import { mcpPaymentRequired, MPP_MCP_CREDENTIAL_META_KEY, receiptObject, MPP_MCP_RECEIPT_META_KEY, MPP_MCP_PAYMENT_REQUIRED_META_KEY } from './chunk-5K4XEAZV.js';
4
+ import { PACKAGE_VERSION } from './chunk-QY3K6PEQ.js';
5
+ import { toLogInfo, toCommerceError, CommerceError, extractReservedInputFields, PAYMENT_INPUT_FIELD, AUTHORIZATION_INPUT_FIELD, toErrorEnvelope, DELIVERY_SUMMARY_META_KEY, toDeliverySummary, toPaymentRequiredEnvelope } from './chunk-QQ3WY5L3.js';
6
+ import { isRecord } from './chunk-3M6U7ILB.js';
7
+ import { createMcpHandler, Server, ProtocolError, ProtocolErrorCode, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/server';
8
+
9
+ var MCP_SUPPORTED_SPEC = `${MCP_MODERN_PROTOCOL_REVISION}, ${LATEST_PROTOCOL_VERSION}`;
10
+ var MCP_CAPABILITIES = ["tools/list", "tools/call"];
11
+ var MCP_UNSUPPORTED = [
12
+ "resources",
13
+ "prompts",
14
+ "sampling",
15
+ "completions",
16
+ "elicitation",
17
+ "roots",
18
+ "logging",
19
+ "notifications/tools/list_changed",
20
+ "tasks",
21
+ "dns-rebinding-protection",
22
+ "authorization",
23
+ "sessions",
24
+ "subscriptions",
25
+ "pagination",
26
+ "HTTP+SSE transport",
27
+ // mppx reads challenges and refusals from tool-result `_meta`.
28
+ "MPP payment-required as JSON-RPC error -32042",
29
+ "MPP verification failure as JSON-RPC error -32043"
30
+ ];
31
+ function buildDescriptor(implementationVersion) {
32
+ return {
33
+ name: "mcp",
34
+ kind: "protocol",
35
+ implementationVersion,
36
+ supportedSpec: MCP_SUPPORTED_SPEC,
37
+ capabilities: MCP_CAPABILITIES,
38
+ status: "stable",
39
+ unsupported: MCP_UNSUPPORTED
40
+ };
41
+ }
42
+
43
+ // src/protocols/mcp/tool-mapping.ts
44
+ function isValidToolName(id) {
45
+ return MCP_TOOL_NAME_PATTERN.test(id);
46
+ }
47
+ function isPaidResource(resource) {
48
+ return resource.pricing.type !== "free";
49
+ }
50
+ function primaryPaymentMethod(resource) {
51
+ return resource.paymentMethods[0];
52
+ }
53
+ function proofCarriers(method) {
54
+ const field = `the "${PAYMENT_INPUT_FIELD}" argument`;
55
+ if (method === "x402") return `_meta["${X402_MCP_PAYMENT_META_KEY}"] or ${field}`;
56
+ if (method === "mpp") return `_meta["${MPP_MCP_CREDENTIAL_META_KEY}"] or ${field}`;
57
+ return field;
58
+ }
59
+ function paymentProofNote(resource) {
60
+ const method = primaryPaymentMethod(resource);
61
+ const rail = method !== void 0 ? `an ${method}` : "a";
62
+ return `Requires ${rail} payment proof in ${proofCarriers(method)}, built from the payment-required response.`;
63
+ }
64
+ var OMIT_NOTE = "Omit it when the client sends the proof in _meta.";
65
+ function paymentInputFieldDescription(resource) {
66
+ const method = primaryPaymentMethod(resource);
67
+ if (method === "x402") {
68
+ return `x402 payment proof (base64 PAYMENT-SIGNATURE value) for a previous payment-required response. ${OMIT_NOTE}`;
69
+ }
70
+ if (method === "mpp") {
71
+ return `MPP credential (the full "Authorization: Payment ..." value) for the challenge in a previous payment-required response. ${OMIT_NOTE}`;
72
+ }
73
+ if (method !== void 0) {
74
+ return `${method} payment proof (base64-encoded) returned from a previous payment-required response.`;
75
+ }
76
+ return "Payment proof returned from a previous payment-required response.";
77
+ }
78
+ function requiredAuthorization(resource) {
79
+ return resource.authorization?.required ?? [];
80
+ }
81
+ function authorizationNote(resource) {
82
+ const methods = requiredAuthorization(resource);
83
+ return methods.length > 0 ? ` Also requires ${methods.join(" and ")} authorization in the "${AUTHORIZATION_INPUT_FIELD}" input field.` : "";
84
+ }
85
+ function buildToolDescription(resource) {
86
+ const base = resource.description ?? resource.name;
87
+ switch (resource.pricing.type) {
88
+ case "free":
89
+ return base;
90
+ case "fixed": {
91
+ const { amount, currency } = resource.pricing;
92
+ return `${base} Costs ${amount} ${currency} per call. ${paymentProofNote(resource)}${authorizationNote(resource)}`;
93
+ }
94
+ case "dynamic":
95
+ return `${base} Requires payment (amount determined at request time). ${paymentProofNote(resource)}${authorizationNote(resource)}`;
96
+ }
97
+ }
98
+ function buildInputSchema(resource) {
99
+ const base = resource.inputSchema;
100
+ const baseIsObject = isRecord(base);
101
+ const basePropertyEntries = baseIsObject && isRecord(base.properties) ? Object.entries(base.properties).filter(
102
+ (entry) => isRecord(entry[1])
103
+ ) : [];
104
+ const properties = Object.fromEntries(basePropertyEntries);
105
+ if (isPaidResource(resource)) {
106
+ properties[PAYMENT_INPUT_FIELD] = {
107
+ type: "string",
108
+ description: paymentInputFieldDescription(resource)
109
+ };
110
+ }
111
+ const methods = requiredAuthorization(resource);
112
+ if (methods.length > 0) {
113
+ properties[AUTHORIZATION_INPUT_FIELD] = {
114
+ type: "object",
115
+ description: "Authorization this resource requires in addition to payment: the method and its serialized proof.",
116
+ properties: {
117
+ method: { type: "string", enum: [...methods] },
118
+ payload: { type: "string", minLength: 1 }
119
+ },
120
+ required: ["method", "payload"]
121
+ };
122
+ }
123
+ const required = baseIsObject && Array.isArray(base.required) ? base.required.filter((v) => typeof v === "string") : [];
124
+ const extra = baseIsObject ? Object.fromEntries(
125
+ Object.entries(base).filter(
126
+ ([key]) => key !== "type" && key !== "properties" && key !== "required"
127
+ )
128
+ ) : {};
129
+ return {
130
+ ...extra,
131
+ type: "object",
132
+ properties,
133
+ ...required.length > 0 ? { required } : {}
134
+ };
135
+ }
136
+
137
+ // src/protocols/mcp/result-mapping.ts
138
+ function toRecord(value) {
139
+ return value;
140
+ }
141
+ function deliveredResult(outcome) {
142
+ const body = outcome.body;
143
+ const text = typeof body === "string" ? body : JSON.stringify(body ?? null);
144
+ const payment = outcome.payment;
145
+ const receipt = payment?.provider === "mpp" ? receiptObject(payment.metadata?.["receipt"]) : void 0;
146
+ return {
147
+ content: [{ type: "text", text }],
148
+ ...isRecord(body) ? { structuredContent: body } : {},
149
+ _meta: {
150
+ [DELIVERY_SUMMARY_META_KEY]: toRecord(toDeliverySummary(outcome)),
151
+ ...payment?.provider === "x402" ? { [X402_MCP_PAYMENT_RESPONSE_META_KEY]: settlementResponse(payment) } : {},
152
+ ...receipt !== void 0 ? { [MPP_MCP_RECEIPT_META_KEY]: receipt } : {}
153
+ }
154
+ };
155
+ }
156
+ function structuredError(structured, sentence, meta) {
157
+ return {
158
+ isError: true,
159
+ content: [
160
+ { type: "text", text: JSON.stringify(structured) },
161
+ { type: "text", text: sentence }
162
+ ],
163
+ structuredContent: structured,
164
+ ...meta !== void 0 ? { _meta: meta } : {}
165
+ };
166
+ }
167
+ function withX402Challenge(envelope, challenge) {
168
+ return { ...challenge, ...envelope };
169
+ }
170
+ function paymentRequiredResult(outcome) {
171
+ const envelope = toPaymentRequiredEnvelope(outcome);
172
+ const p = envelope.payment;
173
+ const x402Required = p.provider === "x402" ? p.envelope : void 0;
174
+ const structured = x402Required !== void 0 ? withX402Challenge(toRecord(envelope), x402Required) : toRecord(envelope);
175
+ const mppRequired = p.provider === "mpp" ? mcpPaymentRequired(p.envelope) : void 0;
176
+ let meta;
177
+ if (x402Required !== void 0) meta = { [X402_MCP_ERROR_META_KEY]: x402Required };
178
+ else if (mppRequired !== void 0) meta = { [MPP_MCP_PAYMENT_REQUIRED_META_KEY]: mppRequired };
179
+ return structuredError(
180
+ structured,
181
+ // A challenge with several offers names no single destination
182
+ `Payment required: ${p.amount} ${p.currency}${p.destination !== void 0 ? ` to ${p.destination}` : ""} for resource "${outcome.resourceId}". Retry with an ${p.provider} payment proof in ${proofCarriers(p.provider)}.`,
183
+ meta
184
+ );
185
+ }
186
+ function errorResult(error, rail) {
187
+ const envelope = toErrorEnvelope(error);
188
+ const details = error.details;
189
+ const challenge = details?.["challenge"];
190
+ const refused = error.httpStatus === 402 && isRecord(challenge);
191
+ const x402Required = rail === "x402" && refused ? {
192
+ ...challenge,
193
+ error: x402ErrorCode(error.code, details?.["reason"], details?.["network"])
194
+ } : void 0;
195
+ const structured = x402Required !== void 0 ? withX402Challenge(toRecord(envelope), x402Required) : toRecord(envelope);
196
+ let meta;
197
+ if (rail === "x402") {
198
+ const settlement = error.code === "PAYMENT_SETTLEMENT_FAILED" ? settlementFailure(details) : settlementResponseFromDetails(details);
199
+ if (settlement !== void 0 || x402Required !== void 0) {
200
+ meta = {
201
+ ...x402Required !== void 0 ? { [X402_MCP_ERROR_META_KEY]: x402Required } : {},
202
+ ...settlement !== void 0 ? { [X402_MCP_PAYMENT_RESPONSE_META_KEY]: settlement } : {}
203
+ };
204
+ }
205
+ } else if (rail === "mpp" && refused) {
206
+ const required = mcpPaymentRequired(challenge, {
207
+ code: error.code,
208
+ reason: details?.["reason"],
209
+ detail: error.message
210
+ });
211
+ if (required !== void 0) meta = { [MPP_MCP_PAYMENT_REQUIRED_META_KEY]: required };
212
+ }
213
+ return structuredError(structured, `${envelope.code}: ${envelope.message}`, meta);
214
+ }
215
+ function mapOutcome(outcome) {
216
+ return outcome.kind === "delivered" ? deliveredResult(outcome) : paymentRequiredResult(outcome);
217
+ }
218
+
219
+ // src/protocols/mcp/adapter.ts
220
+ var DEFAULT_MOUNT_PATH = "/mcp";
221
+ var DEFAULT_SERVER_NAME = "agent-commerce";
222
+ var MAX_CONCURRENT_TOOL_CALLS = 8;
223
+ var MAX_REQUEST_BODY_BYTES = 256 * 1024;
224
+ var MAX_QUEUED_TOOL_CALLS = 64;
225
+ var MCP_BATCH_PROTOCOL_REVISION = "2025-03-26";
226
+ var TOOLS_LIST_CACHE_HINT = { ttlMs: 6e4, cacheScope: "public" };
227
+ var McpProtocolAdapter = class {
228
+ name = "mcp";
229
+ mountPath;
230
+ descriptor;
231
+ serverName;
232
+ serverVersion;
233
+ context;
234
+ started = false;
235
+ tools = [];
236
+ toolsByResourceId = /* @__PURE__ */ new Map();
237
+ skipped = [];
238
+ handler;
239
+ // Counting semaphore on `pipeline.execute()`. It lives on the adapter, not
240
+ // the request, so the cap holds across concurrent `/mcp` requests as well
241
+ // as within one batch.
242
+ inFlightToolCalls = 0;
243
+ toolCallWaiters = [];
244
+ constructor(options = {}) {
245
+ this.mountPath = options.mountPath ?? DEFAULT_MOUNT_PATH;
246
+ this.serverName = options.serverName ?? DEFAULT_SERVER_NAME;
247
+ this.serverVersion = options.serverVersion ?? PACKAGE_VERSION;
248
+ this.descriptor = buildDescriptor(PACKAGE_VERSION);
249
+ }
250
+ async start(context) {
251
+ this.context = context;
252
+ this.started = false;
253
+ let resources = [];
254
+ try {
255
+ resources = context.resources.listExposedVia("mcp");
256
+ } catch (err) {
257
+ context.logger.error(
258
+ { err: toLogInfo(err) },
259
+ "mcp adapter: failed to list mcp-exposed resources"
260
+ );
261
+ resources = [];
262
+ }
263
+ const tools = [];
264
+ const toolsByResourceId = /* @__PURE__ */ new Map();
265
+ const skipped = [];
266
+ for (const resource of resources) {
267
+ try {
268
+ if (!isValidToolName(resource.id)) {
269
+ skipped.push({
270
+ id: resource.id,
271
+ reason: `resource id is not a legal MCP tool name (must match ${MCP_TOOL_NAME_PATTERN.source})`
272
+ });
273
+ continue;
274
+ }
275
+ if (toolsByResourceId.has(resource.id)) {
276
+ skipped.push({ id: resource.id, reason: "duplicate tool name" });
277
+ continue;
278
+ }
279
+ const tool = {
280
+ name: resource.id,
281
+ description: buildToolDescription(resource),
282
+ inputSchema: buildInputSchema(resource)
283
+ };
284
+ tools.push({ resource, tool });
285
+ toolsByResourceId.set(resource.id, resource);
286
+ } catch (err) {
287
+ skipped.push({
288
+ id: resource.id,
289
+ reason: `failed to build MCP tool: ${toCommerceError(err).message}`
290
+ });
291
+ }
292
+ }
293
+ for (const s of skipped) {
294
+ context.logger.warn({ resourceId: s.id, reason: s.reason }, "mcp adapter: resource skipped");
295
+ }
296
+ this.tools = tools;
297
+ this.toolsByResourceId = toolsByResourceId;
298
+ this.skipped = skipped;
299
+ this.handler = createMcpHandler(
300
+ (ctx) => this.buildServer(ctx.requestInfo?.signal ?? new AbortController().signal),
301
+ {
302
+ legacy: "stateless",
303
+ // Reporting only: a request the SDK rejects is already answered
304
+ onerror: (err) => context.logger.debug({ err: toLogInfo(err) }, "mcp adapter: SDK error")
305
+ }
306
+ );
307
+ this.started = true;
308
+ context.logger.info(
309
+ {
310
+ mountPath: this.mountPath,
311
+ toolCount: tools.length,
312
+ skippedCount: skipped.length
313
+ },
314
+ "mcp adapter started"
315
+ );
316
+ }
317
+ async handleHttp(req, res) {
318
+ try {
319
+ if (!this.started || !this.context || !this.handler) {
320
+ this.writeJsonRpcError(res, 503, "MCP adapter is not running.");
321
+ return;
322
+ }
323
+ if (req.method !== "POST") {
324
+ this.writeJsonRpcError(res, 405, "Method not allowed. This endpoint only accepts POST.");
325
+ return;
326
+ }
327
+ const abortController = new AbortController();
328
+ res.once("close", () => abortController.abort());
329
+ const read = await readCappedBody(req, MAX_REQUEST_BODY_BYTES);
330
+ if (read.kind === "too-large") {
331
+ this.writeJsonRpcError(
332
+ res,
333
+ 413,
334
+ `Request body exceeds the ${MAX_REQUEST_BODY_BYTES}-byte limit.`
335
+ );
336
+ return;
337
+ }
338
+ if (read.kind !== "ok") {
339
+ this.writeJsonRpcError(res, 400, "Could not read the request body.");
340
+ return;
341
+ }
342
+ const body = parseJson(read.text);
343
+ const version = req.headers["mcp-protocol-version"];
344
+ if (Array.isArray(body) && version !== void 0 && version !== MCP_BATCH_PROTOCOL_REVISION) {
345
+ this.writeJsonRpcError(
346
+ res,
347
+ 400,
348
+ `Invalid Request: JSON-RPC batches are supported only in MCP ${MCP_BATCH_PROTOCOL_REVISION}.`
349
+ );
350
+ return;
351
+ }
352
+ const malformed = isModernRequest(req) ? void 0 : malformedToolCall(body);
353
+ if (malformed !== void 0) {
354
+ this.writeJsonRpcError(res, 200, malformed.message, -32602, malformed.id);
355
+ return;
356
+ }
357
+ const response = await this.handler.fetch(
358
+ toWebRequest(req, read.text, abortController.signal)
359
+ );
360
+ await writeWebResponse(response, res);
361
+ } catch (err) {
362
+ this.context?.logger.error({ err: toLogInfo(err) }, "mcp adapter: request handling failed");
363
+ this.writeJsonRpcError(res, 500, "Internal server error.");
364
+ }
365
+ }
366
+ async health() {
367
+ const checkedAt = this.context?.clock.nowIso() ?? (/* @__PURE__ */ new Date()).toISOString();
368
+ if (!this.started || !this.context) {
369
+ return { status: "fail", detail: "MCP adapter has not been started.", checkedAt };
370
+ }
371
+ const toolCount = this.tools.length;
372
+ if (this.skipped.length > 0) {
373
+ const detail = `${toolCount} tool(s) registered; ${this.skipped.length} resource(s) skipped: ${this.skipped.map((s) => `${s.id} (${s.reason})`).join("; ")}`;
374
+ return { status: "warn", detail, checkedAt };
375
+ }
376
+ return { status: "pass", detail: `${toolCount} tool(s) registered.`, checkedAt };
377
+ }
378
+ async stop() {
379
+ this.started = false;
380
+ const handler = this.handler;
381
+ this.handler = void 0;
382
+ try {
383
+ await handler?.close();
384
+ } catch {
385
+ }
386
+ this.context = void 0;
387
+ }
388
+ // Use general JSON-RPC codes for transport errors
389
+ writeJsonRpcError(res, status, message, code = status >= 500 ? -32603 : -32600, id = null) {
390
+ if (res.headersSent) return;
391
+ res.writeHead(status, {
392
+ "content-type": "application/json",
393
+ // RFC 9110 requires Allow on a 405, and this endpoint takes only POST
394
+ ...status === 405 ? { allow: "POST" } : {}
395
+ });
396
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id }));
397
+ }
398
+ buildServer(signal) {
399
+ const server = new Server(
400
+ { name: this.serverName, version: this.serverVersion },
401
+ { capabilities: { tools: {} }, cacheHints: { "tools/list": TOOLS_LIST_CACHE_HINT } }
402
+ );
403
+ server.setRequestHandler("tools/list", async (request) => {
404
+ if (request.params?.cursor !== void 0) {
405
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, "Invalid cursor.");
406
+ }
407
+ return { tools: this.tools.map((t) => t.tool) };
408
+ });
409
+ server.setRequestHandler(
410
+ "tools/call",
411
+ async (request) => this.handleToolCall(
412
+ request.params.name,
413
+ request.params.arguments ?? {},
414
+ request.params._meta,
415
+ signal
416
+ )
417
+ );
418
+ return server;
419
+ }
420
+ async handleToolCall(resourceId, rawArgs, meta, signal) {
421
+ const context = this.context;
422
+ if (!context) {
423
+ return errorResult(new CommerceError("INTERNAL_ERROR", "MCP adapter is not running."));
424
+ }
425
+ const resource = this.toolsByResourceId.get(resourceId);
426
+ if (!resource) {
427
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown tool "${resourceId}".`);
428
+ }
429
+ try {
430
+ const requestId = context.ids.next("mcp");
431
+ const fields = extractReservedInputFields(rawArgs, resource, requestId);
432
+ const { input, authorization } = fields;
433
+ const payment = paymentFromMeta(meta, resource) ?? fields.payment;
434
+ const request = {
435
+ requestId,
436
+ resourceId,
437
+ input,
438
+ protocol: "mcp",
439
+ receivedAt: context.clock.nowIso(),
440
+ ...payment !== void 0 ? { payment } : {},
441
+ ...authorization !== void 0 ? { authorization } : {}
442
+ };
443
+ await this.acquireToolCallSlot(signal);
444
+ try {
445
+ if (signal.aborted) {
446
+ return errorResult(
447
+ new CommerceError(
448
+ "PROTOCOL_UNSUPPORTED",
449
+ "Client disconnected before this call started."
450
+ )
451
+ );
452
+ }
453
+ const outcome = await context.pipeline.execute(request);
454
+ return mapOutcome(outcome);
455
+ } finally {
456
+ this.releaseToolCallSlot();
457
+ }
458
+ } catch (err) {
459
+ const error = toCommerceError(err);
460
+ context.logger.warn({ resourceId, err: toLogInfo(error) }, "mcp adapter: execution failed");
461
+ return errorResult(error, resource.paymentMethods[0]);
462
+ }
463
+ }
464
+ /**
465
+ * Takes a permit, or queues for one when MAX_CONCURRENT_TOOL_CALLS are in
466
+ * flight. Throws when the queue is full or the caller disconnected while
467
+ * queued; a throw never leaks a permit.
468
+ */
469
+ async acquireToolCallSlot(signal) {
470
+ if (this.inFlightToolCalls < MAX_CONCURRENT_TOOL_CALLS) {
471
+ this.inFlightToolCalls++;
472
+ return;
473
+ }
474
+ if (this.toolCallWaiters.length >= MAX_QUEUED_TOOL_CALLS) {
475
+ throw new CommerceError(
476
+ "GATEWAY_BUSY",
477
+ "Too many tool calls already queued on this adapter; retry later."
478
+ );
479
+ }
480
+ await new Promise((resolve) => this.toolCallWaiters.push(resolve));
481
+ if (signal.aborted) {
482
+ this.releaseToolCallSlot();
483
+ throw new CommerceError("PROTOCOL_UNSUPPORTED", "Client disconnected while queued.");
484
+ }
485
+ }
486
+ releaseToolCallSlot() {
487
+ const next = this.toolCallWaiters.shift();
488
+ if (next !== void 0) next();
489
+ else this.inFlightToolCalls--;
490
+ }
491
+ };
492
+ function toWebRequest(req, body, signal) {
493
+ const headers = new Headers();
494
+ for (const [name, value] of Object.entries(req.headers)) {
495
+ if (value !== void 0) headers.set(name, Array.isArray(value) ? value.join(", ") : value);
496
+ }
497
+ return new Request(new URL(req.url ?? "/", "http://localhost"), {
498
+ method: req.method ?? "POST",
499
+ headers,
500
+ body,
501
+ signal
502
+ });
503
+ }
504
+ function isModernRequest(req) {
505
+ if (req.headers["mcp-method"] !== void 0) return true;
506
+ const version = req.headers["mcp-protocol-version"];
507
+ return typeof version === "string" && version >= MCP_MODERN_PROTOCOL_REVISION;
508
+ }
509
+ function parseJson(body) {
510
+ try {
511
+ return JSON.parse(body);
512
+ } catch {
513
+ return void 0;
514
+ }
515
+ }
516
+ function malformedToolCall(message) {
517
+ if (!isRecord(message) || message["method"] !== "tools/call") return void 0;
518
+ const id = message["id"];
519
+ if (typeof id !== "string" && typeof id !== "number") return void 0;
520
+ const params = message["params"];
521
+ let problem;
522
+ if (!isRecord(params)) problem = "params must be an object";
523
+ else if (typeof params["name"] !== "string") problem = '"name" must be a string';
524
+ else if (params["arguments"] !== void 0 && !isRecord(params["arguments"])) {
525
+ problem = '"arguments" must be an object';
526
+ } else if (params["_meta"] !== void 0 && !isRecord(params["_meta"])) {
527
+ problem = '"_meta" must be an object';
528
+ }
529
+ return problem === void 0 ? void 0 : { id, message: `Invalid tools/call params: ${problem}.` };
530
+ }
531
+ async function writeWebResponse(response, res) {
532
+ res.writeHead(response.status, Object.fromEntries(response.headers));
533
+ if (response.body !== null) {
534
+ for await (const chunk of response.body) {
535
+ if (res.destroyed) break;
536
+ res.write(chunk);
537
+ }
538
+ }
539
+ res.end();
540
+ }
541
+ function paymentFromMeta(meta, resource) {
542
+ const method = resource.paymentMethods[0];
543
+ if (method === "x402") {
544
+ const payload = x402McpPaymentSubmission(meta?.[X402_MCP_PAYMENT_META_KEY]);
545
+ if (payload !== void 0) return { method, payload };
546
+ }
547
+ const credential = meta?.[MPP_MCP_CREDENTIAL_META_KEY];
548
+ if (method === "mpp" && isRecord(credential)) {
549
+ return { method, payload: JSON.stringify(credential) };
550
+ }
551
+ return void 0;
552
+ }
553
+ function createMcpAdapter(options) {
554
+ return new McpProtocolAdapter(options);
555
+ }
556
+
557
+ export { createMcpAdapter };
558
+ //# sourceMappingURL=mcp-HFD7ZNCQ.js.map
559
+ //# sourceMappingURL=mcp-HFD7ZNCQ.js.map