@paybond/kit 0.3.0 → 0.5.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.
@@ -0,0 +1,932 @@
1
+ #!/usr/bin/env node
2
+ import { GatewayAuthError, GatewayFraudClient, GatewaySignalClient, HarborHttpError, ServiceAccountHarborSession, SignalHttpError, } from "./index.js";
3
+ const SERVER_NAME = "Paybond MCP";
4
+ const SERVER_VERSION = "0.5.0";
5
+ const MCP_PROTOCOL_VERSION = "2025-11-25";
6
+ const DEFAULT_HARBOR_ACCESS_PATH = "/v1/auth/harbor-access";
7
+ const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
8
+ const DEFAULT_RECOGNITION_VERIFIER_ID = "paybond-gateway";
9
+ const agentRecognitionProofHeader = "x-paybond-agent-recognition-proof";
10
+ class GatewayHTTPError extends Error {
11
+ statusCode;
12
+ url;
13
+ bodyText;
14
+ errorCode;
15
+ errorMessage;
16
+ constructor(message, init) {
17
+ super(message);
18
+ this.name = "GatewayHTTPError";
19
+ this.statusCode = init.statusCode;
20
+ this.url = init.url;
21
+ this.bodyText = init.bodyText;
22
+ const parsed = parseGatewayErrorEnvelope(init.bodyText);
23
+ this.errorCode = init.errorCode ?? parsed.errorCode;
24
+ this.errorMessage = init.errorMessage ?? parsed.errorMessage;
25
+ }
26
+ }
27
+ function parseGatewayErrorEnvelope(text) {
28
+ if (!text.trim().startsWith("{")) {
29
+ return {};
30
+ }
31
+ try {
32
+ const body = JSON.parse(text);
33
+ if (body === null || Array.isArray(body) || typeof body !== "object") {
34
+ return {};
35
+ }
36
+ const errorCode = typeof body.error === "string" && body.error.trim() ? body.error.trim() : undefined;
37
+ const errorMessage = typeof body.message === "string" && body.message.trim() ? body.message.trim() : undefined;
38
+ return { errorCode, errorMessage };
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ function gatewayHTTPErrorMessage(method, path, statusCode, bodyText) {
45
+ const parsed = parseGatewayErrorEnvelope(bodyText);
46
+ if (parsed.errorCode) {
47
+ return `Gateway ${method} ${path} HTTP ${statusCode} (${parsed.errorCode}): ${parsed.errorMessage ?? bodyText}`;
48
+ }
49
+ return `Gateway ${method} ${path} HTTP ${statusCode}: ${bodyText}`;
50
+ }
51
+ class GatewayAPIClient {
52
+ gatewayBase;
53
+ apiKey;
54
+ maxRetries;
55
+ constructor(init) {
56
+ this.gatewayBase = normalizeBase(init.gatewayBaseUrl);
57
+ this.apiKey = init.apiKey.trim();
58
+ this.maxRetries = Math.max(1, init.maxRetries ?? 3);
59
+ }
60
+ async getJSON(path, extraHeaders) {
61
+ return this.requestJSON("GET", path, undefined, extraHeaders);
62
+ }
63
+ async postJSON(path, payload, extraHeaders) {
64
+ return this.requestJSON("POST", path, payload, extraHeaders);
65
+ }
66
+ async requestJSON(method, path, payload, extraHeaders) {
67
+ const url = `${this.gatewayBase}${path.startsWith("/") ? path : `/${path}`}`;
68
+ let lastErr;
69
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
70
+ let res;
71
+ try {
72
+ res = await fetch(url, {
73
+ method,
74
+ headers: {
75
+ accept: "application/json",
76
+ authorization: `Bearer ${this.apiKey}`,
77
+ ...(extraHeaders ?? {}),
78
+ ...(payload === undefined ? {} : { "content-type": "application/json" }),
79
+ },
80
+ ...(payload === undefined ? {} : { body: JSON.stringify(payload) }),
81
+ });
82
+ }
83
+ catch (err) {
84
+ lastErr = err;
85
+ if (attempt + 1 >= this.maxRetries) {
86
+ throw err;
87
+ }
88
+ await delay(backoffMs(attempt));
89
+ continue;
90
+ }
91
+ if ([429, 500, 502, 503, 504].includes(res.status) && attempt + 1 < this.maxRetries) {
92
+ const retryAfter = parseRetryAfterSeconds(res.headers.get("retry-after"));
93
+ await delay(retryAfter != null ? retryAfter * 1000 : backoffMs(attempt));
94
+ continue;
95
+ }
96
+ const text = await res.text();
97
+ if (!res.ok) {
98
+ const parsed = parseGatewayErrorEnvelope(text);
99
+ throw new GatewayHTTPError(gatewayHTTPErrorMessage(method, path, res.status, text), {
100
+ statusCode: res.status,
101
+ url,
102
+ bodyText: text,
103
+ errorCode: parsed.errorCode,
104
+ errorMessage: parsed.errorMessage,
105
+ });
106
+ }
107
+ return parseJSONObject(text, `Gateway ${method} ${path}`);
108
+ }
109
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
110
+ }
111
+ }
112
+ class PaybondMCPRuntime {
113
+ settings;
114
+ gateway;
115
+ principalValue = null;
116
+ signalValue = null;
117
+ fraudValue = null;
118
+ harborValue = null;
119
+ constructor(settings) {
120
+ this.settings = {
121
+ gatewayBaseUrl: settings.gatewayBaseUrl,
122
+ apiKey: settings.apiKey,
123
+ harborBaseUrl: settings.harborBaseUrl,
124
+ harborAccessPath: settings.harborAccessPath ?? DEFAULT_HARBOR_ACCESS_PATH,
125
+ principalPath: settings.principalPath ?? DEFAULT_PRINCIPAL_PATH,
126
+ maxRetries: Math.max(1, settings.maxRetries ?? 3),
127
+ clockSkewSeconds: Math.max(0, settings.clockSkewSeconds ?? 90),
128
+ };
129
+ this.gateway = new GatewayAPIClient({
130
+ gatewayBaseUrl: this.settings.gatewayBaseUrl,
131
+ apiKey: this.settings.apiKey,
132
+ maxRetries: this.settings.maxRetries,
133
+ });
134
+ }
135
+ async principal() {
136
+ this.principalValue ??= this.gateway.getJSON(this.settings.principalPath);
137
+ const body = await this.principalValue;
138
+ return { ...body };
139
+ }
140
+ async tenantId() {
141
+ const tenantId = String((await this.principal()).tenant_id ?? "").trim();
142
+ if (!tenantId) {
143
+ throw new Error("gateway principal JSON missing tenant_id");
144
+ }
145
+ return tenantId;
146
+ }
147
+ async signal() {
148
+ this.signalValue ??= (async () => new GatewaySignalClient(this.settings.gatewayBaseUrl, await this.tenantId(), {
149
+ staticGatewayBearerToken: this.settings.apiKey,
150
+ maxRetries: this.settings.maxRetries,
151
+ }))();
152
+ return this.signalValue;
153
+ }
154
+ async fraud() {
155
+ this.fraudValue ??= (async () => new GatewayFraudClient(this.settings.gatewayBaseUrl, await this.tenantId(), {
156
+ staticGatewayBearerToken: this.settings.apiKey,
157
+ maxRetries: this.settings.maxRetries,
158
+ }))();
159
+ return this.fraudValue;
160
+ }
161
+ async harbor() {
162
+ if (!this.settings.harborBaseUrl) {
163
+ throw new Error("PAYBOND_HARBOR_URL is required for direct Harbor mutation tools");
164
+ }
165
+ this.harborValue ??= ServiceAccountHarborSession.open({
166
+ gatewayBaseUrl: this.settings.gatewayBaseUrl,
167
+ apiKey: this.settings.apiKey,
168
+ harborBaseUrl: this.settings.harborBaseUrl,
169
+ harborAccessPath: this.settings.harborAccessPath,
170
+ clockSkewSeconds: this.settings.clockSkewSeconds,
171
+ maxRetries: this.settings.maxRetries,
172
+ });
173
+ return this.harborValue;
174
+ }
175
+ async listIntents(init) {
176
+ const params = new URLSearchParams({
177
+ limit: String(Math.max(1, Math.min(intArg(init.limit ?? 20, "limit"), 200))),
178
+ });
179
+ if (init.status?.trim()) {
180
+ params.set("status", init.status.trim());
181
+ }
182
+ if (init.operatorDid?.trim()) {
183
+ params.set("operator_did", init.operatorDid.trim());
184
+ }
185
+ if (init.cursor?.trim()) {
186
+ params.set("cursor", init.cursor.trim());
187
+ }
188
+ return this.gateway.getJSON(`/harbor/operator/v1/intents?${params.toString()}`, {
189
+ "x-tenant-id": await this.tenantId(),
190
+ });
191
+ }
192
+ async getIntent(intentId) {
193
+ return this.gateway.getJSON(`/harbor/operator/v1/intents/${encodeURIComponent(intentId)}`, {
194
+ "x-tenant-id": await this.tenantId(),
195
+ });
196
+ }
197
+ async getA2AAgentCard() {
198
+ return this.gateway.getJSON("/.well-known/agent-card.json");
199
+ }
200
+ async getA2ATaskContracts() {
201
+ return this.gateway.getJSON("/protocol/v2/a2a/task-contracts");
202
+ }
203
+ async getA2ATaskContract(contractId) {
204
+ return this.gateway.getJSON(`/protocol/v2/a2a/task-contracts/${encodeURIComponent(contractId)}`);
205
+ }
206
+ async verifyCapability(init) {
207
+ const body = await this.gateway.postJSON("/verify", {
208
+ intent_id: init.intentId,
209
+ token: init.token,
210
+ operation: init.operation,
211
+ requested_spend_cents: init.requestedSpendCents ?? 0,
212
+ }, {
213
+ "x-tenant-id": await this.tenantId(),
214
+ });
215
+ const expectedTenant = await this.tenantId();
216
+ const echoedTenant = String(body.tenant ?? "").trim();
217
+ if (echoedTenant !== expectedTenant) {
218
+ throw new Error(`tenant mismatch: expected=${expectedTenant} gateway=${echoedTenant}`);
219
+ }
220
+ const echoedIntent = String(body.intent_id ?? "").trim();
221
+ if (echoedIntent !== init.intentId) {
222
+ throw new Error(`verify intent mismatch: requested=${init.intentId} gateway=${echoedIntent}`);
223
+ }
224
+ return body;
225
+ }
226
+ async verifyAgentMandateV1(signedMandate) {
227
+ return this.gateway.postJSON("/protocol/v2/mandates/verify", signedMandate, {
228
+ "x-tenant-id": await this.tenantId(),
229
+ });
230
+ }
231
+ async verifyAgentRecognitionProofV1(init) {
232
+ const verifier = {
233
+ tenant_id: await this.tenantId(),
234
+ verifier_id: DEFAULT_RECOGNITION_VERIFIER_ID,
235
+ ...(init.expectedVerifier ?? {}),
236
+ };
237
+ return this.gateway.postJSON("/protocol/v2/recognition/verify", {
238
+ proof: init.proof,
239
+ expected_purpose: init.expectedPurpose,
240
+ expected_verifier: verifier,
241
+ expected_request: init.expectedRequest,
242
+ }, {
243
+ "x-tenant-id": await this.tenantId(),
244
+ });
245
+ }
246
+ async importAgentMandateV1(init) {
247
+ const body = await this.gateway.postJSON("/protocol/v2/mandates", {
248
+ signed_mandate: init.signedMandate,
249
+ intent_id: init.intentId,
250
+ transport_binding: init.transportBinding ?? {},
251
+ recognition_proof: init.recognitionProof,
252
+ }, {
253
+ "x-tenant-id": await this.tenantId(),
254
+ });
255
+ const expectedTenant = await this.tenantId();
256
+ const mandate = ensureObject(body.mandate, "mandate");
257
+ const authorization = ensureObject(mandate.authorization, "mandate.authorization");
258
+ const echoedTenant = String(authorization.tenant_id ?? "").trim();
259
+ if (echoedTenant !== expectedTenant) {
260
+ throw new Error(`tenant mismatch: expected=${expectedTenant} gateway=${echoedTenant}`);
261
+ }
262
+ const echoedIntent = String(body.intent_id ?? "").trim();
263
+ if (echoedIntent !== init.intentId) {
264
+ throw new Error(`intent mismatch: requested=${init.intentId} gateway=${echoedIntent}`);
265
+ }
266
+ return body;
267
+ }
268
+ async getSettlementReceiptV1(receiptId) {
269
+ const body = await this.gateway.getJSON(`/protocol/v2/receipts/${encodeURIComponent(receiptId)}`, {
270
+ "x-tenant-id": await this.tenantId(),
271
+ });
272
+ const expectedTenant = await this.tenantId();
273
+ const echoedTenant = String(body.tenant_id ?? "").trim();
274
+ if (echoedTenant !== expectedTenant) {
275
+ throw new Error(`tenant mismatch: expected=${expectedTenant} gateway=${echoedTenant}`);
276
+ }
277
+ const echoedReceipt = String(body.receipt_id ?? "").trim();
278
+ if (echoedReceipt !== receiptId) {
279
+ throw new Error(`receipt mismatch: requested=${receiptId} gateway=${echoedReceipt}`);
280
+ }
281
+ return body;
282
+ }
283
+ async verifyProtocolReceiptV1(receipt) {
284
+ return this.gateway.postJSON("/protocol/v2/receipts/verify", receipt, {
285
+ "x-tenant-id": await this.tenantId(),
286
+ });
287
+ }
288
+ async createHarborIntent(init) {
289
+ return this.gateway.postJSON("/harbor/intents", init.body, gatewayMutationHeaders(await this.tenantId(), init.recognitionProof, optionalMutationHeaders(init.idempotencyKey)));
290
+ }
291
+ async fundHarborIntent(init) {
292
+ return this.gateway.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/fund`, {}, gatewayMutationHeaders(await this.tenantId(), init.recognitionProof, {
293
+ ...optionalMutationHeaders(init.idempotencyKey),
294
+ ...(init.paymentSignature?.trim() ? { "payment-signature": init.paymentSignature.trim() } : {}),
295
+ }));
296
+ }
297
+ async submitHarborEvidence(init) {
298
+ return this.gateway.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/evidence`, init.body, gatewayMutationHeaders(await this.tenantId(), init.recognitionProof, optionalMutationHeaders(init.idempotencyKey)));
299
+ }
300
+ async confirmHarborSettlement(init) {
301
+ return this.gateway.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/settlement/confirm`, init.body, gatewayMutationHeaders(await this.tenantId(), init.recognitionProof, optionalMutationHeaders(init.idempotencyKey)));
302
+ }
303
+ }
304
+ function optionalMutationHeaders(idempotencyKey) {
305
+ if (!idempotencyKey?.trim()) {
306
+ return {};
307
+ }
308
+ return { "idempotency-key": idempotencyKey.trim() };
309
+ }
310
+ function gatewayMutationHeaders(tenantId, recognitionProof, extraHeaders) {
311
+ return {
312
+ ...(extraHeaders ?? {}),
313
+ "x-tenant-id": tenantId,
314
+ [agentRecognitionProofHeader]: encodeRecognitionProofHeader(recognitionProof),
315
+ };
316
+ }
317
+ export class PaybondMCPServer {
318
+ runtime;
319
+ tools;
320
+ initialized = false;
321
+ constructor(settings) {
322
+ if (!settings.gatewayBaseUrl.trim()) {
323
+ throw new Error("PAYBOND_GATEWAY_URL is required");
324
+ }
325
+ if (!settings.apiKey.trim()) {
326
+ throw new Error("PAYBOND_API_KEY is required");
327
+ }
328
+ this.runtime = new PaybondMCPRuntime(settings);
329
+ this.tools = this.buildTools(settings);
330
+ }
331
+ listTools() {
332
+ return this.tools.map((tool) => ({
333
+ name: tool.name,
334
+ description: tool.description,
335
+ inputSchema: tool.inputSchema,
336
+ }));
337
+ }
338
+ async callTool(name, args = {}) {
339
+ const tool = this.tools.find((candidate) => candidate.name === name);
340
+ if (!tool) {
341
+ return {
342
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
343
+ isError: true,
344
+ };
345
+ }
346
+ try {
347
+ const value = await tool.call(args);
348
+ return toToolResult(value);
349
+ }
350
+ catch (err) {
351
+ return {
352
+ content: [{ type: "text", text: formatError(err) }],
353
+ isError: true,
354
+ };
355
+ }
356
+ }
357
+ async handleMessage(message) {
358
+ if (message.jsonrpc !== "2.0") {
359
+ return responseError(message.id ?? null, -32600, "Invalid Request");
360
+ }
361
+ const method = typeof message.method === "string" ? message.method : null;
362
+ if (!method) {
363
+ return responseError(message.id ?? null, -32600, "Invalid Request");
364
+ }
365
+ // Notifications do not receive responses.
366
+ if (message.id === undefined) {
367
+ if (method === "notifications/initialized") {
368
+ this.initialized = true;
369
+ }
370
+ return null;
371
+ }
372
+ switch (method) {
373
+ case "initialize":
374
+ return {
375
+ jsonrpc: "2.0",
376
+ id: message.id,
377
+ result: {
378
+ protocolVersion: MCP_PROTOCOL_VERSION,
379
+ capabilities: {
380
+ tools: {
381
+ listChanged: false,
382
+ },
383
+ },
384
+ serverInfo: {
385
+ name: SERVER_NAME,
386
+ version: SERVER_VERSION,
387
+ },
388
+ instructions: "This MCP server is tenant-bound to the configured Paybond service-account API key. " +
389
+ "It works with any MCP-compatible host and does not assume a specific model provider.",
390
+ },
391
+ };
392
+ case "ping":
393
+ return {
394
+ jsonrpc: "2.0",
395
+ id: message.id,
396
+ result: {},
397
+ };
398
+ case "tools/list":
399
+ return {
400
+ jsonrpc: "2.0",
401
+ id: message.id,
402
+ result: {
403
+ tools: this.listTools(),
404
+ },
405
+ };
406
+ case "tools/call": {
407
+ const params = ensureObject(message.params, "tools/call params");
408
+ const name = stringArg(params.name, "name");
409
+ const args = params.arguments === undefined ? {} : ensureObject(params.arguments, "arguments");
410
+ return {
411
+ jsonrpc: "2.0",
412
+ id: message.id,
413
+ result: await this.callTool(name, args),
414
+ };
415
+ }
416
+ default:
417
+ return responseError(message.id, -32601, `Method not found: ${method}`);
418
+ }
419
+ }
420
+ runStdio() {
421
+ let buffer = "";
422
+ process.stdin.setEncoding("utf8");
423
+ process.stdin.on("data", (chunk) => {
424
+ buffer += chunk;
425
+ while (true) {
426
+ const newlineIndex = buffer.indexOf("\n");
427
+ if (newlineIndex < 0) {
428
+ break;
429
+ }
430
+ const line = buffer.slice(0, newlineIndex).trim();
431
+ buffer = buffer.slice(newlineIndex + 1);
432
+ if (!line) {
433
+ continue;
434
+ }
435
+ void this.handleLine(line);
436
+ }
437
+ });
438
+ process.stdin.on("end", () => {
439
+ if (buffer.trim()) {
440
+ void this.handleLine(buffer.trim());
441
+ }
442
+ });
443
+ process.stdin.resume();
444
+ }
445
+ async handleLine(line) {
446
+ let parsed;
447
+ try {
448
+ parsed = JSON.parse(line);
449
+ }
450
+ catch {
451
+ this.writeResponse(responseError(null, -32700, "Parse error"));
452
+ return;
453
+ }
454
+ const response = await this.handleMessage(parsed);
455
+ if (response !== null) {
456
+ this.writeResponse(response);
457
+ }
458
+ }
459
+ writeResponse(response) {
460
+ process.stdout.write(`${JSON.stringify(response)}\n`);
461
+ }
462
+ buildTools(settings) {
463
+ const tools = [
464
+ {
465
+ name: "paybond_get_principal",
466
+ description: "Resolve the tenant-bound Paybond principal behind the configured service-account API key.",
467
+ inputSchema: emptyObjectSchema(),
468
+ call: async () => this.runtime.principal(),
469
+ },
470
+ {
471
+ name: "paybond_verify_capability",
472
+ description: "Verify a capability token for one tenant-bound Harbor intent through the gateway compatibility route.",
473
+ inputSchema: objectSchema({
474
+ intent_id: { type: "string", description: "Canonical Harbor intent UUID." },
475
+ token: { type: "string", description: "Capability token to verify." },
476
+ operation: { type: "string", description: "Delegated operation or tool name." },
477
+ requested_spend_cents: {
478
+ type: "integer",
479
+ description: "Optional requested spend in cents for this tool call.",
480
+ },
481
+ }, ["intent_id", "token", "operation"]),
482
+ call: async (args) => this.runtime.verifyCapability({
483
+ intentId: uuidArg(args.intent_id, "intent_id"),
484
+ token: stringArg(args.token, "token"),
485
+ operation: stringArg(args.operation, "operation"),
486
+ requestedSpendCents: args.requested_spend_cents === undefined
487
+ ? 0
488
+ : intArg(args.requested_spend_cents, "requested_spend_cents"),
489
+ }),
490
+ },
491
+ {
492
+ name: "paybond_list_intents",
493
+ description: "List tenant-scoped Harbor intents through the gateway operator view with optional filters.",
494
+ inputSchema: objectSchema({
495
+ status: { type: "string" },
496
+ operator_did: { type: "string" },
497
+ limit: { type: "integer", minimum: 1, maximum: 200 },
498
+ cursor: { type: "string" },
499
+ }),
500
+ call: async (args) => this.runtime.listIntents({
501
+ status: optionalString(args.status),
502
+ operatorDid: optionalString(args.operator_did),
503
+ limit: args.limit === undefined ? 20 : intArg(args.limit, "limit"),
504
+ cursor: optionalString(args.cursor),
505
+ }),
506
+ },
507
+ {
508
+ name: "paybond_get_intent",
509
+ description: "Fetch one tenant-scoped Harbor intent detail through the gateway operator view.",
510
+ inputSchema: objectSchema({
511
+ intent_id: { type: "string", description: "Canonical Harbor intent UUID." },
512
+ }, ["intent_id"]),
513
+ call: async (args) => this.runtime.getIntent(uuidArg(args.intent_id, "intent_id")),
514
+ },
515
+ {
516
+ name: "paybond_get_reputation_receipt",
517
+ description: "Fetch the signed Signal receipt for one operator DID.",
518
+ inputSchema: objectSchema({
519
+ operator_did: { type: "string" },
520
+ score_version: { type: "string" },
521
+ }, ["operator_did"]),
522
+ call: async (args) => (await this.runtime.signal()).getReputationReceipt(stringArg(args.operator_did, "operator_did"), optionalString(args.score_version)),
523
+ },
524
+ {
525
+ name: "paybond_get_portfolio_summary",
526
+ description: "Fetch the tenant-scoped Signal portfolio summary.",
527
+ inputSchema: objectSchema({
528
+ score_version: { type: "string" },
529
+ }),
530
+ call: async (args) => (await this.runtime.signal()).getPortfolioSummary(optionalString(args.score_version)),
531
+ },
532
+ {
533
+ name: "paybond_get_signed_portfolio_artifact",
534
+ description: "Fetch the tenant-scoped signed Signal portfolio artifact for portable verifier and partner sharing.",
535
+ inputSchema: objectSchema({
536
+ score_version: { type: "string" },
537
+ }),
538
+ call: async (args) => (await this.runtime.signal()).getSignedPortfolioArtifact(optionalString(args.score_version)),
539
+ },
540
+ {
541
+ name: "paybond_get_fraud_assessment",
542
+ description: "Fetch the read-only fraud assessment for one tenant-scoped operator DID.",
543
+ inputSchema: objectSchema({
544
+ operator_did: { type: "string" },
545
+ score_version: { type: "string" },
546
+ }, ["operator_did"]),
547
+ call: async (args) => (await this.runtime.fraud()).getFraudAssessment(stringArg(args.operator_did, "operator_did"), optionalString(args.score_version)),
548
+ },
549
+ {
550
+ name: "paybond_get_fraud_metrics",
551
+ description: "Fetch tenant-scoped read-only fraud backtesting and monitoring metrics for a supported active window.",
552
+ inputSchema: objectSchema({
553
+ window: { type: "string", enum: ["24h", "7d", "30d"] },
554
+ score_version: { type: "string" },
555
+ }),
556
+ call: async (args) => (await this.runtime.fraud()).getFraudMetrics({
557
+ window: optionalString(args.window),
558
+ scoreVersion: optionalString(args.score_version),
559
+ }),
560
+ },
561
+ {
562
+ name: "paybond_get_a2a_agent_card",
563
+ description: "Fetch the published Paybond A2A discovery card for protocol-trust delegation.",
564
+ inputSchema: objectSchema({}),
565
+ call: async () => this.runtime.getA2AAgentCard(),
566
+ },
567
+ {
568
+ name: "paybond_list_a2a_task_contracts",
569
+ description: "Fetch the published catalog of Paybond A2A task contracts for delegated Harbor workflows.",
570
+ inputSchema: objectSchema({}),
571
+ call: async () => this.runtime.getA2ATaskContracts(),
572
+ },
573
+ {
574
+ name: "paybond_get_a2a_task_contract",
575
+ description: "Fetch one published Paybond A2A task contract by identifier.",
576
+ inputSchema: objectSchema({
577
+ contract_id: { type: "string" },
578
+ }, ["contract_id"]),
579
+ call: async (args) => this.runtime.getA2ATaskContract(stringArg(args.contract_id, "contract_id")),
580
+ },
581
+ {
582
+ name: "paybond_verify_agent_mandate_v1",
583
+ description: "Verify a signed AgentMandateV1 envelope through the gateway v2 protocol surface.",
584
+ inputSchema: objectSchema({
585
+ signed_mandate: { type: "object", additionalProperties: true },
586
+ }, ["signed_mandate"]),
587
+ call: async (args) => this.runtime.verifyAgentMandateV1(ensureObject(args.signed_mandate, "signed_mandate")),
588
+ },
589
+ {
590
+ name: "paybond_verify_agent_recognition_proof_v1",
591
+ description: "Verify a replay-safe AgentRecognitionProofV1 against an expected purpose, verifier context, and request envelope.",
592
+ inputSchema: objectSchema({
593
+ proof: { type: "object", additionalProperties: true },
594
+ expected_purpose: { type: "string" },
595
+ expected_request: { type: "object", additionalProperties: true },
596
+ expected_verifier: { type: "object", additionalProperties: true },
597
+ }, ["proof", "expected_purpose", "expected_request"]),
598
+ call: async (args) => this.runtime.verifyAgentRecognitionProofV1({
599
+ proof: ensureObject(args.proof, "proof"),
600
+ expectedPurpose: stringArg(args.expected_purpose, "expected_purpose"),
601
+ expectedRequest: ensureObject(args.expected_request, "expected_request"),
602
+ expectedVerifier: args.expected_verifier === undefined
603
+ ? undefined
604
+ : ensureObject(args.expected_verifier, "expected_verifier"),
605
+ }),
606
+ },
607
+ {
608
+ name: "paybond_import_agent_mandate_v1",
609
+ description: "Import a signed AgentMandateV1 through the gateway v2 protocol route and bind it to one Harbor intent using a replay-safe recognition proof.",
610
+ inputSchema: objectSchema({
611
+ signed_mandate: { type: "object", additionalProperties: true },
612
+ intent_id: { type: "string" },
613
+ recognition_proof: { type: "object", additionalProperties: true },
614
+ transport_binding: { type: "object", additionalProperties: true },
615
+ }, ["signed_mandate", "intent_id", "recognition_proof"]),
616
+ call: async (args) => this.runtime.importAgentMandateV1({
617
+ signedMandate: ensureObject(args.signed_mandate, "signed_mandate"),
618
+ intentId: uuidArg(args.intent_id, "intent_id"),
619
+ recognitionProof: ensureObject(args.recognition_proof, "recognition_proof"),
620
+ transportBinding: args.transport_binding === undefined
621
+ ? undefined
622
+ : ensureObject(args.transport_binding, "transport_binding"),
623
+ }),
624
+ },
625
+ {
626
+ name: "paybond_get_settlement_receipt_v1",
627
+ description: "Fetch the signed protocol-v2 settlement receipt for one Harbor intent.",
628
+ inputSchema: objectSchema({
629
+ receipt_id: { type: "string" },
630
+ }, ["receipt_id"]),
631
+ call: async (args) => this.runtime.getSettlementReceiptV1(uuidArg(args.receipt_id, "receipt_id")),
632
+ },
633
+ {
634
+ name: "paybond_verify_protocol_receipt_v1",
635
+ description: "Verify a protocol-v2 authorization or settlement receipt through the gateway.",
636
+ inputSchema: objectSchema({
637
+ receipt: { type: "object", additionalProperties: true },
638
+ }, ["receipt"]),
639
+ call: async (args) => this.runtime.verifyProtocolReceiptV1(ensureObject(args.receipt, "receipt")),
640
+ },
641
+ {
642
+ name: "paybond_create_intent",
643
+ description: "Create a Harbor intent through the gateway /harbor intent route. The request body must already be signed upstream and every call requires a recognition proof.",
644
+ inputSchema: objectSchema({
645
+ body: { type: "object", additionalProperties: true },
646
+ recognition_proof: { type: "object", additionalProperties: true },
647
+ idempotency_key: { type: "string" },
648
+ }, ["body", "recognition_proof"]),
649
+ call: async (args) => this.runtime.createHarborIntent({
650
+ body: ensureObject(args.body, "body"),
651
+ recognitionProof: ensureObject(args.recognition_proof, "recognition_proof"),
652
+ idempotencyKey: optionalString(args.idempotency_key),
653
+ }),
654
+ },
655
+ {
656
+ name: "paybond_fund_intent",
657
+ description: "Advance Harbor funding through the gateway /harbor path with a replay-safe recognition proof.",
658
+ inputSchema: objectSchema({
659
+ intent_id: { type: "string" },
660
+ recognition_proof: { type: "object", additionalProperties: true },
661
+ payment_signature: { type: "string" },
662
+ idempotency_key: { type: "string" },
663
+ }, ["intent_id", "recognition_proof"]),
664
+ call: async (args) => this.runtime.fundHarborIntent({
665
+ intentId: uuidArg(args.intent_id, "intent_id"),
666
+ recognitionProof: ensureObject(args.recognition_proof, "recognition_proof"),
667
+ paymentSignature: optionalString(args.payment_signature),
668
+ idempotencyKey: optionalString(args.idempotency_key),
669
+ }),
670
+ },
671
+ {
672
+ name: "paybond_submit_evidence",
673
+ description: "Submit payee evidence through the gateway /harbor path with a replay-safe recognition proof.",
674
+ inputSchema: objectSchema({
675
+ intent_id: { type: "string" },
676
+ body: { type: "object", additionalProperties: true },
677
+ recognition_proof: { type: "object", additionalProperties: true },
678
+ idempotency_key: { type: "string" },
679
+ }, ["intent_id", "body", "recognition_proof"]),
680
+ call: async (args) => this.runtime.submitHarborEvidence({
681
+ intentId: uuidArg(args.intent_id, "intent_id"),
682
+ body: ensureObject(args.body, "body"),
683
+ recognitionProof: ensureObject(args.recognition_proof, "recognition_proof"),
684
+ idempotencyKey: optionalString(args.idempotency_key),
685
+ }),
686
+ },
687
+ {
688
+ name: "paybond_confirm_settlement",
689
+ description: "Confirm Harbor settlement through the gateway /harbor path with a replay-safe recognition proof.",
690
+ inputSchema: objectSchema({
691
+ intent_id: { type: "string" },
692
+ body: { type: "object", additionalProperties: true },
693
+ recognition_proof: { type: "object", additionalProperties: true },
694
+ idempotency_key: { type: "string" },
695
+ }, ["intent_id", "body", "recognition_proof"]),
696
+ call: async (args) => this.runtime.confirmHarborSettlement({
697
+ intentId: uuidArg(args.intent_id, "intent_id"),
698
+ body: ensureObject(args.body, "body"),
699
+ recognitionProof: ensureObject(args.recognition_proof, "recognition_proof"),
700
+ idempotencyKey: optionalString(args.idempotency_key),
701
+ }),
702
+ },
703
+ ];
704
+ if (settings.harborBaseUrl?.trim()) {
705
+ tools.push({
706
+ name: "paybond_create_intent_legacy",
707
+ description: "Legacy direct-Harbor fallback for POST /intents. Prefer paybond_create_intent unless you explicitly need PAYBOND_HARBOR_URL direct mode.",
708
+ inputSchema: objectSchema({
709
+ body: { type: "object", additionalProperties: true },
710
+ idempotency_key: { type: "string" },
711
+ }, ["body"]),
712
+ call: async (args) => (await this.runtime.harbor()).harbor.createIntent(ensureObject(args.body, "body"), { idempotencyKey: optionalString(args.idempotency_key) }),
713
+ }, {
714
+ name: "paybond_fund_intent_legacy",
715
+ description: "Legacy direct-Harbor fallback for POST /intents/{intent_id}/fund.",
716
+ inputSchema: objectSchema({
717
+ intent_id: { type: "string" },
718
+ payment_signature: { type: "string" },
719
+ idempotency_key: { type: "string" },
720
+ }, ["intent_id"]),
721
+ call: async (args) => jsonObjectFromValue(await (await this.runtime.harbor()).harbor.fundIntent(uuidArg(args.intent_id, "intent_id"), {
722
+ paymentSignature: optionalString(args.payment_signature),
723
+ idempotencyKey: optionalString(args.idempotency_key),
724
+ })),
725
+ }, {
726
+ name: "paybond_submit_evidence_legacy",
727
+ description: "Legacy direct-Harbor fallback for POST /intents/{intent_id}/evidence.",
728
+ inputSchema: objectSchema({
729
+ intent_id: { type: "string" },
730
+ body: { type: "object", additionalProperties: true },
731
+ idempotency_key: { type: "string" },
732
+ }, ["intent_id", "body"]),
733
+ call: async (args) => (await this.runtime.harbor()).harbor.submitEvidence(uuidArg(args.intent_id, "intent_id"), ensureObject(args.body, "body"), { idempotencyKey: optionalString(args.idempotency_key) }),
734
+ });
735
+ }
736
+ return tools;
737
+ }
738
+ }
739
+ export function settingsFromEnv(env = process.env) {
740
+ const gatewayBaseUrl = String(env.PAYBOND_GATEWAY_URL ?? "").trim();
741
+ const apiKey = String(env.PAYBOND_API_KEY ?? "").trim();
742
+ if (!gatewayBaseUrl) {
743
+ throw new Error("PAYBOND_GATEWAY_URL is required");
744
+ }
745
+ if (!apiKey) {
746
+ throw new Error("PAYBOND_API_KEY is required");
747
+ }
748
+ return {
749
+ gatewayBaseUrl,
750
+ apiKey,
751
+ harborBaseUrl: optionalEnv(env.PAYBOND_HARBOR_URL),
752
+ harborAccessPath: optionalEnv(env.PAYBOND_HARBOR_ACCESS_PATH) ?? DEFAULT_HARBOR_ACCESS_PATH,
753
+ principalPath: optionalEnv(env.PAYBOND_PRINCIPAL_PATH) ?? DEFAULT_PRINCIPAL_PATH,
754
+ maxRetries: optionalEnv(env.PAYBOND_MCP_MAX_RETRIES)
755
+ ? intArg(optionalEnv(env.PAYBOND_MCP_MAX_RETRIES), "PAYBOND_MCP_MAX_RETRIES")
756
+ : 3,
757
+ clockSkewSeconds: optionalEnv(env.PAYBOND_MCP_CLOCK_SKEW_SECONDS)
758
+ ? numberArg(optionalEnv(env.PAYBOND_MCP_CLOCK_SKEW_SECONDS), "PAYBOND_MCP_CLOCK_SKEW_SECONDS")
759
+ : 90,
760
+ };
761
+ }
762
+ export function main(argv = process.argv.slice(2)) {
763
+ if (argv.includes("--help")) {
764
+ process.stderr.write("Usage: paybond-mcp-server\n\n" +
765
+ "Runs the tenant-bound Paybond MCP server over stdio using PAYBOND_GATEWAY_URL and PAYBOND_API_KEY.\n");
766
+ return 0;
767
+ }
768
+ if (argv.length > 0) {
769
+ process.stderr.write("paybond-mcp-server does not accept positional arguments\n");
770
+ return 1;
771
+ }
772
+ try {
773
+ new PaybondMCPServer(settingsFromEnv()).runStdio();
774
+ return 0;
775
+ }
776
+ catch (err) {
777
+ process.stderr.write(`${formatError(err)}\n`);
778
+ return 1;
779
+ }
780
+ }
781
+ function normalizeBase(url) {
782
+ return url.trim().replace(/\/+$/, "");
783
+ }
784
+ function parseJSONObject(text, context) {
785
+ let parsed;
786
+ try {
787
+ parsed = JSON.parse(text);
788
+ }
789
+ catch {
790
+ throw new Error(`${context} response was not JSON`);
791
+ }
792
+ return ensureObject(parsed, `${context} response`);
793
+ }
794
+ function ensureObject(value, field) {
795
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
796
+ throw new Error(`${field} must be an object`);
797
+ }
798
+ return value;
799
+ }
800
+ function stringArg(value, field) {
801
+ if (typeof value !== "string" || !value.trim()) {
802
+ throw new Error(`${field} must be a non-empty string`);
803
+ }
804
+ return value.trim();
805
+ }
806
+ function optionalString(value) {
807
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
808
+ }
809
+ function intArg(value, field) {
810
+ const parsed = typeof value === "number"
811
+ ? value
812
+ : typeof value === "string" && value.trim()
813
+ ? Number.parseInt(value, 10)
814
+ : Number.NaN;
815
+ if (!Number.isInteger(parsed)) {
816
+ throw new Error(`${field} must be an integer`);
817
+ }
818
+ return parsed;
819
+ }
820
+ function numberArg(value, field) {
821
+ const parsed = typeof value === "number"
822
+ ? value
823
+ : typeof value === "string" && value.trim()
824
+ ? Number(value)
825
+ : Number.NaN;
826
+ if (!Number.isFinite(parsed)) {
827
+ throw new Error(`${field} must be numeric`);
828
+ }
829
+ return parsed;
830
+ }
831
+ function uuidArg(value, field) {
832
+ const raw = stringArg(value, field);
833
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(raw)) {
834
+ throw new Error(`${field} must be a canonical UUID`);
835
+ }
836
+ return raw;
837
+ }
838
+ function encodeRecognitionProofHeader(proof) {
839
+ return Buffer.from(JSON.stringify(proof), "utf8").toString("base64url");
840
+ }
841
+ function objectSchema(properties, required = []) {
842
+ return {
843
+ type: "object",
844
+ properties,
845
+ additionalProperties: false,
846
+ ...(required.length === 0 ? {} : { required }),
847
+ };
848
+ }
849
+ function emptyObjectSchema() {
850
+ return {
851
+ type: "object",
852
+ properties: {},
853
+ additionalProperties: false,
854
+ };
855
+ }
856
+ function toToolResult(value) {
857
+ if (value === null) {
858
+ return {
859
+ content: [{ type: "text", text: "null" }],
860
+ };
861
+ }
862
+ return {
863
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
864
+ structuredContent: value,
865
+ };
866
+ }
867
+ function jsonObjectFromValue(value) {
868
+ if (value === null) {
869
+ return {};
870
+ }
871
+ if (Array.isArray(value)) {
872
+ return { items: value };
873
+ }
874
+ if (typeof value !== "object") {
875
+ return { value };
876
+ }
877
+ return JSON.parse(JSON.stringify(value));
878
+ }
879
+ function responseError(id, code, message) {
880
+ return {
881
+ jsonrpc: "2.0",
882
+ id,
883
+ error: {
884
+ code,
885
+ message,
886
+ },
887
+ };
888
+ }
889
+ function parseRetryAfterSeconds(v) {
890
+ if (!v)
891
+ return null;
892
+ const n = Number.parseFloat(v.trim());
893
+ if (!Number.isFinite(n))
894
+ return null;
895
+ return Math.min(n, 30);
896
+ }
897
+ function backoffMs(attempt) {
898
+ const base = 200 * 2 ** attempt;
899
+ const jitter = Math.random() * 100;
900
+ return Math.min(base + jitter, 5000);
901
+ }
902
+ function delay(ms) {
903
+ return new Promise((resolve) => setTimeout(resolve, ms));
904
+ }
905
+ function optionalEnv(value) {
906
+ return value?.trim() ? value.trim() : undefined;
907
+ }
908
+ function formatError(err) {
909
+ if (err instanceof Error ||
910
+ err instanceof GatewayAuthError ||
911
+ err instanceof GatewayHTTPError ||
912
+ err instanceof HarborHttpError ||
913
+ err instanceof SignalHttpError) {
914
+ return err.message;
915
+ }
916
+ return String(err);
917
+ }
918
+ const isMainModule = (() => {
919
+ const scriptPath = process.argv[1];
920
+ if (!scriptPath) {
921
+ return false;
922
+ }
923
+ try {
924
+ return import.meta.url === new URL(scriptPath, "file://").href;
925
+ }
926
+ catch {
927
+ return import.meta.url.endsWith(scriptPath);
928
+ }
929
+ })();
930
+ if (isMainModule && main() !== 0) {
931
+ process.exitCode = 1;
932
+ }