@paybond/kit 0.2.1 → 0.4.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.
- package/README.md +14 -4
- package/dist/index.d.ts +492 -7
- package/dist/index.js +508 -10
- package/dist/mcp-server.d.ts +50 -0
- package/dist/mcp-server.js +903 -0
- package/dist/principal-intent.d.ts +5 -0
- package/dist/principal-intent.js +14 -0
- package/package.json +10 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Paybond Kit — TypeScript Harbor client with tenant binding, retries, optional upstream JWT,
|
|
3
3
|
* and gateway service-account token exchange (`POST /v1/auth/harbor-access`).
|
|
4
4
|
*/
|
|
5
|
-
import { buildSignedCreateIntentBody } from "./principal-intent.js";
|
|
5
|
+
import { buildSignedCreateIntentBody, } from "./principal-intent.js";
|
|
6
6
|
import { signPayeeEvidenceBinding } from "./payee-evidence.js";
|
|
7
7
|
/**
|
|
8
8
|
* Structured HTTP failure from Harbor with operator-facing diagnostics.
|
|
@@ -47,6 +47,60 @@ export class SignalHttpError extends Error {
|
|
|
47
47
|
this.bodyText = init.bodyText;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
const agentRecognitionProofHeader = "x-paybond-agent-recognition-proof";
|
|
51
|
+
export class A2AHttpError extends Error {
|
|
52
|
+
statusCode;
|
|
53
|
+
url;
|
|
54
|
+
bodyText;
|
|
55
|
+
constructor(message, init) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = "A2AHttpError";
|
|
58
|
+
this.statusCode = init.statusCode;
|
|
59
|
+
this.url = init.url;
|
|
60
|
+
this.bodyText = init.bodyText;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class ProtocolHttpError extends Error {
|
|
64
|
+
statusCode;
|
|
65
|
+
url;
|
|
66
|
+
bodyText;
|
|
67
|
+
errorCode;
|
|
68
|
+
errorMessage;
|
|
69
|
+
constructor(message, init) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = "ProtocolHttpError";
|
|
72
|
+
this.statusCode = init.statusCode;
|
|
73
|
+
this.url = init.url;
|
|
74
|
+
this.bodyText = init.bodyText;
|
|
75
|
+
const parsed = parseGatewayErrorEnvelope(init.bodyText);
|
|
76
|
+
this.errorCode = init.errorCode ?? parsed.errorCode;
|
|
77
|
+
this.errorMessage = init.errorMessage ?? parsed.errorMessage;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function parseGatewayErrorEnvelope(text) {
|
|
81
|
+
if (!text.trim().startsWith("{")) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const body = JSON.parse(text);
|
|
86
|
+
if (body === null || Array.isArray(body) || typeof body !== "object") {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const errorCode = typeof body.error === "string" && body.error.trim() ? body.error.trim() : undefined;
|
|
90
|
+
const errorMessage = typeof body.message === "string" && body.message.trim() ? body.message.trim() : undefined;
|
|
91
|
+
return { errorCode, errorMessage };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function protocolHTTPErrorMessage(prefix, statusCode, bodyText) {
|
|
98
|
+
const parsed = parseGatewayErrorEnvelope(bodyText);
|
|
99
|
+
if (parsed.errorCode) {
|
|
100
|
+
return `${prefix} HTTP ${statusCode} (${parsed.errorCode}): ${parsed.errorMessage ?? bodyText}`;
|
|
101
|
+
}
|
|
102
|
+
return `${prefix} HTTP ${statusCode}: ${bodyText}`;
|
|
103
|
+
}
|
|
50
104
|
function normalizeBase(url) {
|
|
51
105
|
return url.trim().replace(/\/+$/, "");
|
|
52
106
|
}
|
|
@@ -292,7 +346,7 @@ export class HarborClient {
|
|
|
292
346
|
}
|
|
293
347
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
294
348
|
}
|
|
295
|
-
async fetchWithRetries(url, init, { retryBody }) {
|
|
349
|
+
async fetchWithRetries(url, init, { retryBody, retryBodyText, }) {
|
|
296
350
|
let lastErr;
|
|
297
351
|
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
298
352
|
let res;
|
|
@@ -318,10 +372,18 @@ export class HarborClient {
|
|
|
318
372
|
const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
|
|
319
373
|
const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
|
|
320
374
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
375
|
+
if (retryBodyText !== undefined) {
|
|
376
|
+
init = {
|
|
377
|
+
...init,
|
|
378
|
+
body: retryBodyText,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
else if (retryBody !== undefined) {
|
|
382
|
+
init = {
|
|
383
|
+
...init,
|
|
384
|
+
body: JSON.stringify(retryBody),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
325
387
|
continue;
|
|
326
388
|
}
|
|
327
389
|
return res;
|
|
@@ -403,6 +465,79 @@ export class HarborClient {
|
|
|
403
465
|
}
|
|
404
466
|
return JSON.parse(text);
|
|
405
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* POST `/intents/{intentId}/fund` for x402 / USDC-on-Base funding.
|
|
470
|
+
*
|
|
471
|
+
* Harbor returns:
|
|
472
|
+
* - `402` with `paymentRequired` details when a facilitator or wallet must sign
|
|
473
|
+
* - `202` while authorization is pending
|
|
474
|
+
* - `200` once the intent is funded and any capability token is available
|
|
475
|
+
*
|
|
476
|
+
* @throws HarborHttpError for non-funding HTTP errors
|
|
477
|
+
* @throws Error when Harbor echoes a different tenant or intent than requested
|
|
478
|
+
*/
|
|
479
|
+
async fundIntent(intentId, options) {
|
|
480
|
+
const url = `${this.base}intents/${intentId}/fund`;
|
|
481
|
+
const headers = {
|
|
482
|
+
"x-tenant-id": this.tenantId,
|
|
483
|
+
};
|
|
484
|
+
if (options?.idempotencyKey?.trim()) {
|
|
485
|
+
headers["idempotency-key"] = options.idempotencyKey.trim();
|
|
486
|
+
}
|
|
487
|
+
if (options?.paymentSignature?.trim()) {
|
|
488
|
+
headers["payment-signature"] = options.paymentSignature.trim();
|
|
489
|
+
}
|
|
490
|
+
const res = await this.fetchWithRetries(url, {
|
|
491
|
+
method: "POST",
|
|
492
|
+
headers,
|
|
493
|
+
body: "",
|
|
494
|
+
}, { retryBodyText: "" });
|
|
495
|
+
const text = await res.text();
|
|
496
|
+
if (![200, 202, 402].includes(res.status)) {
|
|
497
|
+
throw new HarborHttpError(`Harbor fund intent HTTP ${res.status}: ${text}`, {
|
|
498
|
+
statusCode: res.status,
|
|
499
|
+
url,
|
|
500
|
+
bodyText: text,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
const body = assertJSONObject(JSON.parse(text));
|
|
504
|
+
const tenant = String(body.tenant ?? "");
|
|
505
|
+
if (tenant !== this.tenantId) {
|
|
506
|
+
throw new Error(`fund tenant mismatch: client=${this.tenantId} harbor=${tenant}`);
|
|
507
|
+
}
|
|
508
|
+
const echoedIntentId = String(body.intent_id ?? "");
|
|
509
|
+
if (echoedIntentId !== intentId) {
|
|
510
|
+
throw new Error(`fund intent mismatch: requested=${intentId} harbor=${echoedIntentId}`);
|
|
511
|
+
}
|
|
512
|
+
if (typeof body.state !== "string" || !body.state.trim()) {
|
|
513
|
+
throw new Error("fund response missing state");
|
|
514
|
+
}
|
|
515
|
+
if (typeof body.currency !== "string" || !body.currency.trim()) {
|
|
516
|
+
throw new Error("fund response missing currency");
|
|
517
|
+
}
|
|
518
|
+
const amountCents = Number(body.amount_cents);
|
|
519
|
+
if (!Number.isFinite(amountCents)) {
|
|
520
|
+
throw new Error("fund response missing amount_cents");
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
statusCode: res.status,
|
|
524
|
+
paymentRequired: res.headers.get("payment-required") ?? undefined,
|
|
525
|
+
paymentResponse: res.headers.get("payment-response") ?? undefined,
|
|
526
|
+
intentId: echoedIntentId,
|
|
527
|
+
tenant,
|
|
528
|
+
state: body.state,
|
|
529
|
+
settlementRail: readSettlementRailValue(body.settlement_rail, "fund settlement_rail"),
|
|
530
|
+
currency: body.currency,
|
|
531
|
+
amountCents,
|
|
532
|
+
funded: Boolean(body.funded),
|
|
533
|
+
capabilityToken: typeof body.capability_token === "string" && body.capability_token.trim()
|
|
534
|
+
? body.capability_token
|
|
535
|
+
: undefined,
|
|
536
|
+
funding: body.funding === undefined || body.funding === null
|
|
537
|
+
? undefined
|
|
538
|
+
: parseIntentFundingResult(body.funding),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
406
541
|
/**
|
|
407
542
|
* POST `/intents/{intentId}/evidence` with a signed evidence JSON body.
|
|
408
543
|
*
|
|
@@ -523,12 +658,71 @@ export class HarborClient {
|
|
|
523
658
|
}
|
|
524
659
|
}
|
|
525
660
|
const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
|
|
661
|
+
const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "x402_usdc_base"]);
|
|
526
662
|
function assertJSONObject(value) {
|
|
527
663
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
528
664
|
throw new Error("expected JSON object");
|
|
529
665
|
}
|
|
530
666
|
return value;
|
|
531
667
|
}
|
|
668
|
+
function readSettlementRailValue(value, field) {
|
|
669
|
+
if (typeof value !== "string" || !SETTLEMENT_RAIL_VALUES.has(value)) {
|
|
670
|
+
throw new Error(`invalid ${field}`);
|
|
671
|
+
}
|
|
672
|
+
return value;
|
|
673
|
+
}
|
|
674
|
+
function readStringArrayValue(value, field) {
|
|
675
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
676
|
+
throw new Error(`invalid ${field}`);
|
|
677
|
+
}
|
|
678
|
+
return [...value];
|
|
679
|
+
}
|
|
680
|
+
function parseIntentFundingResult(value) {
|
|
681
|
+
const body = assertJSONObject(value);
|
|
682
|
+
const onchainRaw = body.onchain_transaction_hashes;
|
|
683
|
+
const onchain = onchainRaw === undefined || onchainRaw === null
|
|
684
|
+
? undefined
|
|
685
|
+
: (() => {
|
|
686
|
+
const parsed = assertJSONObject(onchainRaw);
|
|
687
|
+
return {
|
|
688
|
+
authorizations: parsed.authorizations === undefined
|
|
689
|
+
? undefined
|
|
690
|
+
: readStringArrayValue(parsed.authorizations, "funding.onchain_transaction_hashes.authorizations"),
|
|
691
|
+
captures: parsed.captures === undefined
|
|
692
|
+
? undefined
|
|
693
|
+
: readStringArrayValue(parsed.captures, "funding.onchain_transaction_hashes.captures"),
|
|
694
|
+
voids: parsed.voids === undefined
|
|
695
|
+
? undefined
|
|
696
|
+
: readStringArrayValue(parsed.voids, "funding.onchain_transaction_hashes.voids"),
|
|
697
|
+
refunds: parsed.refunds === undefined
|
|
698
|
+
? undefined
|
|
699
|
+
: readStringArrayValue(parsed.refunds, "funding.onchain_transaction_hashes.refunds"),
|
|
700
|
+
};
|
|
701
|
+
})();
|
|
702
|
+
const readOptionalString = (field) => {
|
|
703
|
+
const raw = body[field];
|
|
704
|
+
return typeof raw === "string" && raw.trim() ? raw : undefined;
|
|
705
|
+
};
|
|
706
|
+
return {
|
|
707
|
+
settlementRail: readSettlementRailValue(body.settlement_rail, "funding.settlement_rail"),
|
|
708
|
+
harborFundEndpoint: readOptionalString("harbor_fund_endpoint"),
|
|
709
|
+
status: readOptionalString("status"),
|
|
710
|
+
paymentSessionId: readOptionalString("payment_session_id"),
|
|
711
|
+
paymentUrl: readOptionalString("payment_url"),
|
|
712
|
+
asset: readOptionalString("asset"),
|
|
713
|
+
network: readOptionalString("network"),
|
|
714
|
+
authorizationId: readOptionalString("authorization_id"),
|
|
715
|
+
captureId: readOptionalString("capture_id"),
|
|
716
|
+
voidId: readOptionalString("void_id"),
|
|
717
|
+
refundId: readOptionalString("refund_id"),
|
|
718
|
+
sourceAddress: readOptionalString("source_address"),
|
|
719
|
+
targetAddress: readOptionalString("target_address"),
|
|
720
|
+
authorizationExpiresAt: readOptionalString("authorization_expires_at"),
|
|
721
|
+
captureExpiresAt: readOptionalString("capture_expires_at"),
|
|
722
|
+
refundExpiresAt: readOptionalString("refund_expires_at"),
|
|
723
|
+
onchainTransactionHashes: onchain,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
532
726
|
/**
|
|
533
727
|
* Tenant-bound reader for gateway Signal routes.
|
|
534
728
|
*/
|
|
@@ -630,6 +824,21 @@ export class GatewaySignalClient {
|
|
|
630
824
|
this.assertTenant(body, url);
|
|
631
825
|
return body;
|
|
632
826
|
}
|
|
827
|
+
async getSignedPortfolioArtifact(scoreVersion) {
|
|
828
|
+
const url = `${this.base}signal/v1/portfolio/signed-export${this.scoreQuery(scoreVersion)}`;
|
|
829
|
+
const res = await this.fetchGetWithRetries(url);
|
|
830
|
+
const text = await res.text();
|
|
831
|
+
if (!res.ok) {
|
|
832
|
+
throw new SignalHttpError(`Signal signed portfolio artifact HTTP ${res.status}: ${text}`, {
|
|
833
|
+
statusCode: res.status,
|
|
834
|
+
url,
|
|
835
|
+
bodyText: text,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
const body = assertJSONObject(JSON.parse(text));
|
|
839
|
+
this.assertTenant(body, url);
|
|
840
|
+
return body;
|
|
841
|
+
}
|
|
633
842
|
async getOperatorExplanation(operatorDid, scoreVersion) {
|
|
634
843
|
const enc = encodeURIComponent(operatorDid);
|
|
635
844
|
const url = `${this.base}signal/v1/operators/${enc}/explanation${this.scoreQuery(scoreVersion)}`;
|
|
@@ -675,6 +884,273 @@ export class GatewaySignalClient {
|
|
|
675
884
|
return body;
|
|
676
885
|
}
|
|
677
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Public or optionally authenticated reader for the gateway's A2A discovery surface.
|
|
889
|
+
*/
|
|
890
|
+
export class GatewayA2AClient {
|
|
891
|
+
base;
|
|
892
|
+
bearerToken;
|
|
893
|
+
maxRetries;
|
|
894
|
+
constructor(gatewayBaseUrl, options) {
|
|
895
|
+
this.base = normalizeBase(gatewayBaseUrl) + "/";
|
|
896
|
+
this.bearerToken = options?.staticGatewayBearerToken?.trim() || undefined;
|
|
897
|
+
this.maxRetries = Math.max(1, options?.maxRetries ?? 3);
|
|
898
|
+
}
|
|
899
|
+
async fetchGetWithRetries(url) {
|
|
900
|
+
let lastErr;
|
|
901
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
902
|
+
let res;
|
|
903
|
+
try {
|
|
904
|
+
const headers = new Headers({
|
|
905
|
+
accept: "application/json",
|
|
906
|
+
});
|
|
907
|
+
if (this.bearerToken) {
|
|
908
|
+
headers.set("authorization", `Bearer ${this.bearerToken}`);
|
|
909
|
+
}
|
|
910
|
+
res = await fetch(url, {
|
|
911
|
+
method: "GET",
|
|
912
|
+
headers,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
catch (e) {
|
|
916
|
+
lastErr = e;
|
|
917
|
+
if (attempt + 1 >= this.maxRetries)
|
|
918
|
+
throw e;
|
|
919
|
+
await new Promise((r) => setTimeout(r, backoffMs(attempt)));
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if ([429, 500, 502, 503, 504].includes(res.status)) {
|
|
923
|
+
if (attempt + 1 >= this.maxRetries) {
|
|
924
|
+
return res;
|
|
925
|
+
}
|
|
926
|
+
const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
|
|
927
|
+
const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
|
|
928
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
return res;
|
|
932
|
+
}
|
|
933
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
934
|
+
}
|
|
935
|
+
async getAgentCard() {
|
|
936
|
+
const url = `${this.base}.well-known/agent-card.json`;
|
|
937
|
+
const res = await this.fetchGetWithRetries(url);
|
|
938
|
+
const text = await res.text();
|
|
939
|
+
if (!res.ok) {
|
|
940
|
+
throw new A2AHttpError(`A2A agent card HTTP ${res.status}: ${text}`, {
|
|
941
|
+
statusCode: res.status,
|
|
942
|
+
url,
|
|
943
|
+
bodyText: text,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return assertJSONObject(JSON.parse(text));
|
|
947
|
+
}
|
|
948
|
+
async getTaskContracts() {
|
|
949
|
+
const url = `${this.base}protocol/v2/a2a/task-contracts`;
|
|
950
|
+
const res = await this.fetchGetWithRetries(url);
|
|
951
|
+
const text = await res.text();
|
|
952
|
+
if (!res.ok) {
|
|
953
|
+
throw new A2AHttpError(`A2A task contracts HTTP ${res.status}: ${text}`, {
|
|
954
|
+
statusCode: res.status,
|
|
955
|
+
url,
|
|
956
|
+
bodyText: text,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return assertJSONObject(JSON.parse(text));
|
|
960
|
+
}
|
|
961
|
+
async getTaskContract(contractId) {
|
|
962
|
+
const enc = encodeURIComponent(contractId);
|
|
963
|
+
const url = `${this.base}protocol/v2/a2a/task-contracts/${enc}`;
|
|
964
|
+
const res = await this.fetchGetWithRetries(url);
|
|
965
|
+
const text = await res.text();
|
|
966
|
+
if (!res.ok) {
|
|
967
|
+
throw new A2AHttpError(`A2A task contract HTTP ${res.status}: ${text}`, {
|
|
968
|
+
statusCode: res.status,
|
|
969
|
+
url,
|
|
970
|
+
bodyText: text,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return assertJSONObject(JSON.parse(text));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
export class GatewayProtocolClient {
|
|
977
|
+
tenantId;
|
|
978
|
+
base;
|
|
979
|
+
staticGatewayBearerToken;
|
|
980
|
+
maxRetries;
|
|
981
|
+
constructor(gatewayBaseUrl, tenantId, init) {
|
|
982
|
+
this.base = `${normalizeBase(gatewayBaseUrl)}/`;
|
|
983
|
+
this.tenantId = tenantId.trim();
|
|
984
|
+
this.staticGatewayBearerToken = init?.staticGatewayBearerToken?.trim() || null;
|
|
985
|
+
this.maxRetries = Math.max(1, init?.maxRetries ?? 3);
|
|
986
|
+
}
|
|
987
|
+
async fetchWithRetries(url, init) {
|
|
988
|
+
let lastErr;
|
|
989
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
990
|
+
let res;
|
|
991
|
+
try {
|
|
992
|
+
res = await fetch(url, init);
|
|
993
|
+
}
|
|
994
|
+
catch (e) {
|
|
995
|
+
lastErr = e;
|
|
996
|
+
if (attempt + 1 >= this.maxRetries) {
|
|
997
|
+
throw e;
|
|
998
|
+
}
|
|
999
|
+
await new Promise((r) => setTimeout(r, backoffMs(attempt)));
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if ([429, 500, 502, 503, 504].includes(res.status) && attempt + 1 < this.maxRetries) {
|
|
1003
|
+
const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
|
|
1004
|
+
const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
|
|
1005
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
return res;
|
|
1009
|
+
}
|
|
1010
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
1011
|
+
}
|
|
1012
|
+
headers(extra) {
|
|
1013
|
+
const headers = new Headers(extra);
|
|
1014
|
+
headers.set("accept", "application/json");
|
|
1015
|
+
headers.set("x-tenant-id", this.tenantId);
|
|
1016
|
+
if (this.staticGatewayBearerToken) {
|
|
1017
|
+
headers.set("authorization", `Bearer ${this.staticGatewayBearerToken}`);
|
|
1018
|
+
}
|
|
1019
|
+
return headers;
|
|
1020
|
+
}
|
|
1021
|
+
async postJSON(path, payload, extraHeaders) {
|
|
1022
|
+
const url = `${this.base}${path.replace(/^\/+/, "")}`;
|
|
1023
|
+
const res = await this.fetchWithRetries(url, {
|
|
1024
|
+
method: "POST",
|
|
1025
|
+
headers: this.headers({
|
|
1026
|
+
"content-type": "application/json",
|
|
1027
|
+
...(extraHeaders ?? {}),
|
|
1028
|
+
}),
|
|
1029
|
+
body: JSON.stringify(payload),
|
|
1030
|
+
});
|
|
1031
|
+
const text = await res.text();
|
|
1032
|
+
if (!res.ok) {
|
|
1033
|
+
const parsed = parseGatewayErrorEnvelope(text);
|
|
1034
|
+
throw new ProtocolHttpError(protocolHTTPErrorMessage(`gateway POST ${path}`, res.status, text), {
|
|
1035
|
+
statusCode: res.status,
|
|
1036
|
+
url,
|
|
1037
|
+
bodyText: text,
|
|
1038
|
+
errorCode: parsed.errorCode,
|
|
1039
|
+
errorMessage: parsed.errorMessage,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
return assertJSONObject(JSON.parse(text));
|
|
1043
|
+
}
|
|
1044
|
+
async importAgentMandateV1(init) {
|
|
1045
|
+
const url = `${this.base}protocol/v2/mandates`;
|
|
1046
|
+
const res = await this.fetchWithRetries(url, {
|
|
1047
|
+
method: "POST",
|
|
1048
|
+
headers: this.headers({ "content-type": "application/json" }),
|
|
1049
|
+
body: JSON.stringify({
|
|
1050
|
+
signed_mandate: init.signedMandate,
|
|
1051
|
+
intent_id: init.intentId,
|
|
1052
|
+
transport_binding: init.transportBinding ?? {},
|
|
1053
|
+
recognition_proof: init.recognitionProof,
|
|
1054
|
+
}),
|
|
1055
|
+
});
|
|
1056
|
+
const text = await res.text();
|
|
1057
|
+
if (!res.ok) {
|
|
1058
|
+
const parsed = parseGatewayErrorEnvelope(text);
|
|
1059
|
+
throw new ProtocolHttpError(protocolHTTPErrorMessage("protocol mandate import", res.status, text), {
|
|
1060
|
+
statusCode: res.status,
|
|
1061
|
+
url,
|
|
1062
|
+
bodyText: text,
|
|
1063
|
+
errorCode: parsed.errorCode,
|
|
1064
|
+
errorMessage: parsed.errorMessage,
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
const body = assertJSONObject(JSON.parse(text));
|
|
1068
|
+
if (String(body.intent_id ?? "").trim() !== init.intentId) {
|
|
1069
|
+
throw new Error(`protocol intent mismatch: requested=${init.intentId} gateway=${String(body.intent_id ?? "")}`);
|
|
1070
|
+
}
|
|
1071
|
+
if (String(body.mandate?.authorization?.tenant_id ?? "").trim() !== this.tenantId) {
|
|
1072
|
+
throw new Error(`protocol mandate tenant mismatch: client=${this.tenantId} gateway=${String(body.mandate?.authorization?.tenant_id ?? "")}`);
|
|
1073
|
+
}
|
|
1074
|
+
return body;
|
|
1075
|
+
}
|
|
1076
|
+
async getSettlementReceiptV1(receiptId) {
|
|
1077
|
+
const enc = encodeURIComponent(receiptId);
|
|
1078
|
+
const url = `${this.base}protocol/v2/receipts/${enc}`;
|
|
1079
|
+
const res = await this.fetchWithRetries(url, {
|
|
1080
|
+
method: "GET",
|
|
1081
|
+
headers: this.headers(),
|
|
1082
|
+
});
|
|
1083
|
+
const text = await res.text();
|
|
1084
|
+
if (!res.ok) {
|
|
1085
|
+
const parsed = parseGatewayErrorEnvelope(text);
|
|
1086
|
+
throw new ProtocolHttpError(protocolHTTPErrorMessage("protocol settlement receipt", res.status, text), {
|
|
1087
|
+
statusCode: res.status,
|
|
1088
|
+
url,
|
|
1089
|
+
bodyText: text,
|
|
1090
|
+
errorCode: parsed.errorCode,
|
|
1091
|
+
errorMessage: parsed.errorMessage,
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
const body = assertJSONObject(JSON.parse(text));
|
|
1095
|
+
if (String(body.receipt_id ?? "").trim() !== receiptId) {
|
|
1096
|
+
throw new Error(`protocol receipt mismatch: requested=${receiptId} gateway=${String(body.receipt_id ?? "")}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (String(body.tenant_id ?? "").trim() !== this.tenantId) {
|
|
1099
|
+
throw new Error(`protocol receipt tenant mismatch: client=${this.tenantId} gateway=${String(body.tenant_id ?? "")}`);
|
|
1100
|
+
}
|
|
1101
|
+
return body;
|
|
1102
|
+
}
|
|
1103
|
+
async verifyProtocolReceiptV1(receipt) {
|
|
1104
|
+
const url = `${this.base}protocol/v2/receipts/verify`;
|
|
1105
|
+
const res = await this.fetchWithRetries(url, {
|
|
1106
|
+
method: "POST",
|
|
1107
|
+
headers: this.headers({ "content-type": "application/json" }),
|
|
1108
|
+
body: JSON.stringify(receipt),
|
|
1109
|
+
});
|
|
1110
|
+
const text = await res.text();
|
|
1111
|
+
if (!res.ok) {
|
|
1112
|
+
const parsed = parseGatewayErrorEnvelope(text);
|
|
1113
|
+
throw new ProtocolHttpError(protocolHTTPErrorMessage("protocol receipt verify", res.status, text), {
|
|
1114
|
+
statusCode: res.status,
|
|
1115
|
+
url,
|
|
1116
|
+
bodyText: text,
|
|
1117
|
+
errorCode: parsed.errorCode,
|
|
1118
|
+
errorMessage: parsed.errorMessage,
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
return assertJSONObject(JSON.parse(text));
|
|
1122
|
+
}
|
|
1123
|
+
async createHarborIntent(init) {
|
|
1124
|
+
return this.postJSON("/harbor/intents", init.body, gatewayMutationHeaders(init.recognitionProof, {
|
|
1125
|
+
...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
|
|
1126
|
+
}));
|
|
1127
|
+
}
|
|
1128
|
+
async fundHarborIntent(init) {
|
|
1129
|
+
return this.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/fund`, {}, gatewayMutationHeaders(init.recognitionProof, {
|
|
1130
|
+
...(init.paymentSignature?.trim() ? { "payment-signature": init.paymentSignature.trim() } : {}),
|
|
1131
|
+
...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
|
|
1132
|
+
}));
|
|
1133
|
+
}
|
|
1134
|
+
async submitHarborEvidence(init) {
|
|
1135
|
+
return this.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/evidence`, init.body, gatewayMutationHeaders(init.recognitionProof, {
|
|
1136
|
+
...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
|
|
1137
|
+
}));
|
|
1138
|
+
}
|
|
1139
|
+
async confirmHarborSettlement(init) {
|
|
1140
|
+
return this.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/settlement/confirm`, init.body, gatewayMutationHeaders(init.recognitionProof, {
|
|
1141
|
+
...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
|
|
1142
|
+
}));
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function gatewayMutationHeaders(recognitionProof, headers) {
|
|
1146
|
+
return {
|
|
1147
|
+
...(headers ?? {}),
|
|
1148
|
+
[agentRecognitionProofHeader]: encodeRecognitionProofHeader(recognitionProof),
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
function encodeRecognitionProofHeader(proof) {
|
|
1152
|
+
return Buffer.from(JSON.stringify(proof), "utf8").toString("base64url");
|
|
1153
|
+
}
|
|
678
1154
|
async function resolveGatewayTenantId(gatewayBaseUrl, apiKey, principalPath, maxRetries) {
|
|
679
1155
|
const base = normalizeBase(gatewayBaseUrl);
|
|
680
1156
|
const path = principalPath.startsWith("/") ? principalPath : `/${principalPath}`;
|
|
@@ -743,7 +1219,7 @@ export class ServiceAccountSignalSession {
|
|
|
743
1219
|
}
|
|
744
1220
|
}
|
|
745
1221
|
/**
|
|
746
|
-
* Ergonomic intent helpers: principal-signed intent create and payee-signed evidence.
|
|
1222
|
+
* Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
|
|
747
1223
|
*/
|
|
748
1224
|
export class PaybondIntents {
|
|
749
1225
|
harbor;
|
|
@@ -751,7 +1227,8 @@ export class PaybondIntents {
|
|
|
751
1227
|
this.harbor = harbor;
|
|
752
1228
|
}
|
|
753
1229
|
/**
|
|
754
|
-
* Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be
|
|
1230
|
+
* Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be
|
|
1231
|
+
* 32 bytes. `settlementRail` only requests an allowed rail; destinations stay server-owned.
|
|
755
1232
|
*/
|
|
756
1233
|
async create(params) {
|
|
757
1234
|
const { idempotencyKey, intentId: maybeIntentId, ...fields } = params;
|
|
@@ -763,6 +1240,15 @@ export class PaybondIntents {
|
|
|
763
1240
|
});
|
|
764
1241
|
return this.harbor.createIntent(body, { idempotencyKey });
|
|
765
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Advance Harbor `/intents/{id}/fund` for x402 / USDC-on-Base intents.
|
|
1245
|
+
*/
|
|
1246
|
+
async fund(params) {
|
|
1247
|
+
return this.harbor.fundIntent(params.intentId, {
|
|
1248
|
+
paymentSignature: params.paymentSignature,
|
|
1249
|
+
idempotencyKey: params.idempotencyKey,
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
766
1252
|
/**
|
|
767
1253
|
* Sign payee evidence and POST it. `payeeSigningSeed` must be 32 bytes.
|
|
768
1254
|
*/
|
|
@@ -781,12 +1267,16 @@ export class PaybondIntents {
|
|
|
781
1267
|
export class Paybond {
|
|
782
1268
|
harbor;
|
|
783
1269
|
signal;
|
|
1270
|
+
a2a;
|
|
1271
|
+
protocol;
|
|
784
1272
|
intents;
|
|
785
1273
|
session;
|
|
786
|
-
constructor(session, signal) {
|
|
1274
|
+
constructor(session, signal, a2a, protocol) {
|
|
787
1275
|
this.session = session;
|
|
788
1276
|
this.harbor = session.harbor;
|
|
789
1277
|
this.signal = signal;
|
|
1278
|
+
this.a2a = a2a;
|
|
1279
|
+
this.protocol = protocol;
|
|
790
1280
|
this.intents = new PaybondIntents(session.harbor);
|
|
791
1281
|
}
|
|
792
1282
|
/** Open a tenant-bound session via gateway `harbor-access` exchange. */
|
|
@@ -796,7 +1286,15 @@ export class Paybond {
|
|
|
796
1286
|
staticGatewayBearerToken: init.apiKey,
|
|
797
1287
|
maxRetries: init.maxRetries ?? 3,
|
|
798
1288
|
});
|
|
799
|
-
|
|
1289
|
+
const a2a = new GatewayA2AClient(init.gatewayBaseUrl, {
|
|
1290
|
+
staticGatewayBearerToken: init.apiKey,
|
|
1291
|
+
maxRetries: init.maxRetries ?? 3,
|
|
1292
|
+
});
|
|
1293
|
+
const protocol = new GatewayProtocolClient(init.gatewayBaseUrl, session.harbor.tenantId, {
|
|
1294
|
+
staticGatewayBearerToken: init.apiKey,
|
|
1295
|
+
maxRetries: init.maxRetries ?? 3,
|
|
1296
|
+
});
|
|
1297
|
+
return new Paybond(session, signal, a2a, protocol);
|
|
800
1298
|
}
|
|
801
1299
|
async rotateHarborToken() {
|
|
802
1300
|
await this.session.rotateHarborToken();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type JSONRPCID = string | number | null;
|
|
3
|
+
type JSONRPCRequest = {
|
|
4
|
+
jsonrpc?: unknown;
|
|
5
|
+
id?: JSONRPCID;
|
|
6
|
+
method?: unknown;
|
|
7
|
+
params?: unknown;
|
|
8
|
+
};
|
|
9
|
+
type JSONRPCResponse = {
|
|
10
|
+
jsonrpc: "2.0";
|
|
11
|
+
id: JSONRPCID;
|
|
12
|
+
result?: unknown;
|
|
13
|
+
error?: {
|
|
14
|
+
code: number;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
type MCPCallToolResult = {
|
|
19
|
+
content: Array<{
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}>;
|
|
23
|
+
structuredContent?: Record<string, unknown>;
|
|
24
|
+
isError?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type PaybondMCPSettings = {
|
|
27
|
+
gatewayBaseUrl: string;
|
|
28
|
+
apiKey: string;
|
|
29
|
+
harborBaseUrl?: string;
|
|
30
|
+
harborAccessPath?: string;
|
|
31
|
+
principalPath?: string;
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
clockSkewSeconds?: number;
|
|
34
|
+
};
|
|
35
|
+
export declare class PaybondMCPServer {
|
|
36
|
+
private readonly runtime;
|
|
37
|
+
private readonly tools;
|
|
38
|
+
private initialized;
|
|
39
|
+
constructor(settings: PaybondMCPSettings);
|
|
40
|
+
listTools(): Array<Record<string, unknown>>;
|
|
41
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<MCPCallToolResult>;
|
|
42
|
+
handleMessage(message: JSONRPCRequest): Promise<JSONRPCResponse | null>;
|
|
43
|
+
runStdio(): void;
|
|
44
|
+
private handleLine;
|
|
45
|
+
private writeResponse;
|
|
46
|
+
private buildTools;
|
|
47
|
+
}
|
|
48
|
+
export declare function settingsFromEnv(env?: Record<string, string | undefined>): PaybondMCPSettings;
|
|
49
|
+
export declare function main(argv?: string[]): number;
|
|
50
|
+
export {};
|