@paybond/kit 0.12.3 → 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.
@@ -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.3";
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
- allow: {
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
- allow: {
68
- type: "boolean",
69
- description: "Whether the requested operation is allowed.",
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
- tenant: {
72
- type: "string",
73
- description: "Tenant echoed by the gateway.",
104
+ spend_scope: {
105
+ type: "object",
106
+ description: "Spend scope used for the budget evaluation (scope_type and scope_key).",
107
+ additionalProperties: true,
108
+ },
109
+ policy_version: {
110
+ type: "integer",
111
+ description: "Active spend-control policy version when a policy is configured.",
74
112
  },
75
- intent_id: {
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: "Verified Harbor intent UUID.",
123
+ description: "Normalized policy outcome: allow, approval_required, or deny.",
124
+ },
125
+ reason_codes: {
126
+ type: "array",
127
+ items: { type: "string" },
128
+ description: "Stable policy reason codes from the dry-run evaluation.",
78
129
  },
79
- audit_id: {
130
+ explanation: {
80
131
  type: "string",
81
- description: "Gateway audit identifier when available.",
132
+ description: "Human-readable explanation derived from reason codes.",
82
133
  },
83
- }, ["tenant", "intent_id"]),
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
- tenant_id: { type: "string" },
189
- operator_did: { type: "string" },
190
- signature_hex: { type: "string" },
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
- tenant_id: { type: "string" },
198
- score_model_version: { type: "string" },
199
- operators: {
200
- type: "array",
201
- items: { type: "object", additionalProperties: true },
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: { type: "string" },
220
- operator_did: { type: "string" },
221
- fraud_assessment: { type: "object", additionalProperties: true },
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: { type: "string" },
229
- window: { type: "string" },
230
- flagged_operator_count: { type: "integer" },
231
- critical_signal_count: { type: "integer" },
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: { type: "boolean" },
305
- receipt_id: { type: "string" },
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 = {
@@ -1275,6 +1580,18 @@ export class PaybondMCPServer {
1275
1580
  });
1276
1581
  },
1277
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
+ },
1278
1595
  {
1279
1596
  name: "paybond_bootstrap_sandbox_guardrail",
1280
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.",
@@ -1433,8 +1750,19 @@ export class PaybondMCPServer {
1433
1750
  name: "paybond_get_reputation_receipt",
1434
1751
  description: "Fetch the signed Signal receipt for one operator DID.",
1435
1752
  inputSchema: objectSchema({
1436
- operator_did: { type: "string" },
1437
- score_version: { type: "string" },
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
+ },
1438
1766
  }, ["operator_did"]),
1439
1767
  call: async (args) => (await this.runtime.signal()).getReputationReceipt(stringArg(args.operator_did, "operator_did"), optionalString(args.score_version)),
1440
1768
  },
@@ -1442,7 +1770,11 @@ export class PaybondMCPServer {
1442
1770
  name: "paybond_get_portfolio_summary",
1443
1771
  description: "Fetch the tenant-scoped Signal portfolio summary.",
1444
1772
  inputSchema: objectSchema({
1445
- score_version: { type: "string" },
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
+ },
1446
1778
  }),
1447
1779
  call: async (args) => (await this.runtime.signal()).getPortfolioSummary(optionalString(args.score_version)),
1448
1780
  },
@@ -1458,8 +1790,19 @@ export class PaybondMCPServer {
1458
1790
  name: "paybond_get_fraud_assessment",
1459
1791
  description: "Fetch the read-only fraud assessment for one tenant-scoped operator DID.",
1460
1792
  inputSchema: objectSchema({
1461
- operator_did: { type: "string" },
1462
- score_version: { type: "string" },
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
+ },
1463
1806
  }, ["operator_did"]),
1464
1807
  call: async (args) => (await this.runtime.fraud()).getFraudAssessment(stringArg(args.operator_did, "operator_did"), optionalString(args.score_version)),
1465
1808
  },
@@ -1467,8 +1810,17 @@ export class PaybondMCPServer {
1467
1810
  name: "paybond_get_fraud_metrics",
1468
1811
  description: "Fetch tenant-scoped read-only fraud backtesting and monitoring metrics for a supported active window.",
1469
1812
  inputSchema: objectSchema({
1470
- window: { type: "string", enum: ["24h", "7d", "30d"] },
1471
- score_version: { type: "string" },
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
+ },
1472
1824
  }),
1473
1825
  call: async (args) => (await this.runtime.fraud()).getFraudMetrics({
1474
1826
  window: optionalString(args.window),
@@ -1546,9 +1898,16 @@ export class PaybondMCPServer {
1546
1898
  },
1547
1899
  {
1548
1900
  name: "paybond_verify_protocol_receipt_v1",
1549
- 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.",
1550
1902
  inputSchema: objectSchema({
1551
- receipt: { type: "object", additionalProperties: true },
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
+ },
1552
1911
  }, ["receipt"]),
1553
1912
  call: async (args) => this.runtime.verifyProtocolReceiptV1(ensureObject(args.receipt, "receipt")),
1554
1913
  },
@@ -1722,6 +2081,93 @@ function stringArg(value, field) {
1722
2081
  function optionalString(value) {
1723
2082
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
1724
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
+ }
1725
2171
  function intArg(value, field) {
1726
2172
  const parsed = typeof value === "number"
1727
2173
  ? value