@paybond/kit 0.3.0 → 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/dist/index.js CHANGED
@@ -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
  }
@@ -770,6 +824,21 @@ export class GatewaySignalClient {
770
824
  this.assertTenant(body, url);
771
825
  return body;
772
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
+ }
773
842
  async getOperatorExplanation(operatorDid, scoreVersion) {
774
843
  const enc = encodeURIComponent(operatorDid);
775
844
  const url = `${this.base}signal/v1/operators/${enc}/explanation${this.scoreQuery(scoreVersion)}`;
@@ -815,6 +884,273 @@ export class GatewaySignalClient {
815
884
  return body;
816
885
  }
817
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
+ }
818
1154
  async function resolveGatewayTenantId(gatewayBaseUrl, apiKey, principalPath, maxRetries) {
819
1155
  const base = normalizeBase(gatewayBaseUrl);
820
1156
  const path = principalPath.startsWith("/") ? principalPath : `/${principalPath}`;
@@ -931,12 +1267,16 @@ export class PaybondIntents {
931
1267
  export class Paybond {
932
1268
  harbor;
933
1269
  signal;
1270
+ a2a;
1271
+ protocol;
934
1272
  intents;
935
1273
  session;
936
- constructor(session, signal) {
1274
+ constructor(session, signal, a2a, protocol) {
937
1275
  this.session = session;
938
1276
  this.harbor = session.harbor;
939
1277
  this.signal = signal;
1278
+ this.a2a = a2a;
1279
+ this.protocol = protocol;
940
1280
  this.intents = new PaybondIntents(session.harbor);
941
1281
  }
942
1282
  /** Open a tenant-bound session via gateway `harbor-access` exchange. */
@@ -946,7 +1286,15 @@ export class Paybond {
946
1286
  staticGatewayBearerToken: init.apiKey,
947
1287
  maxRetries: init.maxRetries ?? 3,
948
1288
  });
949
- return new Paybond(session, signal);
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);
950
1298
  }
951
1299
  async rotateHarborToken() {
952
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 {};