@truealter/sdk 0.5.3 → 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 +92 -80
- package/dist/bin/mcp-bridge.js +141 -12
- package/dist/index.cjs +167 -23
- package/dist/index.d.cts +62 -39
- package/dist/index.d.ts +62 -39
- package/dist/index.js +167 -23
- package/package.json +3 -6
- package/dist/bin/alter-identity.js +0 -2641
package/dist/index.d.ts
CHANGED
|
@@ -437,7 +437,7 @@ interface MCPClientInfo {
|
|
|
437
437
|
version: string;
|
|
438
438
|
}
|
|
439
439
|
interface MCPClientOptions {
|
|
440
|
-
/** Streamable HTTP endpoint. Default: https://mcp.truealter.com */
|
|
440
|
+
/** Streamable HTTP endpoint. Default: https://mcp.truealter.com/api/v1/mcp (public discovery). Member bridge runtime sets api.truealter.com via env or explicit option. */
|
|
441
441
|
endpoint?: string;
|
|
442
442
|
/** Optional API key for the `X-ALTER-API-Key` header. */
|
|
443
443
|
apiKey?: string;
|
|
@@ -474,7 +474,7 @@ interface MCPClientOptions {
|
|
|
474
474
|
/**
|
|
475
475
|
* Optional hook invoked once, lazily, before the first network call
|
|
476
476
|
* (the first `initialize()` or any direct `rpc()`). Used by
|
|
477
|
-
* {@link AlterClient} to run the
|
|
477
|
+
* {@link AlterClient} to run the version-floor preflight on
|
|
478
478
|
* first request: not on import, not in the constructor. A throw
|
|
479
479
|
* from the hook propagates to the caller of the first `tools/call`
|
|
480
480
|
* (or `initialize()`).
|
|
@@ -486,7 +486,7 @@ interface MCPSigningOptions {
|
|
|
486
486
|
kid: string;
|
|
487
487
|
/** ES256 P-256 private key: 32-byte scalar or PEM. */
|
|
488
488
|
privateKey: Uint8Array | string;
|
|
489
|
-
/** The caller's bound
|
|
489
|
+
/** The caller's bound handle. */
|
|
490
490
|
handle: string;
|
|
491
491
|
}
|
|
492
492
|
interface MCPCallOptions {
|
|
@@ -545,7 +545,7 @@ declare class MCPClient {
|
|
|
545
545
|
private initialised;
|
|
546
546
|
constructor(opts?: MCPClientOptions);
|
|
547
547
|
/**
|
|
548
|
-
* Run the lazy preflight hook
|
|
548
|
+
* Run the lazy version-floor preflight hook exactly once.
|
|
549
549
|
* Idempotent and serialised: concurrent callers share the same
|
|
550
550
|
* promise. Throws from the hook propagate to every concurrent caller.
|
|
551
551
|
*/
|
|
@@ -1212,7 +1212,8 @@ declare const TOOL_BLAST_RADIUS: Record<ToolName, "low" | "medium" | "high">;
|
|
|
1212
1212
|
* This is the entry point most consumers will use. It bundles
|
|
1213
1213
|
* {@link MCPClient}, {@link X402Client}, discovery, and provenance
|
|
1214
1214
|
* verification into a single ergonomic surface that mirrors the 32
|
|
1215
|
-
* tools exposed at https://mcp.truealter.com/api/v1/mcp
|
|
1215
|
+
* tools exposed at https://mcp.truealter.com/api/v1/mcp (public/discovery)
|
|
1216
|
+
* and https://api.truealter.com/api/v1/mcp (member bearer-first runtime).
|
|
1216
1217
|
*
|
|
1217
1218
|
* Free tier methods require no authentication. Premium methods accept
|
|
1218
1219
|
* an `X402Client` (or fall back to throwing {@link AlterPaymentRequired}
|
|
@@ -1237,8 +1238,13 @@ interface AlterClientOptions extends Omit<MCPClientOptions, 'x402'> {
|
|
|
1237
1238
|
*/
|
|
1238
1239
|
skipDiscovery?: boolean;
|
|
1239
1240
|
/**
|
|
1240
|
-
* URL of the JWKS document used for provenance verification.
|
|
1241
|
-
*
|
|
1241
|
+
* URL of the JWKS document used for provenance verification.
|
|
1242
|
+
*
|
|
1243
|
+
* When unset, the JWKS host is derived from the configured API surface
|
|
1244
|
+
* ({@link resolveJwksUrl}: `apiBase` > `ALTER_API` env > the `endpoint`
|
|
1245
|
+
* origin > the canonical `api.truealter.com`), so the trust anchor
|
|
1246
|
+
* tracks whatever surface the client is actually pointed at rather than
|
|
1247
|
+
* a single hardcoded host.
|
|
1242
1248
|
*
|
|
1243
1249
|
* When set, this URL is used verbatim for every `verifyProvenance`
|
|
1244
1250
|
* call and *overrides* any `verify_at` hint on the server response:
|
|
@@ -1258,7 +1264,7 @@ interface AlterClientOptions extends Omit<MCPClientOptions, 'x402'> {
|
|
|
1258
1264
|
*/
|
|
1259
1265
|
verifyAtAllowlist?: readonly string[];
|
|
1260
1266
|
/**
|
|
1261
|
-
* Skip the
|
|
1267
|
+
* Skip the client-side version-floor preflight (the lazy
|
|
1262
1268
|
* `checkMinVersion()` invocation on first request). Deliberately named
|
|
1263
1269
|
* to discourage casual use: the server-side floor gate (Phase 1
|
|
1264
1270
|
* middleware) still rejects below-floor clients with HTTP 426
|
|
@@ -1402,7 +1408,15 @@ declare class AlterClient {
|
|
|
1402
1408
|
valid: boolean;
|
|
1403
1409
|
reason?: string;
|
|
1404
1410
|
}[]>;
|
|
1405
|
-
/**
|
|
1411
|
+
/**
|
|
1412
|
+
* Fetch the published JWKS for ALTER's signing key (cached 5 min).
|
|
1413
|
+
*
|
|
1414
|
+
* The JWKS URL is derived from the configured API surface
|
|
1415
|
+
* ({@link resolveJwksUrl}: explicit `jwksUrl` > `apiBase` > `ALTER_API` >
|
|
1416
|
+
* the configured `endpoint` origin > the canonical host) rather than a
|
|
1417
|
+
* single hardcoded host, so the trust anchor tracks the surface the
|
|
1418
|
+
* client is actually pointed at.
|
|
1419
|
+
*/
|
|
1406
1420
|
fetchPublicKeys(): Promise<unknown>;
|
|
1407
1421
|
}
|
|
1408
1422
|
|
|
@@ -1477,9 +1491,9 @@ interface SignInvocationOptions {
|
|
|
1477
1491
|
declare function signInvocation(toolName: string, toolArgs: Record<string, unknown>, options: SignInvocationOptions): string;
|
|
1478
1492
|
|
|
1479
1493
|
/**
|
|
1480
|
-
* floor-preflight: SDK-side
|
|
1494
|
+
* floor-preflight: SDK-side client version-floor enforcement.
|
|
1481
1495
|
*
|
|
1482
|
-
* Sister to
|
|
1496
|
+
* Sister to the CLI client's own floor-preflight. This is
|
|
1483
1497
|
* the `@truealter/sdk` implementation: it fires lazily on the first
|
|
1484
1498
|
* authenticated network call from {@link AlterClient}, hits the public
|
|
1485
1499
|
* floor endpoint, verifies the Ed25519 signature against the published
|
|
@@ -1505,8 +1519,8 @@ declare function signInvocation(toolName: string, toolArgs: Record<string, unkno
|
|
|
1505
1519
|
* corresponding public key (SPKI PEM) in {@link KNOWN_FLOOR_PUBLIC_KEYS},
|
|
1506
1520
|
* keyed by `key_id`. No signing secret ships in the client: this is
|
|
1507
1521
|
* asymmetric: the public key cannot forge signatures. MUST stay
|
|
1508
|
-
* byte-compatible with
|
|
1509
|
-
* canonical TypeScript client
|
|
1522
|
+
* byte-compatible with the ALTER backend floor-signing implementation
|
|
1523
|
+
* and the canonical TypeScript CLI client:
|
|
1510
1524
|
* - Algorithm: Ed25519 (RFC 8032). Deterministic: same key + payload
|
|
1511
1525
|
* always yields the same signature.
|
|
1512
1526
|
* - `key_id` = first 8 hex chars of SHA-256(raw 32-byte public key).
|
|
@@ -1534,8 +1548,8 @@ declare const CLIENT_CHANNEL = "npm";
|
|
|
1534
1548
|
/**
|
|
1535
1549
|
* Compute the 8-char `key_id` for an Ed25519 public key (SPKI PEM).
|
|
1536
1550
|
*
|
|
1537
|
-
* Matches the backend `key_id_for_public_key(pub)` in
|
|
1538
|
-
*
|
|
1551
|
+
* Matches the backend `key_id_for_public_key(pub)` in the ALTER
|
|
1552
|
+
* backend floor-signing implementation:
|
|
1539
1553
|
* raw = pub.public_bytes(Encoding.Raw, PublicFormat.Raw) # 32 bytes
|
|
1540
1554
|
* sha256(raw).hexdigest()[:8]
|
|
1541
1555
|
*
|
|
@@ -1552,7 +1566,7 @@ declare function computeKeyId(publicKeyPem: string): string;
|
|
|
1552
1566
|
* stripped via the `,`/`:` separators; non-ASCII passes through as raw UTF-8
|
|
1553
1567
|
* (Node's `JSON.stringify` never `\uXXXX`-escapes non-ASCII, matching the
|
|
1554
1568
|
* backend's `ensure_ascii=False`). The result is the byte-exact input to
|
|
1555
|
-
* Ed25519 signing (backend) and verification (this SDK and
|
|
1569
|
+
* Ed25519 signing (backend) and verification (this SDK and the CLI client) on
|
|
1556
1570
|
* both sides.
|
|
1557
1571
|
*/
|
|
1558
1572
|
declare function canonicalJson(obj: unknown): string;
|
|
@@ -1562,12 +1576,12 @@ declare function canonicalJson(obj: unknown): string;
|
|
|
1562
1576
|
* `key_id` = first 8 hex chars of SHA-256(raw 32-byte Ed25519 public key).
|
|
1563
1577
|
* Use `computeKeyId(spkiPem)` to re-derive and validate the map key.
|
|
1564
1578
|
*
|
|
1565
|
-
* Backend may rotate via
|
|
1579
|
+
* Backend may rotate via a dual-key path: clients keep N keys and
|
|
1566
1580
|
* select via `key_id`. Add new keys additively; remove old keys only after
|
|
1567
1581
|
* all deployed clients have received the new key.
|
|
1568
1582
|
*
|
|
1569
1583
|
* Current keys (mirrors `KNOWN_FLOOR_PUBLIC_KEYS` in the canonical
|
|
1570
|
-
*
|
|
1584
|
+
* CLI client):
|
|
1571
1585
|
* 8aa59e05: dev/non-prod key (local + e2e + staging). Safe to ship publicly;
|
|
1572
1586
|
* this is the PUBLIC key only, never the private key.
|
|
1573
1587
|
* 640f7d9a: prod key. Added 2026-06-05. key_id = first 8 hex of
|
|
@@ -1707,7 +1721,7 @@ declare function compareSemver(a: string, b: string): number;
|
|
|
1707
1721
|
* canonical JSON of `{ floors, served_at }`: `signature` + `key_id` +
|
|
1708
1722
|
* `cache_ttl_seconds` are excluded from the signed bytes.
|
|
1709
1723
|
*
|
|
1710
|
-
* MUST stay byte-compatible with the backend
|
|
1724
|
+
* MUST stay byte-compatible with the ALTER backend floor-signing implementation:
|
|
1711
1725
|
* - Algorithm: Ed25519 (RFC 8032). Deterministic: same key + payload = same sig.
|
|
1712
1726
|
* - Payload: `canonicalJson({floors, served_at})`: sorted-keys + compact.
|
|
1713
1727
|
* - `key_id`: first 8 hex chars of SHA-256(raw 32-byte Ed25519 public key).
|
|
@@ -1715,7 +1729,7 @@ declare function compareSemver(a: string, b: string): number;
|
|
|
1715
1729
|
*
|
|
1716
1730
|
* The `keys` parameter maps `key_id -> SPKI PEM string`; defaults to
|
|
1717
1731
|
* `KNOWN_FLOOR_PUBLIC_KEYS`. Returns false on unknown key_id (triggers refetch
|
|
1718
|
-
* per
|
|
1732
|
+
* per the key-rotation path).
|
|
1719
1733
|
*/
|
|
1720
1734
|
declare function verifyFloorSignature(doc: FloorDocument, keys?: Record<string, string>): boolean;
|
|
1721
1735
|
|
|
@@ -1747,8 +1761,8 @@ declare function generateGenericMcpConfig(opts?: GenerateMcpConfigOptions): Gene
|
|
|
1747
1761
|
/**
|
|
1748
1762
|
* Claude Code MCP config helper.
|
|
1749
1763
|
*
|
|
1750
|
-
* Claude Code reads MCP servers from `.mcp.json`
|
|
1751
|
-
*
|
|
1764
|
+
* Claude Code reads MCP servers from the project-level `.mcp.json` or
|
|
1765
|
+
* the user-level Claude Code config. The shape matches `mcpServers`.
|
|
1752
1766
|
*/
|
|
1753
1767
|
|
|
1754
1768
|
declare function generateClaudeConfig(opts?: GenerateMcpConfigOptions): GenericMcpConfig;
|
|
@@ -1766,10 +1780,11 @@ declare function generateCursorConfig(opts?: GenerateMcpConfigOptions): GenericM
|
|
|
1766
1780
|
* Claude Desktop MCP config helper.
|
|
1767
1781
|
*
|
|
1768
1782
|
* Claude Desktop speaks stdio only: it does not currently dial
|
|
1769
|
-
* Streamable-HTTP MCP servers directly. The canonical bridge is
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
* child process and read
|
|
1783
|
+
* Streamable-HTTP MCP servers directly. The canonical bridge is the
|
|
1784
|
+
* `alter` CLI's `mcp-bridge` subcommand. The SDK no longer publishes a
|
|
1785
|
+
* standalone bridge binary; the CLI launches the bridge by file path.
|
|
1786
|
+
* Desktop hosts spawn `alter mcp-bridge` as a child process and read
|
|
1787
|
+
* JSON-RPC over stdin/stdout.
|
|
1773
1788
|
*
|
|
1774
1789
|
* Config file path varies by platform and is resolved in
|
|
1775
1790
|
* `src/wire/paths.ts`. This adapter only produces the config *shape*.
|
|
@@ -1790,9 +1805,9 @@ interface GenerateClaudeDesktopOptions {
|
|
|
1790
1805
|
apiKey?: string;
|
|
1791
1806
|
/** Identifier used by Claude Desktop for this server. Default: `alter`. */
|
|
1792
1807
|
serverName?: string;
|
|
1793
|
-
/** Override the bridge command (e.g. `npx
|
|
1808
|
+
/** Override the bridge command (e.g. `npx`). Default: the `alter` CLI. */
|
|
1794
1809
|
bridgeCommand?: string;
|
|
1795
|
-
/** Extra args appended after the default bridge
|
|
1810
|
+
/** Extra args appended after the default `mcp-bridge` subcommand arg. */
|
|
1796
1811
|
extraArgs?: string[];
|
|
1797
1812
|
}
|
|
1798
1813
|
declare function generateClaudeDesktopConfig(opts?: GenerateClaudeDesktopOptions): ClaudeDesktopConfig;
|
|
@@ -1943,6 +1958,15 @@ interface WireOptions {
|
|
|
1943
1958
|
only?: readonly ClientId[];
|
|
1944
1959
|
/** Skip any client whose probe said "not installed" even if the caller passed it via `only`. */
|
|
1945
1960
|
skipMissing?: boolean;
|
|
1961
|
+
/**
|
|
1962
|
+
* Absolute path to the `alter` CLI launcher entry (its `dist/index.js`).
|
|
1963
|
+
* When set, Claude Code is wired to run `node <launcherPath> mcp-bridge`,
|
|
1964
|
+
* which injects the ES256 signing credential (ALTER_SIGNING_KEY /
|
|
1965
|
+
* ALTER_SIGNING_KID) from the OS secure store before spawning the bridge,
|
|
1966
|
+
* so MCP tool calls are signed. When absent (standalone SDK use without the
|
|
1967
|
+
* CLI), wiring falls back to `node <bridge>`: anonymous / L0, no signing.
|
|
1968
|
+
*/
|
|
1969
|
+
launcherPath?: string;
|
|
1946
1970
|
}
|
|
1947
1971
|
interface WireReport {
|
|
1948
1972
|
state: WireState;
|
|
@@ -1980,13 +2004,13 @@ declare const TIER_PRICES: Record<string, number>;
|
|
|
1980
2004
|
/** Publicly advertised tool counts. Mirrors canonical-facts.json public_advertised. */
|
|
1981
2005
|
declare const ADVERTISED_TOOL_COUNTS: {
|
|
1982
2006
|
/** Free (L0) tools visible to anonymous / agent-class callers. */
|
|
1983
|
-
readonly free:
|
|
2007
|
+
readonly free: 27;
|
|
1984
2008
|
/** Premium (L1-L5) tools requiring x402 payment. */
|
|
1985
|
-
readonly premium:
|
|
2009
|
+
readonly premium: 9;
|
|
1986
2010
|
/** Messaging tools (member-self-only; excluded from external advertisement). */
|
|
1987
2011
|
readonly messaging: 0;
|
|
1988
2012
|
/** Total publicly advertised (free + premium). */
|
|
1989
|
-
readonly total:
|
|
2013
|
+
readonly total: 36;
|
|
1990
2014
|
};
|
|
1991
2015
|
/** x402 settlement revenue split. Weaver = the data subject earning Identity Income. */
|
|
1992
2016
|
declare const REVENUE_SPLIT: {
|
|
@@ -2164,8 +2188,8 @@ interface HomepageOutput {
|
|
|
2164
2188
|
*/
|
|
2165
2189
|
type HomepageCallerVertical = "workplace" | "education" | "personal" | "civic" | "agent" | "unknown";
|
|
2166
2190
|
/** Maximum sizes from the spec. SDK consumers can use these to validate
|
|
2167
|
-
* input before sending. Mirrored from
|
|
2168
|
-
*
|
|
2191
|
+
* input before sending. Mirrored from the ALTER portfolio manifest spec v1
|
|
2192
|
+
* (forthcoming). */
|
|
2169
2193
|
declare const HOMEPAGE_LIMITS: {
|
|
2170
2194
|
readonly whoami_max_chars: 240;
|
|
2171
2195
|
readonly opener_max_chars: 280;
|
|
@@ -2178,8 +2202,7 @@ declare const HOMEPAGE_LIMITS: {
|
|
|
2178
2202
|
* @truealter/sdk: theme pack types
|
|
2179
2203
|
*
|
|
2180
2204
|
* Wire-format types for ALTER theme packs and `themes.lock` composition
|
|
2181
|
-
* manifests. The full specification lives in
|
|
2182
|
-
* `docs/technical/alter-theme-pack-spec-v1.md`.
|
|
2205
|
+
* manifests. The full specification lives in the ALTER theme pack spec v1.
|
|
2183
2206
|
*
|
|
2184
2207
|
* These types describe the on-the-wire shape of theme manifests as they
|
|
2185
2208
|
* are produced by `alter theme install`, persisted to `themes.lock`,
|
|
@@ -2205,7 +2228,7 @@ interface ThemeMeta {
|
|
|
2205
2228
|
name: string;
|
|
2206
2229
|
/** SemVer-shaped recommended; informational only. Resolution uses pack_id. */
|
|
2207
2230
|
version: string;
|
|
2208
|
-
/** MUST be a ~handle whose
|
|
2231
|
+
/** MUST be a ~handle whose signing public key signs the pack. */
|
|
2209
2232
|
author: string;
|
|
2210
2233
|
/** ≤ 240 characters after NFC. */
|
|
2211
2234
|
description: string;
|
|
@@ -2267,7 +2290,7 @@ interface ThemeManifestV1 {
|
|
|
2267
2290
|
}
|
|
2268
2291
|
/**
|
|
2269
2292
|
* The `.sig` file accompanying every pack. Verification logic lives in
|
|
2270
|
-
*
|
|
2293
|
+
* the CLI client; this is the wire-format type only.
|
|
2271
2294
|
*/
|
|
2272
2295
|
interface ThemeSignatureManifest {
|
|
2273
2296
|
/** SHA-256 multihash of the canonical-form pack. */
|
|
@@ -2303,7 +2326,7 @@ interface ThemeLockEntry {
|
|
|
2303
2326
|
*/
|
|
2304
2327
|
interface ThemesLockV1 {
|
|
2305
2328
|
schema_version: 1;
|
|
2306
|
-
/** e.g. "
|
|
2329
|
+
/** Free-form producer tag, e.g. "@truealter/cli/1.0.0", informational only. */
|
|
2307
2330
|
generated_by: string;
|
|
2308
2331
|
/** RFC 3339 UTC timestamp at lockfile-write time. */
|
|
2309
2332
|
generated_at: string;
|
|
@@ -2342,7 +2365,7 @@ interface ThemeShareOutput {
|
|
|
2342
2365
|
message: string;
|
|
2343
2366
|
};
|
|
2344
2367
|
}
|
|
2345
|
-
/** v1 schema constants. Mirror the
|
|
2368
|
+
/** v1 schema constants. Mirror the ALTER theme pack spec v1. */
|
|
2346
2369
|
declare const THEME_LIMITS: {
|
|
2347
2370
|
readonly meta_name_pattern: RegExp;
|
|
2348
2371
|
readonly meta_description_max_chars: 240;
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,13 @@ var __defProp = Object.defineProperty;
|
|
|
15
15
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
16
16
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
17
17
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
18
|
-
var __esm = (fn, res) => function __init() {
|
|
19
|
-
|
|
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
|
+
}
|
|
20
25
|
};
|
|
21
26
|
var __export = (target, all) => {
|
|
22
27
|
for (var name in all)
|
|
@@ -404,7 +409,7 @@ function ensureMcpPath(url) {
|
|
|
404
409
|
|
|
405
410
|
// src/meta.ts
|
|
406
411
|
var SDK_NAME = "@truealter/sdk";
|
|
407
|
-
var SDK_VERSION = "0.5.
|
|
412
|
+
var SDK_VERSION = "0.5.8" ;
|
|
408
413
|
|
|
409
414
|
// src/floor-preflight.ts
|
|
410
415
|
var MIN_VERSION_ENDPOINT = "/v1/clients/min-version";
|
|
@@ -832,7 +837,7 @@ var MCPClient = class {
|
|
|
832
837
|
this.preflightHook = opts.preflightHook;
|
|
833
838
|
}
|
|
834
839
|
/**
|
|
835
|
-
* Run the lazy preflight hook
|
|
840
|
+
* Run the lazy version-floor preflight hook exactly once.
|
|
836
841
|
* Idempotent and serialised: concurrent callers share the same
|
|
837
842
|
* promise. Throws from the hook propagate to every concurrent caller.
|
|
838
843
|
*/
|
|
@@ -954,7 +959,14 @@ var MCPClient = class {
|
|
|
954
959
|
method: "POST",
|
|
955
960
|
headers: this.buildHeaders(signatureHeader),
|
|
956
961
|
body: JSON.stringify(payload),
|
|
957
|
-
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"
|
|
958
970
|
});
|
|
959
971
|
} catch (err) {
|
|
960
972
|
clearTimeout(timer);
|
|
@@ -971,6 +983,19 @@ var MCPClient = class {
|
|
|
971
983
|
clearTimeout(timer);
|
|
972
984
|
const sessionHeader = resp.headers.get("Mcp-Session-Id");
|
|
973
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
|
+
}
|
|
974
999
|
if (resp.status === 401 || resp.status === 403) {
|
|
975
1000
|
throw new AlterAuthError(`HTTP ${resp.status} on ${method}`, resp.status);
|
|
976
1001
|
}
|
|
@@ -995,11 +1020,56 @@ var MCPClient = class {
|
|
|
995
1020
|
const body2 = await safeText(resp);
|
|
996
1021
|
throw new AlterError("NETWORK", `HTTP ${resp.status} on ${method}: ${body2.slice(0, 200)}`);
|
|
997
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
|
+
}
|
|
998
1064
|
let body;
|
|
999
1065
|
try {
|
|
1000
1066
|
body = await resp.json();
|
|
1001
1067
|
} catch (err) {
|
|
1002
|
-
|
|
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
|
+
);
|
|
1003
1073
|
}
|
|
1004
1074
|
if (body.error) {
|
|
1005
1075
|
const code = body.error.code;
|
|
@@ -1020,7 +1090,7 @@ var MCPClient = class {
|
|
|
1020
1090
|
const headers = {
|
|
1021
1091
|
...this.extraHeaders ?? {},
|
|
1022
1092
|
"Content-Type": "application/json",
|
|
1023
|
-
Accept: "application/json",
|
|
1093
|
+
Accept: "application/json, text/event-stream",
|
|
1024
1094
|
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`,
|
|
1025
1095
|
"X-Alter-Client-Id": "alter-identity",
|
|
1026
1096
|
"X-Alter-Client-Version": SDK_VERSION,
|
|
@@ -1497,7 +1567,23 @@ function canonicalJson2(value) {
|
|
|
1497
1567
|
|
|
1498
1568
|
// src/client.ts
|
|
1499
1569
|
var DEFAULT_ENDPOINT = "https://mcp.truealter.com/api/v1/mcp";
|
|
1570
|
+
var MEMBER_BRIDGE_ENDPOINT = "https://api.truealter.com/api/v1/mcp";
|
|
1500
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
|
+
}
|
|
1501
1587
|
var AlterClient = class {
|
|
1502
1588
|
mcp;
|
|
1503
1589
|
x402;
|
|
@@ -1656,7 +1742,7 @@ var AlterClient = class {
|
|
|
1656
1742
|
// ── Alter-to-Alter Messaging ─────────────────────────────────────────
|
|
1657
1743
|
// Wave 1: cross-handle direct messages between authenticated tilde
|
|
1658
1744
|
// handles. Default closed: recipient must have granted the sender via
|
|
1659
|
-
// alter_message_grant. Spec:
|
|
1745
|
+
// alter_message_grant. Spec: the ALTER Alter-to-Alter Messaging spec.
|
|
1660
1746
|
/** Send a direct message to another tilde handle. */
|
|
1661
1747
|
async messageSend(args) {
|
|
1662
1748
|
return this.mcp.callTool("alter_message_send", args);
|
|
@@ -1695,6 +1781,10 @@ var AlterClient = class {
|
|
|
1695
1781
|
if (!envelope) return { valid: false, reason: "no provenance envelope" };
|
|
1696
1782
|
const inner = envelope.provenance ?? envelope;
|
|
1697
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.
|
|
1698
1788
|
jwksUrl: this.options.jwksUrl,
|
|
1699
1789
|
verifyAtAllowlist: this.options.verifyAtAllowlist
|
|
1700
1790
|
});
|
|
@@ -1707,9 +1797,21 @@ var AlterClient = class {
|
|
|
1707
1797
|
async verifyToolSignatures(tools, signatures) {
|
|
1708
1798
|
return verifyToolSignatures(tools, signatures);
|
|
1709
1799
|
}
|
|
1710
|
-
/**
|
|
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
|
+
*/
|
|
1711
1809
|
async fetchPublicKeys() {
|
|
1712
|
-
const url =
|
|
1810
|
+
const url = resolveJwksUrl({
|
|
1811
|
+
jwksUrl: this.options.jwksUrl,
|
|
1812
|
+
apiBase: this.options.apiBase,
|
|
1813
|
+
endpoint: this.options.endpoint
|
|
1814
|
+
});
|
|
1713
1815
|
return fetchPublicKeys(url);
|
|
1714
1816
|
}
|
|
1715
1817
|
};
|
|
@@ -1744,17 +1846,20 @@ function generateCursorConfig(opts = {}) {
|
|
|
1744
1846
|
// src/adapters/claude-desktop.ts
|
|
1745
1847
|
function generateClaudeDesktopConfig(opts = {}) {
|
|
1746
1848
|
const serverName = opts.serverName ?? "alter";
|
|
1747
|
-
const bridgeCommand = opts.bridgeCommand ?? "alter
|
|
1849
|
+
const bridgeCommand = opts.bridgeCommand ?? "alter";
|
|
1748
1850
|
const env2 = {};
|
|
1749
|
-
env2.ALTER_MCP_ENDPOINT = opts.endpoint ??
|
|
1851
|
+
env2.ALTER_MCP_ENDPOINT = opts.endpoint ?? MEMBER_BRIDGE_ENDPOINT;
|
|
1750
1852
|
if (opts.apiKey) env2.ALTER_API_KEY = opts.apiKey;
|
|
1751
1853
|
const entry = {
|
|
1752
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"],
|
|
1753
1858
|
env: env2,
|
|
1754
1859
|
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1755
1860
|
};
|
|
1756
1861
|
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
1757
|
-
entry.args = [...opts.extraArgs];
|
|
1862
|
+
entry.args = [...entry.args, ...opts.extraArgs];
|
|
1758
1863
|
}
|
|
1759
1864
|
return { mcpServers: { [serverName]: entry } };
|
|
1760
1865
|
}
|
|
@@ -2032,6 +2137,7 @@ function wire(opts = {}) {
|
|
|
2032
2137
|
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
2033
2138
|
const apiKey = opts.apiKey;
|
|
2034
2139
|
const cfAccess = opts.cfAccess ?? readCfAccessEnv();
|
|
2140
|
+
const launcherPath = opts.launcherPath;
|
|
2035
2141
|
const probes = probeAll();
|
|
2036
2142
|
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
2037
2143
|
const ts = TIMESTAMP();
|
|
@@ -2050,7 +2156,7 @@ function wire(opts = {}) {
|
|
|
2050
2156
|
}
|
|
2051
2157
|
try {
|
|
2052
2158
|
if (id === "claude-code") {
|
|
2053
|
-
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
|
|
2159
|
+
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess, launcherPath }));
|
|
2054
2160
|
} else {
|
|
2055
2161
|
targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
|
|
2056
2162
|
}
|
|
@@ -2121,10 +2227,28 @@ function wireFileTarget(args) {
|
|
|
2121
2227
|
postSha256: result.postSha256
|
|
2122
2228
|
};
|
|
2123
2229
|
}
|
|
2124
|
-
function
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
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 [
|
|
2128
2252
|
"mcp",
|
|
2129
2253
|
"add",
|
|
2130
2254
|
"--scope",
|
|
@@ -2135,12 +2259,28 @@ function wireClaudeCode(args) {
|
|
|
2135
2259
|
args.endpoint,
|
|
2136
2260
|
...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
|
|
2137
2261
|
];
|
|
2138
|
-
|
|
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);
|
|
2139
2274
|
const run = spawnSync(cmd, argList, {
|
|
2140
2275
|
encoding: "utf8",
|
|
2141
2276
|
shell: process.platform === "win32",
|
|
2142
2277
|
timeout: 1e4,
|
|
2143
|
-
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
|
|
2144
2284
|
});
|
|
2145
2285
|
if (run.error) {
|
|
2146
2286
|
return {
|
|
@@ -2173,6 +2313,10 @@ function wireClaudeCode(args) {
|
|
|
2173
2313
|
}
|
|
2174
2314
|
function resolveBridgeScript() {
|
|
2175
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;
|
|
2176
2320
|
const siblingBridge = join(here, "..", "dist", "mcp-bridge.js");
|
|
2177
2321
|
if (existsSync(siblingBridge)) return siblingBridge;
|
|
2178
2322
|
const srcBridge = join(here, "..", "mcp-bridge.js");
|
|
@@ -2462,13 +2606,13 @@ var TIER_PRICES = {
|
|
|
2462
2606
|
};
|
|
2463
2607
|
var ADVERTISED_TOOL_COUNTS = {
|
|
2464
2608
|
/** Free (L0) tools visible to anonymous / agent-class callers. */
|
|
2465
|
-
free:
|
|
2609
|
+
free: 27,
|
|
2466
2610
|
/** Premium (L1-L5) tools requiring x402 payment. */
|
|
2467
|
-
premium:
|
|
2611
|
+
premium: 9,
|
|
2468
2612
|
/** Messaging tools (member-self-only; excluded from external advertisement). */
|
|
2469
2613
|
messaging: 0,
|
|
2470
2614
|
/** Total publicly advertised (free + premium). */
|
|
2471
|
-
total:
|
|
2615
|
+
total: 36
|
|
2472
2616
|
};
|
|
2473
2617
|
var REVENUE_SPLIT = {
|
|
2474
2618
|
weaver: 0.75,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truealter/sdk",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.8",
|
|
4
|
+
"description": "~Alter Identity SDK: query the continuous identity field from any JavaScript/TypeScript environment",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
"require": "./dist/index.cjs"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"bin": {
|
|
18
|
-
"alter-identity": "./dist/bin/alter-identity.js",
|
|
19
|
-
"alter-mcp-bridge": "./dist/bin/mcp-bridge.js"
|
|
20
|
-
},
|
|
21
17
|
"files": [
|
|
22
18
|
"dist/",
|
|
23
19
|
"README.md",
|
|
@@ -47,6 +43,7 @@
|
|
|
47
43
|
"vitest": "^4.1.3"
|
|
48
44
|
},
|
|
49
45
|
"overrides": {
|
|
46
|
+
"esbuild": ">=0.28.1",
|
|
50
47
|
"postcss": ">=8.5.10"
|
|
51
48
|
},
|
|
52
49
|
"keywords": [
|