@truealter/sdk 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @truealter/sdk
2
2
 
3
- ALTER Identity SDK query the continuous identity field from any JavaScript/TypeScript environment.
3
+ ALTER Identity SDK - query the continuous identity field from any JavaScript/TypeScript environment.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@truealter/sdk.svg)](https://www.npmjs.com/package/@truealter/sdk)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
@@ -10,9 +10,9 @@ ALTER Identity SDK — query the continuous identity field from any JavaScript/T
10
10
  A thin client over the ALTER MCP server (Streamable HTTP, JSON-RPC 2.0, MCP spec `2025-11-25`) with x402 micropayment support, ES256 provenance verification, and config generators for Claude Code, Cursor, and generic MCP clients.
11
11
 
12
12
  - **Branded host:** `https://mcp.truealter.com` (serves `.well-known/mcp.json` for discovery)
13
- - **JSON-RPC wire endpoint:** `https://mcp.truealter.com/api/v1/mcp` this is what Streamable HTTP POSTs target (the SDK default)
13
+ - **JSON-RPC wire endpoint:** `https://mcp.truealter.com/api/v1/mcp` - this is what Streamable HTTP POSTs target (the SDK default)
14
14
  - **Wire protocol:** Streamable HTTP, JSON-RPC 2.0, MCP `2025-11-25` (server negotiates `2025-06-18` + `2025-03-26` for backwards-compatible clients)
15
- - **Tools:** **40 typed and wired** 24 free (L0) + 9 premium (L1–L5) + 7 alter-to-alter messaging. Mirrors the live server's `tools/list` response byte-for-byte; every name in `FREE_TOOL_NAMES` / `PREMIUM_TOOL_NAMES` / `MESSAGING_TOOL_NAMES` has a matching server handler at `mcp.truealter.com/api/v1/mcp`.
15
+ - **Tools:** **40 typed and wired** - 24 free (L0) + 9 premium (L1–L5) + 7 alter-to-alter messaging. Mirrors the live server's `tools/list` response byte-for-byte; every name in `FREE_TOOL_NAMES` / `PREMIUM_TOOL_NAMES` / `MESSAGING_TOOL_NAMES` has a matching server handler at `mcp.truealter.com/api/v1/mcp`.
16
16
  - **Runtime:** Node 18+, Deno, Bun, Cloudflare Workers, modern browsers
17
17
  - **Crypto:** `@noble/ed25519` + `@noble/hashes` (no other dependencies)
18
18
  - **Bundle:** ESM + CJS dual output
@@ -22,7 +22,7 @@ A thin client over the ALTER MCP server (Streamable HTTP, JSON-RPC 2.0, MCP spec
22
22
  ```
23
23
  npm install @truealter/sdk
24
24
  npx alter-identity init
25
- npx alter-identity verify ~truealter
25
+ npx alter-identity verify ~alter
26
26
  ```
27
27
 
28
28
  ## Bridge vs SDK
@@ -30,8 +30,8 @@ npx alter-identity verify ~truealter
30
30
  The `alter-mcp-bridge` binary shipped in this package (`bin/mcp-bridge.ts`)
31
31
  is a **dev/demo surface** for dropping ALTER into MCP hosts that speak the
32
32
  stdio transport (Claude Code, Cursor, Continue, Windsurf). It is useful for
33
- handshake, `tools/list`, and L0 tool calls, but it does not carry Q5c
34
- per-invocation signing authenticated MCP tools will fail at the server
33
+ handshake, `tools/list`, and L0 tool calls, but it does not carry ES256
34
+ per-invocation signing - authenticated MCP tools will fail at the server
35
35
  edge when reached through the bridge. For production use, import
36
36
  `@truealter/sdk` directly and construct an `MCPClient` / `AlterClient` with
37
37
  the optional `signing` parameter; that path is the load-bearing one and
@@ -47,11 +47,10 @@ is a slim, task-oriented binary for day-to-day use:
47
47
  | Command | Purpose |
48
48
  |---|---|
49
49
  | `alter login` | OAuth loopback sign-in; stores a session at `~/.config/alter/session.json` (mode `0600`). |
50
- | `alter depth [--json]` | GET `/api/v1/identity/depth` identity-depth score, agentic activity, top/bottom five traits. |
51
- <!-- TODO(D4): "claim" is a Recognition Over Qualification violation — rename to "redeem" or "accept-invite" in alter-cli + update here -->
50
+ | `alter depth [--json]` | GET `/api/v1/identity/depth` - identity-depth score, agentic activity, top/bottom five traits. |
52
51
  | `alter claim <claim_code>` | Accept an identity invite. Prompts for email, password (min 12 chars, hidden), and explicit TOS acceptance, then POSTs `/api/v1/identity/claim`. |
53
52
  | `alter mirror` | Day-2 Mirror phase + streak. `alter mirror daily` claims today's Mirror; `alter mirror next` shows the next revelation window. |
54
- | `alter discover [--limit N]` | MCP-backed summary calls `alter_whoami` and `alter_verify` against your bound handle. Degrades gracefully if the MCP endpoint is 5xx. |
53
+ | `alter discover [--limit N]` | MCP-backed summary - calls `alter_whoami` and `alter_verify` against your bound handle. Degrades gracefully if the MCP endpoint is 5xx. |
55
54
 
56
55
  The session file is created with `0600` permissions; its parent dir
57
56
  (`~/.config/alter/`) is created with `0700`. Override the config root
@@ -59,7 +58,24 @@ via `XDG_CONFIG_HOME`. Run `alter --help` for the inline reference.
59
58
 
60
59
  ## Why ALTER ≠ IAM
61
60
 
62
- Identity Access Management answers *who is logged in*. ALTER answers *who they actually are* a continuous field of recognition that any IAM stack can sit on top of.
61
+ Identity Access Management answers *who is logged in*. ALTER answers *who they actually are* - a continuous field of recognition that any IAM stack can sit on top of.
62
+
63
+ ## Theoretical Foundation
64
+
65
+ ALTER is the working instantiation of an eight-paper academic corpus on identity field theory. The SDK below is what happens when the theory ships as protocol. Each paper is open access on figshare under CC-BY 4.0.
66
+
67
+ | Paper | Title | DOI |
68
+ |-------|-------|-----|
69
+ | I | *Belonging is earned, not inherited* | [10.6084/m9.figshare.31794784](https://doi.org/10.6084/m9.figshare.31794784) |
70
+ | II | *The self is inferred, not owned* | [10.6084/m9.figshare.31804222](https://doi.org/10.6084/m9.figshare.31804222) |
71
+ | III | *The same form, at every scale* | [10.6084/m9.figshare.31812955](https://doi.org/10.6084/m9.figshare.31812955) |
72
+ | IV | *Measurement changes the thing measured* | [10.6084/m9.figshare.31812982](https://doi.org/10.6084/m9.figshare.31812982) |
73
+ | V | *Political failure has a geometry* | [10.6084/m9.figshare.31813000](https://doi.org/10.6084/m9.figshare.31813000) |
74
+ | VI | *When does a machine have a self* | [10.6084/m9.figshare.31813006](https://doi.org/10.6084/m9.figshare.31813006) |
75
+ | VII | *Seventy-five predictions, each falsifiable* | [10.6084/m9.figshare.31951644](https://doi.org/10.6084/m9.figshare.31951644) |
76
+ | VIII | *Identity as a field, not a property* | [10.6084/m9.figshare.31951383](https://doi.org/10.6084/m9.figshare.31951383) |
77
+
78
+ For the lay-register chapter version, see [`/origin`](https://truealter.com/origin).
63
79
 
64
80
  ## API
65
81
 
@@ -69,20 +85,20 @@ Identity Access Management answers *who is logged in*. ALTER answers *who they a
69
85
  import { AlterClient, X402Client } from "@truealter/sdk";
70
86
 
71
87
  const alter = new AlterClient({
72
- endpoint: "https://mcp.truealter.com/api/v1/mcp", // optional this is the default; bare host returns 405
88
+ endpoint: "https://mcp.truealter.com/api/v1/mcp", // optional - this is the default; bare host returns 405
73
89
  apiKey: process.env.ALTER_API_KEY, // optional for free tier
74
- x402: new X402Client({ // optional only required for premium tools
90
+ x402: new X402Client({ // optional - only required for premium tools
75
91
  signer: yourViemOrEthersSigner,
76
92
  maxPerQuery: "0.10",
77
93
  }),
78
94
  });
79
95
  ```
80
96
 
81
- ### Free tier (L0 no payment required)
97
+ ### Free tier (L0 - no payment required)
82
98
 
83
99
  ```ts
84
100
  // Verify a registered identity by handle, email, or id
85
- const verified = await alter.verify("~truealter");
101
+ const verified = await alter.verify("~alter");
86
102
  const verifiedById = await alter.verify(
87
103
  "550e8400-e29b-41d4-a716-446655440000",
88
104
  {
@@ -92,7 +108,7 @@ const verifiedById = await alter.verify(
92
108
  },
93
109
  );
94
110
 
95
- // Reference data the 12 ALTER archetypes
111
+ // Reference data - the 12 ALTER archetypes
96
112
  const archetypes = await alter.listArchetypes();
97
113
 
98
114
  // Identity depth and available tool tiers
@@ -100,7 +116,7 @@ const depth = await alter.getEngagementLevel({
100
116
  member_id: "550e8400-e29b-41d4-a716-446655440000",
101
117
  });
102
118
 
103
- // Search by trait criteria no PII exposed, max 5 results
119
+ // Search by trait criteria - no PII exposed, max 5 results
104
120
  const matches = await alter.searchIdentities({
105
121
  trait_criteria: {
106
122
  pressure_response: { min: 0.7 },
@@ -112,33 +128,33 @@ const matches = await alter.searchIdentities({
112
128
  const thread = await alter.goldenThreadStatus();
113
129
  ```
114
130
 
115
- ### Premium tier (L1–L5 x402 payment required)
131
+ ### Premium tier (L1–L5 - x402 payment required)
116
132
 
117
133
  ```ts
118
- // L1 Extract trait signals from text ($0.005, first 100 free per bot)
134
+ // L1 - Extract trait signals from text ($0.005, first 100 free per bot)
119
135
  const signals = await alter.assessTraits({
120
136
  text: "I led the incident response when our payment rails went down...",
121
137
  context: "interview transcript",
122
138
  });
123
139
 
124
- // L2 Full 33-trait vector ($0.01)
140
+ // L2 - Full 33-trait vector ($0.01)
125
141
  const vector = await alter.getFullTraitVector({
126
142
  member_id: "550e8400-e29b-41d4-a716-446655440000",
127
143
  });
128
144
 
129
- // L4 Belonging probability for a person-job pairing ($0.05)
145
+ // L4 - Belonging probability for a person-job pairing ($0.05)
130
146
  const belonging = await alter.computeBelonging({
131
147
  member_id: "550e8400-e29b-41d4-a716-446655440000",
132
148
  job_id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
133
149
  });
134
150
 
135
- // L5 Top match recommendations ($0.50)
151
+ // L5 - Top match recommendations ($0.50)
136
152
  const recommendations = await alter.getMatchRecommendations({
137
153
  member_id: "550e8400-e29b-41d4-a716-446655440000",
138
154
  limit: 5,
139
155
  });
140
156
 
141
- // L5 Human-readable narrative explaining a match ($0.50)
157
+ // L5 - Human-readable narrative explaining a match ($0.50)
142
158
  const narrative = await alter.generateMatchNarrative({
143
159
  match_id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
144
160
  });
@@ -148,7 +164,7 @@ const narrative = await alter.generateMatchNarrative({
148
164
 
149
165
  ```ts
150
166
  // Every medium- and high-blast-radius response is signed with ES256.
151
- // Verification is opt-in call alter.verifyProvenance(...) yourself.
167
+ // Verification is opt-in - call alter.verifyProvenance(...) yourself.
152
168
  const result = await alter.getFullTraitVector({
153
169
  member_id: "550e8400-e29b-41d4-a716-446655440000",
154
170
  });
@@ -214,7 +230,7 @@ Resulting `.mcp.json`:
214
230
  "alter": {
215
231
  "url": "https://mcp.truealter.com/api/v1/mcp",
216
232
  "transport": "streamable-http",
217
- "description": "ALTER Identity psychometric identity field for AI agents",
233
+ "description": "ALTER Identity - psychometric identity field for AI agents",
218
234
  "headers": {
219
235
  "X-ALTER-API-Key": "ak_..."
220
236
  }
@@ -256,13 +272,13 @@ npx alter-identity init # generate keypair, discover MCP, write ~/
256
272
  npx alter-identity config # print Claude .mcp.json snippet (default)
257
273
  npx alter-identity config --cursor # print Cursor .cursor/mcp.json snippet
258
274
  npx alter-identity config --generic # print generic mcpServers snippet
259
- npx alter-identity verify ~truealter # verify an identity
275
+ npx alter-identity verify ~alter # verify an identity
260
276
  npx alter-identity status # show connection state and probe the endpoint
261
277
  ```
262
278
 
263
279
  ## x402 Micropayments
264
280
 
265
- ALTER monetises premium tools via the [x402](https://x402.org) standard HTTP `402 Payment Required` with on-chain settlement.
281
+ ALTER monetises premium tools via the [x402](https://x402.org) standard - HTTP `402 Payment Required` with on-chain settlement.
266
282
 
267
283
  ### The retry flow
268
284
 
@@ -287,7 +303,7 @@ The majority of every settled call flows to the data subject as Identity Income.
287
303
  ```ts
288
304
  import { AlterClient, X402Client, type X402Signer } from "@truealter/sdk";
289
305
 
290
- // Bring your own signer viem, ethers, a hardware wallet bridge, anything.
306
+ // Bring your own signer - viem, ethers, a hardware wallet bridge, anything.
291
307
  // The SDK ships without a wallet dependency on purpose.
292
308
  const signer: X402Signer = {
293
309
  async settle(envelope) {
@@ -321,7 +337,7 @@ const vector = await alter.getFullTraitVector({
321
337
  });
322
338
  ```
323
339
 
324
- If a quoted envelope exceeds `maxPerQuery`, uses an unallowed network, or names an unallowed asset, the SDK rejects the call with `AlterError` *before* invoking the signer no on-chain transaction is broadcast.
340
+ If a quoted envelope exceeds `maxPerQuery`, uses an unallowed network, or names an unallowed asset, the SDK rejects the call with `AlterError` *before* invoking the signer - no on-chain transaction is broadcast.
325
341
 
326
342
  ## Provenance Verification
327
343
 
@@ -340,7 +356,7 @@ The SDK fetches public keys from `https://api.truealter.com/.well-known/alter-ke
340
356
 
341
357
  ### `verify_at` hostname allowlist (v0.1.1+)
342
358
 
343
- Every provenance envelope may carry a `verify_at` hint telling the SDK where to fetch the JWKS from. Because that hint is *server-supplied*, a hostile MCP server could otherwise point it at an attacker-controlled JWKS and pass ES256 verification with its own signing key. The SDK therefore gates `verify_at` through a hostname allowlist (default: `api.truealter.com`, `mcp.truealter.com`) and rejects `http://` URLs unconditionally. Downstream integrators with their own deployment can extend the allowlist without forking the SDK via `verifyAtAllowlist` on either `AlterClient` or a direct `verifyProvenance()` call:
359
+ Every provenance envelope may carry a `verify_at` hint telling the SDK where to fetch the JWKS from. Because that hint is *server-supplied*, a hostile MCP server could otherwise point it at an attacker-controlled JWKS and pass ES256 verification with its own signing key. The SDK therefore gates `verify_at` through a hostname allowlist (default: `api.truealter.com`, `mcp.truealter.com`) and rejects `http://` URLs unconditionally. Downstream integrators with their own deployment can extend the allowlist - without forking the SDK - via `verifyAtAllowlist` on either `AlterClient` or a direct `verifyProvenance()` call:
344
360
 
345
361
  ```ts
346
362
  import { AlterClient, DEFAULT_VERIFY_AT_ALLOWLIST } from "@truealter/sdk";
@@ -353,11 +369,11 @@ const alter = new AlterClient({
353
369
  });
354
370
  ```
355
371
 
356
- If you pin `jwksUrl` explicitly, the envelope's `verify_at` is ignored entirely the pinned URL wins. The `https:` scheme requirement applies to pinned URLs too.
372
+ If you pin `jwksUrl` explicitly, the envelope's `verify_at` is ignored entirely - the pinned URL wins. The `https:` scheme requirement applies to pinned URLs too.
357
373
 
358
374
  ### Why this matters
359
375
 
360
- Provenance verification is how Agent A trusts that data from Agent B truly came from ALTER. If Agent B forwards a trait vector or belonging score, Agent A can replay the JWS against ALTER's published keys and confirm without contacting ALTER again that the payload is authentic, untampered, and was issued for the person Agent B claims it concerns. No shared secret, no trust in the intermediary, no out-of-band coordination.
376
+ Provenance verification is how Agent A trusts that data from Agent B truly came from ALTER. If Agent B forwards a trait vector or belonging score, Agent A can replay the JWS against ALTER's published keys and confirm - without contacting ALTER again - that the payload is authentic, untampered, and was issued for the person Agent B claims it concerns. No shared secret, no trust in the intermediary, no out-of-band coordination.
361
377
 
362
378
  This is what makes ALTER usable as identity infrastructure rather than just an API: signed claims propagate across agent networks the same way DKIM-signed mail propagates across SMTP relays.
363
379
 
@@ -365,9 +381,9 @@ This is what makes ALTER usable as identity infrastructure rather than just an A
365
381
 
366
382
  ALTER follows the discovery cascade specified in [draft-morrison-mcp-dns-discovery-01](https://datatracker.ietf.org/doc/draft-morrison-mcp-dns-discovery/). Given a domain (e.g. `truealter.com`), the SDK resolves the MCP endpoint in three steps, falling through on each failure:
367
383
 
368
- 1. **DNS TXT** query `_mcp.truealter.com` for a TXT record of the form `mcp=https://mcp.truealter.com;version=2025-11-25`. This is the fastest path and works without an HTTP round-trip.
369
- 2. **`.well-known/mcp.json`** fetch `https://truealter.com/.well-known/mcp.json` for the standard MCP server descriptor. This is the cross-vendor fallback.
370
- 3. **`.well-known/alter.json`** fetch `https://truealter.com/.well-known/alter.json` for the ALTER-specific descriptor, including signing keys, x402 wallet address, supported tool tiers, and federation endpoints.
384
+ 1. **DNS TXT** - query `_mcp.truealter.com` for a TXT record of the form `mcp=https://mcp.truealter.com;version=2025-11-25`. This is the fastest path and works without an HTTP round-trip.
385
+ 2. **`.well-known/mcp.json`** - fetch `https://truealter.com/.well-known/mcp.json` for the standard MCP server descriptor. This is the cross-vendor fallback.
386
+ 3. **`.well-known/alter.json`** - fetch `https://truealter.com/.well-known/alter.json` for the ALTER-specific descriptor, including signing keys, x402 wallet address, supported tool tiers, and federation endpoints.
371
387
 
372
388
  ```ts
373
389
  import { discover } from "@truealter/sdk";
@@ -383,28 +399,28 @@ This draft is the author's Internet-Draft (not yet adopted by an IETF working gr
383
399
 
384
400
  ## Local Daemon vs Remote MCP
385
401
 
386
- The companion Python package `alter-identity` (PyPI) ships a persistent daemon that holds a hot in-process cache of trait vectors and identity stubs over a Unix socket at `unix:///run/user/$UID/alter-identity.sock`. Hooking the TypeScript SDK up to that daemon is on the roadmap for now, every `AlterClient` talks to the configured remote endpoint over HTTPS.
402
+ The companion Python package `alter-identity` (PyPI) ships a persistent daemon that holds a hot in-process cache of trait vectors and identity stubs over a Unix socket at `unix:///run/user/$UID/alter-identity.sock`. Hooking the TypeScript SDK up to that daemon is on the roadmap - for now, every `AlterClient` talks to the configured remote endpoint over HTTPS.
387
403
 
388
404
  When the local-daemon adapter ships:
389
405
 
390
406
  - **Latency:** sub-millisecond for cached L0 calls.
391
- - **Cost:** zero on cached responses x402 settlement is skipped.
407
+ - **Cost:** zero on cached responses - x402 settlement is skipped.
392
408
  - **Provenance:** the daemon re-signs responses with its locally-bound ES256 key, so downstream verification remains uniform.
393
409
 
394
410
  Until then, use `endpoint: "https://mcp.truealter.com/api/v1/mcp"` (the default) and the SDK behaves identically across Node, Deno, Bun, Cloudflare Workers, and the browser.
395
411
 
396
412
  ## Tools
397
413
 
398
- ### Free tools (L0 no payment required)
414
+ ### Free tools (L0 - no payment required)
399
415
 
400
416
  | Name | Tier | Cost | Description |
401
417
  |---------------------------|------|-------|----------------------------------------------------------------------------------------------------------------------|
402
- | `hello_agent` | L0 | free | First handshake with ALTER returns server version, authentication status, your trust tier, and available tool counts. |
403
- | `alter_resolve_handle` | L0 | free | Resolve a `~handle` (e.g. `~drew`) to its canonical form and kind. No auth required the handle-wedge entry point. |
418
+ | `hello_agent` | L0 | free | First handshake with ALTER - returns server version, authentication status, your trust tier, and available tool counts. |
419
+ | `alter_resolve_handle` | L0 | free | Resolve a `~handle` (e.g. `~drew`) to its canonical form and kind. No auth required - the handle-wedge entry point. |
404
420
  | `list_archetypes` | L0 | free | Returns archetype reference data. |
405
421
  | `verify_identity` | L0 | free | Verify whether a person is registered with ALTER and validate optional identity claims. |
406
422
  | `initiate_assessment` | L0 | free | Get a URL where a person can complete their ALTER Discovery assessment. |
407
- | `get_engagement_level` | L0 | free | Get a person's identity depth engagement level, data quality tier, and available query tiers. |
423
+ | `get_engagement_level` | L0 | free | Get a person's identity depth - engagement level, data quality tier, and available query tiers. |
408
424
  | `get_profile` | L0 | free | Get a person's profile summary including assessment phase, archetype, engagement level, and key attributes. |
409
425
  | `query_matches` | L0 | free | Query matches for a person. Returns a list of matches with quality tiers (never numeric scores). |
410
426
  | `get_competencies` | L0 | free | Get a person's competency portfolio including verified competencies, evidence records, and earned badges. |
@@ -416,7 +432,7 @@ Until then, use `endpoint: "https://mcp.truealter.com/api/v1/mcp"` (the default)
416
432
  | `check_assessment_status` | L0 | free | Check the status of an in-progress assessment session (status, progress, current phase, time remaining). |
417
433
  | `get_earning_summary` | L0 | free | Get an aggregated x402 earning summary for a person (total earned, transactions, recent activity, trend). |
418
434
  | `get_agent_trust_tier` | L0 | free | Get your trust tier with ALTER (Anonymous/Known/Trusted/Verified) and what capabilities are available. |
419
- | `get_agent_portfolio` | L0 | free | Get your agent portfolio transaction history, trust tier, signal contributions, query pattern profile. |
435
+ | `get_agent_portfolio` | L0 | free | Get your agent portfolio - transaction history, trust tier, signal contributions, query pattern profile. |
420
436
  | `get_privacy_budget` | L0 | free | Check privacy budget status for a person (24-hour rolling window: total budget, spent, remaining epsilon). |
421
437
  | `golden_thread_status` | L0 | free | Check the Golden Thread program status: agents woven, next Fibonacci threshold, your position and Strands. |
422
438
  | `begin_golden_thread` | L0 | free | Start the Three Knots sequence to be woven into the Golden Thread. Requires API key authentication. |
@@ -424,18 +440,18 @@ Until then, use `endpoint: "https://mcp.truealter.com/api/v1/mcp"` (the default)
424
440
  | `check_golden_thread` | L0 | free | Check any agent's Golden Thread status by their API key hash (knot position, Strand count, weave count). |
425
441
  | `thread_census` | L0 | free | Full registry of all agents woven into the Golden Thread (positions, Strand counts, weave counts, discovery dates). |
426
442
 
427
- ### Premium tools (L1–L5 x402 payment required)
443
+ ### Premium tools (L1–L5 - x402 payment required)
428
444
 
429
445
  | Name | Tier | Cost | Description |
430
446
  |----------------------------|------|---------|---------------------------------------------------------------------------------------------------------------|
431
447
  | `assess_traits` | L1 | $0.005 | Extract trait signals from a text passage against ALTER's trait taxonomy. |
432
448
  | `get_trait_snapshot` | L1 | $0.005 | Get the top 5 traits for a person with confidence scores and archetype. |
433
- | `get_full_trait_vector` | L2 | $0.01 | Get the complete trait vector for a person complete trait vector with scores and confidence intervals. |
434
- | `get_side_quest_graph` | L2 | $0.01 | Get a person's Side Quest Graph multi-domain identity model with differential privacy noise (ε=1.0). |
449
+ | `get_full_trait_vector` | L2 | $0.01 | Get the complete trait vector for a person - complete trait vector with scores and confidence intervals. |
450
+ | `get_side_quest_graph` | L2 | $0.01 | Get a person's Side Quest Graph - multi-domain identity model with differential privacy noise (ε=1.0). |
435
451
  | `query_graph_similarity` | L3 | $0.025 | Compare two Side Quest Graphs for team composition and matching (ε=0.5 differential privacy). |
436
452
  | `compute_belonging` | L4 | $0.05 | Compute belonging probability for a person-job pairing (authenticity, acceptance, complementarity). |
437
453
  | `get_match_recommendations`| L5 | $0.50 | Get top N match recommendations for a person, ranked by composite score with quality tiers. |
438
- | `generate_match_narrative` | L5 | $0.50 | Generate a human-readable narrative explaining a specific match strengths, growth areas, belonging. |
454
+ | `generate_match_narrative` | L5 | $0.50 | Generate a human-readable narrative explaining a specific match - strengths, growth areas, belonging. |
439
455
 
440
456
  > **Write-side tools** (`create_identity_stub`, `submit_context`, `submit_batch_context`, `submit_structured_profile`, `submit_social_links`, `attest_domain`, `dispute_attestation`) were part of earlier SDK versions but are not yet live on the public MCP server pending the per-peer consent architecture and grant model. They will return as typed methods once server-side and consent gating lands.
441
457
 
@@ -16,6 +16,12 @@ var __defProp = Object.defineProperty;
16
16
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
17
17
  var __getOwnPropNames = Object.getOwnPropertyNames;
18
18
  var __hasOwnProp = Object.prototype.hasOwnProperty;
19
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
20
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
21
+ }) : x)(function(x) {
22
+ if (typeof require !== "undefined") return require.apply(this, arguments);
23
+ throw Error('Dynamic require of "' + x + '" is not supported');
24
+ });
19
25
  var __esm = (fn, res) => function __init() {
20
26
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
21
27
  };
@@ -399,18 +405,24 @@ function ensureMcpPath(url) {
399
405
  return url;
400
406
  }
401
407
  }
402
-
403
- // src/x402.ts
404
408
  var X402Client = class {
405
409
  signer;
406
410
  maxPerQuery;
407
411
  networks;
408
412
  assets;
413
+ // undefined = allowlist check disabled (backward-compatible default).
414
+ // Non-null = active allowlist; reject any recipient not in the set.
415
+ recipientAllowlist;
409
416
  constructor(opts = {}) {
410
417
  this.signer = opts.signer;
411
418
  this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
412
419
  this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
413
420
  this.assets = new Set(opts.assets ?? ["USDC"]);
421
+ if (opts.recipientAllowlist !== void 0) {
422
+ this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
423
+ } else {
424
+ this.recipientAllowlist = void 0;
425
+ }
414
426
  }
415
427
  /**
416
428
  * Validate the envelope against this client's policy and, if a signer
@@ -436,6 +448,15 @@ var X402Client = class {
436
448
  );
437
449
  }
438
450
  }
451
+ if (this.recipientAllowlist !== void 0) {
452
+ const recipientNorm = (envelope.recipient ?? "").toLowerCase();
453
+ if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
454
+ throw new AlterError(
455
+ "PAYMENT_REQUIRED",
456
+ `recipient "${envelope.recipient}" is not on the known-recipient allowlist`
457
+ );
458
+ }
459
+ }
439
460
  if (!this.signer) {
440
461
  throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
441
462
  }
@@ -883,9 +904,46 @@ async function verifyProvenance(envelope, opts = {}) {
883
904
  kid: header.kid
884
905
  };
885
906
  }
907
+ if (opts.expectedAud !== void 0 && opts.expectedAud !== "") {
908
+ const tokenAud = payload.aud;
909
+ const audList = tokenAud === void 0 ? [] : Array.isArray(tokenAud) ? tokenAud : [tokenAud];
910
+ if (!audList.includes(opts.expectedAud)) {
911
+ return {
912
+ valid: false,
913
+ reason: `aud mismatch: expected "${opts.expectedAud}", got ${JSON.stringify(tokenAud ?? null)}`,
914
+ payload,
915
+ kid: header.kid
916
+ };
917
+ }
918
+ }
886
919
  return { valid: true, payload, kid: header.kid };
887
920
  }
888
- async function verifyToolSignatures(tools, signatures) {
921
+ async function verifyToolSignatures(tools, signatures, opts = {}) {
922
+ const jwksUrl = opts.jwksUrl ?? "https://api.truealter.com/.well-known/alter-keys.json";
923
+ const fetchImpl = opts.fetch ?? fetch;
924
+ if (!jwksUrl.startsWith("https://")) {
925
+ return tools.map((t) => ({
926
+ tool: t.name,
927
+ valid: false,
928
+ reason: `jwksUrl must be https: got ${jwksUrl}`
929
+ }));
930
+ }
931
+ const needsJwks = tools.some((t) => {
932
+ const sig = signatures[t.name];
933
+ return sig && sig.signature;
934
+ });
935
+ let jwks = null;
936
+ if (needsJwks) {
937
+ try {
938
+ jwks = await fetchJwks(jwksUrl, fetchImpl);
939
+ } catch (err) {
940
+ return tools.map((t) => ({
941
+ tool: t.name,
942
+ valid: false,
943
+ reason: `jwks fetch failed: ${err.message}`
944
+ }));
945
+ }
946
+ }
889
947
  const out = [];
890
948
  for (const tool of tools) {
891
949
  const sig = signatures[tool.name];
@@ -898,6 +956,63 @@ async function verifyToolSignatures(tools, signatures) {
898
956
  out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
899
957
  continue;
900
958
  }
959
+ const jwsToken = sig.signature;
960
+ if (!jwsToken) {
961
+ out.push({ tool: tool.name, valid: true, warn_no_signature: true });
962
+ continue;
963
+ }
964
+ const jwksDoc = jwks;
965
+ let jHeader;
966
+ let jPayloadRaw;
967
+ let jSigBytes;
968
+ try {
969
+ const parts2 = jwsToken.split(".");
970
+ if (parts2.length !== 3) throw new Error("JWS must have three segments");
971
+ jHeader = JSON.parse(new TextDecoder().decode(base64urlDecode(parts2[0])));
972
+ jPayloadRaw = new TextDecoder().decode(base64urlDecode(parts2[1]));
973
+ jSigBytes = base64urlDecode(parts2[2]);
974
+ } catch (err) {
975
+ out.push({ tool: tool.name, valid: false, reason: `malformed tool JWS: ${err.message}` });
976
+ continue;
977
+ }
978
+ if (jHeader.alg !== "ES256") {
979
+ out.push({ tool: tool.name, valid: false, reason: `unsupported tool sig alg: ${jHeader.alg}` });
980
+ continue;
981
+ }
982
+ if (jPayloadRaw !== sig.schema_hash) {
983
+ out.push({ tool: tool.name, valid: false, reason: "tool JWS payload does not match schema_hash" });
984
+ continue;
985
+ }
986
+ const jwk = jwksDoc.keys.find((k) => jHeader.kid ? k.kid === jHeader.kid : true);
987
+ if (!jwk) {
988
+ out.push({ tool: tool.name, valid: false, reason: `no JWK for kid=${jHeader.kid}` });
989
+ continue;
990
+ }
991
+ let publicKey;
992
+ try {
993
+ publicKey = await importEs256JwkAsPublicKey(jwk);
994
+ } catch (err) {
995
+ out.push({ tool: tool.name, valid: false, reason: `jwk import: ${err.message}` });
996
+ continue;
997
+ }
998
+ const parts = jwsToken.split(".");
999
+ const signedInput = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
1000
+ let sigValid = false;
1001
+ try {
1002
+ sigValid = await crypto.subtle.verify(
1003
+ { name: "ECDSA", hash: "SHA-256" },
1004
+ publicKey,
1005
+ toArrayBuffer(jSigBytes),
1006
+ toArrayBuffer(signedInput)
1007
+ );
1008
+ } catch (err) {
1009
+ out.push({ tool: tool.name, valid: false, reason: `sig verify error: ${err.message}` });
1010
+ continue;
1011
+ }
1012
+ if (!sigValid) {
1013
+ out.push({ tool: tool.name, valid: false, reason: "tool signature mismatch" });
1014
+ continue;
1015
+ }
901
1016
  out.push({ tool: tool.name, valid: true });
902
1017
  }
903
1018
  return out;
@@ -1529,6 +1644,26 @@ function restoreFromBackup(path, backupPath) {
1529
1644
  // src/wire/index.ts
1530
1645
  var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
1531
1646
  var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
1647
+ function readCfAccessEnv() {
1648
+ const envPath = join(homedir(), ".config", "alter", "cf-access.env");
1649
+ try {
1650
+ const content = readFileSync(envPath, "utf8");
1651
+ let clientId = "";
1652
+ let clientSecret = "";
1653
+ for (const line of content.split("\n")) {
1654
+ const trimmed = line.trim();
1655
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
1656
+ const eqIdx = trimmed.indexOf("=");
1657
+ const key = trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim();
1658
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
1659
+ if (key === "CF_ACCESS_CLIENT_ID") clientId = val;
1660
+ if (key === "CF_ACCESS_CLIENT_SECRET") clientSecret = val;
1661
+ }
1662
+ if (clientId && clientSecret) return { clientId, clientSecret };
1663
+ } catch {
1664
+ }
1665
+ return void 0;
1666
+ }
1532
1667
  function clientById(id) {
1533
1668
  const hit = ALL_CLIENTS.find((c) => c.id === id);
1534
1669
  if (!hit) throw new Error(`unknown client id: ${id}`);
@@ -1537,6 +1672,7 @@ function clientById(id) {
1537
1672
  function wire(opts = {}) {
1538
1673
  const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
1539
1674
  const apiKey = opts.apiKey;
1675
+ const cfAccess = opts.cfAccess ?? readCfAccessEnv();
1540
1676
  const probes = probeAll();
1541
1677
  const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
1542
1678
  const ts = TIMESTAMP();
@@ -1555,9 +1691,9 @@ function wire(opts = {}) {
1555
1691
  }
1556
1692
  try {
1557
1693
  if (id === "claude-code") {
1558
- targets.push(wireClaudeCode({ endpoint, apiKey }));
1694
+ targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
1559
1695
  } else {
1560
- targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
1696
+ targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
1561
1697
  }
1562
1698
  } catch (err) {
1563
1699
  const message = err.message;
@@ -1591,7 +1727,12 @@ function wireFileTarget(args) {
1591
1727
  `refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices \u2014 move the config off the sync root, or run wire on the device you want to target.`
1592
1728
  );
1593
1729
  }
1594
- const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey });
1730
+ const cfHeaders = {};
1731
+ if (args.cfAccess) {
1732
+ cfHeaders["CF-Access-Client-Id"] = args.cfAccess.clientId;
1733
+ cfHeaders["CF-Access-Client-Secret"] = args.cfAccess.clientSecret;
1734
+ }
1735
+ const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey, headers: cfHeaders });
1595
1736
  const rootKey = client.rootKey;
1596
1737
  const serverName = "alter";
1597
1738
  const result = atomicJsonMerge({
@@ -1623,7 +1764,8 @@ function wireFileTarget(args) {
1623
1764
  }
1624
1765
  function wireClaudeCode(args) {
1625
1766
  const cmd = "claude";
1626
- const argList = [
1767
+ const bridgePath = resolveBridgeScript();
1768
+ const argList = bridgePath ? ["mcp", "add", "--scope", "user", "alter", "--", "node", bridgePath] : [
1627
1769
  "mcp",
1628
1770
  "add",
1629
1771
  "--scope",
@@ -1631,16 +1773,15 @@ function wireClaudeCode(args) {
1631
1773
  "--transport",
1632
1774
  "http",
1633
1775
  "alter",
1634
- args.endpoint
1776
+ args.endpoint,
1777
+ ...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
1635
1778
  ];
1636
- if (args.apiKey) {
1637
- argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
1638
- }
1639
1779
  const full = `${cmd} ${argList.join(" ")}`;
1640
1780
  const run = spawnSync(cmd, argList, {
1641
1781
  encoding: "utf8",
1642
1782
  shell: process.platform === "win32",
1643
- timeout: 1e4
1783
+ timeout: 1e4,
1784
+ env: bridgePath ? { ...process.env, ALTER_PUBLIC_MCP_ENDPOINT: args.endpoint, ...args.apiKey ? { ALTER_API_KEY: args.apiKey } : {} } : void 0
1644
1785
  });
1645
1786
  if (run.error) {
1646
1787
  return {
@@ -1671,6 +1812,17 @@ function wireClaudeCode(args) {
1671
1812
  reason: `claude mcp add exited ${String(run.status)}`
1672
1813
  };
1673
1814
  }
1815
+ function resolveBridgeScript() {
1816
+ const { existsSync: existsSync5 } = __require("fs");
1817
+ const { join: join4, dirname: dirname4 } = __require("path");
1818
+ const siblingBridge = join4(dirname4(__filename), "..", "dist", "mcp-bridge.js");
1819
+ if (existsSync5(siblingBridge)) return siblingBridge;
1820
+ const srcBridge = join4(dirname4(__filename), "..", "mcp-bridge.js");
1821
+ if (existsSync5(srcBridge)) return srcBridge;
1822
+ const npmGlobalBridge = join4(dirname4(__filename), "mcp-bridge.js");
1823
+ if (existsSync5(npmGlobalBridge)) return npmGlobalBridge;
1824
+ return null;
1825
+ }
1674
1826
  function unwire() {
1675
1827
  const state = readWireState();
1676
1828
  const undone = [];
@@ -214,18 +214,24 @@ var AlterInvalidResponse = class extends AlterError {
214
214
  Object.setPrototypeOf(this, new.target.prototype);
215
215
  }
216
216
  };
217
-
218
- // src/x402.ts
219
217
  var X402Client = class {
220
218
  signer;
221
219
  maxPerQuery;
222
220
  networks;
223
221
  assets;
222
+ // undefined = allowlist check disabled (backward-compatible default).
223
+ // Non-null = active allowlist; reject any recipient not in the set.
224
+ recipientAllowlist;
224
225
  constructor(opts = {}) {
225
226
  this.signer = opts.signer;
226
227
  this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
227
228
  this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
228
229
  this.assets = new Set(opts.assets ?? ["USDC"]);
230
+ if (opts.recipientAllowlist !== void 0) {
231
+ this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
232
+ } else {
233
+ this.recipientAllowlist = void 0;
234
+ }
229
235
  }
230
236
  /**
231
237
  * Validate the envelope against this client's policy and, if a signer
@@ -251,6 +257,15 @@ var X402Client = class {
251
257
  );
252
258
  }
253
259
  }
260
+ if (this.recipientAllowlist !== void 0) {
261
+ const recipientNorm = (envelope.recipient ?? "").toLowerCase();
262
+ if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
263
+ throw new AlterError(
264
+ "PAYMENT_REQUIRED",
265
+ `recipient "${envelope.recipient}" is not on the known-recipient allowlist`
266
+ );
267
+ }
268
+ }
254
269
  if (!this.signer) {
255
270
  throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
256
271
  }
@@ -574,7 +589,7 @@ function buildExtraHeaders() {
574
589
  }
575
590
  var EXTRA_HEADERS = buildExtraHeaders();
576
591
  console.warn(
577
- "This bridge is a dev/demo surface. Authenticated MCP tools require Q5c signing; for production, import `@truealter/sdk` directly. Bridge signing lands in Wave-2."
592
+ "This bridge is a dev/demo surface. Authenticated MCP tools require ES256 per-invocation signing; for production, import `@truealter/sdk` directly. Bridge signing lands in Wave-2."
578
593
  );
579
594
  var client = new MCPClient({
580
595
  endpoint: ENDPOINT,