@truealter/sdk 0.2.4 → 0.4.1
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 +1 -0
- package/dist/bin/alter-identity.js +831 -27
- package/dist/bin/mcp-bridge.js +179 -8
- package/dist/index.cjs +753 -21
- package/dist/index.d.cts +288 -16
- package/dist/index.d.ts +288 -16
- package/dist/index.js +685 -25
- package/package.json +2 -1
package/dist/bin/mcp-bridge.js
CHANGED
|
@@ -1,7 +1,152 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { p256 } from '@noble/curves/p256';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
4
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
2
5
|
import { createInterface } from 'readline';
|
|
3
6
|
import { env, stderr, exit, stdin, stdout } from 'process';
|
|
4
7
|
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
13
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
14
|
+
}) : x)(function(x) {
|
|
15
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
16
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
17
|
+
});
|
|
18
|
+
var __esm = (fn, res) => function __init() {
|
|
19
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
25
|
+
var __copyProps = (to, from, except, desc) => {
|
|
26
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
27
|
+
for (let key of __getOwnPropNames(from))
|
|
28
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
29
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
30
|
+
}
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
34
|
+
|
|
35
|
+
// src/signing.ts
|
|
36
|
+
var signing_exports = {};
|
|
37
|
+
__export(signing_exports, {
|
|
38
|
+
canonicalArgsSha256: () => canonicalArgsSha256,
|
|
39
|
+
canonicalStringify: () => canonicalStringify,
|
|
40
|
+
loadPrivateKey: () => loadPrivateKey,
|
|
41
|
+
signInvocation: () => signInvocation
|
|
42
|
+
});
|
|
43
|
+
function canonicalStringify(value) {
|
|
44
|
+
return stringifyInner(value);
|
|
45
|
+
}
|
|
46
|
+
function stringifyInner(value) {
|
|
47
|
+
if (value === null) return "null";
|
|
48
|
+
if (value === void 0) {
|
|
49
|
+
throw new TypeError("canonicalStringify: undefined is not representable in JSON");
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
52
|
+
if (typeof value === "number") {
|
|
53
|
+
if (!Number.isFinite(value)) {
|
|
54
|
+
throw new TypeError("canonicalStringify: non-finite numbers are not representable");
|
|
55
|
+
}
|
|
56
|
+
return JSON.stringify(value);
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === "string") return encodeString(value);
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
return "[" + value.map((v) => stringifyInner(v)).join(",") + "]";
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === "object") {
|
|
63
|
+
const obj = value;
|
|
64
|
+
const keys = Object.keys(obj).sort();
|
|
65
|
+
return "{" + keys.map((k) => encodeString(k) + ":" + stringifyInner(obj[k])).join(",") + "}";
|
|
66
|
+
}
|
|
67
|
+
throw new TypeError(`canonicalStringify: unsupported type ${typeof value}`);
|
|
68
|
+
}
|
|
69
|
+
function encodeString(s) {
|
|
70
|
+
return JSON.stringify(s);
|
|
71
|
+
}
|
|
72
|
+
function canonicalArgsSha256(toolArgs) {
|
|
73
|
+
const canonical = canonicalStringify(toolArgs ?? {});
|
|
74
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
75
|
+
const digest = sha256(bytes);
|
|
76
|
+
return bytesToHex(digest);
|
|
77
|
+
}
|
|
78
|
+
function bytesToHex(bytes) {
|
|
79
|
+
let out = "";
|
|
80
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
81
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function base64urlEncode(bytes) {
|
|
86
|
+
const raw = typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes;
|
|
87
|
+
if (typeof Buffer !== "undefined") {
|
|
88
|
+
return Buffer.from(raw).toString("base64url");
|
|
89
|
+
}
|
|
90
|
+
let binary = "";
|
|
91
|
+
for (let i = 0; i < raw.length; i++) binary += String.fromCharCode(raw[i]);
|
|
92
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
93
|
+
}
|
|
94
|
+
function loadPrivateKey(key) {
|
|
95
|
+
if (key instanceof Uint8Array) {
|
|
96
|
+
if (key.length !== 32) {
|
|
97
|
+
throw new TypeError("ES256 raw private key must be 32 bytes.");
|
|
98
|
+
}
|
|
99
|
+
return key;
|
|
100
|
+
}
|
|
101
|
+
if (typeof key === "string" && key.includes("-----BEGIN")) {
|
|
102
|
+
const nodeCrypto = __require("crypto");
|
|
103
|
+
const keyObj = nodeCrypto.createPrivateKey({ key, format: "pem" });
|
|
104
|
+
const jwk = keyObj.export({ format: "jwk" });
|
|
105
|
+
if (jwk.crv !== "P-256" || !jwk.d) {
|
|
106
|
+
throw new TypeError("PEM is not a P-256 private key.");
|
|
107
|
+
}
|
|
108
|
+
return base64urlDecodeToBytes(jwk.d);
|
|
109
|
+
}
|
|
110
|
+
throw new TypeError("loadPrivateKey: expected Uint8Array(32) or PEM string.");
|
|
111
|
+
}
|
|
112
|
+
function base64urlDecodeToBytes(s) {
|
|
113
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
114
|
+
const b64 = (s + pad).replace(/-/g, "+").replace(/_/g, "/");
|
|
115
|
+
if (typeof Buffer !== "undefined") {
|
|
116
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
117
|
+
}
|
|
118
|
+
const binary = atob(b64);
|
|
119
|
+
const out = new Uint8Array(binary.length);
|
|
120
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
function signInvocation(toolName, toolArgs, options) {
|
|
124
|
+
const { kid, privateKey, handle: handle2 } = options;
|
|
125
|
+
const nonce = options.nonce ?? base64urlEncode(randomBytes(24));
|
|
126
|
+
const iat = options.iatSeconds ?? Math.floor(Date.now() / 1e3);
|
|
127
|
+
const claims = {
|
|
128
|
+
tool: toolName,
|
|
129
|
+
args_sha256: canonicalArgsSha256(toolArgs ?? {}),
|
|
130
|
+
nonce,
|
|
131
|
+
iat,
|
|
132
|
+
iss: handle2
|
|
133
|
+
};
|
|
134
|
+
const headerB64 = base64urlEncode(JSON.stringify({ alg: "ES256", kid }));
|
|
135
|
+
const payloadB64 = base64urlEncode(JSON.stringify(claims));
|
|
136
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
137
|
+
const signingBytes = new TextEncoder().encode(signingInput);
|
|
138
|
+
const dBytes = loadPrivateKey(privateKey);
|
|
139
|
+
const digest = sha256(signingBytes);
|
|
140
|
+
const sig = p256.sign(digest, dBytes, { prehash: false });
|
|
141
|
+
const sigBytes = sig.toCompactRawBytes();
|
|
142
|
+
const sigB64 = base64urlEncode(sigBytes);
|
|
143
|
+
return `${signingInput}.${sigB64}`;
|
|
144
|
+
}
|
|
145
|
+
var init_signing = __esm({
|
|
146
|
+
"src/signing.ts"() {
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
5
150
|
// src/errors.ts
|
|
6
151
|
var AlterError = class extends Error {
|
|
7
152
|
code;
|
|
@@ -103,11 +248,14 @@ var X402Client = class {
|
|
|
103
248
|
if (!this.assets.has(envelope.asset)) {
|
|
104
249
|
throw new AlterError("PAYMENT_REQUIRED", `asset ${envelope.asset} not permitted by client policy`);
|
|
105
250
|
}
|
|
106
|
-
if (this.maxPerQuery !== void 0
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
251
|
+
if (this.maxPerQuery !== void 0) {
|
|
252
|
+
const amt = Number(envelope.amount);
|
|
253
|
+
if (!Number.isFinite(amt) || amt < 0 || amt > this.maxPerQuery) {
|
|
254
|
+
throw new AlterError(
|
|
255
|
+
"PAYMENT_REQUIRED",
|
|
256
|
+
`quote ${envelope.amount} ${envelope.asset} exceeds maxPerQuery ${this.maxPerQuery}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
111
259
|
}
|
|
112
260
|
if (!this.signer) {
|
|
113
261
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
@@ -165,6 +313,7 @@ var MCPClient = class {
|
|
|
165
313
|
maxRetries;
|
|
166
314
|
clientInfo;
|
|
167
315
|
x402;
|
|
316
|
+
signing;
|
|
168
317
|
requestCounter = 0;
|
|
169
318
|
initialised = false;
|
|
170
319
|
constructor(opts = {}) {
|
|
@@ -175,6 +324,7 @@ var MCPClient = class {
|
|
|
175
324
|
this.maxRetries = opts.maxRetries ?? 2;
|
|
176
325
|
this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
|
|
177
326
|
this.x402 = opts.x402;
|
|
327
|
+
this.signing = opts.signing;
|
|
178
328
|
}
|
|
179
329
|
/**
|
|
180
330
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
@@ -261,6 +411,7 @@ var MCPClient = class {
|
|
|
261
411
|
method
|
|
262
412
|
};
|
|
263
413
|
if (params !== void 0) payload.params = params;
|
|
414
|
+
const signatureHeader = this.buildSignatureHeader(method, params);
|
|
264
415
|
let attempt = 0;
|
|
265
416
|
let lastErr = null;
|
|
266
417
|
while (attempt <= this.maxRetries) {
|
|
@@ -271,7 +422,7 @@ var MCPClient = class {
|
|
|
271
422
|
try {
|
|
272
423
|
resp = await this.fetchImpl(this.endpoint, {
|
|
273
424
|
method: "POST",
|
|
274
|
-
headers: this.buildHeaders(),
|
|
425
|
+
headers: this.buildHeaders(signatureHeader),
|
|
275
426
|
body: JSON.stringify(payload),
|
|
276
427
|
signal: controller.signal
|
|
277
428
|
});
|
|
@@ -298,7 +449,8 @@ var MCPClient = class {
|
|
|
298
449
|
throw new AlterPaymentRequired(this.guessToolName(payload), envelope);
|
|
299
450
|
}
|
|
300
451
|
if (resp.status === 429) {
|
|
301
|
-
const
|
|
452
|
+
const rawRetryAfter = Number(resp.headers.get("Retry-After") ?? 60);
|
|
453
|
+
const retryAfter = Number.isFinite(rawRetryAfter) && rawRetryAfter >= 0 ? Math.min(rawRetryAfter, 300) : 60;
|
|
302
454
|
if (attempt > this.maxRetries) {
|
|
303
455
|
throw new AlterRateLimited(`HTTP 429 on ${method}`, retryAfter);
|
|
304
456
|
}
|
|
@@ -334,7 +486,7 @@ var MCPClient = class {
|
|
|
334
486
|
}
|
|
335
487
|
throw lastErr ?? new AlterNetworkError(`MCP ${method}: exhausted retries`);
|
|
336
488
|
}
|
|
337
|
-
buildHeaders() {
|
|
489
|
+
buildHeaders(extra) {
|
|
338
490
|
const headers = {
|
|
339
491
|
"Content-Type": "application/json",
|
|
340
492
|
Accept: "application/json",
|
|
@@ -342,8 +494,27 @@ var MCPClient = class {
|
|
|
342
494
|
};
|
|
343
495
|
if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
|
|
344
496
|
if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
|
|
497
|
+
if (extra) Object.assign(headers, extra);
|
|
345
498
|
return headers;
|
|
346
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Produce the `Mcp-Invocation-Signature` header for a `tools/call`
|
|
502
|
+
* payload, when signing is configured. Returns `undefined` when no
|
|
503
|
+
* signing key is attached or the method is not `tools/call`.
|
|
504
|
+
*/
|
|
505
|
+
buildSignatureHeader(method, params) {
|
|
506
|
+
if (!this.signing) return void 0;
|
|
507
|
+
if (method !== "tools/call") return void 0;
|
|
508
|
+
const p = params;
|
|
509
|
+
if (!p?.name) return void 0;
|
|
510
|
+
const { signInvocation: signInvocation2 } = (init_signing(), __toCommonJS(signing_exports));
|
|
511
|
+
const headerValue = signInvocation2(p.name, p.arguments ?? {}, {
|
|
512
|
+
kid: this.signing.kid,
|
|
513
|
+
privateKey: this.signing.privateKey,
|
|
514
|
+
handle: this.signing.handle
|
|
515
|
+
});
|
|
516
|
+
return { "Mcp-Invocation-Signature": headerValue };
|
|
517
|
+
}
|
|
347
518
|
async extractPaymentEnvelope(resp) {
|
|
348
519
|
const headerValue = resp.headers.get("X-402-Payment") ?? resp.headers.get("x-402-payment");
|
|
349
520
|
if (headerValue) {
|