@paybond/kit 0.12.2 → 0.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/command-spec.js +14 -4
- package/dist/cli/commands/workflows.d.ts +2 -0
- package/dist/cli/commands/workflows.js +117 -0
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +3 -1
- package/dist/cli/router.js +9 -1
- package/dist/dev/offline-gateway.js +11 -0
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +543 -56
- package/package.json +1 -1
- package/templates/openai-shopping-agent/package-lock.json +7 -7
- package/templates/paybond-aws-operator/package-lock.json +4 -4
- package/templates/paybond-claude-agents-demo/package-lock.json +7 -7
- package/templates/paybond-mastra-travel-agent/package-lock.json +23 -23
- package/templates/paybond-mcp-coding-agent/package-lock.json +4 -4
- package/templates/paybond-openai-agents-demo/package-lock.json +7 -7
- package/templates/paybond-procurement-agent/package-lock.json +4 -4
- package/templates/paybond-shopify-shopping-agent/package-lock.json +4 -4
- package/templates/paybond-stripe-agent-demo/package-lock.json +4 -4
- package/templates/paybond-travel-agent/package-lock.json +4 -4
- package/templates/paybond-vercel-shopping-agent/package-lock.json +4 -4
package/dist/mcp-server.js
CHANGED
|
@@ -15,12 +15,51 @@ import { verifyAgentReceiptV1FromJSON } from "./agent-receipt.js";
|
|
|
15
15
|
import { agentReceiptResourceTemplateDefinition, agentReceiptResourceUri, MCP_AGENT_RECEIPT_RESOURCE_MIME_TYPE, parseAgentReceiptResourceUri, } from "./mcp-receipt-resource.js";
|
|
16
16
|
import { DEFAULT_PAYBOND_GATEWAY_BASE_URL, GatewayFraudClient, GatewaySignalClient, } from "./index.js";
|
|
17
17
|
const SERVER_NAME = "Paybond MCP";
|
|
18
|
-
const SERVER_VERSION = "0.12.
|
|
18
|
+
const SERVER_VERSION = "0.12.4";
|
|
19
19
|
const MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
20
20
|
const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
|
|
21
21
|
const DEFAULT_RECOGNITION_VERIFIER_ID = "paybond-gateway";
|
|
22
22
|
const agentRecognitionProofHeader = "x-paybond-agent-recognition-proof";
|
|
23
23
|
const DEFAULT_ENV_FILE = ".env.local";
|
|
24
|
+
const AUTHORIZE_SPEND_OUTPUT_PROPERTIES = {
|
|
25
|
+
allow: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "Whether the requested operation is allowed.",
|
|
28
|
+
},
|
|
29
|
+
tenant: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Tenant echoed by the gateway.",
|
|
32
|
+
},
|
|
33
|
+
intent_id: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Verified Harbor intent UUID.",
|
|
36
|
+
},
|
|
37
|
+
audit_id: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Gateway audit identifier when available.",
|
|
40
|
+
},
|
|
41
|
+
remaining_cents: {
|
|
42
|
+
type: "integer",
|
|
43
|
+
description: "Remaining spend budget in cents for the evaluated scope, when available.",
|
|
44
|
+
},
|
|
45
|
+
reason_codes: {
|
|
46
|
+
type: "array",
|
|
47
|
+
items: { type: "string" },
|
|
48
|
+
description: "Stable spend-policy reason codes from the authorization decision.",
|
|
49
|
+
},
|
|
50
|
+
message: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Human-readable decision message when present.",
|
|
53
|
+
},
|
|
54
|
+
decision_id: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Persisted spend decision identifier when authorization creates one.",
|
|
57
|
+
},
|
|
58
|
+
approval_request_id: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Approval request identifier when human approval is required.",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
24
63
|
const TOOL_SELECTION_METADATA = {
|
|
25
64
|
paybond_get_principal: {
|
|
26
65
|
title: "Get Paybond Principal",
|
|
@@ -40,22 +79,7 @@ const TOOL_SELECTION_METADATA = {
|
|
|
40
79
|
"Do not use this to create, fund, or modify intents; use paybond_authorize_agent_spend as the clearer gate before side-effecting agent tools.",
|
|
41
80
|
annotations: additiveMutationToolAnnotations("Verify Paybond Capability"),
|
|
42
81
|
outputSchema: outputObjectSchema({
|
|
43
|
-
|
|
44
|
-
type: "boolean",
|
|
45
|
-
description: "Whether the requested operation is allowed.",
|
|
46
|
-
},
|
|
47
|
-
tenant: {
|
|
48
|
-
type: "string",
|
|
49
|
-
description: "Tenant echoed by the gateway.",
|
|
50
|
-
},
|
|
51
|
-
intent_id: {
|
|
52
|
-
type: "string",
|
|
53
|
-
description: "Verified Harbor intent UUID.",
|
|
54
|
-
},
|
|
55
|
-
audit_id: {
|
|
56
|
-
type: "string",
|
|
57
|
-
description: "Gateway audit identifier when available.",
|
|
58
|
-
},
|
|
82
|
+
...AUTHORIZE_SPEND_OUTPUT_PROPERTIES,
|
|
59
83
|
}, ["tenant", "intent_id"]),
|
|
60
84
|
},
|
|
61
85
|
paybond_authorize_agent_spend: {
|
|
@@ -64,23 +88,58 @@ const TOOL_SELECTION_METADATA = {
|
|
|
64
88
|
"Do not use this for creating, funding, or changing intents; call paybond_create_spend_intent or paybond_fund_intent first when no funded capability token exists.",
|
|
65
89
|
annotations: additiveMutationToolAnnotations("Authorize Agent Spend"),
|
|
66
90
|
outputSchema: outputObjectSchema({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
...AUTHORIZE_SPEND_OUTPUT_PROPERTIES,
|
|
92
|
+
}, ["tenant", "intent_id"]),
|
|
93
|
+
},
|
|
94
|
+
paybond_get_budget_remaining: {
|
|
95
|
+
title: "Get Budget Remaining",
|
|
96
|
+
description: "Use this when you need a read-only dry-run of remaining spend budget for a tenant-bound intent before authorizing a paid tool. " +
|
|
97
|
+
"Do not use this to authorize spend or create decisions; call paybond_authorize_agent_spend when you are ready to gate a side-effecting tool.",
|
|
98
|
+
annotations: readOnlyToolAnnotations("Get Budget Remaining"),
|
|
99
|
+
outputSchema: outputObjectSchema({
|
|
100
|
+
remaining_cents: {
|
|
101
|
+
type: "integer",
|
|
102
|
+
description: "Remaining spend budget in cents for the evaluated scope, when available.",
|
|
70
103
|
},
|
|
71
|
-
|
|
72
|
-
type: "
|
|
73
|
-
description: "
|
|
104
|
+
spend_scope: {
|
|
105
|
+
type: "object",
|
|
106
|
+
description: "Spend scope used for the budget evaluation (scope_type and scope_key).",
|
|
107
|
+
additionalProperties: true,
|
|
74
108
|
},
|
|
75
|
-
|
|
109
|
+
policy_version: {
|
|
110
|
+
type: "integer",
|
|
111
|
+
description: "Active spend-control policy version when a policy is configured.",
|
|
112
|
+
},
|
|
113
|
+
}, []),
|
|
114
|
+
},
|
|
115
|
+
paybond_explain_policy: {
|
|
116
|
+
title: "Explain Spend Policy",
|
|
117
|
+
description: "Use this when you need a read-only explanation of whether a proposed spend would allow, require approval, or deny under the tenant spend-control policy. " +
|
|
118
|
+
"Do not use this to authorize spend or create approval requests; call paybond_authorize_agent_spend to persist a decision.",
|
|
119
|
+
annotations: readOnlyToolAnnotations("Explain Spend Policy"),
|
|
120
|
+
outputSchema: outputObjectSchema({
|
|
121
|
+
outcome: {
|
|
76
122
|
type: "string",
|
|
77
|
-
description: "
|
|
123
|
+
description: "Normalized policy outcome: allow, approval_required, or deny.",
|
|
78
124
|
},
|
|
79
|
-
|
|
125
|
+
reason_codes: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: { type: "string" },
|
|
128
|
+
description: "Stable policy reason codes from the dry-run evaluation.",
|
|
129
|
+
},
|
|
130
|
+
explanation: {
|
|
80
131
|
type: "string",
|
|
81
|
-
description: "
|
|
132
|
+
description: "Human-readable explanation derived from reason codes.",
|
|
82
133
|
},
|
|
83
|
-
|
|
134
|
+
remaining_cents: {
|
|
135
|
+
type: "integer",
|
|
136
|
+
description: "Remaining spend budget in cents for the evaluated scope, when available.",
|
|
137
|
+
},
|
|
138
|
+
approval_threshold_exceeded: {
|
|
139
|
+
type: "boolean",
|
|
140
|
+
description: "True when the dry-run indicates the request is at or above the approval threshold.",
|
|
141
|
+
},
|
|
142
|
+
}, ["outcome", "explanation"]),
|
|
84
143
|
},
|
|
85
144
|
paybond_bootstrap_sandbox_guardrail: {
|
|
86
145
|
title: "Bootstrap Sandbox Guardrail",
|
|
@@ -183,22 +242,89 @@ const TOOL_SELECTION_METADATA = {
|
|
|
183
242
|
},
|
|
184
243
|
paybond_get_reputation_receipt: {
|
|
185
244
|
title: "Get Reputation Receipt",
|
|
245
|
+
description: "Use this when you need the signed Signal reputation receipt for one known tenant-scoped operator DID " +
|
|
246
|
+
"(score, metrics, reason codes, and Ed25519 signing material under receipt). " +
|
|
247
|
+
"Requires PAYBOND_API_KEY with Signal analytics read access. " +
|
|
248
|
+
"Do not use this for tenant-wide aggregates—call paybond_get_portfolio_summary—or a portable signed operator list—call " +
|
|
249
|
+
"paybond_get_signed_portfolio_artifact—or one operator's fraud review posture—call paybond_get_fraud_assessment. " +
|
|
250
|
+
"Idempotent read with no side effects; returns null when no receipt exists for that operator and score_version.",
|
|
186
251
|
annotations: readOnlyToolAnnotations("Get Reputation Receipt"),
|
|
187
252
|
outputSchema: outputObjectSchema({
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
253
|
+
schema_version: {
|
|
254
|
+
type: "integer",
|
|
255
|
+
description: "Reputation receipt envelope schema version.",
|
|
256
|
+
},
|
|
257
|
+
updated_at: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "RFC3339 timestamp when the stored receipt row was last updated.",
|
|
260
|
+
},
|
|
261
|
+
receipt: {
|
|
262
|
+
type: "object",
|
|
263
|
+
additionalProperties: true,
|
|
264
|
+
description: "Signed Signal receipt for the operator (tenant_id, operator_did, score_version, score, metrics, reason_codes, signing_algorithm, message_digest_hex, signing_public_key_hex, signature_hex).",
|
|
265
|
+
examples: [
|
|
266
|
+
{
|
|
267
|
+
tenant_id: "tenant-a",
|
|
268
|
+
operator_did: "did:web:vendor.example#booker-agent",
|
|
269
|
+
score_version: "1.0",
|
|
270
|
+
score: 812,
|
|
271
|
+
signature_hex: "ab".repeat(32),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
191
275
|
}),
|
|
192
276
|
},
|
|
193
277
|
paybond_get_portfolio_summary: {
|
|
194
278
|
title: "Get Portfolio Summary",
|
|
279
|
+
description: "Use this when you need a read-only, tenant-scoped Signal portfolio aggregate for the authenticated API key " +
|
|
280
|
+
"(operator_count, average_score, total_terminal_intents, total_receipted_volume_cents, operators_under_review, " +
|
|
281
|
+
"and checkpoint_last_ledger_seq). Requires PAYBOND_API_KEY with Signal analytics read access and the private-dashboards feature. " +
|
|
282
|
+
"Do not use this when you need a portable signed operator list for partner or verifier sharing—call " +
|
|
283
|
+
"paybond_get_signed_portfolio_artifact instead—or for one operator's signed receipt—call paybond_get_reputation_receipt. " +
|
|
284
|
+
"Idempotent read with no side effects; auth, RBAC, feature, or gateway failures surface as tool errors.",
|
|
195
285
|
annotations: readOnlyToolAnnotations("Get Portfolio Summary"),
|
|
196
286
|
outputSchema: outputObjectSchema({
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
287
|
+
schema_version: {
|
|
288
|
+
type: "integer",
|
|
289
|
+
description: "Portfolio summary schema version (currently 1).",
|
|
290
|
+
},
|
|
291
|
+
tenant_id: {
|
|
292
|
+
type: "string",
|
|
293
|
+
description: "Tenant echoed by the gateway for the authenticated API key (example: tenant-a).",
|
|
294
|
+
examples: ["tenant-a"],
|
|
295
|
+
},
|
|
296
|
+
score_model_version: {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Score model version used for the aggregate (echoes the requested score_version or the gateway default 1.0).",
|
|
299
|
+
examples: ["1.0"],
|
|
300
|
+
},
|
|
301
|
+
scoring_model: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "Scoring model identifier used by Signal for this summary.",
|
|
304
|
+
},
|
|
305
|
+
checkpoint_last_ledger_seq: {
|
|
306
|
+
type: "integer",
|
|
307
|
+
description: "Last ledger sequence included in the tenant Signal checkpoint.",
|
|
308
|
+
},
|
|
309
|
+
operator_count: {
|
|
310
|
+
type: "integer",
|
|
311
|
+
description: "Number of operators with reputation data for this score model version.",
|
|
312
|
+
},
|
|
313
|
+
average_score: {
|
|
314
|
+
type: "number",
|
|
315
|
+
description: "Average operator score across the tenant portfolio for this score model version.",
|
|
316
|
+
},
|
|
317
|
+
total_terminal_intents: {
|
|
318
|
+
type: "integer",
|
|
319
|
+
description: "Aggregate terminal Harbor intents across operators in the portfolio.",
|
|
320
|
+
},
|
|
321
|
+
total_receipted_volume_cents: {
|
|
322
|
+
type: "integer",
|
|
323
|
+
description: "Aggregate receipted settlement volume in cents across the portfolio.",
|
|
324
|
+
},
|
|
325
|
+
operators_under_review: {
|
|
326
|
+
type: "integer",
|
|
327
|
+
description: "Count of operators currently under Signal review for this score model version.",
|
|
202
328
|
},
|
|
203
329
|
}),
|
|
204
330
|
},
|
|
@@ -214,21 +340,106 @@ const TOOL_SELECTION_METADATA = {
|
|
|
214
340
|
},
|
|
215
341
|
paybond_get_fraud_assessment: {
|
|
216
342
|
title: "Get Fraud Assessment",
|
|
343
|
+
description: "Use this when you need the read-only fraud assessment and review posture for one known tenant-scoped operator DID (review state, fraud signals, and compact fraud_assessment). " +
|
|
344
|
+
"Example: look up operator_did=did:web:vendor.example#booker-agent (optionally score_version=1.0) before deciding whether to continue a spend workflow for that operator. " +
|
|
345
|
+
"Do not use this for tenant-wide fraud backtesting metrics—call paybond_get_fraud_metrics instead—or for Harbor intent escrow detail—call paybond_get_intent. " +
|
|
346
|
+
"Idempotent read; returns null when no assessment exists for that operator.",
|
|
217
347
|
annotations: readOnlyToolAnnotations("Get Fraud Assessment"),
|
|
218
348
|
outputSchema: outputObjectSchema({
|
|
219
|
-
tenant_id: {
|
|
220
|
-
|
|
221
|
-
|
|
349
|
+
tenant_id: {
|
|
350
|
+
type: "string",
|
|
351
|
+
description: "Tenant echoed by the gateway for the authenticated API key (example: tenant-a).",
|
|
352
|
+
examples: ["tenant-a"],
|
|
353
|
+
},
|
|
354
|
+
operator_did: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Operator DID echoed from the assessment response (example: did:web:vendor.example#booker-agent).",
|
|
357
|
+
examples: ["did:web:vendor.example#booker-agent"],
|
|
358
|
+
},
|
|
359
|
+
fraud_assessment: {
|
|
360
|
+
type: "object",
|
|
361
|
+
additionalProperties: true,
|
|
362
|
+
description: "Compact fraud assessment for the operator (level, severity, signal counts, summary). Example shape: {\"level\":\"high\",\"highest_severity\":\"high\",\"signal_count\":1,\"summary\":\"level=high\"}.",
|
|
363
|
+
examples: [
|
|
364
|
+
{
|
|
365
|
+
level: "high",
|
|
366
|
+
highest_severity: "high",
|
|
367
|
+
signal_count: 1,
|
|
368
|
+
summary: "level=high",
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
},
|
|
222
372
|
}),
|
|
223
373
|
},
|
|
224
374
|
paybond_get_fraud_metrics: {
|
|
225
375
|
title: "Get Fraud Metrics",
|
|
376
|
+
description: "Use this when you need tenant-wide Signal fraud backtesting and monitoring metrics over a rolling window " +
|
|
377
|
+
"(flagged operators, severity counts, review outcomes, precision/false-positive rates, and backtest_summary). " +
|
|
378
|
+
"Requires PAYBOND_API_KEY with Signal analytics read access and the private-dashboards feature. " +
|
|
379
|
+
"Do not use this for one operator's fraud posture—call paybond_get_fraud_assessment instead—or for Harbor intent escrow detail—call paybond_get_intent. " +
|
|
380
|
+
"Idempotent read with no side effects; omit window to default to 24h; unsupported windows fail with HTTP 400 " +
|
|
381
|
+
"(\"window must be one of 24h, 7d, or 30d\").",
|
|
226
382
|
annotations: readOnlyToolAnnotations("Get Fraud Metrics"),
|
|
227
383
|
outputSchema: outputObjectSchema({
|
|
228
|
-
tenant_id: {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
384
|
+
tenant_id: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "Tenant echoed by the gateway for the authenticated API key (example: tenant-a).",
|
|
387
|
+
examples: ["tenant-a"],
|
|
388
|
+
},
|
|
389
|
+
score_model_version: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Score model version used for the metrics (echoes the requested score_version or the gateway default 1.0).",
|
|
392
|
+
examples: ["1.0"],
|
|
393
|
+
},
|
|
394
|
+
window: {
|
|
395
|
+
type: "string",
|
|
396
|
+
description: "Active metrics window label: 24h, 7d, or 30d.",
|
|
397
|
+
examples: ["24h", "7d", "30d"],
|
|
398
|
+
},
|
|
399
|
+
window_started_at: {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "RFC3339 start of the evaluated rolling window.",
|
|
402
|
+
},
|
|
403
|
+
window_ended_at: {
|
|
404
|
+
type: "string",
|
|
405
|
+
description: "RFC3339 end of the evaluated rolling window.",
|
|
406
|
+
},
|
|
407
|
+
flagged_operator_count: {
|
|
408
|
+
type: "integer",
|
|
409
|
+
description: "Operators with at least one fraud signal in the window.",
|
|
410
|
+
},
|
|
411
|
+
critical_signal_count: {
|
|
412
|
+
type: "integer",
|
|
413
|
+
description: "Count of critical-severity fraud signals in the window.",
|
|
414
|
+
},
|
|
415
|
+
high_signal_count: {
|
|
416
|
+
type: "integer",
|
|
417
|
+
description: "Count of high-severity fraud signals in the window.",
|
|
418
|
+
},
|
|
419
|
+
elevated_signal_count: {
|
|
420
|
+
type: "integer",
|
|
421
|
+
description: "Count of elevated-severity fraud signals in the window.",
|
|
422
|
+
},
|
|
423
|
+
review_open_count: {
|
|
424
|
+
type: "integer",
|
|
425
|
+
description: "Operators currently in an open review state.",
|
|
426
|
+
},
|
|
427
|
+
labeled_outcome_count: {
|
|
428
|
+
type: "integer",
|
|
429
|
+
description: "Review outcomes labeled in the window (confirmed risk, false positive, or needs more evidence).",
|
|
430
|
+
},
|
|
431
|
+
confirmed_risk_count: {
|
|
432
|
+
type: "integer",
|
|
433
|
+
description: "Labeled confirmed-risk outcomes in the window.",
|
|
434
|
+
},
|
|
435
|
+
false_positive_count: {
|
|
436
|
+
type: "integer",
|
|
437
|
+
description: "Labeled false-positive outcomes in the window.",
|
|
438
|
+
},
|
|
439
|
+
backtest_summary: {
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Human-readable backtest summary derived from the window metrics.",
|
|
442
|
+
},
|
|
232
443
|
}),
|
|
233
444
|
},
|
|
234
445
|
paybond_get_a2a_agent_card: {
|
|
@@ -299,10 +510,40 @@ const TOOL_SELECTION_METADATA = {
|
|
|
299
510
|
},
|
|
300
511
|
paybond_verify_protocol_receipt_v1: {
|
|
301
512
|
title: "Verify Protocol Receipt",
|
|
513
|
+
description: "Use this when you already have a signed protocol-v2 authorization or settlement receipt JSON object and need offline Ed25519 verification (structure, message digest, and signature) through the gateway. " +
|
|
514
|
+
"Do not use this to verify AgentMandateV1 envelopes—call paybond_verify_agent_mandate_v1—or to check a Harbor capability token before spend—call paybond_verify_capability or paybond_authorize_agent_spend. " +
|
|
515
|
+
"To load a settlement receipt by intent UUID first, call paybond_get_settlement_receipt_v1 then pass its body here. " +
|
|
516
|
+
"Read-only and side-effect free: success returns valid=true with kind, receipt_id, tenant_id, and the normalized receipt; unsupported kind, malformed JSON, digest mismatch, or bad signature fail with a gateway error (typically HTTP 400).",
|
|
302
517
|
annotations: readOnlyToolAnnotations("Verify Protocol Receipt"),
|
|
303
518
|
outputSchema: outputObjectSchema({
|
|
304
|
-
valid: {
|
|
305
|
-
|
|
519
|
+
valid: {
|
|
520
|
+
type: "boolean",
|
|
521
|
+
description: "True when the gateway accepted the receipt structure and Ed25519 signature. Example: true.",
|
|
522
|
+
examples: [true],
|
|
523
|
+
},
|
|
524
|
+
kind: {
|
|
525
|
+
type: "string",
|
|
526
|
+
description: "Verified receipt kind echoed from the normalized receipt. One of paybond.protocol_authorization_receipt_v1 or paybond.protocol_settlement_receipt_v1.",
|
|
527
|
+
examples: [
|
|
528
|
+
"paybond.protocol_authorization_receipt_v1",
|
|
529
|
+
"paybond.protocol_settlement_receipt_v1",
|
|
530
|
+
],
|
|
531
|
+
},
|
|
532
|
+
receipt_id: {
|
|
533
|
+
type: "string",
|
|
534
|
+
description: "Canonical receipt identifier from the verified receipt.",
|
|
535
|
+
examples: ["550e8400-e29b-41d4-a716-446655440000"],
|
|
536
|
+
},
|
|
537
|
+
tenant_id: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "Tenant id embedded in the verified receipt (not invented by the caller).",
|
|
540
|
+
examples: ["acme-pilot"],
|
|
541
|
+
},
|
|
542
|
+
receipt: {
|
|
543
|
+
type: "object",
|
|
544
|
+
additionalProperties: true,
|
|
545
|
+
description: "Normalized verified receipt object matching the input kind (authorization or settlement fields plus signing material).",
|
|
546
|
+
},
|
|
306
547
|
}),
|
|
307
548
|
},
|
|
308
549
|
paybond_create_intent: {
|
|
@@ -732,6 +973,70 @@ class PaybondMCPRuntime {
|
|
|
732
973
|
}
|
|
733
974
|
return body;
|
|
734
975
|
}
|
|
976
|
+
/**
|
|
977
|
+
* Side-effect-free spend policy dry-run via `POST /v1/spend/preflight`.
|
|
978
|
+
* Tenant scope comes from the authenticated API key only.
|
|
979
|
+
*/
|
|
980
|
+
async spendPreflight(init) {
|
|
981
|
+
const payload = {
|
|
982
|
+
intent_id: init.intentId,
|
|
983
|
+
operation: init.operation?.trim() || "*",
|
|
984
|
+
requested_spend_cents: init.requestedSpendCents ?? 0,
|
|
985
|
+
};
|
|
986
|
+
if (init.vendorId?.trim()) {
|
|
987
|
+
payload.vendor_id = init.vendorId.trim();
|
|
988
|
+
}
|
|
989
|
+
if (init.toolName?.trim()) {
|
|
990
|
+
payload.tool_name = init.toolName.trim();
|
|
991
|
+
}
|
|
992
|
+
if (init.taskId?.trim()) {
|
|
993
|
+
payload.task_id = init.taskId.trim();
|
|
994
|
+
}
|
|
995
|
+
if (init.workflowId?.trim()) {
|
|
996
|
+
payload.workflow_id = init.workflowId.trim();
|
|
997
|
+
}
|
|
998
|
+
if (init.toolCallId?.trim()) {
|
|
999
|
+
payload.tool_call_id = init.toolCallId.trim();
|
|
1000
|
+
}
|
|
1001
|
+
if (init.currency?.trim()) {
|
|
1002
|
+
payload.currency = init.currency.trim();
|
|
1003
|
+
}
|
|
1004
|
+
if (init.agentSubject?.trim()) {
|
|
1005
|
+
payload.agent_subject = init.agentSubject.trim();
|
|
1006
|
+
}
|
|
1007
|
+
if (init.approvalToken?.trim()) {
|
|
1008
|
+
payload.approval_token = init.approvalToken.trim();
|
|
1009
|
+
}
|
|
1010
|
+
return this.gateway.postJSON("/v1/spend/preflight", payload, {
|
|
1011
|
+
"x-tenant-id": await this.tenantId(),
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
async getBudgetRemaining(init) {
|
|
1015
|
+
const body = await this.spendPreflight(init);
|
|
1016
|
+
return {
|
|
1017
|
+
remaining_cents: body.remaining_cents ?? null,
|
|
1018
|
+
spend_scope: body.spend_scope ?? null,
|
|
1019
|
+
policy_version: body.policy_version ?? null,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
async explainPolicy(init) {
|
|
1023
|
+
const body = await this.spendPreflight(init);
|
|
1024
|
+
const reasonCodes = Array.isArray(body.reason_codes)
|
|
1025
|
+
? body.reason_codes.map((code) => String(code))
|
|
1026
|
+
: [];
|
|
1027
|
+
const outcome = normalizeExplainPolicyOutcome(String(body.outcome ?? ""), String(body.classification ?? ""));
|
|
1028
|
+
const result = {
|
|
1029
|
+
outcome,
|
|
1030
|
+
reason_codes: reasonCodes,
|
|
1031
|
+
explanation: String(body.explanation ?? ""),
|
|
1032
|
+
remaining_cents: body.remaining_cents ?? null,
|
|
1033
|
+
};
|
|
1034
|
+
if (reasonCodes.includes("approval_threshold_exceeded") ||
|
|
1035
|
+
outcome === "approval_required") {
|
|
1036
|
+
result.approval_threshold_exceeded = reasonCodes.includes("approval_threshold_exceeded");
|
|
1037
|
+
}
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
735
1040
|
async bootstrapSandboxGuardrail(init) {
|
|
736
1041
|
const expectedTenant = await this.tenantId();
|
|
737
1042
|
const payload = {
|
|
@@ -904,11 +1209,17 @@ export function formatMcpStdioFrame(response) {
|
|
|
904
1209
|
const body = JSON.stringify(response);
|
|
905
1210
|
return `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
|
|
906
1211
|
}
|
|
1212
|
+
/** Newline-delimited JSON-RPC frame used by mcp-proxy and recent MCP SDKs. */
|
|
1213
|
+
export function formatMcpNdjsonFrame(response) {
|
|
1214
|
+
return `${JSON.stringify(response)}\n`;
|
|
1215
|
+
}
|
|
907
1216
|
export class PaybondMCPServer {
|
|
908
1217
|
runtime;
|
|
909
1218
|
tools;
|
|
910
1219
|
toolPolicy;
|
|
911
1220
|
initialized = false;
|
|
1221
|
+
/** Framing negotiated from the first successfully parsed stdin message. */
|
|
1222
|
+
stdioFraming = "content-length";
|
|
912
1223
|
constructor(settings) {
|
|
913
1224
|
if (!settings.apiKey.trim()) {
|
|
914
1225
|
throw new Error("PAYBOND_API_KEY is required");
|
|
@@ -1109,7 +1420,11 @@ export class PaybondMCPServer {
|
|
|
1109
1420
|
runStdio() {
|
|
1110
1421
|
let buffer = Buffer.alloc(0);
|
|
1111
1422
|
process.stdin.on("data", (chunk) => {
|
|
1112
|
-
buffer = Buffer.concat([
|
|
1423
|
+
buffer = Buffer.concat([
|
|
1424
|
+
buffer,
|
|
1425
|
+
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8"),
|
|
1426
|
+
]);
|
|
1427
|
+
// Prefer Content-Length frames when present (LSP-style MCP hosts).
|
|
1113
1428
|
while (true) {
|
|
1114
1429
|
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
1115
1430
|
if (headerEnd < 0) {
|
|
@@ -1131,8 +1446,36 @@ export class PaybondMCPServer {
|
|
|
1131
1446
|
}
|
|
1132
1447
|
const body = buffer.subarray(bodyStart, frameEnd).toString("utf8");
|
|
1133
1448
|
buffer = buffer.subarray(frameEnd);
|
|
1449
|
+
this.stdioFraming = "content-length";
|
|
1134
1450
|
void this.handleLine(body);
|
|
1135
1451
|
}
|
|
1452
|
+
// Do not let NDJSON parsing consume an in-progress Content-Length frame.
|
|
1453
|
+
const preview = buffer
|
|
1454
|
+
.subarray(0, Math.min(buffer.length, 64))
|
|
1455
|
+
.toString("utf8")
|
|
1456
|
+
.trimStart()
|
|
1457
|
+
.toLowerCase();
|
|
1458
|
+
if (preview.startsWith("content-length:")) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
// Newline-delimited JSON-RPC (mcp-proxy / recent MCP SDKs).
|
|
1462
|
+
while (true) {
|
|
1463
|
+
const newline = buffer.indexOf("\n");
|
|
1464
|
+
if (newline < 0) {
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
const rawLine = buffer
|
|
1468
|
+
.subarray(0, newline)
|
|
1469
|
+
.toString("utf8")
|
|
1470
|
+
.replace(/\r$/, "");
|
|
1471
|
+
buffer = buffer.subarray(newline + 1);
|
|
1472
|
+
const line = rawLine.trim();
|
|
1473
|
+
if (!line.startsWith("{")) {
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
this.stdioFraming = "ndjson";
|
|
1477
|
+
void this.handleLine(line);
|
|
1478
|
+
}
|
|
1136
1479
|
});
|
|
1137
1480
|
process.stdin.on("end", () => {
|
|
1138
1481
|
if (buffer.length > 0) {
|
|
@@ -1156,7 +1499,10 @@ export class PaybondMCPServer {
|
|
|
1156
1499
|
}
|
|
1157
1500
|
}
|
|
1158
1501
|
writeResponse(response) {
|
|
1159
|
-
|
|
1502
|
+
const frame = this.stdioFraming === "ndjson"
|
|
1503
|
+
? formatMcpNdjsonFrame(response)
|
|
1504
|
+
: formatMcpStdioFrame(response);
|
|
1505
|
+
process.stdout.write(frame);
|
|
1160
1506
|
}
|
|
1161
1507
|
buildTools(settings) {
|
|
1162
1508
|
const tools = [
|
|
@@ -1234,6 +1580,18 @@ export class PaybondMCPServer {
|
|
|
1234
1580
|
});
|
|
1235
1581
|
},
|
|
1236
1582
|
},
|
|
1583
|
+
{
|
|
1584
|
+
name: "paybond_get_budget_remaining",
|
|
1585
|
+
description: "Read-only dry-run of remaining spend budget for a tenant-bound intent via gateway spend preflight.",
|
|
1586
|
+
inputSchema: spendPreflightInputSchema(),
|
|
1587
|
+
call: async (args) => this.runtime.getBudgetRemaining(parseSpendPreflightArgs(args)),
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
name: "paybond_explain_policy",
|
|
1591
|
+
description: "Read-only dry-run explanation of whether proposed spend would allow, require approval, or deny under tenant spend policy.",
|
|
1592
|
+
inputSchema: spendPreflightInputSchema(),
|
|
1593
|
+
call: async (args) => this.runtime.explainPolicy(parseSpendPreflightArgs(args)),
|
|
1594
|
+
},
|
|
1237
1595
|
{
|
|
1238
1596
|
name: "paybond_bootstrap_sandbox_guardrail",
|
|
1239
1597
|
description: "Bootstrap a sandbox-only Paybond guardrail intent for a first paid-tool integration. Tenant scope is derived from the configured service-account API key and the route never touches live settlement rails.",
|
|
@@ -1392,8 +1750,19 @@ export class PaybondMCPServer {
|
|
|
1392
1750
|
name: "paybond_get_reputation_receipt",
|
|
1393
1751
|
description: "Fetch the signed Signal receipt for one operator DID.",
|
|
1394
1752
|
inputSchema: objectSchema({
|
|
1395
|
-
operator_did: {
|
|
1396
|
-
|
|
1753
|
+
operator_did: {
|
|
1754
|
+
type: "string",
|
|
1755
|
+
description: "Tenant-scoped operator DID whose signed Signal reputation receipt to fetch. Must belong to the authenticated tenant; do not invent tenant identifiers. Examples: did:web:vendor.example#booker-agent, did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.",
|
|
1756
|
+
examples: [
|
|
1757
|
+
"did:web:vendor.example#booker-agent",
|
|
1758
|
+
"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
1759
|
+
],
|
|
1760
|
+
},
|
|
1761
|
+
score_version: {
|
|
1762
|
+
type: "string",
|
|
1763
|
+
description: "Optional Signal score model version to query. Omit to use the gateway default current model (1.0). Example: 1.0.",
|
|
1764
|
+
examples: ["1.0"],
|
|
1765
|
+
},
|
|
1397
1766
|
}, ["operator_did"]),
|
|
1398
1767
|
call: async (args) => (await this.runtime.signal()).getReputationReceipt(stringArg(args.operator_did, "operator_did"), optionalString(args.score_version)),
|
|
1399
1768
|
},
|
|
@@ -1401,7 +1770,11 @@ export class PaybondMCPServer {
|
|
|
1401
1770
|
name: "paybond_get_portfolio_summary",
|
|
1402
1771
|
description: "Fetch the tenant-scoped Signal portfolio summary.",
|
|
1403
1772
|
inputSchema: objectSchema({
|
|
1404
|
-
score_version: {
|
|
1773
|
+
score_version: {
|
|
1774
|
+
type: "string",
|
|
1775
|
+
description: "Optional Signal score model version to query. Omit to use the gateway default current model (1.0). Example: 1.0.",
|
|
1776
|
+
examples: ["1.0"],
|
|
1777
|
+
},
|
|
1405
1778
|
}),
|
|
1406
1779
|
call: async (args) => (await this.runtime.signal()).getPortfolioSummary(optionalString(args.score_version)),
|
|
1407
1780
|
},
|
|
@@ -1417,8 +1790,19 @@ export class PaybondMCPServer {
|
|
|
1417
1790
|
name: "paybond_get_fraud_assessment",
|
|
1418
1791
|
description: "Fetch the read-only fraud assessment for one tenant-scoped operator DID.",
|
|
1419
1792
|
inputSchema: objectSchema({
|
|
1420
|
-
operator_did: {
|
|
1421
|
-
|
|
1793
|
+
operator_did: {
|
|
1794
|
+
type: "string",
|
|
1795
|
+
description: "Tenant-scoped operator DID to assess. Must belong to the authenticated tenant; do not invent tenant identifiers. Examples: did:web:vendor.example#booker-agent, did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.",
|
|
1796
|
+
examples: [
|
|
1797
|
+
"did:web:vendor.example#booker-agent",
|
|
1798
|
+
"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
1799
|
+
],
|
|
1800
|
+
},
|
|
1801
|
+
score_version: {
|
|
1802
|
+
type: "string",
|
|
1803
|
+
description: "Optional Signal score model version to query. Omit to use the gateway default current model. Example: 1.0.",
|
|
1804
|
+
examples: ["1.0"],
|
|
1805
|
+
},
|
|
1422
1806
|
}, ["operator_did"]),
|
|
1423
1807
|
call: async (args) => (await this.runtime.fraud()).getFraudAssessment(stringArg(args.operator_did, "operator_did"), optionalString(args.score_version)),
|
|
1424
1808
|
},
|
|
@@ -1426,8 +1810,17 @@ export class PaybondMCPServer {
|
|
|
1426
1810
|
name: "paybond_get_fraud_metrics",
|
|
1427
1811
|
description: "Fetch tenant-scoped read-only fraud backtesting and monitoring metrics for a supported active window.",
|
|
1428
1812
|
inputSchema: objectSchema({
|
|
1429
|
-
window: {
|
|
1430
|
-
|
|
1813
|
+
window: {
|
|
1814
|
+
type: "string",
|
|
1815
|
+
enum: ["24h", "7d", "30d"],
|
|
1816
|
+
description: "Rolling metrics window. Allowed values: 24h, 7d, 30d. Omit to use the gateway default 24h. Unsupported values fail with HTTP 400.",
|
|
1817
|
+
examples: ["24h", "7d", "30d"],
|
|
1818
|
+
},
|
|
1819
|
+
score_version: {
|
|
1820
|
+
type: "string",
|
|
1821
|
+
description: "Optional Signal score model version to query. Omit to use the gateway default current model (1.0). Example: 1.0.",
|
|
1822
|
+
examples: ["1.0"],
|
|
1823
|
+
},
|
|
1431
1824
|
}),
|
|
1432
1825
|
call: async (args) => (await this.runtime.fraud()).getFraudMetrics({
|
|
1433
1826
|
window: optionalString(args.window),
|
|
@@ -1505,9 +1898,16 @@ export class PaybondMCPServer {
|
|
|
1505
1898
|
},
|
|
1506
1899
|
{
|
|
1507
1900
|
name: "paybond_verify_protocol_receipt_v1",
|
|
1508
|
-
description: "Verify a protocol-v2 authorization or settlement receipt through the gateway.",
|
|
1901
|
+
description: "Verify a signed protocol-v2 authorization or settlement receipt through the gateway.",
|
|
1509
1902
|
inputSchema: objectSchema({
|
|
1510
|
-
receipt: {
|
|
1903
|
+
receipt: {
|
|
1904
|
+
type: "object",
|
|
1905
|
+
additionalProperties: true,
|
|
1906
|
+
description: "Complete signed protocol receipt object posted as the verify request body (not a receipt_id string). " +
|
|
1907
|
+
"Discriminate on kind: paybond.protocol_authorization_receipt_v1 requires schema_version=1, receipt_version=\"1\", receipt_id, issued_at, status (authorized), intent_id, tenant_id, verifier_id, transport_binding, mandate_digest_sha256_hex, imported_mandate_signing_public_key_ed25519_hex, authorization, agent, allowed_actions, allowed_tools, spend_ceiling, settlement, constraint, expires_at, nonce, human_presence_mode, plus signing_algorithm=ed25519-sha256-json-v1, message_digest_sha256_hex, signing_public_key_ed25519_hex, and ed25519_signature_hex. " +
|
|
1908
|
+
"paybond.protocol_settlement_receipt_v1 requires schema_version=1, receipt_version=\"1\", receipt_id, issued_at, intent_id, tenant_id, verifier_id, transport_binding, authorization_receipt_id, mandate_digest_sha256_hex, harbor_state, settlement_rail, settlement_mode, principal_did, payee_did, currency, amount_cents, terminal_observed_at, optional predicate_passed, and the same Ed25519 signing fields. " +
|
|
1909
|
+
"Obtain receipts from mandate import, paybond_get_settlement_receipt_v1, audit export, or partner handoff—do not invent digests or signatures.",
|
|
1910
|
+
},
|
|
1511
1911
|
}, ["receipt"]),
|
|
1512
1912
|
call: async (args) => this.runtime.verifyProtocolReceiptV1(ensureObject(args.receipt, "receipt")),
|
|
1513
1913
|
},
|
|
@@ -1681,6 +2081,93 @@ function stringArg(value, field) {
|
|
|
1681
2081
|
function optionalString(value) {
|
|
1682
2082
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1683
2083
|
}
|
|
2084
|
+
function spendPreflightInputSchema() {
|
|
2085
|
+
return objectSchema({
|
|
2086
|
+
intent_id: {
|
|
2087
|
+
type: "string",
|
|
2088
|
+
description: "Canonical Harbor intent UUID.",
|
|
2089
|
+
},
|
|
2090
|
+
operation: {
|
|
2091
|
+
type: "string",
|
|
2092
|
+
description: "Optional delegated operation or tool name. Defaults to * when omitted.",
|
|
2093
|
+
},
|
|
2094
|
+
requested_spend_cents: {
|
|
2095
|
+
type: "integer",
|
|
2096
|
+
description: "Optional proposed spend in cents for the dry-run evaluation.",
|
|
2097
|
+
},
|
|
2098
|
+
vendor_id: {
|
|
2099
|
+
type: "string",
|
|
2100
|
+
description: "Optional vendor scope hint for policy evaluation.",
|
|
2101
|
+
},
|
|
2102
|
+
tool_name: {
|
|
2103
|
+
type: "string",
|
|
2104
|
+
description: "Optional tool name scope hint for policy evaluation.",
|
|
2105
|
+
},
|
|
2106
|
+
task_id: {
|
|
2107
|
+
type: "string",
|
|
2108
|
+
description: "Optional task scope hint for policy evaluation.",
|
|
2109
|
+
},
|
|
2110
|
+
workflow_id: {
|
|
2111
|
+
type: "string",
|
|
2112
|
+
description: "Optional workflow scope hint for policy evaluation.",
|
|
2113
|
+
},
|
|
2114
|
+
tool_call_id: {
|
|
2115
|
+
type: "string",
|
|
2116
|
+
description: "Optional tool-call correlation id for policy evaluation.",
|
|
2117
|
+
},
|
|
2118
|
+
currency: {
|
|
2119
|
+
type: "string",
|
|
2120
|
+
description: "Optional ISO currency code for the proposed spend.",
|
|
2121
|
+
},
|
|
2122
|
+
agent_subject: {
|
|
2123
|
+
type: "string",
|
|
2124
|
+
description: "Optional agent subject for agent-scoped caps.",
|
|
2125
|
+
},
|
|
2126
|
+
approval_token: {
|
|
2127
|
+
type: "string",
|
|
2128
|
+
description: "Optional approval token to evaluate against pending approval state (not consumed).",
|
|
2129
|
+
},
|
|
2130
|
+
}, ["intent_id"]);
|
|
2131
|
+
}
|
|
2132
|
+
function parseSpendPreflightArgs(args) {
|
|
2133
|
+
return {
|
|
2134
|
+
intentId: uuidArg(args.intent_id, "intent_id"),
|
|
2135
|
+
operation: optionalString(args.operation),
|
|
2136
|
+
requestedSpendCents: args.requested_spend_cents === undefined
|
|
2137
|
+
? undefined
|
|
2138
|
+
: intArg(args.requested_spend_cents, "requested_spend_cents"),
|
|
2139
|
+
vendorId: optionalString(args.vendor_id),
|
|
2140
|
+
toolName: optionalString(args.tool_name),
|
|
2141
|
+
taskId: optionalString(args.task_id),
|
|
2142
|
+
workflowId: optionalString(args.workflow_id),
|
|
2143
|
+
toolCallId: optionalString(args.tool_call_id),
|
|
2144
|
+
currency: optionalString(args.currency),
|
|
2145
|
+
agentSubject: optionalString(args.agent_subject),
|
|
2146
|
+
approvalToken: optionalString(args.approval_token),
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
/** Normalize gateway preflight outcomes to the agent-facing explain-policy set. */
|
|
2150
|
+
function normalizeExplainPolicyOutcome(outcome, classification) {
|
|
2151
|
+
const normalized = outcome.trim().toLowerCase();
|
|
2152
|
+
if (normalized === "allow" || normalized === "anomaly_observe") {
|
|
2153
|
+
return "allow";
|
|
2154
|
+
}
|
|
2155
|
+
if (normalized === "approval_required" ||
|
|
2156
|
+
normalized === "anomaly_escalate") {
|
|
2157
|
+
return "approval_required";
|
|
2158
|
+
}
|
|
2159
|
+
if (normalized === "deny") {
|
|
2160
|
+
return "deny";
|
|
2161
|
+
}
|
|
2162
|
+
const classNorm = classification.trim().toLowerCase();
|
|
2163
|
+
if (classNorm === "allow") {
|
|
2164
|
+
return "allow";
|
|
2165
|
+
}
|
|
2166
|
+
if (classNorm === "hold") {
|
|
2167
|
+
return "approval_required";
|
|
2168
|
+
}
|
|
2169
|
+
return "deny";
|
|
2170
|
+
}
|
|
1684
2171
|
function intArg(value, field) {
|
|
1685
2172
|
const parsed = typeof value === "number"
|
|
1686
2173
|
? value
|