@truealter/sdk 0.2.2 → 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 +23 -25
- package/dist/bin/alter-identity.js +881 -53
- package/dist/bin/mcp-bridge.js +179 -10
- package/dist/index.cjs +815 -81
- package/dist/index.d.cts +333 -143
- package/dist/index.d.ts +333 -143
- package/dist/index.js +747 -85
- package/package.json +11 -5
- package/dist/bin/alter-identity.js.map +0 -1
- package/dist/bin/mcp-bridge.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,156 @@
|
|
|
1
|
+
import { p256 } from '@noble/curves/p256';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
+
import { randomBytes, bytesToHex as bytesToHex$1, hexToBytes } from '@noble/hashes/utils';
|
|
1
4
|
import * as ed25519 from '@noble/ed25519';
|
|
2
5
|
import { sha512 } from '@noble/hashes/sha512';
|
|
3
|
-
import {
|
|
6
|
+
import { spawnSync } from 'child_process';
|
|
7
|
+
import { homedir, platform } from 'os';
|
|
8
|
+
import { join, resolve, dirname } from 'path';
|
|
9
|
+
import { env } from 'process';
|
|
10
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, renameSync, unlinkSync } from 'fs';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
15
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
18
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
19
|
+
}) : x)(function(x) {
|
|
20
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
21
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
22
|
+
});
|
|
23
|
+
var __esm = (fn, res) => function __init() {
|
|
24
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
25
|
+
};
|
|
26
|
+
var __export = (target, all) => {
|
|
27
|
+
for (var name in all)
|
|
28
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
29
|
+
};
|
|
30
|
+
var __copyProps = (to, from, except, desc) => {
|
|
31
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
32
|
+
for (let key of __getOwnPropNames(from))
|
|
33
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
34
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
35
|
+
}
|
|
36
|
+
return to;
|
|
37
|
+
};
|
|
38
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
39
|
+
|
|
40
|
+
// src/signing.ts
|
|
41
|
+
var signing_exports = {};
|
|
42
|
+
__export(signing_exports, {
|
|
43
|
+
canonicalArgsSha256: () => canonicalArgsSha256,
|
|
44
|
+
canonicalStringify: () => canonicalStringify,
|
|
45
|
+
loadPrivateKey: () => loadPrivateKey,
|
|
46
|
+
signInvocation: () => signInvocation
|
|
47
|
+
});
|
|
48
|
+
function canonicalStringify(value) {
|
|
49
|
+
return stringifyInner(value);
|
|
50
|
+
}
|
|
51
|
+
function stringifyInner(value) {
|
|
52
|
+
if (value === null) return "null";
|
|
53
|
+
if (value === void 0) {
|
|
54
|
+
throw new TypeError("canonicalStringify: undefined is not representable in JSON");
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
57
|
+
if (typeof value === "number") {
|
|
58
|
+
if (!Number.isFinite(value)) {
|
|
59
|
+
throw new TypeError("canonicalStringify: non-finite numbers are not representable");
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify(value);
|
|
62
|
+
}
|
|
63
|
+
if (typeof value === "string") return encodeString(value);
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return "[" + value.map((v) => stringifyInner(v)).join(",") + "]";
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === "object") {
|
|
68
|
+
const obj = value;
|
|
69
|
+
const keys = Object.keys(obj).sort();
|
|
70
|
+
return "{" + keys.map((k) => encodeString(k) + ":" + stringifyInner(obj[k])).join(",") + "}";
|
|
71
|
+
}
|
|
72
|
+
throw new TypeError(`canonicalStringify: unsupported type ${typeof value}`);
|
|
73
|
+
}
|
|
74
|
+
function encodeString(s) {
|
|
75
|
+
return JSON.stringify(s);
|
|
76
|
+
}
|
|
77
|
+
function canonicalArgsSha256(toolArgs) {
|
|
78
|
+
const canonical = canonicalStringify(toolArgs ?? {});
|
|
79
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
80
|
+
const digest = sha256(bytes);
|
|
81
|
+
return bytesToHex(digest);
|
|
82
|
+
}
|
|
83
|
+
function bytesToHex(bytes) {
|
|
84
|
+
let out = "";
|
|
85
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
86
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function base64urlEncode(bytes) {
|
|
91
|
+
const raw = typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes;
|
|
92
|
+
if (typeof Buffer !== "undefined") {
|
|
93
|
+
return Buffer.from(raw).toString("base64url");
|
|
94
|
+
}
|
|
95
|
+
let binary = "";
|
|
96
|
+
for (let i = 0; i < raw.length; i++) binary += String.fromCharCode(raw[i]);
|
|
97
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
98
|
+
}
|
|
99
|
+
function loadPrivateKey(key) {
|
|
100
|
+
if (key instanceof Uint8Array) {
|
|
101
|
+
if (key.length !== 32) {
|
|
102
|
+
throw new TypeError("ES256 raw private key must be 32 bytes.");
|
|
103
|
+
}
|
|
104
|
+
return key;
|
|
105
|
+
}
|
|
106
|
+
if (typeof key === "string" && key.includes("-----BEGIN")) {
|
|
107
|
+
const nodeCrypto = __require("crypto");
|
|
108
|
+
const keyObj = nodeCrypto.createPrivateKey({ key, format: "pem" });
|
|
109
|
+
const jwk = keyObj.export({ format: "jwk" });
|
|
110
|
+
if (jwk.crv !== "P-256" || !jwk.d) {
|
|
111
|
+
throw new TypeError("PEM is not a P-256 private key.");
|
|
112
|
+
}
|
|
113
|
+
return base64urlDecodeToBytes(jwk.d);
|
|
114
|
+
}
|
|
115
|
+
throw new TypeError("loadPrivateKey: expected Uint8Array(32) or PEM string.");
|
|
116
|
+
}
|
|
117
|
+
function base64urlDecodeToBytes(s) {
|
|
118
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
119
|
+
const b64 = (s + pad).replace(/-/g, "+").replace(/_/g, "/");
|
|
120
|
+
if (typeof Buffer !== "undefined") {
|
|
121
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
122
|
+
}
|
|
123
|
+
const binary = atob(b64);
|
|
124
|
+
const out = new Uint8Array(binary.length);
|
|
125
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
function signInvocation(toolName, toolArgs, options) {
|
|
129
|
+
const { kid, privateKey, handle } = options;
|
|
130
|
+
const nonce = options.nonce ?? base64urlEncode(randomBytes(24));
|
|
131
|
+
const iat = options.iatSeconds ?? Math.floor(Date.now() / 1e3);
|
|
132
|
+
const claims = {
|
|
133
|
+
tool: toolName,
|
|
134
|
+
args_sha256: canonicalArgsSha256(toolArgs ?? {}),
|
|
135
|
+
nonce,
|
|
136
|
+
iat,
|
|
137
|
+
iss: handle
|
|
138
|
+
};
|
|
139
|
+
const headerB64 = base64urlEncode(JSON.stringify({ alg: "ES256", kid }));
|
|
140
|
+
const payloadB64 = base64urlEncode(JSON.stringify(claims));
|
|
141
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
142
|
+
const signingBytes = new TextEncoder().encode(signingInput);
|
|
143
|
+
const dBytes = loadPrivateKey(privateKey);
|
|
144
|
+
const digest = sha256(signingBytes);
|
|
145
|
+
const sig = p256.sign(digest, dBytes, { prehash: false });
|
|
146
|
+
const sigBytes = sig.toCompactRawBytes();
|
|
147
|
+
const sigB64 = base64urlEncode(sigBytes);
|
|
148
|
+
return `${signingInput}.${sigB64}`;
|
|
149
|
+
}
|
|
150
|
+
var init_signing = __esm({
|
|
151
|
+
"src/signing.ts"() {
|
|
152
|
+
}
|
|
153
|
+
});
|
|
4
154
|
|
|
5
155
|
// src/errors.ts
|
|
6
156
|
var AlterError = class extends Error {
|
|
@@ -101,7 +251,12 @@ async function discover(domain, opts = {}) {
|
|
|
101
251
|
try {
|
|
102
252
|
const dnsHit = await tryDns(host);
|
|
103
253
|
if (dnsHit) {
|
|
104
|
-
const
|
|
254
|
+
const parsed = validateDiscoveredUrl(dnsHit, "dns");
|
|
255
|
+
const result = {
|
|
256
|
+
url: parsed.toString().replace(/\/$/, ""),
|
|
257
|
+
transport: "streamable-http",
|
|
258
|
+
source: "dns"
|
|
259
|
+
};
|
|
105
260
|
if (cache) _cache.set(host, result);
|
|
106
261
|
return result;
|
|
107
262
|
}
|
|
@@ -142,6 +297,28 @@ function normaliseDomain(input) {
|
|
|
142
297
|
if (!host) throw new AlterDiscoveryError(`Empty domain: "${input}"`);
|
|
143
298
|
return host;
|
|
144
299
|
}
|
|
300
|
+
function validateDiscoveredUrl(url, source) {
|
|
301
|
+
let parsed;
|
|
302
|
+
try {
|
|
303
|
+
parsed = new URL(url);
|
|
304
|
+
} catch {
|
|
305
|
+
throw new AlterDiscoveryError(`${source}: malformed URL ${url}`);
|
|
306
|
+
}
|
|
307
|
+
if (parsed.protocol !== "https:") {
|
|
308
|
+
throw new AlterDiscoveryError(
|
|
309
|
+
`${source}: non-https MCP endpoint rejected (got ${parsed.protocol}//${parsed.hostname})`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (parsed.username || parsed.password) {
|
|
313
|
+
throw new AlterDiscoveryError(
|
|
314
|
+
`${source}: MCP endpoint must not contain userinfo (user:pass@host)`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (!parsed.hostname) {
|
|
318
|
+
throw new AlterDiscoveryError(`${source}: MCP endpoint missing hostname`);
|
|
319
|
+
}
|
|
320
|
+
return parsed;
|
|
321
|
+
}
|
|
145
322
|
async function tryDns(host) {
|
|
146
323
|
let resolveTxt;
|
|
147
324
|
try {
|
|
@@ -202,14 +379,17 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
|
|
|
202
379
|
if (file === "mcp.json") {
|
|
203
380
|
const remotes = doc.remotes || [];
|
|
204
381
|
const remote = remotes.find((r) => r.transportType === "streamable-http" || r.transportType === "http");
|
|
205
|
-
const
|
|
206
|
-
if (!
|
|
207
|
-
|
|
382
|
+
const rawUrl = remote?.url || doc.url;
|
|
383
|
+
if (!rawUrl) return null;
|
|
384
|
+
const parsed = validateDiscoveredUrl(rawUrl, "mcp.json");
|
|
385
|
+
return { url: parsed.toString().replace(/\/$/, ""), transport: "streamable-http", source: "mcp.json", raw: doc };
|
|
208
386
|
}
|
|
209
387
|
const mcpHost = doc.mcp;
|
|
210
388
|
if (!mcpHost) return null;
|
|
389
|
+
const normalised = ensureMcpPath(mcpHost);
|
|
390
|
+
validateDiscoveredUrl(normalised, "alter.json");
|
|
211
391
|
return {
|
|
212
|
-
url:
|
|
392
|
+
url: normalised,
|
|
213
393
|
transport: "streamable-http",
|
|
214
394
|
source: "alter.json",
|
|
215
395
|
publicKey: doc.pk,
|
|
@@ -255,11 +435,14 @@ var X402Client = class {
|
|
|
255
435
|
if (!this.assets.has(envelope.asset)) {
|
|
256
436
|
throw new AlterError("PAYMENT_REQUIRED", `asset ${envelope.asset} not permitted by client policy`);
|
|
257
437
|
}
|
|
258
|
-
if (this.maxPerQuery !== void 0
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
438
|
+
if (this.maxPerQuery !== void 0) {
|
|
439
|
+
const amt = Number(envelope.amount);
|
|
440
|
+
if (!Number.isFinite(amt) || amt < 0 || amt > this.maxPerQuery) {
|
|
441
|
+
throw new AlterError(
|
|
442
|
+
"PAYMENT_REQUIRED",
|
|
443
|
+
`quote ${envelope.amount} ${envelope.asset} exceeds maxPerQuery ${this.maxPerQuery}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
263
446
|
}
|
|
264
447
|
if (!this.signer) {
|
|
265
448
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
@@ -317,6 +500,7 @@ var MCPClient = class {
|
|
|
317
500
|
maxRetries;
|
|
318
501
|
clientInfo;
|
|
319
502
|
x402;
|
|
503
|
+
signing;
|
|
320
504
|
requestCounter = 0;
|
|
321
505
|
initialised = false;
|
|
322
506
|
constructor(opts = {}) {
|
|
@@ -327,6 +511,7 @@ var MCPClient = class {
|
|
|
327
511
|
this.maxRetries = opts.maxRetries ?? 2;
|
|
328
512
|
this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
|
|
329
513
|
this.x402 = opts.x402;
|
|
514
|
+
this.signing = opts.signing;
|
|
330
515
|
}
|
|
331
516
|
/**
|
|
332
517
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
@@ -413,6 +598,7 @@ var MCPClient = class {
|
|
|
413
598
|
method
|
|
414
599
|
};
|
|
415
600
|
if (params !== void 0) payload.params = params;
|
|
601
|
+
const signatureHeader = this.buildSignatureHeader(method, params);
|
|
416
602
|
let attempt = 0;
|
|
417
603
|
let lastErr = null;
|
|
418
604
|
while (attempt <= this.maxRetries) {
|
|
@@ -423,7 +609,7 @@ var MCPClient = class {
|
|
|
423
609
|
try {
|
|
424
610
|
resp = await this.fetchImpl(this.endpoint, {
|
|
425
611
|
method: "POST",
|
|
426
|
-
headers: this.buildHeaders(),
|
|
612
|
+
headers: this.buildHeaders(signatureHeader),
|
|
427
613
|
body: JSON.stringify(payload),
|
|
428
614
|
signal: controller.signal
|
|
429
615
|
});
|
|
@@ -450,7 +636,8 @@ var MCPClient = class {
|
|
|
450
636
|
throw new AlterPaymentRequired(this.guessToolName(payload), envelope);
|
|
451
637
|
}
|
|
452
638
|
if (resp.status === 429) {
|
|
453
|
-
const
|
|
639
|
+
const rawRetryAfter = Number(resp.headers.get("Retry-After") ?? 60);
|
|
640
|
+
const retryAfter = Number.isFinite(rawRetryAfter) && rawRetryAfter >= 0 ? Math.min(rawRetryAfter, 300) : 60;
|
|
454
641
|
if (attempt > this.maxRetries) {
|
|
455
642
|
throw new AlterRateLimited(`HTTP 429 on ${method}`, retryAfter);
|
|
456
643
|
}
|
|
@@ -486,7 +673,7 @@ var MCPClient = class {
|
|
|
486
673
|
}
|
|
487
674
|
throw lastErr ?? new AlterNetworkError(`MCP ${method}: exhausted retries`);
|
|
488
675
|
}
|
|
489
|
-
buildHeaders() {
|
|
676
|
+
buildHeaders(extra) {
|
|
490
677
|
const headers = {
|
|
491
678
|
"Content-Type": "application/json",
|
|
492
679
|
Accept: "application/json",
|
|
@@ -494,8 +681,27 @@ var MCPClient = class {
|
|
|
494
681
|
};
|
|
495
682
|
if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
|
|
496
683
|
if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
|
|
684
|
+
if (extra) Object.assign(headers, extra);
|
|
497
685
|
return headers;
|
|
498
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Produce the `Mcp-Invocation-Signature` header for a `tools/call`
|
|
689
|
+
* payload, when signing is configured. Returns `undefined` when no
|
|
690
|
+
* signing key is attached or the method is not `tools/call`.
|
|
691
|
+
*/
|
|
692
|
+
buildSignatureHeader(method, params) {
|
|
693
|
+
if (!this.signing) return void 0;
|
|
694
|
+
if (method !== "tools/call") return void 0;
|
|
695
|
+
const p = params;
|
|
696
|
+
if (!p?.name) return void 0;
|
|
697
|
+
const { signInvocation: signInvocation2 } = (init_signing(), __toCommonJS(signing_exports));
|
|
698
|
+
const headerValue = signInvocation2(p.name, p.arguments ?? {}, {
|
|
699
|
+
kid: this.signing.kid,
|
|
700
|
+
privateKey: this.signing.privateKey,
|
|
701
|
+
handle: this.signing.handle
|
|
702
|
+
});
|
|
703
|
+
return { "Mcp-Invocation-Signature": headerValue };
|
|
704
|
+
}
|
|
499
705
|
async extractPaymentEnvelope(resp) {
|
|
500
706
|
const headerValue = resp.headers.get("X-402-Payment") ?? resp.headers.get("x-402-payment");
|
|
501
707
|
if (headerValue) {
|
|
@@ -538,8 +744,8 @@ function generateKeypair() {
|
|
|
538
744
|
const privateKey = randomBytes(32);
|
|
539
745
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
540
746
|
return {
|
|
541
|
-
privateKey: bytesToHex(privateKey),
|
|
542
|
-
publicKey: bytesToHex(publicKey),
|
|
747
|
+
privateKey: bytesToHex$1(privateKey),
|
|
748
|
+
publicKey: bytesToHex$1(publicKey),
|
|
543
749
|
did: encodeDid(publicKey)
|
|
544
750
|
};
|
|
545
751
|
}
|
|
@@ -551,7 +757,7 @@ function keypairFromPrivateKey(privateKeyHex) {
|
|
|
551
757
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
552
758
|
return {
|
|
553
759
|
privateKey: privateKeyHex,
|
|
554
|
-
publicKey: bytesToHex(publicKey),
|
|
760
|
+
publicKey: bytesToHex$1(publicKey),
|
|
555
761
|
did: encodeDid(publicKey)
|
|
556
762
|
};
|
|
557
763
|
}
|
|
@@ -559,7 +765,7 @@ async function sign(privateKeyHex, message) {
|
|
|
559
765
|
const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
560
766
|
const privateKey = hexToBytes(privateKeyHex);
|
|
561
767
|
const sig = await ed25519.signAsync(msgBytes, privateKey);
|
|
562
|
-
return bytesToHex(sig);
|
|
768
|
+
return bytesToHex$1(sig);
|
|
563
769
|
}
|
|
564
770
|
async function verify(publicKeyHex, signatureHex, message) {
|
|
565
771
|
try {
|
|
@@ -571,14 +777,14 @@ async function verify(publicKeyHex, signatureHex, message) {
|
|
|
571
777
|
}
|
|
572
778
|
function encodeDid(publicKey) {
|
|
573
779
|
const bytes = typeof publicKey === "string" ? hexToBytes(publicKey) : publicKey;
|
|
574
|
-
return `ed25519:${
|
|
780
|
+
return `ed25519:${base64urlEncode2(bytes)}`;
|
|
575
781
|
}
|
|
576
782
|
function decodeDid(did) {
|
|
577
783
|
const ed25519Match = did.match(/^ed25519:(.+)$/);
|
|
578
784
|
if (ed25519Match) return base64urlDecode(ed25519Match[1]);
|
|
579
785
|
throw new Error(`Unrecognised DID encoding: ${did}`);
|
|
580
786
|
}
|
|
581
|
-
function
|
|
787
|
+
function base64urlEncode2(bytes) {
|
|
582
788
|
let b64;
|
|
583
789
|
if (typeof Buffer !== "undefined") {
|
|
584
790
|
b64 = Buffer.from(bytes).toString("base64");
|
|
@@ -603,6 +809,8 @@ function base64urlDecode(input) {
|
|
|
603
809
|
// src/provenance.ts
|
|
604
810
|
var _jwksCache = /* @__PURE__ */ new Map();
|
|
605
811
|
var JWKS_TTL_MS = 5 * 60 * 1e3;
|
|
812
|
+
var JWKS_MAX_BYTES = 64 * 1024;
|
|
813
|
+
var JWKS_CACHE_MAX_ENTRIES = 32;
|
|
606
814
|
var DEFAULT_VERIFY_AT_ALLOWLIST = Object.freeze([
|
|
607
815
|
"api.truealter.com",
|
|
608
816
|
"mcp.truealter.com"
|
|
@@ -712,7 +920,8 @@ async function fetchPublicKeys(jwksUrl, fetchImpl = fetch) {
|
|
|
712
920
|
return fetchJwks(jwksUrl, fetchImpl);
|
|
713
921
|
}
|
|
714
922
|
async function fetchJwks(url, fetchImpl) {
|
|
715
|
-
const
|
|
923
|
+
const cacheKey = jwksCacheKey(url);
|
|
924
|
+
const cached = _jwksCache.get(cacheKey);
|
|
716
925
|
if (cached && Date.now() - cached.fetched < JWKS_TTL_MS) return cached.jwks;
|
|
717
926
|
let resp;
|
|
718
927
|
try {
|
|
@@ -729,13 +938,45 @@ async function fetchJwks(url, fetchImpl) {
|
|
|
729
938
|
);
|
|
730
939
|
}
|
|
731
940
|
if (!resp.ok) throw new AlterNetworkError(`${url} \u2192 HTTP ${resp.status}`);
|
|
732
|
-
const
|
|
941
|
+
const contentLength = resp.headers.get("content-length");
|
|
942
|
+
if (contentLength !== null) {
|
|
943
|
+
const n = Number.parseInt(contentLength, 10);
|
|
944
|
+
if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
|
|
945
|
+
throw new AlterProvenanceError(
|
|
946
|
+
`${url} \u2192 JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const body = await resp.text();
|
|
951
|
+
if (body.length > JWKS_MAX_BYTES) {
|
|
952
|
+
throw new AlterProvenanceError(
|
|
953
|
+
`${url} \u2192 JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
let doc;
|
|
957
|
+
try {
|
|
958
|
+
doc = JSON.parse(body);
|
|
959
|
+
} catch (err) {
|
|
960
|
+
throw new AlterProvenanceError(`invalid JWKS at ${url}: ${err.message}`);
|
|
961
|
+
}
|
|
733
962
|
if (!doc || !Array.isArray(doc.keys)) {
|
|
734
963
|
throw new AlterProvenanceError(`invalid JWKS at ${url}`);
|
|
735
964
|
}
|
|
736
|
-
_jwksCache.
|
|
965
|
+
if (_jwksCache.size >= JWKS_CACHE_MAX_ENTRIES && !_jwksCache.has(cacheKey)) {
|
|
966
|
+
const oldest = _jwksCache.keys().next().value;
|
|
967
|
+
if (oldest !== void 0) _jwksCache.delete(oldest);
|
|
968
|
+
}
|
|
969
|
+
_jwksCache.set(cacheKey, { fetched: Date.now(), jwks: doc });
|
|
737
970
|
return doc;
|
|
738
971
|
}
|
|
972
|
+
function jwksCacheKey(url) {
|
|
973
|
+
try {
|
|
974
|
+
const parsed = new URL(url);
|
|
975
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
976
|
+
} catch {
|
|
977
|
+
return url;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
739
980
|
function resolveVerifyAt(verifyAt, allowlist = DEFAULT_VERIFY_AT_ALLOWLIST) {
|
|
740
981
|
if (typeof verifyAt !== "string" || verifyAt.length === 0) {
|
|
741
982
|
throw new Error("verify_at must be a non-empty string");
|
|
@@ -758,6 +999,9 @@ function resolveVerifyAt(verifyAt, allowlist = DEFAULT_VERIFY_AT_ALLOWLIST) {
|
|
|
758
999
|
if (parsed.protocol !== "https:") {
|
|
759
1000
|
throw new Error(`verify_at must be https: ${verifyAt}`);
|
|
760
1001
|
}
|
|
1002
|
+
if (parsed.username || parsed.password) {
|
|
1003
|
+
throw new Error(`verify_at must not contain userinfo: ${verifyAt}`);
|
|
1004
|
+
}
|
|
761
1005
|
const host = parsed.hostname.toLowerCase();
|
|
762
1006
|
const allowed = allowlist.some((h) => h.toLowerCase() === host);
|
|
763
1007
|
if (!allowed) {
|
|
@@ -838,6 +1082,15 @@ var AlterClient = class {
|
|
|
838
1082
|
await this.mcp.initialize();
|
|
839
1083
|
}
|
|
840
1084
|
// ── Free tier ────────────────────────────────────────────────────────
|
|
1085
|
+
/** First handshake — confirms the connection, returns trust tier and tool counts. */
|
|
1086
|
+
async helloAgent() {
|
|
1087
|
+
return this.mcp.callTool("hello_agent", {});
|
|
1088
|
+
}
|
|
1089
|
+
/** Resolve a ~handle (e.g. ~drew) to its canonical form and kind. No auth required. */
|
|
1090
|
+
async resolveHandle(args) {
|
|
1091
|
+
const payload = typeof args === "string" ? { query: args } : args;
|
|
1092
|
+
return this.mcp.callTool("alter_resolve_handle", payload);
|
|
1093
|
+
}
|
|
841
1094
|
/** Verify a person is registered with ALTER (handle or id). */
|
|
842
1095
|
async verify(handleOrId, claims) {
|
|
843
1096
|
const args = handleOrId.includes("@") ? { candidate_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
|
|
@@ -874,12 +1127,6 @@ var AlterClient = class {
|
|
|
874
1127
|
async getCompetencies(args) {
|
|
875
1128
|
return this.mcp.callTool("get_competencies", args);
|
|
876
1129
|
}
|
|
877
|
-
async createIdentityStub(args) {
|
|
878
|
-
return this.mcp.callTool("create_identity_stub", args);
|
|
879
|
-
}
|
|
880
|
-
async submitContext(args) {
|
|
881
|
-
return this.mcp.callTool("submit_context", args);
|
|
882
|
-
}
|
|
883
1130
|
async searchIdentities(args) {
|
|
884
1131
|
return this.mcp.callTool("search_identities", args);
|
|
885
1132
|
}
|
|
@@ -904,9 +1151,6 @@ var AlterClient = class {
|
|
|
904
1151
|
async getPrivacyBudget(args) {
|
|
905
1152
|
return this.mcp.callTool("get_privacy_budget", args);
|
|
906
1153
|
}
|
|
907
|
-
async disputeAttestation(args) {
|
|
908
|
-
return this.mcp.callTool("dispute_attestation", args);
|
|
909
|
-
}
|
|
910
1154
|
// ── Golden Thread ────────────────────────────────────────────────────
|
|
911
1155
|
async goldenThreadStatus() {
|
|
912
1156
|
return this.mcp.callTool("golden_thread_status", {});
|
|
@@ -942,18 +1186,6 @@ var AlterClient = class {
|
|
|
942
1186
|
async generateMatchNarrative(args, opts) {
|
|
943
1187
|
return this.mcp.callTool("generate_match_narrative", args, opts);
|
|
944
1188
|
}
|
|
945
|
-
async submitBatchContext(args, opts) {
|
|
946
|
-
return this.mcp.callTool("submit_batch_context", args, opts);
|
|
947
|
-
}
|
|
948
|
-
async submitStructuredProfile(args, opts) {
|
|
949
|
-
return this.mcp.callTool("submit_structured_profile", args, opts);
|
|
950
|
-
}
|
|
951
|
-
async submitSocialLinks(args, opts) {
|
|
952
|
-
return this.mcp.callTool("submit_social_links", args, opts);
|
|
953
|
-
}
|
|
954
|
-
async attestDomain(args, opts) {
|
|
955
|
-
return this.mcp.callTool("attest_domain", args, opts);
|
|
956
|
-
}
|
|
957
1189
|
async getSideQuestGraph(args, opts) {
|
|
958
1190
|
return this.mcp.callTool("get_side_quest_graph", args, opts);
|
|
959
1191
|
}
|
|
@@ -1021,6 +1253,9 @@ var AlterClient = class {
|
|
|
1021
1253
|
}
|
|
1022
1254
|
};
|
|
1023
1255
|
|
|
1256
|
+
// src/index.ts
|
|
1257
|
+
init_signing();
|
|
1258
|
+
|
|
1024
1259
|
// src/adapters/generic-mcp.ts
|
|
1025
1260
|
function generateGenericMcpConfig(opts = {}) {
|
|
1026
1261
|
const serverName = opts.serverName ?? "alter";
|
|
@@ -1045,8 +1280,465 @@ function generateCursorConfig(opts = {}) {
|
|
|
1045
1280
|
return generateGenericMcpConfig({ serverName: "alter", ...opts });
|
|
1046
1281
|
}
|
|
1047
1282
|
|
|
1283
|
+
// src/adapters/claude-desktop.ts
|
|
1284
|
+
function generateClaudeDesktopConfig(opts = {}) {
|
|
1285
|
+
const serverName = opts.serverName ?? "alter";
|
|
1286
|
+
const bridgeCommand = opts.bridgeCommand ?? "alter-mcp-bridge";
|
|
1287
|
+
const env2 = {};
|
|
1288
|
+
env2.ALTER_MCP_ENDPOINT = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1289
|
+
if (opts.apiKey) env2.ALTER_API_KEY = opts.apiKey;
|
|
1290
|
+
const entry = {
|
|
1291
|
+
command: bridgeCommand,
|
|
1292
|
+
env: env2,
|
|
1293
|
+
description: "ALTER Identity \u2014 psychometric identity field for AI agents"
|
|
1294
|
+
};
|
|
1295
|
+
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
1296
|
+
entry.args = [...opts.extraArgs];
|
|
1297
|
+
}
|
|
1298
|
+
return { mcpServers: { [serverName]: entry } };
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/meta.ts
|
|
1302
|
+
var SDK_NAME = "@truealter/sdk";
|
|
1303
|
+
var SDK_VERSION = "0.3.0";
|
|
1304
|
+
var HOME = homedir();
|
|
1305
|
+
var PLAT = platform();
|
|
1306
|
+
function appData() {
|
|
1307
|
+
return env.APPDATA ?? join(HOME, "AppData", "Roaming");
|
|
1308
|
+
}
|
|
1309
|
+
function xdgConfig() {
|
|
1310
|
+
return env.XDG_CONFIG_HOME ?? join(HOME, ".config");
|
|
1311
|
+
}
|
|
1312
|
+
function macAppSupport() {
|
|
1313
|
+
return join(HOME, "Library", "Application Support");
|
|
1314
|
+
}
|
|
1315
|
+
function claudeDesktopConfigPath() {
|
|
1316
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Claude", "claude_desktop_config.json");
|
|
1317
|
+
if (PLAT === "win32") return join(appData(), "Claude", "claude_desktop_config.json");
|
|
1318
|
+
return join(xdgConfig(), "Claude", "claude_desktop_config.json");
|
|
1319
|
+
}
|
|
1320
|
+
function claudeDesktopDir() {
|
|
1321
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Claude");
|
|
1322
|
+
if (PLAT === "win32") return join(appData(), "Claude");
|
|
1323
|
+
return join(xdgConfig(), "Claude");
|
|
1324
|
+
}
|
|
1325
|
+
function vscodeConfigPath() {
|
|
1326
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Code", "User", "mcp.json");
|
|
1327
|
+
if (PLAT === "win32") return join(appData(), "Code", "User", "mcp.json");
|
|
1328
|
+
return join(xdgConfig(), "Code", "User", "mcp.json");
|
|
1329
|
+
}
|
|
1330
|
+
function vscodeDir() {
|
|
1331
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Code", "User");
|
|
1332
|
+
if (PLAT === "win32") return join(appData(), "Code", "User");
|
|
1333
|
+
return join(xdgConfig(), "Code", "User");
|
|
1334
|
+
}
|
|
1335
|
+
var cursorDir = join(HOME, ".cursor");
|
|
1336
|
+
var cursorConfigPath = join(cursorDir, "mcp.json");
|
|
1337
|
+
var claudeCodeProbeDir = join(HOME, ".claude");
|
|
1338
|
+
var CLAUDE_CODE = {
|
|
1339
|
+
id: "claude-code",
|
|
1340
|
+
label: "Claude Code",
|
|
1341
|
+
configPath: null,
|
|
1342
|
+
probeDir: claudeCodeProbeDir,
|
|
1343
|
+
rootKey: "mcpServers"
|
|
1344
|
+
};
|
|
1345
|
+
var CURSOR = {
|
|
1346
|
+
id: "cursor",
|
|
1347
|
+
label: "Cursor",
|
|
1348
|
+
configPath: cursorConfigPath,
|
|
1349
|
+
probeDir: cursorDir,
|
|
1350
|
+
rootKey: "mcpServers"
|
|
1351
|
+
};
|
|
1352
|
+
var CLAUDE_DESKTOP = {
|
|
1353
|
+
id: "claude-desktop",
|
|
1354
|
+
label: "Claude Desktop",
|
|
1355
|
+
configPath: claudeDesktopConfigPath(),
|
|
1356
|
+
probeDir: claudeDesktopDir(),
|
|
1357
|
+
rootKey: "mcpServers"
|
|
1358
|
+
};
|
|
1359
|
+
var VSCODE = {
|
|
1360
|
+
id: "vscode",
|
|
1361
|
+
label: "VS Code",
|
|
1362
|
+
configPath: vscodeConfigPath(),
|
|
1363
|
+
probeDir: vscodeDir(),
|
|
1364
|
+
// VS Code's user-scoped mcp.json uses `servers`, not `mcpServers`.
|
|
1365
|
+
rootKey: "servers"
|
|
1366
|
+
};
|
|
1367
|
+
var ALL_CLIENTS = [CLAUDE_CODE, CURSOR, CLAUDE_DESKTOP, VSCODE];
|
|
1368
|
+
function alterConfigDir() {
|
|
1369
|
+
return join(xdgConfig(), "alter");
|
|
1370
|
+
}
|
|
1371
|
+
function wireStatePath() {
|
|
1372
|
+
return join(alterConfigDir(), "wire-state.json");
|
|
1373
|
+
}
|
|
1374
|
+
function probeClaudeCode() {
|
|
1375
|
+
try {
|
|
1376
|
+
const result = spawnSync("claude", ["--version"], {
|
|
1377
|
+
encoding: "utf8",
|
|
1378
|
+
shell: process.platform === "win32",
|
|
1379
|
+
timeout: 5e3
|
|
1380
|
+
});
|
|
1381
|
+
if (result.error) {
|
|
1382
|
+
return {
|
|
1383
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1384
|
+
installed: false,
|
|
1385
|
+
reason: `claude binary not on PATH (${result.error.message})`
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
if (result.status === 0) {
|
|
1389
|
+
return {
|
|
1390
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1391
|
+
installed: true,
|
|
1392
|
+
version: result.stdout.trim() || void 0,
|
|
1393
|
+
reason: "claude --version returned 0"
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
return {
|
|
1397
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1398
|
+
installed: false,
|
|
1399
|
+
reason: `claude --version exited ${String(result.status)}`
|
|
1400
|
+
};
|
|
1401
|
+
} catch (err) {
|
|
1402
|
+
return {
|
|
1403
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1404
|
+
installed: false,
|
|
1405
|
+
reason: err.message
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function probeByDir(id) {
|
|
1410
|
+
const client = ALL_CLIENTS.find((c) => c.id === id);
|
|
1411
|
+
if (!client) throw new Error(`unknown client id: ${id}`);
|
|
1412
|
+
const installed = existsSync(client.probeDir);
|
|
1413
|
+
return {
|
|
1414
|
+
client,
|
|
1415
|
+
installed,
|
|
1416
|
+
reason: installed ? `found ${client.probeDir}` : `no directory at ${client.probeDir}`
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function probeAll() {
|
|
1420
|
+
return [
|
|
1421
|
+
probeClaudeCode(),
|
|
1422
|
+
probeByDir("cursor"),
|
|
1423
|
+
probeByDir("claude-desktop"),
|
|
1424
|
+
probeByDir("vscode")
|
|
1425
|
+
];
|
|
1426
|
+
}
|
|
1427
|
+
var SYNC_PREFIXES = [
|
|
1428
|
+
// iCloud Drive — both the new and legacy mounts.
|
|
1429
|
+
"Library/Mobile Documents/com~apple~CloudDocs",
|
|
1430
|
+
"iCloud Drive",
|
|
1431
|
+
// OneDrive variants Microsoft ships across editions.
|
|
1432
|
+
"OneDrive",
|
|
1433
|
+
"OneDrive - ",
|
|
1434
|
+
// Dropbox standard + enterprise mounts.
|
|
1435
|
+
"Dropbox",
|
|
1436
|
+
"Dropbox (",
|
|
1437
|
+
// Google Drive (ALTER does not integrate with Google; still refuse).
|
|
1438
|
+
"Google Drive",
|
|
1439
|
+
"GoogleDrive",
|
|
1440
|
+
"CloudStorage/GoogleDrive",
|
|
1441
|
+
// Box, pCloud, Sync.com, MEGA — high-signal names worth refusing.
|
|
1442
|
+
"Box Sync",
|
|
1443
|
+
"pCloud Drive",
|
|
1444
|
+
"Sync.com",
|
|
1445
|
+
"MEGAsync"
|
|
1446
|
+
];
|
|
1447
|
+
function detectSyncedVolume(path) {
|
|
1448
|
+
const absolute = resolve(path);
|
|
1449
|
+
const normalised = platform() === "win32" ? absolute.replace(/\\/g, "/") : absolute;
|
|
1450
|
+
for (const prefix of SYNC_PREFIXES) {
|
|
1451
|
+
if (normalised.includes(`/${prefix}/`) || normalised.includes(`/${prefix}`)) {
|
|
1452
|
+
return { refused: true, matchedPrefix: prefix, resolvedPath: absolute };
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
var WIRE_STATE_VERSION = 1;
|
|
1458
|
+
function readWireState() {
|
|
1459
|
+
const path = wireStatePath();
|
|
1460
|
+
if (!existsSync(path)) return null;
|
|
1461
|
+
try {
|
|
1462
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1463
|
+
if (parsed.version !== WIRE_STATE_VERSION) {
|
|
1464
|
+
throw new Error(
|
|
1465
|
+
`wire-state.json version ${String(parsed.version)} is not supported by this SDK (expected ${WIRE_STATE_VERSION})`
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
return parsed;
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
throw new Error(`failed to parse wire-state.json: ${err.message}`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function writeWireState(state) {
|
|
1474
|
+
const path = wireStatePath();
|
|
1475
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
1476
|
+
writeFileSync(path, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
1477
|
+
}
|
|
1478
|
+
function sha2562(bytes) {
|
|
1479
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
1480
|
+
}
|
|
1481
|
+
function atomicJsonMerge(opts) {
|
|
1482
|
+
const { path, timestamp, merge, idempotent = true } = opts;
|
|
1483
|
+
const tmpPath = `${path}.alter-tmp-${timestamp}`;
|
|
1484
|
+
const backupPath = `${path}.alter-backup-${timestamp}`;
|
|
1485
|
+
let existed = false;
|
|
1486
|
+
let preBytes = null;
|
|
1487
|
+
let parsed = {};
|
|
1488
|
+
if (existsSync(path)) {
|
|
1489
|
+
existed = true;
|
|
1490
|
+
preBytes = readFileSync(path, "utf8");
|
|
1491
|
+
if (preBytes.trim().length > 0) {
|
|
1492
|
+
try {
|
|
1493
|
+
parsed = JSON.parse(preBytes);
|
|
1494
|
+
} catch (err) {
|
|
1495
|
+
throw new Error(
|
|
1496
|
+
`refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter-identity wire\`.`
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
|
|
1500
|
+
throw new Error(`refusing to wire ${path}: existing JSON root is not an object`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
const merged = merge(parsed);
|
|
1505
|
+
const serialised = JSON.stringify(merged, null, 2) + "\n";
|
|
1506
|
+
if (idempotent && preBytes !== null && preBytes === serialised) {
|
|
1507
|
+
return {
|
|
1508
|
+
path,
|
|
1509
|
+
backupPath: null,
|
|
1510
|
+
preSha256: sha2562(preBytes),
|
|
1511
|
+
postSha256: sha2562(preBytes),
|
|
1512
|
+
noop: true
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1516
|
+
writeFileSync(tmpPath, serialised, { mode: 384 });
|
|
1517
|
+
try {
|
|
1518
|
+
if (existed) copyFileSync(path, backupPath);
|
|
1519
|
+
renameSync(tmpPath, path);
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
try {
|
|
1522
|
+
unlinkSync(tmpPath);
|
|
1523
|
+
} catch {
|
|
1524
|
+
}
|
|
1525
|
+
throw err;
|
|
1526
|
+
}
|
|
1527
|
+
return {
|
|
1528
|
+
path,
|
|
1529
|
+
backupPath: existed ? backupPath : null,
|
|
1530
|
+
preSha256: preBytes === null ? null : sha2562(preBytes),
|
|
1531
|
+
postSha256: sha2562(serialised),
|
|
1532
|
+
noop: false
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function restoreFromBackup(path, backupPath) {
|
|
1536
|
+
if (backupPath === null) {
|
|
1537
|
+
if (existsSync(path)) unlinkSync(path);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (!existsSync(backupPath)) {
|
|
1541
|
+
throw new Error(`cannot restore ${path}: backup missing at ${backupPath}`);
|
|
1542
|
+
}
|
|
1543
|
+
renameSync(backupPath, path);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/wire/index.ts
|
|
1547
|
+
var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
|
|
1548
|
+
var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1549
|
+
function clientById(id) {
|
|
1550
|
+
const hit = ALL_CLIENTS.find((c) => c.id === id);
|
|
1551
|
+
if (!hit) throw new Error(`unknown client id: ${id}`);
|
|
1552
|
+
return hit;
|
|
1553
|
+
}
|
|
1554
|
+
function wire(opts = {}) {
|
|
1555
|
+
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1556
|
+
const apiKey = opts.apiKey;
|
|
1557
|
+
const probes = probeAll();
|
|
1558
|
+
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1559
|
+
const ts = TIMESTAMP();
|
|
1560
|
+
const targets = [];
|
|
1561
|
+
for (const id of selection) {
|
|
1562
|
+
const probe = id === "claude-code" ? probeClaudeCode() : probeByDir(id);
|
|
1563
|
+
if (!probe.installed && opts.skipMissing !== false) {
|
|
1564
|
+
targets.push({
|
|
1565
|
+
client: id,
|
|
1566
|
+
method: id === "claude-code" ? "cli" : "file",
|
|
1567
|
+
status: "skipped",
|
|
1568
|
+
...id === "claude-code" ? { command: "" } : { path: clientById(id).configPath ?? "", backupPath: null, rootKey: clientById(id).rootKey, serverName: "alter", preSha256: null, postSha256: "" },
|
|
1569
|
+
reason: probe.reason
|
|
1570
|
+
});
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
if (id === "claude-code") {
|
|
1575
|
+
targets.push(wireClaudeCode({ endpoint, apiKey }));
|
|
1576
|
+
} else {
|
|
1577
|
+
targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
|
|
1578
|
+
}
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
const message = err.message;
|
|
1581
|
+
targets.push({
|
|
1582
|
+
client: id,
|
|
1583
|
+
method: id === "claude-code" ? "cli" : "file",
|
|
1584
|
+
status: "failed",
|
|
1585
|
+
...id === "claude-code" ? { command: "" } : { path: clientById(id).configPath ?? "", backupPath: null, rootKey: clientById(id).rootKey, serverName: "alter", preSha256: null, postSha256: "" },
|
|
1586
|
+
reason: message
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
const state = {
|
|
1591
|
+
version: 1,
|
|
1592
|
+
sdkVersion: SDK_VERSION,
|
|
1593
|
+
writtenAt: ISO_NOW(),
|
|
1594
|
+
endpoint,
|
|
1595
|
+
targets
|
|
1596
|
+
};
|
|
1597
|
+
writeWireState(state);
|
|
1598
|
+
return { state, probes };
|
|
1599
|
+
}
|
|
1600
|
+
function wireFileTarget(args) {
|
|
1601
|
+
const client = clientById(args.id);
|
|
1602
|
+
if (!client.configPath) {
|
|
1603
|
+
throw new Error(`client ${client.id} has no file-based config path`);
|
|
1604
|
+
}
|
|
1605
|
+
const sync = detectSyncedVolume(client.configPath);
|
|
1606
|
+
if (sync) {
|
|
1607
|
+
throw new Error(
|
|
1608
|
+
`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.`
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey });
|
|
1612
|
+
const rootKey = client.rootKey;
|
|
1613
|
+
const serverName = "alter";
|
|
1614
|
+
const result = atomicJsonMerge({
|
|
1615
|
+
path: client.configPath,
|
|
1616
|
+
timestamp: args.timestamp,
|
|
1617
|
+
merge: (existing) => {
|
|
1618
|
+
const bucket = existing[rootKey] ?? {};
|
|
1619
|
+
const source = entry.mcpServers.alter;
|
|
1620
|
+
return {
|
|
1621
|
+
...existing,
|
|
1622
|
+
[rootKey]: {
|
|
1623
|
+
...bucket,
|
|
1624
|
+
[serverName]: source
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
return {
|
|
1630
|
+
client: args.id,
|
|
1631
|
+
method: "file",
|
|
1632
|
+
status: result.noop ? "already-wired" : "written",
|
|
1633
|
+
path: result.path,
|
|
1634
|
+
backupPath: result.backupPath,
|
|
1635
|
+
rootKey,
|
|
1636
|
+
serverName,
|
|
1637
|
+
preSha256: result.preSha256,
|
|
1638
|
+
postSha256: result.postSha256
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function wireClaudeCode(args) {
|
|
1642
|
+
const cmd = "claude";
|
|
1643
|
+
const argList = [
|
|
1644
|
+
"mcp",
|
|
1645
|
+
"add",
|
|
1646
|
+
"--scope",
|
|
1647
|
+
"user",
|
|
1648
|
+
"--transport",
|
|
1649
|
+
"http",
|
|
1650
|
+
"alter",
|
|
1651
|
+
args.endpoint
|
|
1652
|
+
];
|
|
1653
|
+
if (args.apiKey) {
|
|
1654
|
+
argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
|
|
1655
|
+
}
|
|
1656
|
+
const full = `${cmd} ${argList.join(" ")}`;
|
|
1657
|
+
const run = spawnSync(cmd, argList, {
|
|
1658
|
+
encoding: "utf8",
|
|
1659
|
+
shell: process.platform === "win32",
|
|
1660
|
+
timeout: 1e4
|
|
1661
|
+
});
|
|
1662
|
+
if (run.error) {
|
|
1663
|
+
return {
|
|
1664
|
+
client: "claude-code",
|
|
1665
|
+
method: "cli",
|
|
1666
|
+
status: "failed",
|
|
1667
|
+
command: full,
|
|
1668
|
+
stdout: run.stdout,
|
|
1669
|
+
stderr: run.stderr,
|
|
1670
|
+
reason: run.error.message
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
const stderr = (run.stderr ?? "").toLowerCase();
|
|
1674
|
+
const alreadyExists = stderr.includes("already exists") || stderr.includes("already configured");
|
|
1675
|
+
if (run.status === 0) {
|
|
1676
|
+
return { client: "claude-code", method: "cli", status: "written", command: full, stdout: run.stdout, stderr: run.stderr };
|
|
1677
|
+
}
|
|
1678
|
+
if (alreadyExists) {
|
|
1679
|
+
return { client: "claude-code", method: "cli", status: "already-wired", command: full, stdout: run.stdout, stderr: run.stderr };
|
|
1680
|
+
}
|
|
1681
|
+
return {
|
|
1682
|
+
client: "claude-code",
|
|
1683
|
+
method: "cli",
|
|
1684
|
+
status: "failed",
|
|
1685
|
+
command: full,
|
|
1686
|
+
stdout: run.stdout,
|
|
1687
|
+
stderr: run.stderr,
|
|
1688
|
+
reason: `claude mcp add exited ${String(run.status)}`
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function unwire() {
|
|
1692
|
+
const state = readWireState();
|
|
1693
|
+
const undone = [];
|
|
1694
|
+
if (!state || state.targets.length === 0) {
|
|
1695
|
+
return { state, undone };
|
|
1696
|
+
}
|
|
1697
|
+
for (const target of state.targets) {
|
|
1698
|
+
try {
|
|
1699
|
+
if (target.method === "file") {
|
|
1700
|
+
if (target.status === "written") {
|
|
1701
|
+
restoreFromBackup(target.path, target.backupPath);
|
|
1702
|
+
undone.push({ client: target.client, action: target.backupPath ? "restored" : "removed" });
|
|
1703
|
+
} else {
|
|
1704
|
+
undone.push({ client: target.client, action: "skipped", reason: `target status was ${target.status}` });
|
|
1705
|
+
}
|
|
1706
|
+
} else if (target.method === "cli") {
|
|
1707
|
+
if (target.status === "written") {
|
|
1708
|
+
const run = spawnSync("claude", ["mcp", "remove", "--scope", "user", "alter"], {
|
|
1709
|
+
encoding: "utf8",
|
|
1710
|
+
shell: process.platform === "win32",
|
|
1711
|
+
timeout: 1e4
|
|
1712
|
+
});
|
|
1713
|
+
if (run.error) {
|
|
1714
|
+
undone.push({ client: target.client, action: "failed", reason: run.error.message });
|
|
1715
|
+
} else if (run.status === 0) {
|
|
1716
|
+
undone.push({ client: target.client, action: "cli-removed" });
|
|
1717
|
+
} else {
|
|
1718
|
+
undone.push({ client: target.client, action: "failed", reason: `claude mcp remove exited ${String(run.status)}` });
|
|
1719
|
+
}
|
|
1720
|
+
} else {
|
|
1721
|
+
undone.push({ client: target.client, action: "skipped", reason: `target status was ${target.status}` });
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
} catch (err) {
|
|
1725
|
+
undone.push({ client: target.client, action: "failed", reason: err.message });
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
writeWireState({
|
|
1729
|
+
version: 1,
|
|
1730
|
+
sdkVersion: state.sdkVersion,
|
|
1731
|
+
writtenAt: ISO_NOW(),
|
|
1732
|
+
endpoint: state.endpoint,
|
|
1733
|
+
targets: []
|
|
1734
|
+
});
|
|
1735
|
+
return { state, undone };
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1048
1738
|
// src/types.ts
|
|
1049
1739
|
var FREE_TOOL_NAMES = [
|
|
1740
|
+
"hello_agent",
|
|
1741
|
+
"alter_resolve_handle",
|
|
1050
1742
|
"list_archetypes",
|
|
1051
1743
|
"verify_identity",
|
|
1052
1744
|
"initiate_assessment",
|
|
@@ -1054,8 +1746,6 @@ var FREE_TOOL_NAMES = [
|
|
|
1054
1746
|
"get_profile",
|
|
1055
1747
|
"query_matches",
|
|
1056
1748
|
"get_competencies",
|
|
1057
|
-
"create_identity_stub",
|
|
1058
|
-
"submit_context",
|
|
1059
1749
|
"search_identities",
|
|
1060
1750
|
"get_identity_earnings",
|
|
1061
1751
|
"get_network_stats",
|
|
@@ -1066,7 +1756,6 @@ var FREE_TOOL_NAMES = [
|
|
|
1066
1756
|
"get_agent_trust_tier",
|
|
1067
1757
|
"get_agent_portfolio",
|
|
1068
1758
|
"get_privacy_budget",
|
|
1069
|
-
"dispute_attestation",
|
|
1070
1759
|
"golden_thread_status",
|
|
1071
1760
|
"begin_golden_thread",
|
|
1072
1761
|
"complete_knot",
|
|
@@ -1080,15 +1769,13 @@ var PREMIUM_TOOL_NAMES = [
|
|
|
1080
1769
|
"compute_belonging",
|
|
1081
1770
|
"get_match_recommendations",
|
|
1082
1771
|
"generate_match_narrative",
|
|
1083
|
-
"submit_batch_context",
|
|
1084
|
-
"submit_structured_profile",
|
|
1085
|
-
"submit_social_links",
|
|
1086
|
-
"attest_domain",
|
|
1087
1772
|
"get_side_quest_graph",
|
|
1088
1773
|
"query_graph_similarity"
|
|
1089
1774
|
];
|
|
1090
1775
|
var TOOL_TIERS = {
|
|
1091
1776
|
// L0 (free)
|
|
1777
|
+
hello_agent: 0,
|
|
1778
|
+
alter_resolve_handle: 0,
|
|
1092
1779
|
list_archetypes: 0,
|
|
1093
1780
|
verify_identity: 0,
|
|
1094
1781
|
initiate_assessment: 0,
|
|
@@ -1096,9 +1783,7 @@ var TOOL_TIERS = {
|
|
|
1096
1783
|
get_profile: 0,
|
|
1097
1784
|
query_matches: 0,
|
|
1098
1785
|
get_competencies: 0,
|
|
1099
|
-
|
|
1100
|
-
submit_context: 1,
|
|
1101
|
-
search_identities: 1,
|
|
1786
|
+
search_identities: 0,
|
|
1102
1787
|
get_identity_earnings: 0,
|
|
1103
1788
|
get_network_stats: 0,
|
|
1104
1789
|
recommend_tool: 0,
|
|
@@ -1106,8 +1791,6 @@ var TOOL_TIERS = {
|
|
|
1106
1791
|
check_assessment_status: 0,
|
|
1107
1792
|
get_earning_summary: 0,
|
|
1108
1793
|
get_privacy_budget: 0,
|
|
1109
|
-
dispute_attestation: 0,
|
|
1110
|
-
// Free tools not present in upstream TOOL_TIERS — default to 0
|
|
1111
1794
|
get_agent_trust_tier: 0,
|
|
1112
1795
|
get_agent_portfolio: 0,
|
|
1113
1796
|
golden_thread_status: 0,
|
|
@@ -1118,12 +1801,8 @@ var TOOL_TIERS = {
|
|
|
1118
1801
|
// L1
|
|
1119
1802
|
assess_traits: 1,
|
|
1120
1803
|
get_trait_snapshot: 1,
|
|
1121
|
-
submit_structured_profile: 1,
|
|
1122
|
-
submit_social_links: 1,
|
|
1123
|
-
attest_domain: 1,
|
|
1124
1804
|
// L2
|
|
1125
1805
|
get_full_trait_vector: 2,
|
|
1126
|
-
submit_batch_context: 2,
|
|
1127
1806
|
get_side_quest_graph: 2,
|
|
1128
1807
|
// L3
|
|
1129
1808
|
query_graph_similarity: 3,
|
|
@@ -1135,6 +1814,8 @@ var TOOL_TIERS = {
|
|
|
1135
1814
|
};
|
|
1136
1815
|
var TOOL_COSTS = {
|
|
1137
1816
|
// L0 free
|
|
1817
|
+
hello_agent: 0,
|
|
1818
|
+
alter_resolve_handle: 0,
|
|
1138
1819
|
list_archetypes: 0,
|
|
1139
1820
|
verify_identity: 0,
|
|
1140
1821
|
initiate_assessment: 0,
|
|
@@ -1142,7 +1823,6 @@ var TOOL_COSTS = {
|
|
|
1142
1823
|
get_profile: 0,
|
|
1143
1824
|
query_matches: 0,
|
|
1144
1825
|
get_competencies: 0,
|
|
1145
|
-
create_identity_stub: 0,
|
|
1146
1826
|
search_identities: 0,
|
|
1147
1827
|
get_identity_earnings: 0,
|
|
1148
1828
|
get_network_stats: 0,
|
|
@@ -1153,22 +1833,16 @@ var TOOL_COSTS = {
|
|
|
1153
1833
|
get_agent_trust_tier: 0,
|
|
1154
1834
|
get_agent_portfolio: 0,
|
|
1155
1835
|
get_privacy_budget: 0,
|
|
1156
|
-
dispute_attestation: 0,
|
|
1157
1836
|
golden_thread_status: 0,
|
|
1158
1837
|
begin_golden_thread: 0,
|
|
1159
1838
|
complete_knot: 0,
|
|
1160
1839
|
check_golden_thread: 0,
|
|
1161
1840
|
thread_census: 0,
|
|
1162
1841
|
// L1 ($0.005)
|
|
1163
|
-
submit_context: 5e-3,
|
|
1164
1842
|
assess_traits: 5e-3,
|
|
1165
1843
|
get_trait_snapshot: 5e-3,
|
|
1166
|
-
submit_structured_profile: 5e-3,
|
|
1167
|
-
submit_social_links: 5e-3,
|
|
1168
|
-
attest_domain: 5e-3,
|
|
1169
1844
|
// L2 ($0.01)
|
|
1170
1845
|
get_full_trait_vector: 0.01,
|
|
1171
|
-
submit_batch_context: 0.01,
|
|
1172
1846
|
get_side_quest_graph: 0.01,
|
|
1173
1847
|
// L3 ($0.025)
|
|
1174
1848
|
query_graph_similarity: 0.025,
|
|
@@ -1180,6 +1854,8 @@ var TOOL_COSTS = {
|
|
|
1180
1854
|
};
|
|
1181
1855
|
var TOOL_BLAST_RADIUS = {
|
|
1182
1856
|
// Low: read-only reference
|
|
1857
|
+
hello_agent: "low",
|
|
1858
|
+
alter_resolve_handle: "low",
|
|
1183
1859
|
list_archetypes: "low",
|
|
1184
1860
|
verify_identity: "low",
|
|
1185
1861
|
get_engagement_level: "low",
|
|
@@ -1192,13 +1868,12 @@ var TOOL_BLAST_RADIUS = {
|
|
|
1192
1868
|
begin_golden_thread: "low",
|
|
1193
1869
|
check_golden_thread: "low",
|
|
1194
1870
|
thread_census: "low",
|
|
1195
|
-
dispute_attestation: "low",
|
|
1196
1871
|
get_identity_earnings: "low",
|
|
1197
1872
|
get_identity_trust_score: "low",
|
|
1198
1873
|
initiate_assessment: "low",
|
|
1874
|
+
get_agent_trust_tier: "low",
|
|
1875
|
+
get_agent_portfolio: "low",
|
|
1199
1876
|
// Medium: writes data or searches
|
|
1200
|
-
create_identity_stub: "medium",
|
|
1201
|
-
submit_context: "medium",
|
|
1202
1877
|
search_identities: "medium",
|
|
1203
1878
|
get_profile: "medium",
|
|
1204
1879
|
query_matches: "medium",
|
|
@@ -1206,26 +1881,13 @@ var TOOL_BLAST_RADIUS = {
|
|
|
1206
1881
|
complete_knot: "medium",
|
|
1207
1882
|
assess_traits: "medium",
|
|
1208
1883
|
get_trait_snapshot: "medium",
|
|
1209
|
-
submit_structured_profile: "medium",
|
|
1210
|
-
submit_social_links: "medium",
|
|
1211
|
-
submit_batch_context: "medium",
|
|
1212
|
-
attest_domain: "medium",
|
|
1213
1884
|
// High: returns sensitive identity data or computes scores
|
|
1214
1885
|
get_full_trait_vector: "high",
|
|
1215
1886
|
compute_belonging: "high",
|
|
1216
1887
|
get_match_recommendations: "high",
|
|
1217
1888
|
generate_match_narrative: "high",
|
|
1218
1889
|
get_side_quest_graph: "high",
|
|
1219
|
-
query_graph_similarity: "high"
|
|
1220
|
-
// Tools not in upstream TOOL_BLAST_RADIUS — default to "low"
|
|
1221
|
-
get_agent_trust_tier: "low",
|
|
1222
|
-
get_agent_portfolio: "low"
|
|
1890
|
+
query_graph_similarity: "high"
|
|
1223
1891
|
};
|
|
1224
1892
|
|
|
1225
|
-
|
|
1226
|
-
var SDK_NAME = "@truealter/sdk";
|
|
1227
|
-
var SDK_VERSION = "0.1.1";
|
|
1228
|
-
|
|
1229
|
-
export { AlterAuthError, AlterClient, AlterDiscoveryError, AlterError, AlterInvalidResponse, AlterNetworkError, AlterPaymentRequired, AlterProvenanceError, AlterRateLimited, AlterTimeoutError, AlterToolError, DEFAULT_DOMAIN, DEFAULT_ENDPOINT, DEFAULT_VERIFY_AT_ALLOWLIST, FREE_TOOL_NAMES, MCPClient, MCP_PROTOCOL_VERSION, PREMIUM_TOOL_NAMES, SDK_NAME, SDK_VERSION, TOOL_BLAST_RADIUS, TOOL_COSTS, TOOL_TIERS, X402Client, base64urlDecode, base64urlEncode, clearDiscoveryCache, decodeDid, discover, encodeDid, fetchPublicKeys, generateClaudeConfig, generateCursorConfig, generateGenericMcpConfig, generateKeypair, keypairFromPrivateKey, parsePaymentHeader, resolveVerifyAt, sign, verify, verifyProvenance, verifyToolSignatures };
|
|
1230
|
-
//# sourceMappingURL=index.js.map
|
|
1231
|
-
//# sourceMappingURL=index.js.map
|
|
1893
|
+
export { ALL_CLIENTS, AlterAuthError, AlterClient, AlterDiscoveryError, AlterError, AlterInvalidResponse, AlterNetworkError, AlterPaymentRequired, AlterProvenanceError, AlterRateLimited, AlterTimeoutError, AlterToolError, CLAUDE_CODE, CLAUDE_DESKTOP, CURSOR, DEFAULT_DOMAIN, DEFAULT_ENDPOINT, DEFAULT_VERIFY_AT_ALLOWLIST, FREE_TOOL_NAMES, MCPClient, MCP_PROTOCOL_VERSION, PREMIUM_TOOL_NAMES, SDK_NAME, SDK_VERSION, TOOL_BLAST_RADIUS, TOOL_COSTS, TOOL_TIERS, VSCODE, X402Client, base64urlDecode, base64urlEncode2 as base64urlEncode, canonicalArgsSha256, canonicalStringify, clearDiscoveryCache, decodeDid, detectSyncedVolume, discover, encodeDid, fetchPublicKeys, generateClaudeConfig, generateClaudeDesktopConfig, generateCursorConfig, generateGenericMcpConfig, generateKeypair, keypairFromPrivateKey, loadPrivateKey, parsePaymentHeader, probeAll, probeByDir, probeClaudeCode, readWireState, resolveVerifyAt, sha2562 as sha256, sign, signInvocation, unwire, verify, verifyProvenance, verifyToolSignatures, wire, writeWireState };
|