@truealter/sdk 0.5.0 → 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 +61 -45
- package/dist/bin/alter-identity.js +164 -12
- package/dist/bin/mcp-bridge.js +18 -3
- package/dist/index.cjs +167 -13
- package/dist/index.d.cts +56 -19
- package/dist/index.d.ts +56 -19
- package/dist/index.js +167 -15
- package/dist/mcp-bridge.js +166 -0
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -6,11 +6,11 @@ var utils = require('@noble/hashes/utils');
|
|
|
6
6
|
var crypto$1 = require('crypto');
|
|
7
7
|
var ed25519 = require('@noble/ed25519');
|
|
8
8
|
var sha512 = require('@noble/hashes/sha512');
|
|
9
|
-
var
|
|
10
|
-
var os = require('os');
|
|
9
|
+
var fs = require('fs');
|
|
11
10
|
var path = require('path');
|
|
11
|
+
var os = require('os');
|
|
12
|
+
var child_process = require('child_process');
|
|
12
13
|
var process$1 = require('process');
|
|
13
|
-
var fs = require('fs');
|
|
14
14
|
|
|
15
15
|
function _interopNamespace(e) {
|
|
16
16
|
if (e && e.__esModule) return e;
|
|
@@ -36,6 +36,12 @@ var __defProp = Object.defineProperty;
|
|
|
36
36
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
37
37
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
38
38
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
39
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
40
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
41
|
+
}) : x)(function(x) {
|
|
42
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
43
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
44
|
+
});
|
|
39
45
|
var __esm = (fn, res) => function __init() {
|
|
40
46
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
41
47
|
};
|
|
@@ -450,11 +456,19 @@ var X402Client = class {
|
|
|
450
456
|
maxPerQuery;
|
|
451
457
|
networks;
|
|
452
458
|
assets;
|
|
459
|
+
// undefined = allowlist check disabled (backward-compatible default).
|
|
460
|
+
// Non-null = active allowlist; reject any recipient not in the set.
|
|
461
|
+
recipientAllowlist;
|
|
453
462
|
constructor(opts = {}) {
|
|
454
463
|
this.signer = opts.signer;
|
|
455
464
|
this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
|
|
456
465
|
this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
|
|
457
466
|
this.assets = new Set(opts.assets ?? ["USDC"]);
|
|
467
|
+
if (opts.recipientAllowlist !== void 0) {
|
|
468
|
+
this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
|
|
469
|
+
} else {
|
|
470
|
+
this.recipientAllowlist = void 0;
|
|
471
|
+
}
|
|
458
472
|
}
|
|
459
473
|
/**
|
|
460
474
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -480,6 +494,15 @@ var X402Client = class {
|
|
|
480
494
|
);
|
|
481
495
|
}
|
|
482
496
|
}
|
|
497
|
+
if (this.recipientAllowlist !== void 0) {
|
|
498
|
+
const recipientNorm = (envelope.recipient ?? "").toLowerCase();
|
|
499
|
+
if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
|
|
500
|
+
throw new AlterError(
|
|
501
|
+
"PAYMENT_REQUIRED",
|
|
502
|
+
`recipient "${envelope.recipient}" is not on the known-recipient allowlist`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
483
506
|
if (!this.signer) {
|
|
484
507
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
485
508
|
}
|
|
@@ -952,9 +975,46 @@ async function verifyProvenance(envelope, opts = {}) {
|
|
|
952
975
|
kid: header.kid
|
|
953
976
|
};
|
|
954
977
|
}
|
|
978
|
+
if (opts.expectedAud !== void 0 && opts.expectedAud !== "") {
|
|
979
|
+
const tokenAud = payload.aud;
|
|
980
|
+
const audList = tokenAud === void 0 ? [] : Array.isArray(tokenAud) ? tokenAud : [tokenAud];
|
|
981
|
+
if (!audList.includes(opts.expectedAud)) {
|
|
982
|
+
return {
|
|
983
|
+
valid: false,
|
|
984
|
+
reason: `aud mismatch: expected "${opts.expectedAud}", got ${JSON.stringify(tokenAud ?? null)}`,
|
|
985
|
+
payload,
|
|
986
|
+
kid: header.kid
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
}
|
|
955
990
|
return { valid: true, payload, kid: header.kid };
|
|
956
991
|
}
|
|
957
|
-
async function verifyToolSignatures(tools, signatures) {
|
|
992
|
+
async function verifyToolSignatures(tools, signatures, opts = {}) {
|
|
993
|
+
const jwksUrl = opts.jwksUrl ?? "https://api.truealter.com/.well-known/alter-keys.json";
|
|
994
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
995
|
+
if (!jwksUrl.startsWith("https://")) {
|
|
996
|
+
return tools.map((t) => ({
|
|
997
|
+
tool: t.name,
|
|
998
|
+
valid: false,
|
|
999
|
+
reason: `jwksUrl must be https: got ${jwksUrl}`
|
|
1000
|
+
}));
|
|
1001
|
+
}
|
|
1002
|
+
const needsJwks = tools.some((t) => {
|
|
1003
|
+
const sig = signatures[t.name];
|
|
1004
|
+
return sig && sig.signature;
|
|
1005
|
+
});
|
|
1006
|
+
let jwks = null;
|
|
1007
|
+
if (needsJwks) {
|
|
1008
|
+
try {
|
|
1009
|
+
jwks = await fetchJwks(jwksUrl, fetchImpl);
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
return tools.map((t) => ({
|
|
1012
|
+
tool: t.name,
|
|
1013
|
+
valid: false,
|
|
1014
|
+
reason: `jwks fetch failed: ${err.message}`
|
|
1015
|
+
}));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
958
1018
|
const out = [];
|
|
959
1019
|
for (const tool of tools) {
|
|
960
1020
|
const sig = signatures[tool.name];
|
|
@@ -967,6 +1027,63 @@ async function verifyToolSignatures(tools, signatures) {
|
|
|
967
1027
|
out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
|
|
968
1028
|
continue;
|
|
969
1029
|
}
|
|
1030
|
+
const jwsToken = sig.signature;
|
|
1031
|
+
if (!jwsToken) {
|
|
1032
|
+
out.push({ tool: tool.name, valid: true, warn_no_signature: true });
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
const jwksDoc = jwks;
|
|
1036
|
+
let jHeader;
|
|
1037
|
+
let jPayloadRaw;
|
|
1038
|
+
let jSigBytes;
|
|
1039
|
+
try {
|
|
1040
|
+
const parts2 = jwsToken.split(".");
|
|
1041
|
+
if (parts2.length !== 3) throw new Error("JWS must have three segments");
|
|
1042
|
+
jHeader = JSON.parse(new TextDecoder().decode(base64urlDecode(parts2[0])));
|
|
1043
|
+
jPayloadRaw = new TextDecoder().decode(base64urlDecode(parts2[1]));
|
|
1044
|
+
jSigBytes = base64urlDecode(parts2[2]);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
out.push({ tool: tool.name, valid: false, reason: `malformed tool JWS: ${err.message}` });
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (jHeader.alg !== "ES256") {
|
|
1050
|
+
out.push({ tool: tool.name, valid: false, reason: `unsupported tool sig alg: ${jHeader.alg}` });
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (jPayloadRaw !== sig.schema_hash) {
|
|
1054
|
+
out.push({ tool: tool.name, valid: false, reason: "tool JWS payload does not match schema_hash" });
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
const jwk = jwksDoc.keys.find((k) => jHeader.kid ? k.kid === jHeader.kid : true);
|
|
1058
|
+
if (!jwk) {
|
|
1059
|
+
out.push({ tool: tool.name, valid: false, reason: `no JWK for kid=${jHeader.kid}` });
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
let publicKey;
|
|
1063
|
+
try {
|
|
1064
|
+
publicKey = await importEs256JwkAsPublicKey(jwk);
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
out.push({ tool: tool.name, valid: false, reason: `jwk import: ${err.message}` });
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const parts = jwsToken.split(".");
|
|
1070
|
+
const signedInput = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
1071
|
+
let sigValid = false;
|
|
1072
|
+
try {
|
|
1073
|
+
sigValid = await crypto.subtle.verify(
|
|
1074
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
1075
|
+
publicKey,
|
|
1076
|
+
toArrayBuffer(jSigBytes),
|
|
1077
|
+
toArrayBuffer(signedInput)
|
|
1078
|
+
);
|
|
1079
|
+
} catch (err) {
|
|
1080
|
+
out.push({ tool: tool.name, valid: false, reason: `sig verify error: ${err.message}` });
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (!sigValid) {
|
|
1084
|
+
out.push({ tool: tool.name, valid: false, reason: "tool signature mismatch" });
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
970
1087
|
out.push({ tool: tool.name, valid: true });
|
|
971
1088
|
}
|
|
972
1089
|
return out;
|
|
@@ -1626,6 +1743,26 @@ function restoreFromBackup(path, backupPath) {
|
|
|
1626
1743
|
// src/wire/index.ts
|
|
1627
1744
|
var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
|
|
1628
1745
|
var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1746
|
+
function readCfAccessEnv() {
|
|
1747
|
+
const envPath = path.join(os.homedir(), ".config", "alter", "cf-access.env");
|
|
1748
|
+
try {
|
|
1749
|
+
const content = fs.readFileSync(envPath, "utf8");
|
|
1750
|
+
let clientId = "";
|
|
1751
|
+
let clientSecret = "";
|
|
1752
|
+
for (const line of content.split("\n")) {
|
|
1753
|
+
const trimmed = line.trim();
|
|
1754
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
1755
|
+
const eqIdx = trimmed.indexOf("=");
|
|
1756
|
+
const key = trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim();
|
|
1757
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
1758
|
+
if (key === "CF_ACCESS_CLIENT_ID") clientId = val;
|
|
1759
|
+
if (key === "CF_ACCESS_CLIENT_SECRET") clientSecret = val;
|
|
1760
|
+
}
|
|
1761
|
+
if (clientId && clientSecret) return { clientId, clientSecret };
|
|
1762
|
+
} catch {
|
|
1763
|
+
}
|
|
1764
|
+
return void 0;
|
|
1765
|
+
}
|
|
1629
1766
|
function clientById(id) {
|
|
1630
1767
|
const hit = ALL_CLIENTS.find((c) => c.id === id);
|
|
1631
1768
|
if (!hit) throw new Error(`unknown client id: ${id}`);
|
|
@@ -1634,6 +1771,7 @@ function clientById(id) {
|
|
|
1634
1771
|
function wire(opts = {}) {
|
|
1635
1772
|
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1636
1773
|
const apiKey = opts.apiKey;
|
|
1774
|
+
const cfAccess = opts.cfAccess ?? readCfAccessEnv();
|
|
1637
1775
|
const probes = probeAll();
|
|
1638
1776
|
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1639
1777
|
const ts = TIMESTAMP();
|
|
@@ -1652,9 +1790,9 @@ function wire(opts = {}) {
|
|
|
1652
1790
|
}
|
|
1653
1791
|
try {
|
|
1654
1792
|
if (id === "claude-code") {
|
|
1655
|
-
targets.push(wireClaudeCode({ endpoint, apiKey }));
|
|
1793
|
+
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
|
|
1656
1794
|
} else {
|
|
1657
|
-
targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
|
|
1795
|
+
targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
|
|
1658
1796
|
}
|
|
1659
1797
|
} catch (err) {
|
|
1660
1798
|
const message = err.message;
|
|
@@ -1688,7 +1826,12 @@ function wireFileTarget(args) {
|
|
|
1688
1826
|
`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.`
|
|
1689
1827
|
);
|
|
1690
1828
|
}
|
|
1691
|
-
const
|
|
1829
|
+
const cfHeaders = {};
|
|
1830
|
+
if (args.cfAccess) {
|
|
1831
|
+
cfHeaders["CF-Access-Client-Id"] = args.cfAccess.clientId;
|
|
1832
|
+
cfHeaders["CF-Access-Client-Secret"] = args.cfAccess.clientSecret;
|
|
1833
|
+
}
|
|
1834
|
+
const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey, headers: cfHeaders });
|
|
1692
1835
|
const rootKey = client.rootKey;
|
|
1693
1836
|
const serverName = "alter";
|
|
1694
1837
|
const result = atomicJsonMerge({
|
|
@@ -1720,7 +1863,8 @@ function wireFileTarget(args) {
|
|
|
1720
1863
|
}
|
|
1721
1864
|
function wireClaudeCode(args) {
|
|
1722
1865
|
const cmd = "claude";
|
|
1723
|
-
const
|
|
1866
|
+
const bridgePath = resolveBridgeScript();
|
|
1867
|
+
const argList = bridgePath ? ["mcp", "add", "--scope", "user", "alter", "--", "node", bridgePath] : [
|
|
1724
1868
|
"mcp",
|
|
1725
1869
|
"add",
|
|
1726
1870
|
"--scope",
|
|
@@ -1728,16 +1872,15 @@ function wireClaudeCode(args) {
|
|
|
1728
1872
|
"--transport",
|
|
1729
1873
|
"http",
|
|
1730
1874
|
"alter",
|
|
1731
|
-
args.endpoint
|
|
1875
|
+
args.endpoint,
|
|
1876
|
+
...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
|
|
1732
1877
|
];
|
|
1733
|
-
if (args.apiKey) {
|
|
1734
|
-
argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
|
|
1735
|
-
}
|
|
1736
1878
|
const full = `${cmd} ${argList.join(" ")}`;
|
|
1737
1879
|
const run = child_process.spawnSync(cmd, argList, {
|
|
1738
1880
|
encoding: "utf8",
|
|
1739
1881
|
shell: process.platform === "win32",
|
|
1740
|
-
timeout: 1e4
|
|
1882
|
+
timeout: 1e4,
|
|
1883
|
+
env: bridgePath ? { ...process.env, ALTER_PUBLIC_MCP_ENDPOINT: args.endpoint, ...args.apiKey ? { ALTER_API_KEY: args.apiKey } : {} } : void 0
|
|
1741
1884
|
});
|
|
1742
1885
|
if (run.error) {
|
|
1743
1886
|
return {
|
|
@@ -1768,6 +1911,17 @@ function wireClaudeCode(args) {
|
|
|
1768
1911
|
reason: `claude mcp add exited ${String(run.status)}`
|
|
1769
1912
|
};
|
|
1770
1913
|
}
|
|
1914
|
+
function resolveBridgeScript() {
|
|
1915
|
+
const { existsSync: existsSync4 } = __require("fs");
|
|
1916
|
+
const { join: join3, dirname: dirname3 } = __require("path");
|
|
1917
|
+
const siblingBridge = join3(dirname3(__filename), "..", "dist", "mcp-bridge.js");
|
|
1918
|
+
if (existsSync4(siblingBridge)) return siblingBridge;
|
|
1919
|
+
const srcBridge = join3(dirname3(__filename), "..", "mcp-bridge.js");
|
|
1920
|
+
if (existsSync4(srcBridge)) return srcBridge;
|
|
1921
|
+
const npmGlobalBridge = join3(dirname3(__filename), "mcp-bridge.js");
|
|
1922
|
+
if (existsSync4(npmGlobalBridge)) return npmGlobalBridge;
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1771
1925
|
function unwire() {
|
|
1772
1926
|
const state = readWireState();
|
|
1773
1927
|
const undone = [];
|
package/dist/index.d.cts
CHANGED
|
@@ -130,6 +130,7 @@ interface ProvenanceEnvelope {
|
|
|
130
130
|
}
|
|
131
131
|
interface ProvenancePayload {
|
|
132
132
|
iss: string;
|
|
133
|
+
aud?: string | string[];
|
|
133
134
|
iat: number;
|
|
134
135
|
exp: number;
|
|
135
136
|
purpose: string;
|
|
@@ -196,6 +197,13 @@ interface VerifyProvenanceOptions {
|
|
|
196
197
|
* recommended for production use.
|
|
197
198
|
*/
|
|
198
199
|
expectedIss?: string;
|
|
200
|
+
/**
|
|
201
|
+
* Expected `aud` claim. When provided and non-empty, the token's `aud`
|
|
202
|
+
* claim must contain this value (RFC 7519 §4.1.3). Pass an empty string
|
|
203
|
+
* to disable the check. Defaults to no check (undefined) for backward
|
|
204
|
+
* compatibility — callers SHOULD supply this in production environments.
|
|
205
|
+
*/
|
|
206
|
+
expectedAud?: string;
|
|
199
207
|
}
|
|
200
208
|
/**
|
|
201
209
|
* Verify a provenance JWS token against ALTER's published JWKS.
|
|
@@ -216,7 +224,15 @@ declare function verifyProvenance(envelope: ProvenanceEnvelope | string, opts?:
|
|
|
216
224
|
*
|
|
217
225
|
* ALTER signs each tool's input schema at startup and exposes the
|
|
218
226
|
* signatures via the MCP `tools/list` `_meta.signatures` map. This helper
|
|
219
|
-
* checks that each tool's schema hash matches the signed value
|
|
227
|
+
* checks that each tool's schema hash matches the signed value, AND
|
|
228
|
+
* cryptographically verifies the ES256 JWS signature carried in
|
|
229
|
+
* `sig.signature` / `sig.kid` against the JWKS published at `jwksUrl`.
|
|
230
|
+
*
|
|
231
|
+
* H-9 remediation: previously only the schema hash was verified; the
|
|
232
|
+
* `sig.signature` and `sig.kid` fields were carried but never checked,
|
|
233
|
+
* allowing a hostile MCP server to substitute a fake tool schema whose
|
|
234
|
+
* hash matched a maliciously crafted `schema_hash` without needing the
|
|
235
|
+
* signing key.
|
|
220
236
|
*/
|
|
221
237
|
interface SignedToolDefinition {
|
|
222
238
|
name: string;
|
|
@@ -230,10 +246,22 @@ interface ToolSignatureMap {
|
|
|
230
246
|
kid?: string | null;
|
|
231
247
|
};
|
|
232
248
|
}
|
|
233
|
-
|
|
249
|
+
interface VerifyToolSignaturesOptions {
|
|
250
|
+
/**
|
|
251
|
+
* JWKS URL to verify ES256 signatures against. Must be https.
|
|
252
|
+
* Defaults to `https://api.truealter.com/.well-known/alter-keys.json`.
|
|
253
|
+
* When a tool's `signature` field is null/absent, the tool is marked
|
|
254
|
+
* valid-on-hash (legacy path) and a `warn_no_signature: true` flag is
|
|
255
|
+
* included in the result.
|
|
256
|
+
*/
|
|
257
|
+
jwksUrl?: string;
|
|
258
|
+
fetch?: typeof fetch;
|
|
259
|
+
}
|
|
260
|
+
declare function verifyToolSignatures(tools: SignedToolDefinition[], signatures: ToolSignatureMap, opts?: VerifyToolSignaturesOptions): Promise<{
|
|
234
261
|
tool: string;
|
|
235
262
|
valid: boolean;
|
|
236
263
|
reason?: string;
|
|
264
|
+
warn_no_signature?: boolean;
|
|
237
265
|
}[]>;
|
|
238
266
|
/**
|
|
239
267
|
* Fetch the ALTER public key set. Cached in-process for five minutes.
|
|
@@ -369,12 +397,21 @@ interface X402ClientOptions {
|
|
|
369
397
|
networks?: string[];
|
|
370
398
|
/** Permitted assets. Defaults to `['USDC']`. */
|
|
371
399
|
assets?: string[];
|
|
400
|
+
/**
|
|
401
|
+
* Known recipient addresses. Defaults to {@link DEFAULT_RECIPIENT_ALLOWLIST}.
|
|
402
|
+
* Passing a list here *replaces* the default — include the ALTER canonical
|
|
403
|
+
* address if you still want it accepted.
|
|
404
|
+
*
|
|
405
|
+
* An empty array disables the check entirely (not recommended for production).
|
|
406
|
+
*/
|
|
407
|
+
recipientAllowlist?: string[];
|
|
372
408
|
}
|
|
373
409
|
declare class X402Client {
|
|
374
410
|
private readonly signer?;
|
|
375
411
|
private readonly maxPerQuery?;
|
|
376
412
|
private readonly networks;
|
|
377
413
|
private readonly assets;
|
|
414
|
+
private readonly recipientAllowlist;
|
|
378
415
|
constructor(opts?: X402ClientOptions);
|
|
379
416
|
/**
|
|
380
417
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -415,7 +452,7 @@ interface MCPClientOptions {
|
|
|
415
452
|
/** Optional x402 client for automatic premium tool payment. */
|
|
416
453
|
x402?: X402Client;
|
|
417
454
|
/**
|
|
418
|
-
*
|
|
455
|
+
* ES256 per-invocation signing. When present, every `tools/call` is
|
|
419
456
|
* ES256-signed and submitted with the `Mcp-Invocation-Signature`
|
|
420
457
|
* header. The public half of `privateKey` MUST have been
|
|
421
458
|
* registered via `POST /api/v1/agents/keys` against the same API
|
|
@@ -1595,11 +1632,17 @@ declare function sha256(bytes: string | Buffer): string;
|
|
|
1595
1632
|
* deterministic ordering is worth the tiny blocking cost.
|
|
1596
1633
|
*/
|
|
1597
1634
|
|
|
1635
|
+
interface CfAccessCredentials {
|
|
1636
|
+
clientId: string;
|
|
1637
|
+
clientSecret: string;
|
|
1638
|
+
}
|
|
1598
1639
|
interface WireOptions {
|
|
1599
1640
|
/** Override the endpoint written into every client config. Defaults to DEFAULT_ENDPOINT. */
|
|
1600
1641
|
endpoint?: string;
|
|
1601
1642
|
/** Optional API key written into `headers['X-ALTER-API-Key']` for each target. */
|
|
1602
1643
|
apiKey?: string;
|
|
1644
|
+
/** CF Access service token credentials. Auto-read from ~/.config/alter/cf-access.env when absent. */
|
|
1645
|
+
cfAccess?: CfAccessCredentials;
|
|
1603
1646
|
/** Restrict to a subset of client ids. Default: every detected client. */
|
|
1604
1647
|
only?: readonly ClientId[];
|
|
1605
1648
|
/** Skip any client whose probe said "not installed" even if the caller passed it via `only`. */
|
|
@@ -1624,15 +1667,12 @@ declare function unwire(): UnwireReport;
|
|
|
1624
1667
|
* @truealter/sdk — alter_homepage MCP tool types
|
|
1625
1668
|
*
|
|
1626
1669
|
* Wire-format types for the user-authored, externally-queryable identity
|
|
1627
|
-
* homepage surface
|
|
1628
|
-
* alter-internal Session 54.
|
|
1670
|
+
* homepage surface.
|
|
1629
1671
|
*
|
|
1630
|
-
* Tool name note:
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* companion docs may still say "portfolio" — the wire-format and
|
|
1635
|
-
* tool-name-on-server are `homepage`.
|
|
1672
|
+
* Tool name note: the wire-format and server-side tool name are
|
|
1673
|
+
* `alter_homepage`. The name `alter_portfolio` is reserved by the
|
|
1674
|
+
* verified-attestations tool in `mcp-alter` (a different concept), so
|
|
1675
|
+
* homepage types ship under `homepage`.
|
|
1636
1676
|
*
|
|
1637
1677
|
* Wire-format rule: every field name matches the JSON Schema property
|
|
1638
1678
|
* name exactly (snake_case). These are passed straight into JSON-RPC
|
|
@@ -1690,7 +1730,7 @@ interface HomepageManifest {
|
|
|
1690
1730
|
*/
|
|
1691
1731
|
opener?: HomepageField<string>;
|
|
1692
1732
|
/**
|
|
1693
|
-
* Composed-glyph string (from typed primitives
|
|
1733
|
+
* Composed-glyph string (from typed primitives). The
|
|
1694
1734
|
* sigil is a string of renderer-recognised primitive references —
|
|
1695
1735
|
* not raw glyph codes — so different consumers can render the same
|
|
1696
1736
|
* sigil distinctly. Provenance is `declared` for user-composed,
|
|
@@ -1774,8 +1814,7 @@ interface HomepageOutput {
|
|
|
1774
1814
|
type HomepageCallerVertical = "workplace" | "education" | "personal" | "civic" | "agent" | "unknown";
|
|
1775
1815
|
/** Maximum sizes from the spec. SDK consumers can use these to validate
|
|
1776
1816
|
* input before sending. Mirrored from
|
|
1777
|
-
* `docs/technical/alter-portfolio-manifest-v1.md` (forthcoming)
|
|
1778
|
-
* the proposed-D-CUST-PORTFOLIO-1 DR. */
|
|
1817
|
+
* `docs/technical/alter-portfolio-manifest-v1.md` (forthcoming). */
|
|
1779
1818
|
declare const HOMEPAGE_LIMITS: {
|
|
1780
1819
|
readonly whoami_max_chars: 240;
|
|
1781
1820
|
readonly opener_max_chars: 280;
|
|
@@ -1784,13 +1823,11 @@ declare const HOMEPAGE_LIMITS: {
|
|
|
1784
1823
|
};
|
|
1785
1824
|
|
|
1786
1825
|
/**
|
|
1787
|
-
* @truealter/sdk — theme pack types
|
|
1826
|
+
* @truealter/sdk — theme pack types
|
|
1788
1827
|
*
|
|
1789
1828
|
* Wire-format types for ALTER theme packs and `themes.lock` composition
|
|
1790
1829
|
* manifests. The full specification lives in
|
|
1791
|
-
* `docs/technical/alter-theme-pack-spec-v1.md
|
|
1792
|
-
* (with threat model F1–F10) lives in
|
|
1793
|
-
* `.repos/internal/02-Technical-Strategy/alter-theme-packs-architecture-spike.md`.
|
|
1830
|
+
* `docs/technical/alter-theme-pack-spec-v1.md`.
|
|
1794
1831
|
*
|
|
1795
1832
|
* These types describe the on-the-wire shape of theme manifests as they
|
|
1796
1833
|
* are produced by `alter theme install`, persisted to `themes.lock`,
|
|
@@ -1930,7 +1967,7 @@ interface ThemesLockV1 {
|
|
|
1930
1967
|
* Input arguments for the `theme_share` MCP tool. Sharing emits a 5:1
|
|
1931
1968
|
* return event to the sharer (recognition credit + pack citation) and
|
|
1932
1969
|
* to the recipient (discovery signal). Implementation lives in
|
|
1933
|
-
* `mcp-alter
|
|
1970
|
+
* `mcp-alter`.
|
|
1934
1971
|
*/
|
|
1935
1972
|
interface ThemeShareInput {
|
|
1936
1973
|
/** Recipient ~handle. */
|
package/dist/index.d.ts
CHANGED
|
@@ -130,6 +130,7 @@ interface ProvenanceEnvelope {
|
|
|
130
130
|
}
|
|
131
131
|
interface ProvenancePayload {
|
|
132
132
|
iss: string;
|
|
133
|
+
aud?: string | string[];
|
|
133
134
|
iat: number;
|
|
134
135
|
exp: number;
|
|
135
136
|
purpose: string;
|
|
@@ -196,6 +197,13 @@ interface VerifyProvenanceOptions {
|
|
|
196
197
|
* recommended for production use.
|
|
197
198
|
*/
|
|
198
199
|
expectedIss?: string;
|
|
200
|
+
/**
|
|
201
|
+
* Expected `aud` claim. When provided and non-empty, the token's `aud`
|
|
202
|
+
* claim must contain this value (RFC 7519 §4.1.3). Pass an empty string
|
|
203
|
+
* to disable the check. Defaults to no check (undefined) for backward
|
|
204
|
+
* compatibility — callers SHOULD supply this in production environments.
|
|
205
|
+
*/
|
|
206
|
+
expectedAud?: string;
|
|
199
207
|
}
|
|
200
208
|
/**
|
|
201
209
|
* Verify a provenance JWS token against ALTER's published JWKS.
|
|
@@ -216,7 +224,15 @@ declare function verifyProvenance(envelope: ProvenanceEnvelope | string, opts?:
|
|
|
216
224
|
*
|
|
217
225
|
* ALTER signs each tool's input schema at startup and exposes the
|
|
218
226
|
* signatures via the MCP `tools/list` `_meta.signatures` map. This helper
|
|
219
|
-
* checks that each tool's schema hash matches the signed value
|
|
227
|
+
* checks that each tool's schema hash matches the signed value, AND
|
|
228
|
+
* cryptographically verifies the ES256 JWS signature carried in
|
|
229
|
+
* `sig.signature` / `sig.kid` against the JWKS published at `jwksUrl`.
|
|
230
|
+
*
|
|
231
|
+
* H-9 remediation: previously only the schema hash was verified; the
|
|
232
|
+
* `sig.signature` and `sig.kid` fields were carried but never checked,
|
|
233
|
+
* allowing a hostile MCP server to substitute a fake tool schema whose
|
|
234
|
+
* hash matched a maliciously crafted `schema_hash` without needing the
|
|
235
|
+
* signing key.
|
|
220
236
|
*/
|
|
221
237
|
interface SignedToolDefinition {
|
|
222
238
|
name: string;
|
|
@@ -230,10 +246,22 @@ interface ToolSignatureMap {
|
|
|
230
246
|
kid?: string | null;
|
|
231
247
|
};
|
|
232
248
|
}
|
|
233
|
-
|
|
249
|
+
interface VerifyToolSignaturesOptions {
|
|
250
|
+
/**
|
|
251
|
+
* JWKS URL to verify ES256 signatures against. Must be https.
|
|
252
|
+
* Defaults to `https://api.truealter.com/.well-known/alter-keys.json`.
|
|
253
|
+
* When a tool's `signature` field is null/absent, the tool is marked
|
|
254
|
+
* valid-on-hash (legacy path) and a `warn_no_signature: true` flag is
|
|
255
|
+
* included in the result.
|
|
256
|
+
*/
|
|
257
|
+
jwksUrl?: string;
|
|
258
|
+
fetch?: typeof fetch;
|
|
259
|
+
}
|
|
260
|
+
declare function verifyToolSignatures(tools: SignedToolDefinition[], signatures: ToolSignatureMap, opts?: VerifyToolSignaturesOptions): Promise<{
|
|
234
261
|
tool: string;
|
|
235
262
|
valid: boolean;
|
|
236
263
|
reason?: string;
|
|
264
|
+
warn_no_signature?: boolean;
|
|
237
265
|
}[]>;
|
|
238
266
|
/**
|
|
239
267
|
* Fetch the ALTER public key set. Cached in-process for five minutes.
|
|
@@ -369,12 +397,21 @@ interface X402ClientOptions {
|
|
|
369
397
|
networks?: string[];
|
|
370
398
|
/** Permitted assets. Defaults to `['USDC']`. */
|
|
371
399
|
assets?: string[];
|
|
400
|
+
/**
|
|
401
|
+
* Known recipient addresses. Defaults to {@link DEFAULT_RECIPIENT_ALLOWLIST}.
|
|
402
|
+
* Passing a list here *replaces* the default — include the ALTER canonical
|
|
403
|
+
* address if you still want it accepted.
|
|
404
|
+
*
|
|
405
|
+
* An empty array disables the check entirely (not recommended for production).
|
|
406
|
+
*/
|
|
407
|
+
recipientAllowlist?: string[];
|
|
372
408
|
}
|
|
373
409
|
declare class X402Client {
|
|
374
410
|
private readonly signer?;
|
|
375
411
|
private readonly maxPerQuery?;
|
|
376
412
|
private readonly networks;
|
|
377
413
|
private readonly assets;
|
|
414
|
+
private readonly recipientAllowlist;
|
|
378
415
|
constructor(opts?: X402ClientOptions);
|
|
379
416
|
/**
|
|
380
417
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -415,7 +452,7 @@ interface MCPClientOptions {
|
|
|
415
452
|
/** Optional x402 client for automatic premium tool payment. */
|
|
416
453
|
x402?: X402Client;
|
|
417
454
|
/**
|
|
418
|
-
*
|
|
455
|
+
* ES256 per-invocation signing. When present, every `tools/call` is
|
|
419
456
|
* ES256-signed and submitted with the `Mcp-Invocation-Signature`
|
|
420
457
|
* header. The public half of `privateKey` MUST have been
|
|
421
458
|
* registered via `POST /api/v1/agents/keys` against the same API
|
|
@@ -1595,11 +1632,17 @@ declare function sha256(bytes: string | Buffer): string;
|
|
|
1595
1632
|
* deterministic ordering is worth the tiny blocking cost.
|
|
1596
1633
|
*/
|
|
1597
1634
|
|
|
1635
|
+
interface CfAccessCredentials {
|
|
1636
|
+
clientId: string;
|
|
1637
|
+
clientSecret: string;
|
|
1638
|
+
}
|
|
1598
1639
|
interface WireOptions {
|
|
1599
1640
|
/** Override the endpoint written into every client config. Defaults to DEFAULT_ENDPOINT. */
|
|
1600
1641
|
endpoint?: string;
|
|
1601
1642
|
/** Optional API key written into `headers['X-ALTER-API-Key']` for each target. */
|
|
1602
1643
|
apiKey?: string;
|
|
1644
|
+
/** CF Access service token credentials. Auto-read from ~/.config/alter/cf-access.env when absent. */
|
|
1645
|
+
cfAccess?: CfAccessCredentials;
|
|
1603
1646
|
/** Restrict to a subset of client ids. Default: every detected client. */
|
|
1604
1647
|
only?: readonly ClientId[];
|
|
1605
1648
|
/** Skip any client whose probe said "not installed" even if the caller passed it via `only`. */
|
|
@@ -1624,15 +1667,12 @@ declare function unwire(): UnwireReport;
|
|
|
1624
1667
|
* @truealter/sdk — alter_homepage MCP tool types
|
|
1625
1668
|
*
|
|
1626
1669
|
* Wire-format types for the user-authored, externally-queryable identity
|
|
1627
|
-
* homepage surface
|
|
1628
|
-
* alter-internal Session 54.
|
|
1670
|
+
* homepage surface.
|
|
1629
1671
|
*
|
|
1630
|
-
* Tool name note:
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* companion docs may still say "portfolio" — the wire-format and
|
|
1635
|
-
* tool-name-on-server are `homepage`.
|
|
1672
|
+
* Tool name note: the wire-format and server-side tool name are
|
|
1673
|
+
* `alter_homepage`. The name `alter_portfolio` is reserved by the
|
|
1674
|
+
* verified-attestations tool in `mcp-alter` (a different concept), so
|
|
1675
|
+
* homepage types ship under `homepage`.
|
|
1636
1676
|
*
|
|
1637
1677
|
* Wire-format rule: every field name matches the JSON Schema property
|
|
1638
1678
|
* name exactly (snake_case). These are passed straight into JSON-RPC
|
|
@@ -1690,7 +1730,7 @@ interface HomepageManifest {
|
|
|
1690
1730
|
*/
|
|
1691
1731
|
opener?: HomepageField<string>;
|
|
1692
1732
|
/**
|
|
1693
|
-
* Composed-glyph string (from typed primitives
|
|
1733
|
+
* Composed-glyph string (from typed primitives). The
|
|
1694
1734
|
* sigil is a string of renderer-recognised primitive references —
|
|
1695
1735
|
* not raw glyph codes — so different consumers can render the same
|
|
1696
1736
|
* sigil distinctly. Provenance is `declared` for user-composed,
|
|
@@ -1774,8 +1814,7 @@ interface HomepageOutput {
|
|
|
1774
1814
|
type HomepageCallerVertical = "workplace" | "education" | "personal" | "civic" | "agent" | "unknown";
|
|
1775
1815
|
/** Maximum sizes from the spec. SDK consumers can use these to validate
|
|
1776
1816
|
* input before sending. Mirrored from
|
|
1777
|
-
* `docs/technical/alter-portfolio-manifest-v1.md` (forthcoming)
|
|
1778
|
-
* the proposed-D-CUST-PORTFOLIO-1 DR. */
|
|
1817
|
+
* `docs/technical/alter-portfolio-manifest-v1.md` (forthcoming). */
|
|
1779
1818
|
declare const HOMEPAGE_LIMITS: {
|
|
1780
1819
|
readonly whoami_max_chars: 240;
|
|
1781
1820
|
readonly opener_max_chars: 280;
|
|
@@ -1784,13 +1823,11 @@ declare const HOMEPAGE_LIMITS: {
|
|
|
1784
1823
|
};
|
|
1785
1824
|
|
|
1786
1825
|
/**
|
|
1787
|
-
* @truealter/sdk — theme pack types
|
|
1826
|
+
* @truealter/sdk — theme pack types
|
|
1788
1827
|
*
|
|
1789
1828
|
* Wire-format types for ALTER theme packs and `themes.lock` composition
|
|
1790
1829
|
* manifests. The full specification lives in
|
|
1791
|
-
* `docs/technical/alter-theme-pack-spec-v1.md
|
|
1792
|
-
* (with threat model F1–F10) lives in
|
|
1793
|
-
* `.repos/internal/02-Technical-Strategy/alter-theme-packs-architecture-spike.md`.
|
|
1830
|
+
* `docs/technical/alter-theme-pack-spec-v1.md`.
|
|
1794
1831
|
*
|
|
1795
1832
|
* These types describe the on-the-wire shape of theme manifests as they
|
|
1796
1833
|
* are produced by `alter theme install`, persisted to `themes.lock`,
|
|
@@ -1930,7 +1967,7 @@ interface ThemesLockV1 {
|
|
|
1930
1967
|
* Input arguments for the `theme_share` MCP tool. Sharing emits a 5:1
|
|
1931
1968
|
* return event to the sharer (recognition credit + pack citation) and
|
|
1932
1969
|
* to the recipient (discovery signal). Implementation lives in
|
|
1933
|
-
* `mcp-alter
|
|
1970
|
+
* `mcp-alter`.
|
|
1934
1971
|
*/
|
|
1935
1972
|
interface ThemeShareInput {
|
|
1936
1973
|
/** Recipient ~handle. */
|