@truealter/sdk 0.4.1 → 0.5.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 +103 -74
- package/dist/bin/alter-identity.js +177 -19
- package/dist/bin/mcp-bridge.js +47 -11
- package/dist/index.cjs +203 -20
- package/dist/index.d.cts +431 -30
- package/dist/index.d.ts +431 -30
- package/dist/index.js +199 -23
- package/dist/mcp-bridge.js +166 -0
- package/package.json +7 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { p256 } from '@noble/curves/p256';
|
|
3
3
|
import { sha256 } from '@noble/hashes/sha256';
|
|
4
4
|
import { hexToBytes, bytesToHex as bytesToHex$1, randomBytes } from '@noble/hashes/utils';
|
|
5
|
+
import { createHash, createPrivateKey } from 'crypto';
|
|
5
6
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, renameSync, copyFileSync } from 'fs';
|
|
6
7
|
import { homedir, platform } from 'os';
|
|
7
8
|
import { join, dirname, resolve } from 'path';
|
|
@@ -10,7 +11,6 @@ import { createInterface } from 'readline';
|
|
|
10
11
|
import * as ed25519 from '@noble/ed25519';
|
|
11
12
|
import { sha512 } from '@noble/hashes/sha512';
|
|
12
13
|
import { spawnSync } from 'child_process';
|
|
13
|
-
import { createHash } from 'crypto';
|
|
14
14
|
|
|
15
15
|
var __defProp = Object.defineProperty;
|
|
16
16
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -106,8 +106,7 @@ function loadPrivateKey(key) {
|
|
|
106
106
|
return key;
|
|
107
107
|
}
|
|
108
108
|
if (typeof key === "string" && key.includes("-----BEGIN")) {
|
|
109
|
-
const
|
|
110
|
-
const keyObj = nodeCrypto.createPrivateKey({ key, format: "pem" });
|
|
109
|
+
const keyObj = createPrivateKey({ key, format: "pem" });
|
|
111
110
|
const jwk = keyObj.export({ format: "jwk" });
|
|
112
111
|
if (jwk.crv !== "P-256" || !jwk.d) {
|
|
113
112
|
throw new TypeError("PEM is not a P-256 private key.");
|
|
@@ -406,18 +405,24 @@ function ensureMcpPath(url) {
|
|
|
406
405
|
return url;
|
|
407
406
|
}
|
|
408
407
|
}
|
|
409
|
-
|
|
410
|
-
// src/x402.ts
|
|
411
408
|
var X402Client = class {
|
|
412
409
|
signer;
|
|
413
410
|
maxPerQuery;
|
|
414
411
|
networks;
|
|
415
412
|
assets;
|
|
413
|
+
// undefined = allowlist check disabled (backward-compatible default).
|
|
414
|
+
// Non-null = active allowlist; reject any recipient not in the set.
|
|
415
|
+
recipientAllowlist;
|
|
416
416
|
constructor(opts = {}) {
|
|
417
417
|
this.signer = opts.signer;
|
|
418
418
|
this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
|
|
419
419
|
this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
|
|
420
420
|
this.assets = new Set(opts.assets ?? ["USDC"]);
|
|
421
|
+
if (opts.recipientAllowlist !== void 0) {
|
|
422
|
+
this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
|
|
423
|
+
} else {
|
|
424
|
+
this.recipientAllowlist = void 0;
|
|
425
|
+
}
|
|
421
426
|
}
|
|
422
427
|
/**
|
|
423
428
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -443,6 +448,15 @@ var X402Client = class {
|
|
|
443
448
|
);
|
|
444
449
|
}
|
|
445
450
|
}
|
|
451
|
+
if (this.recipientAllowlist !== void 0) {
|
|
452
|
+
const recipientNorm = (envelope.recipient ?? "").toLowerCase();
|
|
453
|
+
if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
|
|
454
|
+
throw new AlterError(
|
|
455
|
+
"PAYMENT_REQUIRED",
|
|
456
|
+
`recipient "${envelope.recipient}" is not on the known-recipient allowlist`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
446
460
|
if (!this.signer) {
|
|
447
461
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
448
462
|
}
|
|
@@ -500,6 +514,7 @@ var MCPClient = class {
|
|
|
500
514
|
clientInfo;
|
|
501
515
|
x402;
|
|
502
516
|
signing;
|
|
517
|
+
extraHeaders;
|
|
503
518
|
requestCounter = 0;
|
|
504
519
|
initialised = false;
|
|
505
520
|
constructor(opts = {}) {
|
|
@@ -511,6 +526,7 @@ var MCPClient = class {
|
|
|
511
526
|
this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
|
|
512
527
|
this.x402 = opts.x402;
|
|
513
528
|
this.signing = opts.signing;
|
|
529
|
+
this.extraHeaders = opts.extraHeaders;
|
|
514
530
|
}
|
|
515
531
|
/**
|
|
516
532
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
@@ -674,6 +690,7 @@ var MCPClient = class {
|
|
|
674
690
|
}
|
|
675
691
|
buildHeaders(extra) {
|
|
676
692
|
const headers = {
|
|
693
|
+
...this.extraHeaders ?? {},
|
|
677
694
|
"Content-Type": "application/json",
|
|
678
695
|
Accept: "application/json",
|
|
679
696
|
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`
|
|
@@ -795,6 +812,7 @@ var DEFAULT_VERIFY_AT_ALLOWLIST = Object.freeze([
|
|
|
795
812
|
"api.truealter.com",
|
|
796
813
|
"mcp.truealter.com"
|
|
797
814
|
]);
|
|
815
|
+
var ALTER_PLATFORM_ISS = "did:alter:platform";
|
|
798
816
|
async function verifyProvenance(envelope, opts = {}) {
|
|
799
817
|
const token = typeof envelope === "string" ? envelope : envelope.token;
|
|
800
818
|
if (!token) return { valid: false, reason: "empty token" };
|
|
@@ -877,9 +895,55 @@ async function verifyProvenance(envelope, opts = {}) {
|
|
|
877
895
|
if (typeof payload.iat === "number" && payload.iat > now + 300) {
|
|
878
896
|
return { valid: false, reason: "issued in the future", payload, kid: header.kid };
|
|
879
897
|
}
|
|
898
|
+
const expectedIss = opts.expectedIss !== void 0 ? opts.expectedIss : ALTER_PLATFORM_ISS;
|
|
899
|
+
if (expectedIss !== "" && payload.iss !== expectedIss) {
|
|
900
|
+
return {
|
|
901
|
+
valid: false,
|
|
902
|
+
reason: `iss mismatch: expected "${expectedIss}", got "${payload.iss}"`,
|
|
903
|
+
payload,
|
|
904
|
+
kid: header.kid
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
if (opts.expectedAud !== void 0 && opts.expectedAud !== "") {
|
|
908
|
+
const tokenAud = payload.aud;
|
|
909
|
+
const audList = tokenAud === void 0 ? [] : Array.isArray(tokenAud) ? tokenAud : [tokenAud];
|
|
910
|
+
if (!audList.includes(opts.expectedAud)) {
|
|
911
|
+
return {
|
|
912
|
+
valid: false,
|
|
913
|
+
reason: `aud mismatch: expected "${opts.expectedAud}", got ${JSON.stringify(tokenAud ?? null)}`,
|
|
914
|
+
payload,
|
|
915
|
+
kid: header.kid
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
880
919
|
return { valid: true, payload, kid: header.kid };
|
|
881
920
|
}
|
|
882
|
-
async function verifyToolSignatures(tools, signatures) {
|
|
921
|
+
async function verifyToolSignatures(tools, signatures, opts = {}) {
|
|
922
|
+
const jwksUrl = opts.jwksUrl ?? "https://api.truealter.com/.well-known/alter-keys.json";
|
|
923
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
924
|
+
if (!jwksUrl.startsWith("https://")) {
|
|
925
|
+
return tools.map((t) => ({
|
|
926
|
+
tool: t.name,
|
|
927
|
+
valid: false,
|
|
928
|
+
reason: `jwksUrl must be https: got ${jwksUrl}`
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
const needsJwks = tools.some((t) => {
|
|
932
|
+
const sig = signatures[t.name];
|
|
933
|
+
return sig && sig.signature;
|
|
934
|
+
});
|
|
935
|
+
let jwks = null;
|
|
936
|
+
if (needsJwks) {
|
|
937
|
+
try {
|
|
938
|
+
jwks = await fetchJwks(jwksUrl, fetchImpl);
|
|
939
|
+
} catch (err) {
|
|
940
|
+
return tools.map((t) => ({
|
|
941
|
+
tool: t.name,
|
|
942
|
+
valid: false,
|
|
943
|
+
reason: `jwks fetch failed: ${err.message}`
|
|
944
|
+
}));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
883
947
|
const out = [];
|
|
884
948
|
for (const tool of tools) {
|
|
885
949
|
const sig = signatures[tool.name];
|
|
@@ -892,6 +956,63 @@ async function verifyToolSignatures(tools, signatures) {
|
|
|
892
956
|
out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
|
|
893
957
|
continue;
|
|
894
958
|
}
|
|
959
|
+
const jwsToken = sig.signature;
|
|
960
|
+
if (!jwsToken) {
|
|
961
|
+
out.push({ tool: tool.name, valid: true, warn_no_signature: true });
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
const jwksDoc = jwks;
|
|
965
|
+
let jHeader;
|
|
966
|
+
let jPayloadRaw;
|
|
967
|
+
let jSigBytes;
|
|
968
|
+
try {
|
|
969
|
+
const parts2 = jwsToken.split(".");
|
|
970
|
+
if (parts2.length !== 3) throw new Error("JWS must have three segments");
|
|
971
|
+
jHeader = JSON.parse(new TextDecoder().decode(base64urlDecode(parts2[0])));
|
|
972
|
+
jPayloadRaw = new TextDecoder().decode(base64urlDecode(parts2[1]));
|
|
973
|
+
jSigBytes = base64urlDecode(parts2[2]);
|
|
974
|
+
} catch (err) {
|
|
975
|
+
out.push({ tool: tool.name, valid: false, reason: `malformed tool JWS: ${err.message}` });
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (jHeader.alg !== "ES256") {
|
|
979
|
+
out.push({ tool: tool.name, valid: false, reason: `unsupported tool sig alg: ${jHeader.alg}` });
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (jPayloadRaw !== sig.schema_hash) {
|
|
983
|
+
out.push({ tool: tool.name, valid: false, reason: "tool JWS payload does not match schema_hash" });
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
const jwk = jwksDoc.keys.find((k) => jHeader.kid ? k.kid === jHeader.kid : true);
|
|
987
|
+
if (!jwk) {
|
|
988
|
+
out.push({ tool: tool.name, valid: false, reason: `no JWK for kid=${jHeader.kid}` });
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
let publicKey;
|
|
992
|
+
try {
|
|
993
|
+
publicKey = await importEs256JwkAsPublicKey(jwk);
|
|
994
|
+
} catch (err) {
|
|
995
|
+
out.push({ tool: tool.name, valid: false, reason: `jwk import: ${err.message}` });
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
const parts = jwsToken.split(".");
|
|
999
|
+
const signedInput = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
1000
|
+
let sigValid = false;
|
|
1001
|
+
try {
|
|
1002
|
+
sigValid = await crypto.subtle.verify(
|
|
1003
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
1004
|
+
publicKey,
|
|
1005
|
+
toArrayBuffer(jSigBytes),
|
|
1006
|
+
toArrayBuffer(signedInput)
|
|
1007
|
+
);
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
out.push({ tool: tool.name, valid: false, reason: `sig verify error: ${err.message}` });
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (!sigValid) {
|
|
1013
|
+
out.push({ tool: tool.name, valid: false, reason: "tool signature mismatch" });
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
895
1016
|
out.push({ tool: tool.name, valid: true });
|
|
896
1017
|
}
|
|
897
1018
|
return out;
|
|
@@ -1073,10 +1194,10 @@ var AlterClient = class {
|
|
|
1073
1194
|
}
|
|
1074
1195
|
/** Verify a person is registered with ALTER (handle or id). */
|
|
1075
1196
|
async verify(handleOrId, claims) {
|
|
1076
|
-
const args = handleOrId.includes("@") ? {
|
|
1077
|
-
// ~handle — server resolves these via the
|
|
1078
|
-
{
|
|
1079
|
-
) : {
|
|
1197
|
+
const args = handleOrId.includes("@") ? { member_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
|
|
1198
|
+
// ~handle — server resolves these via the member_id field
|
|
1199
|
+
{ member_id: handleOrId }
|
|
1200
|
+
) : { member_id: handleOrId };
|
|
1080
1201
|
if (claims) args.claims = claims;
|
|
1081
1202
|
return this.mcp.callTool("verify_identity", args);
|
|
1082
1203
|
}
|
|
@@ -1523,6 +1644,26 @@ function restoreFromBackup(path, backupPath) {
|
|
|
1523
1644
|
// src/wire/index.ts
|
|
1524
1645
|
var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
|
|
1525
1646
|
var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1647
|
+
function readCfAccessEnv() {
|
|
1648
|
+
const envPath = join(homedir(), ".config", "alter", "cf-access.env");
|
|
1649
|
+
try {
|
|
1650
|
+
const content = readFileSync(envPath, "utf8");
|
|
1651
|
+
let clientId = "";
|
|
1652
|
+
let clientSecret = "";
|
|
1653
|
+
for (const line of content.split("\n")) {
|
|
1654
|
+
const trimmed = line.trim();
|
|
1655
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
1656
|
+
const eqIdx = trimmed.indexOf("=");
|
|
1657
|
+
const key = trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim();
|
|
1658
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
1659
|
+
if (key === "CF_ACCESS_CLIENT_ID") clientId = val;
|
|
1660
|
+
if (key === "CF_ACCESS_CLIENT_SECRET") clientSecret = val;
|
|
1661
|
+
}
|
|
1662
|
+
if (clientId && clientSecret) return { clientId, clientSecret };
|
|
1663
|
+
} catch {
|
|
1664
|
+
}
|
|
1665
|
+
return void 0;
|
|
1666
|
+
}
|
|
1526
1667
|
function clientById(id) {
|
|
1527
1668
|
const hit = ALL_CLIENTS.find((c) => c.id === id);
|
|
1528
1669
|
if (!hit) throw new Error(`unknown client id: ${id}`);
|
|
@@ -1531,6 +1672,7 @@ function clientById(id) {
|
|
|
1531
1672
|
function wire(opts = {}) {
|
|
1532
1673
|
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1533
1674
|
const apiKey = opts.apiKey;
|
|
1675
|
+
const cfAccess = opts.cfAccess ?? readCfAccessEnv();
|
|
1534
1676
|
const probes = probeAll();
|
|
1535
1677
|
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1536
1678
|
const ts = TIMESTAMP();
|
|
@@ -1549,9 +1691,9 @@ function wire(opts = {}) {
|
|
|
1549
1691
|
}
|
|
1550
1692
|
try {
|
|
1551
1693
|
if (id === "claude-code") {
|
|
1552
|
-
targets.push(wireClaudeCode({ endpoint, apiKey }));
|
|
1694
|
+
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
|
|
1553
1695
|
} else {
|
|
1554
|
-
targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
|
|
1696
|
+
targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
|
|
1555
1697
|
}
|
|
1556
1698
|
} catch (err) {
|
|
1557
1699
|
const message = err.message;
|
|
@@ -1585,7 +1727,12 @@ function wireFileTarget(args) {
|
|
|
1585
1727
|
`refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices \u2014 move the config off the sync root, or run wire on the device you want to target.`
|
|
1586
1728
|
);
|
|
1587
1729
|
}
|
|
1588
|
-
const
|
|
1730
|
+
const cfHeaders = {};
|
|
1731
|
+
if (args.cfAccess) {
|
|
1732
|
+
cfHeaders["CF-Access-Client-Id"] = args.cfAccess.clientId;
|
|
1733
|
+
cfHeaders["CF-Access-Client-Secret"] = args.cfAccess.clientSecret;
|
|
1734
|
+
}
|
|
1735
|
+
const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey, headers: cfHeaders });
|
|
1589
1736
|
const rootKey = client.rootKey;
|
|
1590
1737
|
const serverName = "alter";
|
|
1591
1738
|
const result = atomicJsonMerge({
|
|
@@ -1617,7 +1764,8 @@ function wireFileTarget(args) {
|
|
|
1617
1764
|
}
|
|
1618
1765
|
function wireClaudeCode(args) {
|
|
1619
1766
|
const cmd = "claude";
|
|
1620
|
-
const
|
|
1767
|
+
const bridgePath = resolveBridgeScript();
|
|
1768
|
+
const argList = bridgePath ? ["mcp", "add", "--scope", "user", "alter", "--", "node", bridgePath] : [
|
|
1621
1769
|
"mcp",
|
|
1622
1770
|
"add",
|
|
1623
1771
|
"--scope",
|
|
@@ -1625,16 +1773,15 @@ function wireClaudeCode(args) {
|
|
|
1625
1773
|
"--transport",
|
|
1626
1774
|
"http",
|
|
1627
1775
|
"alter",
|
|
1628
|
-
args.endpoint
|
|
1776
|
+
args.endpoint,
|
|
1777
|
+
...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
|
|
1629
1778
|
];
|
|
1630
|
-
if (args.apiKey) {
|
|
1631
|
-
argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
|
|
1632
|
-
}
|
|
1633
1779
|
const full = `${cmd} ${argList.join(" ")}`;
|
|
1634
1780
|
const run = spawnSync(cmd, argList, {
|
|
1635
1781
|
encoding: "utf8",
|
|
1636
1782
|
shell: process.platform === "win32",
|
|
1637
|
-
timeout: 1e4
|
|
1783
|
+
timeout: 1e4,
|
|
1784
|
+
env: bridgePath ? { ...process.env, ALTER_PUBLIC_MCP_ENDPOINT: args.endpoint, ...args.apiKey ? { ALTER_API_KEY: args.apiKey } : {} } : void 0
|
|
1638
1785
|
});
|
|
1639
1786
|
if (run.error) {
|
|
1640
1787
|
return {
|
|
@@ -1665,6 +1812,17 @@ function wireClaudeCode(args) {
|
|
|
1665
1812
|
reason: `claude mcp add exited ${String(run.status)}`
|
|
1666
1813
|
};
|
|
1667
1814
|
}
|
|
1815
|
+
function resolveBridgeScript() {
|
|
1816
|
+
const { existsSync: existsSync5 } = __require("fs");
|
|
1817
|
+
const { join: join4, dirname: dirname4 } = __require("path");
|
|
1818
|
+
const siblingBridge = join4(dirname4(__filename), "..", "dist", "mcp-bridge.js");
|
|
1819
|
+
if (existsSync5(siblingBridge)) return siblingBridge;
|
|
1820
|
+
const srcBridge = join4(dirname4(__filename), "..", "mcp-bridge.js");
|
|
1821
|
+
if (existsSync5(srcBridge)) return srcBridge;
|
|
1822
|
+
const npmGlobalBridge = join4(dirname4(__filename), "mcp-bridge.js");
|
|
1823
|
+
if (existsSync5(npmGlobalBridge)) return npmGlobalBridge;
|
|
1824
|
+
return null;
|
|
1825
|
+
}
|
|
1668
1826
|
function unwire() {
|
|
1669
1827
|
const state = readWireState();
|
|
1670
1828
|
const undone = [];
|
package/dist/bin/mcp-bridge.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { p256 } from '@noble/curves/p256';
|
|
3
3
|
import { sha256 } from '@noble/hashes/sha256';
|
|
4
4
|
import { randomBytes } from '@noble/hashes/utils';
|
|
5
|
+
import { createPrivateKey } from 'crypto';
|
|
5
6
|
import { createInterface } from 'readline';
|
|
6
7
|
import { env, stderr, exit, stdin, stdout } from 'process';
|
|
7
8
|
|
|
@@ -9,12 +10,6 @@ var __defProp = Object.defineProperty;
|
|
|
9
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
11
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
12
|
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
13
|
var __esm = (fn, res) => function __init() {
|
|
19
14
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
20
15
|
};
|
|
@@ -99,8 +94,7 @@ function loadPrivateKey(key) {
|
|
|
99
94
|
return key;
|
|
100
95
|
}
|
|
101
96
|
if (typeof key === "string" && key.includes("-----BEGIN")) {
|
|
102
|
-
const
|
|
103
|
-
const keyObj = nodeCrypto.createPrivateKey({ key, format: "pem" });
|
|
97
|
+
const keyObj = createPrivateKey({ key, format: "pem" });
|
|
104
98
|
const jwk = keyObj.export({ format: "jwk" });
|
|
105
99
|
if (jwk.crv !== "P-256" || !jwk.d) {
|
|
106
100
|
throw new TypeError("PEM is not a P-256 private key.");
|
|
@@ -220,18 +214,24 @@ var AlterInvalidResponse = class extends AlterError {
|
|
|
220
214
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
221
215
|
}
|
|
222
216
|
};
|
|
223
|
-
|
|
224
|
-
// src/x402.ts
|
|
225
217
|
var X402Client = class {
|
|
226
218
|
signer;
|
|
227
219
|
maxPerQuery;
|
|
228
220
|
networks;
|
|
229
221
|
assets;
|
|
222
|
+
// undefined = allowlist check disabled (backward-compatible default).
|
|
223
|
+
// Non-null = active allowlist; reject any recipient not in the set.
|
|
224
|
+
recipientAllowlist;
|
|
230
225
|
constructor(opts = {}) {
|
|
231
226
|
this.signer = opts.signer;
|
|
232
227
|
this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
|
|
233
228
|
this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
|
|
234
229
|
this.assets = new Set(opts.assets ?? ["USDC"]);
|
|
230
|
+
if (opts.recipientAllowlist !== void 0) {
|
|
231
|
+
this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
|
|
232
|
+
} else {
|
|
233
|
+
this.recipientAllowlist = void 0;
|
|
234
|
+
}
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
237
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -257,6 +257,15 @@ var X402Client = class {
|
|
|
257
257
|
);
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
if (this.recipientAllowlist !== void 0) {
|
|
261
|
+
const recipientNorm = (envelope.recipient ?? "").toLowerCase();
|
|
262
|
+
if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
|
|
263
|
+
throw new AlterError(
|
|
264
|
+
"PAYMENT_REQUIRED",
|
|
265
|
+
`recipient "${envelope.recipient}" is not on the known-recipient allowlist`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
260
269
|
if (!this.signer) {
|
|
261
270
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
262
271
|
}
|
|
@@ -314,6 +323,7 @@ var MCPClient = class {
|
|
|
314
323
|
clientInfo;
|
|
315
324
|
x402;
|
|
316
325
|
signing;
|
|
326
|
+
extraHeaders;
|
|
317
327
|
requestCounter = 0;
|
|
318
328
|
initialised = false;
|
|
319
329
|
constructor(opts = {}) {
|
|
@@ -325,6 +335,7 @@ var MCPClient = class {
|
|
|
325
335
|
this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
|
|
326
336
|
this.x402 = opts.x402;
|
|
327
337
|
this.signing = opts.signing;
|
|
338
|
+
this.extraHeaders = opts.extraHeaders;
|
|
328
339
|
}
|
|
329
340
|
/**
|
|
330
341
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
@@ -488,6 +499,7 @@ var MCPClient = class {
|
|
|
488
499
|
}
|
|
489
500
|
buildHeaders(extra) {
|
|
490
501
|
const headers = {
|
|
502
|
+
...this.extraHeaders ?? {},
|
|
491
503
|
"Content-Type": "application/json",
|
|
492
504
|
Accept: "application/json",
|
|
493
505
|
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`
|
|
@@ -556,10 +568,34 @@ async function safeText(resp) {
|
|
|
556
568
|
// bin/mcp-bridge.ts
|
|
557
569
|
var ENDPOINT = env.ALTER_MCP_ENDPOINT ?? "https://mcp.truealter.com/api/v1/mcp";
|
|
558
570
|
var API_KEY = env.ALTER_API_KEY ?? void 0;
|
|
571
|
+
function buildExtraHeaders() {
|
|
572
|
+
const headers = {};
|
|
573
|
+
if (env.CF_ACCESS_CLIENT_ID && env.CF_ACCESS_CLIENT_SECRET) {
|
|
574
|
+
headers["CF-Access-Client-Id"] = env.CF_ACCESS_CLIENT_ID;
|
|
575
|
+
headers["CF-Access-Client-Secret"] = env.CF_ACCESS_CLIENT_SECRET;
|
|
576
|
+
}
|
|
577
|
+
if (env.ALTER_BRIDGE_HEADERS) {
|
|
578
|
+
try {
|
|
579
|
+
const parsed = JSON.parse(env.ALTER_BRIDGE_HEADERS);
|
|
580
|
+
Object.assign(headers, parsed);
|
|
581
|
+
} catch (err) {
|
|
582
|
+
stderr.write(
|
|
583
|
+
`[alter-bridge] warning: ALTER_BRIDGE_HEADERS is not valid JSON; ignored (${err.message})
|
|
584
|
+
`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return Object.keys(headers).length ? headers : void 0;
|
|
589
|
+
}
|
|
590
|
+
var EXTRA_HEADERS = buildExtraHeaders();
|
|
591
|
+
console.warn(
|
|
592
|
+
"This bridge is a dev/demo surface. Authenticated MCP tools require ES256 per-invocation signing; for production, import `@truealter/sdk` directly. Bridge signing lands in Wave-2."
|
|
593
|
+
);
|
|
559
594
|
var client = new MCPClient({
|
|
560
595
|
endpoint: ENDPOINT,
|
|
561
596
|
apiKey: API_KEY,
|
|
562
|
-
clientInfo: { name: "@truealter/sdk-mcp-bridge", version: "0.2.0" }
|
|
597
|
+
clientInfo: { name: "@truealter/sdk-mcp-bridge", version: "0.2.0" },
|
|
598
|
+
extraHeaders: EXTRA_HEADERS
|
|
563
599
|
});
|
|
564
600
|
function send(response) {
|
|
565
601
|
stdout.write(JSON.stringify(response) + "\n");
|