@truealter/sdk 0.5.1 → 0.5.8
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 +185 -108
- package/dist/bin/mcp-bridge.js +178 -13
- package/dist/index.cjs +679 -82
- package/dist/index.d.cts +524 -149
- package/dist/index.d.ts +524 -149
- package/dist/index.js +658 -83
- package/package.json +4 -7
- package/dist/bin/alter-identity.js +0 -2306
- package/dist/mcp-bridge.js +0 -166
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { p256 } from '@noble/curves/p256';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
3
|
import { randomBytes, bytesToHex as bytesToHex$1, hexToBytes } from '@noble/hashes/utils';
|
|
4
|
-
import { createPrivateKey, createHash } from 'crypto';
|
|
4
|
+
import { createPrivateKey, createPublicKey, createHash, verify as verify$1 } from 'crypto';
|
|
5
|
+
import { statSync, readFileSync, mkdirSync, writeFileSync, chmodSync, renameSync, existsSync, copyFileSync, unlinkSync } from 'fs';
|
|
6
|
+
import { homedir, platform } from 'os';
|
|
7
|
+
import { join, dirname, resolve } from 'path';
|
|
5
8
|
import * as ed25519 from '@noble/ed25519';
|
|
6
9
|
import { sha512 } from '@noble/hashes/sha512';
|
|
7
|
-
import {
|
|
8
|
-
import { join, resolve, dirname } from 'path';
|
|
9
|
-
import { homedir, platform } from 'os';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
10
11
|
import { spawnSync } from 'child_process';
|
|
11
12
|
import { env } from 'process';
|
|
12
13
|
|
|
@@ -14,14 +15,13 @@ var __defProp = Object.defineProperty;
|
|
|
14
15
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
15
16
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
17
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
|
-
var
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
var __esm = (fn, res, err) => function __init() {
|
|
19
|
+
if (err) throw err[0];
|
|
20
|
+
try {
|
|
21
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw err = [e], e;
|
|
24
|
+
}
|
|
25
25
|
};
|
|
26
26
|
var __export = (target, all) => {
|
|
27
27
|
for (var name in all)
|
|
@@ -369,11 +369,11 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
|
|
|
369
369
|
}
|
|
370
370
|
if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
|
|
371
371
|
throw new AlterNetworkError(
|
|
372
|
-
`${url}
|
|
372
|
+
`${url} -> redirect rejected (discovery must not follow redirects; validate the server configuration)`
|
|
373
373
|
);
|
|
374
374
|
}
|
|
375
375
|
if (resp.status === 404) return null;
|
|
376
|
-
if (!resp.ok) throw new AlterNetworkError(`${url}
|
|
376
|
+
if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
|
|
377
377
|
const doc = await resp.json();
|
|
378
378
|
if (file === "mcp.json") {
|
|
379
379
|
const remotes = doc.remotes || [];
|
|
@@ -406,6 +406,309 @@ function ensureMcpPath(url) {
|
|
|
406
406
|
return url;
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
|
+
|
|
410
|
+
// src/meta.ts
|
|
411
|
+
var SDK_NAME = "@truealter/sdk";
|
|
412
|
+
var SDK_VERSION = "0.5.8" ;
|
|
413
|
+
|
|
414
|
+
// src/floor-preflight.ts
|
|
415
|
+
var MIN_VERSION_ENDPOINT = "/v1/clients/min-version";
|
|
416
|
+
var CLIENT_ID = "alter-identity";
|
|
417
|
+
var CLIENT_CHANNEL = "npm";
|
|
418
|
+
var IN_MEMORY_TTL_DEFAULT_MS = 60 * 60 * 1e3;
|
|
419
|
+
var IN_MEMORY_TTL_MIN_MS = 60 * 1e3;
|
|
420
|
+
var IN_MEMORY_TTL_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
421
|
+
var DISK_FRESH_MS = 24 * 60 * 60 * 1e3;
|
|
422
|
+
var DISK_WARN_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
423
|
+
var FETCH_TIMEOUT_MS = 4e3;
|
|
424
|
+
function computeKeyId(publicKeyPem) {
|
|
425
|
+
if (!publicKeyPem) return "00000000";
|
|
426
|
+
const pub = createPublicKey({ key: publicKeyPem, format: "pem" });
|
|
427
|
+
const jwk = pub.export({ format: "jwk" });
|
|
428
|
+
const rawBytes = Buffer.from(jwk.x, "base64url");
|
|
429
|
+
return createHash("sha256").update(rawBytes).digest("hex").slice(0, 8);
|
|
430
|
+
}
|
|
431
|
+
function canonicalJson(obj) {
|
|
432
|
+
return JSON.stringify(sortKeysDeep(obj));
|
|
433
|
+
}
|
|
434
|
+
function sortKeysDeep(value) {
|
|
435
|
+
if (Array.isArray(value)) {
|
|
436
|
+
return value.map(sortKeysDeep);
|
|
437
|
+
}
|
|
438
|
+
if (value !== null && typeof value === "object") {
|
|
439
|
+
const obj = value;
|
|
440
|
+
const sorted = {};
|
|
441
|
+
for (const k of Object.keys(obj).sort()) {
|
|
442
|
+
sorted[k] = sortKeysDeep(obj[k]);
|
|
443
|
+
}
|
|
444
|
+
return sorted;
|
|
445
|
+
}
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
var KNOWN_FLOOR_PUBLIC_KEYS = {
|
|
449
|
+
"8aa59e05": `-----BEGIN PUBLIC KEY-----
|
|
450
|
+
MCowBQYDK2VwAyEAgqw28dlniOuiTE1f4BxCPSEgMLaPtHsO8wN5RWEwEhE=
|
|
451
|
+
-----END PUBLIC KEY-----`,
|
|
452
|
+
"640f7d9a": `-----BEGIN PUBLIC KEY-----
|
|
453
|
+
MCowBQYDK2VwAyEARzvAWayDwHvZRfOZizGZe+/a7PF082WGhyMS3tx06H4=
|
|
454
|
+
-----END PUBLIC KEY-----`
|
|
455
|
+
};
|
|
456
|
+
var BelowFloorError = class extends Error {
|
|
457
|
+
name = "BelowFloorError";
|
|
458
|
+
code = "client_below_floor";
|
|
459
|
+
client_version;
|
|
460
|
+
min_version;
|
|
461
|
+
upgrade_cmd;
|
|
462
|
+
channel;
|
|
463
|
+
envelope;
|
|
464
|
+
constructor(envelope) {
|
|
465
|
+
super(envelope.error.message);
|
|
466
|
+
this.envelope = envelope;
|
|
467
|
+
this.client_version = envelope.error.client_version;
|
|
468
|
+
this.min_version = envelope.error.min_version;
|
|
469
|
+
this.upgrade_cmd = envelope.error.upgrade_cmd;
|
|
470
|
+
this.channel = envelope.error.channel;
|
|
471
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var memCache = null;
|
|
475
|
+
async function checkMinVersion(opts = {}) {
|
|
476
|
+
const apiBase = opts.apiBase ?? defaultApiBase();
|
|
477
|
+
const clientVersion = opts.clientVersion ?? SDK_VERSION;
|
|
478
|
+
const clientId = opts.clientId ?? CLIENT_ID;
|
|
479
|
+
const channel = opts.channel ?? CLIENT_CHANNEL;
|
|
480
|
+
const knownKeys = opts.knownFloorPublicKeys ?? KNOWN_FLOOR_PUBLIC_KEYS;
|
|
481
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
482
|
+
const now = opts.now ?? Date.now;
|
|
483
|
+
const cachePath = opts.diskCachePath === void 0 ? defaultDiskCachePath() : opts.diskCachePath;
|
|
484
|
+
const mem = readInMemoryCache(now);
|
|
485
|
+
if (mem) {
|
|
486
|
+
return compareAndPermit(mem, {
|
|
487
|
+
clientVersion,
|
|
488
|
+
clientId,
|
|
489
|
+
channel,
|
|
490
|
+
diagnostic: "mem-cache-hit"
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
const disk = cachePath ? readDiskCache(cachePath, knownKeys) : null;
|
|
494
|
+
const diskAgeMs = disk ? now() - disk.fetched_at_ms : Number.POSITIVE_INFINITY;
|
|
495
|
+
let fetched = null;
|
|
496
|
+
let fetchError = null;
|
|
497
|
+
if (!disk || diskAgeMs > IN_MEMORY_TTL_DEFAULT_MS) {
|
|
498
|
+
try {
|
|
499
|
+
fetched = await fetchFloorDoc(apiBase, fetchImpl, knownKeys);
|
|
500
|
+
} catch (err) {
|
|
501
|
+
fetchError = err.message ?? "fetch-error";
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (fetched) {
|
|
505
|
+
populateMemCache(fetched, now());
|
|
506
|
+
if (cachePath) writeDiskCache(cachePath, fetched, now());
|
|
507
|
+
return compareAndPermit(fetched, {
|
|
508
|
+
clientVersion,
|
|
509
|
+
clientId,
|
|
510
|
+
channel,
|
|
511
|
+
diagnostic: "fetched"
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
if (disk) {
|
|
515
|
+
populateMemCache(disk.doc, disk.fetched_at_ms);
|
|
516
|
+
if (diskAgeMs > DISK_WARN_MS) {
|
|
517
|
+
return compareAndPermit(disk.doc, {
|
|
518
|
+
clientVersion,
|
|
519
|
+
clientId,
|
|
520
|
+
channel,
|
|
521
|
+
diagnostic: "below-floor-offline-stale-or-permit",
|
|
522
|
+
warn: `floor cache is >7d old and backend unreachable (${fetchError ?? "no refresh attempted"}); permitting if above floor`
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (diskAgeMs > DISK_FRESH_MS) {
|
|
526
|
+
return compareAndPermit(disk.doc, {
|
|
527
|
+
clientVersion,
|
|
528
|
+
clientId,
|
|
529
|
+
channel,
|
|
530
|
+
diagnostic: "warn-stale-permit",
|
|
531
|
+
warn: `floor cache is ${Math.round(diskAgeMs / (60 * 60 * 1e3))}h old; refresh recommended`
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return compareAndPermit(disk.doc, {
|
|
535
|
+
clientVersion,
|
|
536
|
+
clientId,
|
|
537
|
+
channel,
|
|
538
|
+
diagnostic: "disk-cache-hit"
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
ok: true,
|
|
543
|
+
floor: null,
|
|
544
|
+
diagnostic: "no-cache-no-fetch-permit",
|
|
545
|
+
warn: `floor preflight skipped: backend unreachable (${fetchError ?? "unknown"})`
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function compareAndPermit(doc, ctx) {
|
|
549
|
+
const floor = lookupFloor(doc, ctx.clientId, ctx.channel);
|
|
550
|
+
if (!floor) {
|
|
551
|
+
return { ok: true, floor: null, diagnostic: `${ctx.diagnostic}+no-floor`, warn: ctx.warn };
|
|
552
|
+
}
|
|
553
|
+
if (compareSemver(ctx.clientVersion, floor.min_version) >= 0) {
|
|
554
|
+
return { ok: true, floor, diagnostic: ctx.diagnostic, warn: ctx.warn };
|
|
555
|
+
}
|
|
556
|
+
const envelope = {
|
|
557
|
+
error: {
|
|
558
|
+
code: "client_below_floor",
|
|
559
|
+
message: `Your ${ctx.clientId} is too old. Upgrade required.`,
|
|
560
|
+
client_version: ctx.clientVersion,
|
|
561
|
+
min_version: floor.min_version,
|
|
562
|
+
upgrade_cmd: floor.upgrade_cmd,
|
|
563
|
+
channel: ctx.channel
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
throw new BelowFloorError(envelope);
|
|
567
|
+
}
|
|
568
|
+
function lookupFloor(doc, clientId, channel) {
|
|
569
|
+
const entry = doc.floors[clientId];
|
|
570
|
+
if (!entry) return null;
|
|
571
|
+
if (isChannelFloor(entry)) return entry;
|
|
572
|
+
const exact = entry[channel];
|
|
573
|
+
if (exact) return exact;
|
|
574
|
+
const fallback = entry["unknown"];
|
|
575
|
+
if (fallback) return fallback;
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
function isChannelFloor(v) {
|
|
579
|
+
return typeof v.min_version === "string" && typeof v.upgrade_cmd === "string";
|
|
580
|
+
}
|
|
581
|
+
function compareSemver(a, b) {
|
|
582
|
+
const [aMaj, aMin, aPat, aPre] = parseSemver(a);
|
|
583
|
+
const [bMaj, bMin, bPat, bPre] = parseSemver(b);
|
|
584
|
+
if (aMaj !== bMaj) return aMaj - bMaj;
|
|
585
|
+
if (aMin !== bMin) return aMin - bMin;
|
|
586
|
+
if (aPat !== bPat) return aPat - bPat;
|
|
587
|
+
if (aPre && !bPre) return -1;
|
|
588
|
+
if (!aPre && bPre) return 1;
|
|
589
|
+
if (aPre && bPre) return aPre.localeCompare(bPre);
|
|
590
|
+
return 0;
|
|
591
|
+
}
|
|
592
|
+
function parseSemver(v) {
|
|
593
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);
|
|
594
|
+
if (!m) return [0, 0, 0, null];
|
|
595
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
|
|
596
|
+
}
|
|
597
|
+
function verifyFloorSignature(doc, keys = KNOWN_FLOOR_PUBLIC_KEYS) {
|
|
598
|
+
const pem = keys[doc.key_id];
|
|
599
|
+
if (!pem) return false;
|
|
600
|
+
if (computeKeyId(pem) !== doc.key_id) return false;
|
|
601
|
+
try {
|
|
602
|
+
const pubKeyObject = createPublicKey({ key: pem, format: "pem" });
|
|
603
|
+
const canonical = canonicalJson({
|
|
604
|
+
floors: doc.floors,
|
|
605
|
+
served_at: doc.served_at
|
|
606
|
+
});
|
|
607
|
+
return verify$1(
|
|
608
|
+
null,
|
|
609
|
+
Buffer.from(canonical, "utf-8"),
|
|
610
|
+
pubKeyObject,
|
|
611
|
+
Buffer.from(doc.signature, "hex")
|
|
612
|
+
);
|
|
613
|
+
} catch {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function fetchFloorDoc(apiBase, fetchImpl, knownKeys) {
|
|
618
|
+
const url = `${apiBase.replace(/\/+$/, "")}${MIN_VERSION_ENDPOINT}`;
|
|
619
|
+
let response;
|
|
620
|
+
try {
|
|
621
|
+
response = await fetchImpl(url, {
|
|
622
|
+
headers: {
|
|
623
|
+
accept: "application/json",
|
|
624
|
+
"X-Alter-Client-Id": CLIENT_ID,
|
|
625
|
+
"X-Alter-Client-Version": SDK_VERSION,
|
|
626
|
+
"X-Alter-Client-Channel": CLIENT_CHANNEL,
|
|
627
|
+
"User-Agent": `${SDK_NAME}/${SDK_VERSION}`
|
|
628
|
+
},
|
|
629
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
630
|
+
});
|
|
631
|
+
} catch (err) {
|
|
632
|
+
throw new Error(`network: ${err.message ?? String(err)}`);
|
|
633
|
+
}
|
|
634
|
+
if (!response.ok) throw new Error(`http-${response.status}`);
|
|
635
|
+
const body = await response.json();
|
|
636
|
+
if (!body || !body.floors || !body.signature || !body.key_id) {
|
|
637
|
+
throw new Error("malformed-floor-doc");
|
|
638
|
+
}
|
|
639
|
+
if (!verifyFloorSignature(body, knownKeys)) {
|
|
640
|
+
throw new Error("signature-invalid");
|
|
641
|
+
}
|
|
642
|
+
return body;
|
|
643
|
+
}
|
|
644
|
+
function readInMemoryCache(now) {
|
|
645
|
+
if (!memCache) return null;
|
|
646
|
+
if (now() - memCache.fetched_at_ms > memCache.ttl_ms) {
|
|
647
|
+
memCache = null;
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
return memCache.doc;
|
|
651
|
+
}
|
|
652
|
+
function populateMemCache(doc, fetched_at_ms) {
|
|
653
|
+
const ttlSec = doc.cache_ttl_seconds ?? 3600;
|
|
654
|
+
const ttlMs = Math.min(
|
|
655
|
+
Math.max(ttlSec * 1e3, IN_MEMORY_TTL_MIN_MS),
|
|
656
|
+
IN_MEMORY_TTL_MAX_MS
|
|
657
|
+
);
|
|
658
|
+
memCache = { doc, fetched_at_ms, ttl_ms: ttlMs };
|
|
659
|
+
}
|
|
660
|
+
function defaultDiskCachePath() {
|
|
661
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
662
|
+
return join(xdg, "alter", "floor-cache.json");
|
|
663
|
+
}
|
|
664
|
+
function readDiskCache(path, knownKeys) {
|
|
665
|
+
if (process.platform !== "win32") {
|
|
666
|
+
let st;
|
|
667
|
+
try {
|
|
668
|
+
st = statSync(path);
|
|
669
|
+
} catch {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
const euid = typeof process.geteuid === "function" ? process.geteuid() : st.uid;
|
|
673
|
+
if (st.uid !== euid) return null;
|
|
674
|
+
if ((st.mode & 511) !== 384) return null;
|
|
675
|
+
}
|
|
676
|
+
let raw;
|
|
677
|
+
try {
|
|
678
|
+
raw = readFileSync(path, "utf-8");
|
|
679
|
+
} catch {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
let parsed;
|
|
683
|
+
try {
|
|
684
|
+
parsed = JSON.parse(raw);
|
|
685
|
+
} catch {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
if (!parsed.doc || typeof parsed.fetched_at_ms !== "number") return null;
|
|
689
|
+
if (!verifyFloorSignature(parsed.doc, knownKeys)) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
return parsed;
|
|
693
|
+
}
|
|
694
|
+
function writeDiskCache(path, doc, now_ms) {
|
|
695
|
+
const entry = { doc, fetched_at_ms: now_ms };
|
|
696
|
+
const payload = JSON.stringify(entry);
|
|
697
|
+
try {
|
|
698
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
699
|
+
const tmp = `${path}.tmp`;
|
|
700
|
+
writeFileSync(tmp, payload, { mode: 384 });
|
|
701
|
+
try {
|
|
702
|
+
chmodSync(tmp, 384);
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
renameSync(tmp, path);
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function defaultApiBase() {
|
|
710
|
+
return process.env.ALTER_API ?? "https://api.truealter.com";
|
|
711
|
+
}
|
|
409
712
|
var X402Client = class {
|
|
410
713
|
signer;
|
|
411
714
|
maxPerQuery;
|
|
@@ -516,6 +819,9 @@ var MCPClient = class {
|
|
|
516
819
|
x402;
|
|
517
820
|
signing;
|
|
518
821
|
extraHeaders;
|
|
822
|
+
preflightHook;
|
|
823
|
+
preflightPromise = null;
|
|
824
|
+
preflightDone = false;
|
|
519
825
|
requestCounter = 0;
|
|
520
826
|
initialised = false;
|
|
521
827
|
constructor(opts = {}) {
|
|
@@ -524,17 +830,43 @@ var MCPClient = class {
|
|
|
524
830
|
this.fetchImpl = opts.fetch ?? fetch;
|
|
525
831
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
526
832
|
this.maxRetries = opts.maxRetries ?? 2;
|
|
527
|
-
this.clientInfo = opts.clientInfo ?? { name:
|
|
833
|
+
this.clientInfo = opts.clientInfo ?? { name: SDK_NAME, version: SDK_VERSION };
|
|
528
834
|
this.x402 = opts.x402;
|
|
529
835
|
this.signing = opts.signing;
|
|
530
836
|
this.extraHeaders = opts.extraHeaders;
|
|
837
|
+
this.preflightHook = opts.preflightHook;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Run the lazy version-floor preflight hook exactly once.
|
|
841
|
+
* Idempotent and serialised: concurrent callers share the same
|
|
842
|
+
* promise. Throws from the hook propagate to every concurrent caller.
|
|
843
|
+
*/
|
|
844
|
+
async runPreflight() {
|
|
845
|
+
if (this.preflightDone) return;
|
|
846
|
+
if (!this.preflightHook) {
|
|
847
|
+
this.preflightDone = true;
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (!this.preflightPromise) {
|
|
851
|
+
this.preflightPromise = this.preflightHook().then(
|
|
852
|
+
() => {
|
|
853
|
+
this.preflightDone = true;
|
|
854
|
+
},
|
|
855
|
+
(err) => {
|
|
856
|
+
this.preflightPromise = null;
|
|
857
|
+
throw err;
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
await this.preflightPromise;
|
|
531
862
|
}
|
|
532
863
|
/**
|
|
533
864
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
534
|
-
* id. Idempotent
|
|
865
|
+
* id. Idempotent: safe to call multiple times.
|
|
535
866
|
*/
|
|
536
867
|
async initialize() {
|
|
537
868
|
if (this.initialised) return null;
|
|
869
|
+
await this.runPreflight();
|
|
538
870
|
const result = await this.rpc("initialize", {
|
|
539
871
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
540
872
|
capabilities: {},
|
|
@@ -627,7 +959,14 @@ var MCPClient = class {
|
|
|
627
959
|
method: "POST",
|
|
628
960
|
headers: this.buildHeaders(signatureHeader),
|
|
629
961
|
body: JSON.stringify(payload),
|
|
630
|
-
signal: controller.signal
|
|
962
|
+
signal: controller.signal,
|
|
963
|
+
// Prevent fetch from silently following 3xx redirects. When
|
|
964
|
+
// Cloudflare Access credentials are absent or expired the edge
|
|
965
|
+
// returns HTTP 302 → CF Access login page (text/html). Without
|
|
966
|
+
// this option undici follows the redirect, lands on a 200 HTML
|
|
967
|
+
// body, and resp.json() throws the opaque "invalid JSON body"
|
|
968
|
+
// error that was surfaced as "MCP <method>: invalid JSON body".
|
|
969
|
+
redirect: "manual"
|
|
631
970
|
});
|
|
632
971
|
} catch (err) {
|
|
633
972
|
clearTimeout(timer);
|
|
@@ -644,6 +983,19 @@ var MCPClient = class {
|
|
|
644
983
|
clearTimeout(timer);
|
|
645
984
|
const sessionHeader = resp.headers.get("Mcp-Session-Id");
|
|
646
985
|
if (sessionHeader) this.sessionId = sessionHeader;
|
|
986
|
+
if (resp.status >= 300 && resp.status < 400) {
|
|
987
|
+
const location = resp.headers.get("Location") ?? "";
|
|
988
|
+
const isAuthRedirect = location.includes("cloudflareaccess.com") || location.includes("/cdn-cgi/access/") || !location.startsWith("/") && !location.startsWith(new URL(this.endpoint).origin);
|
|
989
|
+
if (isAuthRedirect) {
|
|
990
|
+
throw new AlterAuthError(
|
|
991
|
+
`MCP ${method}: Cloudflare Access blocked the request (session expired or credentials missing). Run \`alter login\` to re-authenticate.`,
|
|
992
|
+
302
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
throw new AlterNetworkError(
|
|
996
|
+
`MCP ${method}: unexpected redirect ${resp.status} to ${location || "(no Location)"}`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
647
999
|
if (resp.status === 401 || resp.status === 403) {
|
|
648
1000
|
throw new AlterAuthError(`HTTP ${resp.status} on ${method}`, resp.status);
|
|
649
1001
|
}
|
|
@@ -668,11 +1020,56 @@ var MCPClient = class {
|
|
|
668
1020
|
const body2 = await safeText(resp);
|
|
669
1021
|
throw new AlterError("NETWORK", `HTTP ${resp.status} on ${method}: ${body2.slice(0, 200)}`);
|
|
670
1022
|
}
|
|
1023
|
+
const contentType = resp.headers.get("Content-Type") ?? "";
|
|
1024
|
+
const isHtml = contentType.includes("text/html");
|
|
1025
|
+
const isSse = contentType.includes("text/event-stream");
|
|
1026
|
+
if (isHtml || isSse) {
|
|
1027
|
+
if (isSse) {
|
|
1028
|
+
const rawText = await safeText(resp);
|
|
1029
|
+
const dataLine = rawText.split("\n").find((l) => l.startsWith("data:"));
|
|
1030
|
+
if (dataLine) {
|
|
1031
|
+
const jsonPart = dataLine.slice("data:".length).trim();
|
|
1032
|
+
try {
|
|
1033
|
+
const parsed = JSON.parse(jsonPart);
|
|
1034
|
+
if (parsed.error) {
|
|
1035
|
+
const code = parsed.error.code;
|
|
1036
|
+
const message = parsed.error.message ?? `MCP ${method} error`;
|
|
1037
|
+
throw new AlterToolError(this.guessToolName(payload), message, code);
|
|
1038
|
+
}
|
|
1039
|
+
return parsed.result;
|
|
1040
|
+
} catch (parseErr) {
|
|
1041
|
+
if (parseErr instanceof AlterError) throw parseErr;
|
|
1042
|
+
throw new AlterInvalidResponse(
|
|
1043
|
+
`MCP ${method}: could not parse SSE data frame as JSON`,
|
|
1044
|
+
parseErr
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
throw new AlterInvalidResponse(
|
|
1049
|
+
`MCP ${method}: received text/event-stream response with no data: frame`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
const excerpt = (await safeText(resp)).slice(0, 300);
|
|
1053
|
+
const looksLikeLoginPage = excerpt.toLowerCase().includes("cloudflareaccess") || excerpt.toLowerCase().includes("access denied") || excerpt.toLowerCase().includes("<title>");
|
|
1054
|
+
if (looksLikeLoginPage) {
|
|
1055
|
+
throw new AlterAuthError(
|
|
1056
|
+
`MCP ${method}: received an HTML login page instead of JSON (Content-Type: ${contentType}). Run \`alter login\` to re-authenticate.`,
|
|
1057
|
+
200
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
throw new AlterInvalidResponse(
|
|
1061
|
+
`MCP ${method}: unexpected Content-Type "${contentType}" (expected application/json). Body excerpt: ${excerpt.slice(0, 120)}`
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
671
1064
|
let body;
|
|
672
1065
|
try {
|
|
673
1066
|
body = await resp.json();
|
|
674
1067
|
} catch (err) {
|
|
675
|
-
|
|
1068
|
+
const hint = contentType ? ` (Content-Type: ${contentType})` : "";
|
|
1069
|
+
throw new AlterInvalidResponse(
|
|
1070
|
+
`MCP ${method}: failed to parse JSON response${hint}. The server may have returned a non-JSON body. Run \`alter login\` if the session is expired.`,
|
|
1071
|
+
err
|
|
1072
|
+
);
|
|
676
1073
|
}
|
|
677
1074
|
if (body.error) {
|
|
678
1075
|
const code = body.error.code;
|
|
@@ -693,8 +1090,11 @@ var MCPClient = class {
|
|
|
693
1090
|
const headers = {
|
|
694
1091
|
...this.extraHeaders ?? {},
|
|
695
1092
|
"Content-Type": "application/json",
|
|
696
|
-
Accept: "application/json",
|
|
697
|
-
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}
|
|
1093
|
+
Accept: "application/json, text/event-stream",
|
|
1094
|
+
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`,
|
|
1095
|
+
"X-Alter-Client-Id": "alter-identity",
|
|
1096
|
+
"X-Alter-Client-Version": SDK_VERSION,
|
|
1097
|
+
"X-Alter-Client-Channel": "npm"
|
|
698
1098
|
};
|
|
699
1099
|
if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
|
|
700
1100
|
if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
|
|
@@ -971,7 +1371,7 @@ async function verifyToolSignatures(tools, signatures, opts = {}) {
|
|
|
971
1371
|
out.push({ tool: tool.name, valid: false, reason: "no signature published" });
|
|
972
1372
|
continue;
|
|
973
1373
|
}
|
|
974
|
-
const expectedHash = await sha256Hex(
|
|
1374
|
+
const expectedHash = await sha256Hex(canonicalJson2(tool.inputSchema));
|
|
975
1375
|
if (expectedHash !== sig.schema_hash) {
|
|
976
1376
|
out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
|
|
977
1377
|
continue;
|
|
@@ -1055,23 +1455,23 @@ async function fetchJwks(url, fetchImpl) {
|
|
|
1055
1455
|
}
|
|
1056
1456
|
if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
|
|
1057
1457
|
throw new AlterProvenanceError(
|
|
1058
|
-
`${url}
|
|
1458
|
+
`${url} -> redirect rejected (allowlist enforces initial URL only)`
|
|
1059
1459
|
);
|
|
1060
1460
|
}
|
|
1061
|
-
if (!resp.ok) throw new AlterNetworkError(`${url}
|
|
1461
|
+
if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
|
|
1062
1462
|
const contentLength = resp.headers.get("content-length");
|
|
1063
1463
|
if (contentLength !== null) {
|
|
1064
1464
|
const n = Number.parseInt(contentLength, 10);
|
|
1065
1465
|
if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
|
|
1066
1466
|
throw new AlterProvenanceError(
|
|
1067
|
-
`${url}
|
|
1467
|
+
`${url} -> JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
|
|
1068
1468
|
);
|
|
1069
1469
|
}
|
|
1070
1470
|
}
|
|
1071
1471
|
const body = await resp.text();
|
|
1072
1472
|
if (body.length > JWKS_MAX_BYTES) {
|
|
1073
1473
|
throw new AlterProvenanceError(
|
|
1074
|
-
`${url}
|
|
1474
|
+
`${url} -> JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
|
|
1075
1475
|
);
|
|
1076
1476
|
}
|
|
1077
1477
|
let doc;
|
|
@@ -1155,19 +1555,35 @@ async function sha256Hex(input) {
|
|
|
1155
1555
|
function toArrayBuffer(view) {
|
|
1156
1556
|
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
1157
1557
|
}
|
|
1158
|
-
function
|
|
1558
|
+
function canonicalJson2(value) {
|
|
1159
1559
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1160
1560
|
if (Array.isArray(value)) {
|
|
1161
|
-
return `[${value.map(
|
|
1561
|
+
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
1162
1562
|
}
|
|
1163
1563
|
const obj = value;
|
|
1164
1564
|
const keys = Object.keys(obj).sort();
|
|
1165
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${
|
|
1565
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson2(obj[k])}`).join(",")}}`;
|
|
1166
1566
|
}
|
|
1167
1567
|
|
|
1168
1568
|
// src/client.ts
|
|
1169
1569
|
var DEFAULT_ENDPOINT = "https://mcp.truealter.com/api/v1/mcp";
|
|
1570
|
+
var MEMBER_BRIDGE_ENDPOINT = "https://api.truealter.com/api/v1/mcp";
|
|
1170
1571
|
var DEFAULT_DOMAIN = "truealter.com";
|
|
1572
|
+
var CANONICAL_API_BASE = "https://api.truealter.com";
|
|
1573
|
+
var JWKS_WELL_KNOWN_PATH = "/.well-known/alter-keys.json";
|
|
1574
|
+
function resolveJwksUrl(opts = {}) {
|
|
1575
|
+
if (opts.jwksUrl) return opts.jwksUrl;
|
|
1576
|
+
const base = opts.apiBase ?? (typeof process !== "undefined" ? process.env?.ALTER_API : void 0) ?? originOf(opts.endpoint) ?? CANONICAL_API_BASE;
|
|
1577
|
+
return `${base.replace(/\/+$/, "")}${JWKS_WELL_KNOWN_PATH}`;
|
|
1578
|
+
}
|
|
1579
|
+
function originOf(url) {
|
|
1580
|
+
if (!url) return void 0;
|
|
1581
|
+
try {
|
|
1582
|
+
return new URL(url).origin;
|
|
1583
|
+
} catch {
|
|
1584
|
+
return void 0;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1171
1587
|
var AlterClient = class {
|
|
1172
1588
|
mcp;
|
|
1173
1589
|
x402;
|
|
@@ -1178,11 +1594,21 @@ var AlterClient = class {
|
|
|
1178
1594
|
this.options = options;
|
|
1179
1595
|
this.x402 = options.x402;
|
|
1180
1596
|
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
1181
|
-
|
|
1597
|
+
const preflightHook = options.unsafe_skipVersionCheck ? void 0 : () => checkMinVersion({
|
|
1598
|
+
apiBase: options.apiBase,
|
|
1599
|
+
knownFloorPublicKeys: options.knownFloorPublicKeys,
|
|
1600
|
+
fetchImpl: options.fetch
|
|
1601
|
+
}).then(() => void 0);
|
|
1602
|
+
this.mcp = new MCPClient({
|
|
1603
|
+
...options,
|
|
1604
|
+
endpoint,
|
|
1605
|
+
x402: options.x402,
|
|
1606
|
+
preflightHook
|
|
1607
|
+
});
|
|
1182
1608
|
}
|
|
1183
1609
|
/**
|
|
1184
1610
|
* Resolve the MCP endpoint via discovery if requested. Safe to call
|
|
1185
|
-
* multiple times
|
|
1611
|
+
* multiple times: the first successful lookup is cached.
|
|
1186
1612
|
*/
|
|
1187
1613
|
async discoverEndpoint() {
|
|
1188
1614
|
if (this.discovered) return this.discovered;
|
|
@@ -1195,7 +1621,7 @@ var AlterClient = class {
|
|
|
1195
1621
|
return this.discoveryPromise;
|
|
1196
1622
|
}
|
|
1197
1623
|
/**
|
|
1198
|
-
* Initialise the MCP session. Optional
|
|
1624
|
+
* Initialise the MCP session. Optional: every method calls
|
|
1199
1625
|
* `mcp.initialize()` lazily, but you can call this once at startup if
|
|
1200
1626
|
* you want fail-fast behaviour.
|
|
1201
1627
|
*/
|
|
@@ -1203,11 +1629,11 @@ var AlterClient = class {
|
|
|
1203
1629
|
await this.mcp.initialize();
|
|
1204
1630
|
}
|
|
1205
1631
|
// ── Free tier ────────────────────────────────────────────────────────
|
|
1206
|
-
/** First handshake
|
|
1632
|
+
/** First handshake: confirms the connection, returns trust tier and tool counts. */
|
|
1207
1633
|
async helloAgent() {
|
|
1208
1634
|
return this.mcp.callTool("hello_agent", {});
|
|
1209
1635
|
}
|
|
1210
|
-
/** Resolve a ~handle (e.g. ~
|
|
1636
|
+
/** Resolve a ~handle (e.g. ~example) to its canonical form and kind. No auth required. */
|
|
1211
1637
|
async resolveHandle(args) {
|
|
1212
1638
|
const payload = typeof args === "string" ? { query: args } : args;
|
|
1213
1639
|
return this.mcp.callTool("alter_resolve_handle", payload);
|
|
@@ -1215,7 +1641,7 @@ var AlterClient = class {
|
|
|
1215
1641
|
/** Verify a person is registered with ALTER (handle or id). */
|
|
1216
1642
|
async verify(handleOrId, claims) {
|
|
1217
1643
|
const args = handleOrId.includes("@") ? { member_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
|
|
1218
|
-
// ~handle
|
|
1644
|
+
// ~handle: server resolves these via the member_id field
|
|
1219
1645
|
{ member_id: handleOrId }
|
|
1220
1646
|
) : { member_id: handleOrId };
|
|
1221
1647
|
if (claims) args.claims = claims;
|
|
@@ -1315,8 +1741,8 @@ var AlterClient = class {
|
|
|
1315
1741
|
}
|
|
1316
1742
|
// ── Alter-to-Alter Messaging ─────────────────────────────────────────
|
|
1317
1743
|
// Wave 1: cross-handle direct messages between authenticated tilde
|
|
1318
|
-
// handles. Default closed
|
|
1319
|
-
// alter_message_grant. Spec:
|
|
1744
|
+
// handles. Default closed: recipient must have granted the sender via
|
|
1745
|
+
// alter_message_grant. Spec: the ALTER Alter-to-Alter Messaging spec.
|
|
1320
1746
|
/** Send a direct message to another tilde handle. */
|
|
1321
1747
|
async messageSend(args) {
|
|
1322
1748
|
return this.mcp.callTool("alter_message_send", args);
|
|
@@ -1349,12 +1775,16 @@ var AlterClient = class {
|
|
|
1349
1775
|
/**
|
|
1350
1776
|
* Verify the ES256 provenance attestation on a tool response.
|
|
1351
1777
|
* Accepts either a {@link ProvenanceEnvelope} or the raw `_meta`
|
|
1352
|
-
* object
|
|
1778
|
+
* object: the latter is more convenient for ad-hoc verification.
|
|
1353
1779
|
*/
|
|
1354
1780
|
async verifyProvenance(envelope) {
|
|
1355
1781
|
if (!envelope) return { valid: false, reason: "no provenance envelope" };
|
|
1356
1782
|
const inner = envelope.provenance ?? envelope;
|
|
1357
1783
|
return verifyProvenance(inner, {
|
|
1784
|
+
// Pass an explicit jwksUrl only when the caller pinned one. Leaving it
|
|
1785
|
+
// undefined preserves the envelope `verify_at` (allowlist-gated)
|
|
1786
|
+
// resolution path inside verifyProvenance; the allowlist is the
|
|
1787
|
+
// trust-anchor gate there, not a bare hardcoded host.
|
|
1358
1788
|
jwksUrl: this.options.jwksUrl,
|
|
1359
1789
|
verifyAtAllowlist: this.options.verifyAtAllowlist
|
|
1360
1790
|
});
|
|
@@ -1367,9 +1797,21 @@ var AlterClient = class {
|
|
|
1367
1797
|
async verifyToolSignatures(tools, signatures) {
|
|
1368
1798
|
return verifyToolSignatures(tools, signatures);
|
|
1369
1799
|
}
|
|
1370
|
-
/**
|
|
1800
|
+
/**
|
|
1801
|
+
* Fetch the published JWKS for ALTER's signing key (cached 5 min).
|
|
1802
|
+
*
|
|
1803
|
+
* The JWKS URL is derived from the configured API surface
|
|
1804
|
+
* ({@link resolveJwksUrl}: explicit `jwksUrl` > `apiBase` > `ALTER_API` >
|
|
1805
|
+
* the configured `endpoint` origin > the canonical host) rather than a
|
|
1806
|
+
* single hardcoded host, so the trust anchor tracks the surface the
|
|
1807
|
+
* client is actually pointed at.
|
|
1808
|
+
*/
|
|
1371
1809
|
async fetchPublicKeys() {
|
|
1372
|
-
const url =
|
|
1810
|
+
const url = resolveJwksUrl({
|
|
1811
|
+
jwksUrl: this.options.jwksUrl,
|
|
1812
|
+
apiBase: this.options.apiBase,
|
|
1813
|
+
endpoint: this.options.endpoint
|
|
1814
|
+
});
|
|
1373
1815
|
return fetchPublicKeys(url);
|
|
1374
1816
|
}
|
|
1375
1817
|
};
|
|
@@ -1385,7 +1827,7 @@ function generateGenericMcpConfig(opts = {}) {
|
|
|
1385
1827
|
const entry = {
|
|
1386
1828
|
url: opts.endpoint ?? DEFAULT_ENDPOINT,
|
|
1387
1829
|
transport: "streamable-http",
|
|
1388
|
-
description: "ALTER Identity
|
|
1830
|
+
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1389
1831
|
};
|
|
1390
1832
|
if (Object.keys(headers).length > 0) entry.headers = headers;
|
|
1391
1833
|
return { mcpServers: { [serverName]: entry } };
|
|
@@ -1404,24 +1846,23 @@ function generateCursorConfig(opts = {}) {
|
|
|
1404
1846
|
// src/adapters/claude-desktop.ts
|
|
1405
1847
|
function generateClaudeDesktopConfig(opts = {}) {
|
|
1406
1848
|
const serverName = opts.serverName ?? "alter";
|
|
1407
|
-
const bridgeCommand = opts.bridgeCommand ?? "alter
|
|
1849
|
+
const bridgeCommand = opts.bridgeCommand ?? "alter";
|
|
1408
1850
|
const env2 = {};
|
|
1409
|
-
env2.ALTER_MCP_ENDPOINT = opts.endpoint ??
|
|
1851
|
+
env2.ALTER_MCP_ENDPOINT = opts.endpoint ?? MEMBER_BRIDGE_ENDPOINT;
|
|
1410
1852
|
if (opts.apiKey) env2.ALTER_API_KEY = opts.apiKey;
|
|
1411
1853
|
const entry = {
|
|
1412
1854
|
command: bridgeCommand,
|
|
1855
|
+
// The `mcp-bridge` subcommand is always the first arg now that the bridge
|
|
1856
|
+
// is invoked through the `alter` CLI, not a bare bridge binary.
|
|
1857
|
+
args: ["mcp-bridge"],
|
|
1413
1858
|
env: env2,
|
|
1414
|
-
description: "ALTER Identity
|
|
1859
|
+
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1415
1860
|
};
|
|
1416
1861
|
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
1417
|
-
entry.args = [...opts.extraArgs];
|
|
1862
|
+
entry.args = [...entry.args, ...opts.extraArgs];
|
|
1418
1863
|
}
|
|
1419
1864
|
return { mcpServers: { [serverName]: entry } };
|
|
1420
1865
|
}
|
|
1421
|
-
|
|
1422
|
-
// src/meta.ts
|
|
1423
|
-
var SDK_NAME = "@truealter/sdk";
|
|
1424
|
-
var SDK_VERSION = "0.3.0";
|
|
1425
1866
|
var HOME = homedir();
|
|
1426
1867
|
var PLAT = platform();
|
|
1427
1868
|
function appData() {
|
|
@@ -1546,7 +1987,7 @@ function probeAll() {
|
|
|
1546
1987
|
];
|
|
1547
1988
|
}
|
|
1548
1989
|
var SYNC_PREFIXES = [
|
|
1549
|
-
// iCloud Drive
|
|
1990
|
+
// iCloud Drive: both the new and legacy mounts.
|
|
1550
1991
|
"Library/Mobile Documents/com~apple~CloudDocs",
|
|
1551
1992
|
"iCloud Drive",
|
|
1552
1993
|
// OneDrive variants Microsoft ships across editions.
|
|
@@ -1559,7 +2000,7 @@ var SYNC_PREFIXES = [
|
|
|
1559
2000
|
"Google Drive",
|
|
1560
2001
|
"GoogleDrive",
|
|
1561
2002
|
"CloudStorage/GoogleDrive",
|
|
1562
|
-
// Box, pCloud, Sync.com, MEGA
|
|
2003
|
+
// Box, pCloud, Sync.com, MEGA: high-signal names worth refusing.
|
|
1563
2004
|
"Box Sync",
|
|
1564
2005
|
"pCloud Drive",
|
|
1565
2006
|
"Sync.com",
|
|
@@ -1611,10 +2052,10 @@ function atomicJsonMerge(opts) {
|
|
|
1611
2052
|
preBytes = readFileSync(path, "utf8");
|
|
1612
2053
|
if (preBytes.trim().length > 0) {
|
|
1613
2054
|
try {
|
|
1614
|
-
parsed = JSON.parse(preBytes);
|
|
2055
|
+
parsed = JSON.parse(preBytes.replace(/^\uFEFF/, ""));
|
|
1615
2056
|
} catch (err) {
|
|
1616
2057
|
throw new Error(
|
|
1617
|
-
`refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter
|
|
2058
|
+
`refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter wire\`.`
|
|
1618
2059
|
);
|
|
1619
2060
|
}
|
|
1620
2061
|
if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
|
|
@@ -1696,6 +2137,7 @@ function wire(opts = {}) {
|
|
|
1696
2137
|
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1697
2138
|
const apiKey = opts.apiKey;
|
|
1698
2139
|
const cfAccess = opts.cfAccess ?? readCfAccessEnv();
|
|
2140
|
+
const launcherPath = opts.launcherPath;
|
|
1699
2141
|
const probes = probeAll();
|
|
1700
2142
|
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1701
2143
|
const ts = TIMESTAMP();
|
|
@@ -1714,7 +2156,7 @@ function wire(opts = {}) {
|
|
|
1714
2156
|
}
|
|
1715
2157
|
try {
|
|
1716
2158
|
if (id === "claude-code") {
|
|
1717
|
-
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
|
|
2159
|
+
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess, launcherPath }));
|
|
1718
2160
|
} else {
|
|
1719
2161
|
targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
|
|
1720
2162
|
}
|
|
@@ -1747,7 +2189,7 @@ function wireFileTarget(args) {
|
|
|
1747
2189
|
const sync = detectSyncedVolume(client.configPath);
|
|
1748
2190
|
if (sync) {
|
|
1749
2191
|
throw new Error(
|
|
1750
|
-
`refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices
|
|
2192
|
+
`refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices: move the config off the sync root, or run wire on the device you want to target.`
|
|
1751
2193
|
);
|
|
1752
2194
|
}
|
|
1753
2195
|
const cfHeaders = {};
|
|
@@ -1785,10 +2227,28 @@ function wireFileTarget(args) {
|
|
|
1785
2227
|
postSha256: result.postSha256
|
|
1786
2228
|
};
|
|
1787
2229
|
}
|
|
1788
|
-
function
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2230
|
+
function redactSecret(text, secret) {
|
|
2231
|
+
if (!secret) return text;
|
|
2232
|
+
return text.split(secret).join("***redacted***");
|
|
2233
|
+
}
|
|
2234
|
+
function buildClaudeCodeAddArgs(args) {
|
|
2235
|
+
if (args.subprocessArgv) {
|
|
2236
|
+
return [
|
|
2237
|
+
"mcp",
|
|
2238
|
+
"add",
|
|
2239
|
+
"--scope",
|
|
2240
|
+
"user",
|
|
2241
|
+
"alter",
|
|
2242
|
+
"--env",
|
|
2243
|
+
`ALTER_MCP_ENDPOINT=${MEMBER_BRIDGE_ENDPOINT}`,
|
|
2244
|
+
"--env",
|
|
2245
|
+
`ALTER_PUBLIC_MCP_ENDPOINT=${MEMBER_BRIDGE_ENDPOINT}`,
|
|
2246
|
+
...args.apiKey ? ["--env", `ALTER_API_KEY=${args.apiKey}`] : [],
|
|
2247
|
+
"--",
|
|
2248
|
+
...args.subprocessArgv
|
|
2249
|
+
];
|
|
2250
|
+
}
|
|
2251
|
+
return [
|
|
1792
2252
|
"mcp",
|
|
1793
2253
|
"add",
|
|
1794
2254
|
"--scope",
|
|
@@ -1799,12 +2259,28 @@ function wireClaudeCode(args) {
|
|
|
1799
2259
|
args.endpoint,
|
|
1800
2260
|
...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
|
|
1801
2261
|
];
|
|
1802
|
-
|
|
2262
|
+
}
|
|
2263
|
+
function wireClaudeCode(args) {
|
|
2264
|
+
const cmd = "claude";
|
|
2265
|
+
const bridgePath = resolveBridgeScript();
|
|
2266
|
+
const launcher = args.launcherPath && existsSync(args.launcherPath) ? args.launcherPath : null;
|
|
2267
|
+
const subprocessArgv = launcher ? ["node", launcher, "mcp-bridge"] : bridgePath ? ["node", bridgePath] : null;
|
|
2268
|
+
const argList = buildClaudeCodeAddArgs({
|
|
2269
|
+
apiKey: args.apiKey,
|
|
2270
|
+
subprocessArgv,
|
|
2271
|
+
endpoint: args.endpoint
|
|
2272
|
+
});
|
|
2273
|
+
const full = redactSecret(`${cmd} ${argList.join(" ")}`, args.apiKey);
|
|
1803
2274
|
const run = spawnSync(cmd, argList, {
|
|
1804
2275
|
encoding: "utf8",
|
|
1805
2276
|
shell: process.platform === "win32",
|
|
1806
2277
|
timeout: 1e4,
|
|
1807
|
-
env:
|
|
2278
|
+
env: subprocessArgv ? {
|
|
2279
|
+
...process.env,
|
|
2280
|
+
ALTER_MCP_ENDPOINT: MEMBER_BRIDGE_ENDPOINT,
|
|
2281
|
+
ALTER_PUBLIC_MCP_ENDPOINT: MEMBER_BRIDGE_ENDPOINT,
|
|
2282
|
+
...args.apiKey ? { ALTER_API_KEY: args.apiKey } : {}
|
|
2283
|
+
} : void 0
|
|
1808
2284
|
});
|
|
1809
2285
|
if (run.error) {
|
|
1810
2286
|
return {
|
|
@@ -1836,14 +2312,17 @@ function wireClaudeCode(args) {
|
|
|
1836
2312
|
};
|
|
1837
2313
|
}
|
|
1838
2314
|
function resolveBridgeScript() {
|
|
1839
|
-
const
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2315
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
2316
|
+
const distBinBridge = join(here, "bin", "mcp-bridge.js");
|
|
2317
|
+
if (existsSync(distBinBridge)) return distBinBridge;
|
|
2318
|
+
const nestedDistBinBridge = join(here, "..", "dist", "bin", "mcp-bridge.js");
|
|
2319
|
+
if (existsSync(nestedDistBinBridge)) return nestedDistBinBridge;
|
|
2320
|
+
const siblingBridge = join(here, "..", "dist", "mcp-bridge.js");
|
|
2321
|
+
if (existsSync(siblingBridge)) return siblingBridge;
|
|
2322
|
+
const srcBridge = join(here, "..", "mcp-bridge.js");
|
|
2323
|
+
if (existsSync(srcBridge)) return srcBridge;
|
|
2324
|
+
const npmGlobalBridge = join(here, "mcp-bridge.js");
|
|
2325
|
+
if (existsSync(npmGlobalBridge)) return npmGlobalBridge;
|
|
1847
2326
|
return null;
|
|
1848
2327
|
}
|
|
1849
2328
|
function unwire() {
|
|
@@ -1996,19 +2475,19 @@ var TOOL_COSTS = {
|
|
|
1996
2475
|
complete_knot: 0,
|
|
1997
2476
|
check_golden_thread: 0,
|
|
1998
2477
|
thread_census: 0,
|
|
1999
|
-
// L1 ($0.
|
|
2000
|
-
assess_traits:
|
|
2001
|
-
get_trait_snapshot:
|
|
2002
|
-
// L2 ($0.
|
|
2003
|
-
get_full_trait_vector: 0.
|
|
2004
|
-
get_side_quest_graph: 0.
|
|
2005
|
-
// L3 ($0.
|
|
2006
|
-
query_graph_similarity: 0.
|
|
2007
|
-
// L4 ($0.
|
|
2008
|
-
compute_belonging: 0.
|
|
2009
|
-
// L5 ($
|
|
2010
|
-
get_match_recommendations:
|
|
2011
|
-
generate_match_narrative:
|
|
2478
|
+
// L1 ($0.01)
|
|
2479
|
+
assess_traits: 0.01,
|
|
2480
|
+
get_trait_snapshot: 0.01,
|
|
2481
|
+
// L2 ($0.10)
|
|
2482
|
+
get_full_trait_vector: 0.1,
|
|
2483
|
+
get_side_quest_graph: 0.1,
|
|
2484
|
+
// L3 ($0.30)
|
|
2485
|
+
query_graph_similarity: 0.3,
|
|
2486
|
+
// L4 ($0.60)
|
|
2487
|
+
compute_belonging: 0.6,
|
|
2488
|
+
// L5 ($1.00)
|
|
2489
|
+
get_match_recommendations: 1,
|
|
2490
|
+
generate_match_narrative: 1
|
|
2012
2491
|
};
|
|
2013
2492
|
var TOOL_BLAST_RADIUS = {
|
|
2014
2493
|
// Low: read-only reference
|
|
@@ -2048,12 +2527,108 @@ var TOOL_BLAST_RADIUS = {
|
|
|
2048
2527
|
query_graph_similarity: "high"
|
|
2049
2528
|
};
|
|
2050
2529
|
|
|
2530
|
+
// src/pricing.generated.ts
|
|
2531
|
+
var GENERATED_TOOL_TIERS = {
|
|
2532
|
+
// L0 (free)
|
|
2533
|
+
alter_presence_read: 0,
|
|
2534
|
+
alter_resolve_handle: 0,
|
|
2535
|
+
check_assessment_status: 0,
|
|
2536
|
+
create_identity_stub: 0,
|
|
2537
|
+
dispute_attestation: 0,
|
|
2538
|
+
get_competencies: 0,
|
|
2539
|
+
get_earning_summary: 0,
|
|
2540
|
+
get_engagement_level: 0,
|
|
2541
|
+
get_identity_earnings: 0,
|
|
2542
|
+
get_identity_trust_score: 0,
|
|
2543
|
+
get_network_stats: 0,
|
|
2544
|
+
get_privacy_budget: 0,
|
|
2545
|
+
get_profile: 0,
|
|
2546
|
+
initiate_assessment: 0,
|
|
2547
|
+
list_archetypes: 0,
|
|
2548
|
+
query_matches: 0,
|
|
2549
|
+
recommend_tool: 0,
|
|
2550
|
+
search_identities: 0,
|
|
2551
|
+
verify_identity: 0,
|
|
2552
|
+
// L1 ($0.01)
|
|
2553
|
+
assess_traits: 1,
|
|
2554
|
+
attest_domain: 1,
|
|
2555
|
+
get_trait_snapshot: 1,
|
|
2556
|
+
poll_requirement_matches: 1,
|
|
2557
|
+
submit_context: 1,
|
|
2558
|
+
submit_social_links: 1,
|
|
2559
|
+
submit_structured_profile: 1,
|
|
2560
|
+
// L2 ($0.10)
|
|
2561
|
+
attest_claim_provenance: 2,
|
|
2562
|
+
get_full_trait_vector: 2,
|
|
2563
|
+
get_side_quest_graph: 2,
|
|
2564
|
+
submit_batch_context: 2,
|
|
2565
|
+
// L3 ($0.30)
|
|
2566
|
+
alter_alignment: 3,
|
|
2567
|
+
query_graph_similarity: 3,
|
|
2568
|
+
// L4 ($0.60)
|
|
2569
|
+
compute_belonging: 4,
|
|
2570
|
+
// L5 ($1.00)
|
|
2571
|
+
generate_match_narrative: 5,
|
|
2572
|
+
get_match_recommendations: 5,
|
|
2573
|
+
query_field: 5
|
|
2574
|
+
};
|
|
2575
|
+
var GENERATED_TOOL_PRICING = {
|
|
2576
|
+
// L1: $0.01
|
|
2577
|
+
assess_traits: 0.01,
|
|
2578
|
+
attest_domain: 0.01,
|
|
2579
|
+
get_trait_snapshot: 0.01,
|
|
2580
|
+
poll_requirement_matches: 0.01,
|
|
2581
|
+
submit_context: 0.01,
|
|
2582
|
+
submit_social_links: 0.01,
|
|
2583
|
+
submit_structured_profile: 0.01,
|
|
2584
|
+
// L2: $0.10
|
|
2585
|
+
attest_claim_provenance: 0.1,
|
|
2586
|
+
get_full_trait_vector: 0.1,
|
|
2587
|
+
get_side_quest_graph: 0.1,
|
|
2588
|
+
submit_batch_context: 0.1,
|
|
2589
|
+
// L3: $0.30
|
|
2590
|
+
alter_alignment: 0.3,
|
|
2591
|
+
query_graph_similarity: 0.3,
|
|
2592
|
+
// L4: $0.60
|
|
2593
|
+
compute_belonging: 0.6,
|
|
2594
|
+
// L5: $1.00
|
|
2595
|
+
generate_match_narrative: 1,
|
|
2596
|
+
get_match_recommendations: 1,
|
|
2597
|
+
query_field: 1
|
|
2598
|
+
};
|
|
2599
|
+
var TIER_PRICES = {
|
|
2600
|
+
L0: 0,
|
|
2601
|
+
L1: 0.01,
|
|
2602
|
+
L2: 0.1,
|
|
2603
|
+
L3: 0.3,
|
|
2604
|
+
L4: 0.6,
|
|
2605
|
+
L5: 1
|
|
2606
|
+
};
|
|
2607
|
+
var ADVERTISED_TOOL_COUNTS = {
|
|
2608
|
+
/** Free (L0) tools visible to anonymous / agent-class callers. */
|
|
2609
|
+
free: 27,
|
|
2610
|
+
/** Premium (L1-L5) tools requiring x402 payment. */
|
|
2611
|
+
premium: 9,
|
|
2612
|
+
/** Messaging tools (member-self-only; excluded from external advertisement). */
|
|
2613
|
+
messaging: 0,
|
|
2614
|
+
/** Total publicly advertised (free + premium). */
|
|
2615
|
+
total: 36
|
|
2616
|
+
};
|
|
2617
|
+
var REVENUE_SPLIT = {
|
|
2618
|
+
weaver: 0.75,
|
|
2619
|
+
// data subject (Identity Income)
|
|
2620
|
+
facilitator: 0.05,
|
|
2621
|
+
alter: 0.15,
|
|
2622
|
+
treasury: 0.05
|
|
2623
|
+
};
|
|
2624
|
+
|
|
2051
2625
|
// src/homepage.ts
|
|
2052
2626
|
var HOMEPAGE_LIMITS = {
|
|
2053
2627
|
whoami_max_chars: 240,
|
|
2054
2628
|
opener_max_chars: 280,
|
|
2055
2629
|
pronouns_max_chars: 32,
|
|
2056
|
-
attunement_glyph_max_chars: 16
|
|
2630
|
+
attunement_glyph_max_chars: 16,
|
|
2631
|
+
contact_email_max_chars: 254
|
|
2057
2632
|
};
|
|
2058
2633
|
|
|
2059
2634
|
// src/themes.ts
|
|
@@ -2066,4 +2641,4 @@ var THEME_LIMITS = {
|
|
|
2066
2641
|
};
|
|
2067
2642
|
var OSC8_ALLOWED_SCHEMES = ["https:", "mailto:"];
|
|
2068
2643
|
|
|
2069
|
-
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, HOMEPAGE_LIMITS, MCPClient, MCP_PROTOCOL_VERSION, OSC8_ALLOWED_SCHEMES, PREMIUM_TOOL_NAMES, SDK_NAME, SDK_VERSION, THEME_LIMITS, 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 };
|
|
2644
|
+
export { ADVERTISED_TOOL_COUNTS, ALL_CLIENTS, AlterAuthError, AlterClient, AlterDiscoveryError, AlterError, AlterInvalidResponse, AlterNetworkError, AlterPaymentRequired, AlterProvenanceError, AlterRateLimited, AlterTimeoutError, AlterToolError, BelowFloorError, CLAUDE_CODE, CLAUDE_DESKTOP, CLIENT_CHANNEL, CLIENT_ID, CURSOR, DEFAULT_DOMAIN, DEFAULT_ENDPOINT, DEFAULT_VERIFY_AT_ALLOWLIST, FREE_TOOL_NAMES, GENERATED_TOOL_PRICING, GENERATED_TOOL_TIERS, HOMEPAGE_LIMITS, KNOWN_FLOOR_PUBLIC_KEYS, MCPClient, MCP_PROTOCOL_VERSION, MIN_VERSION_ENDPOINT, OSC8_ALLOWED_SCHEMES, PREMIUM_TOOL_NAMES, REVENUE_SPLIT, SDK_NAME, SDK_VERSION, THEME_LIMITS, TIER_PRICES, TOOL_BLAST_RADIUS, TOOL_COSTS, TOOL_TIERS, VSCODE, X402Client, base64urlDecode, base64urlEncode2 as base64urlEncode, canonicalArgsSha256, canonicalJson, canonicalStringify, checkMinVersion, clearDiscoveryCache, compareSemver, computeKeyId, 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, verifyFloorSignature, verifyProvenance, verifyToolSignatures, wire, writeWireState };
|