@truealter/sdk 0.2.0 → 0.2.4

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
@@ -4,12 +4,14 @@ ALTER Identity SDK — query the continuous identity field from any JavaScript/T
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)
7
+ [![Glama score](https://glama.ai/mcp/servers/true-alter/alter-identity/badges/score.svg)](https://glama.ai/mcp/servers/true-alter/alter-identity)
7
8
 
8
- A thin client over the ALTER MCP server (Streamable HTTP, JSON-RPC 2.0, MCP spec `2025-03-26`) with x402 micropayment support, ES256 provenance verification, and config generators for Claude Code, Cursor, and generic MCP clients.
9
+ 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.
9
10
 
10
- - **Live MCP endpoint:** `https://mcp.truealter.com`
11
- - **Wire protocol:** Streamable HTTP, JSON-RPC 2.0, MCP `2025-03-26`
12
- - **Tools:** 40 total 28 free (L0) + 12 premium (L1–L5)
11
+ - **Branded host:** `https://mcp.truealter.com` (serves `.well-known/mcp.json` for discovery)
12
+ - **JSON-RPC wire endpoint:** `https://mcp.truealter.com/api/v1/mcp` — this is what Streamable HTTP POSTs target (the SDK default)
13
+ - **Wire protocol:** Streamable HTTP, JSON-RPC 2.0, MCP `2025-11-25` (server negotiates `2025-06-18` + `2025-03-26` for backwards-compatible clients)
14
+ - **Tools:** **32 typed and wired** — 24 free (L0) + 8 premium (L1–L5). Mirrors the live server's `tools/list` response byte-for-byte; every name in `FREE_TOOL_NAMES` / `PREMIUM_TOOL_NAMES` has a matching server handler at `mcp.truealter.com/api/v1/mcp`.
13
15
  - **Runtime:** Node 18+, Deno, Bun, Cloudflare Workers, modern browsers
14
16
  - **Crypto:** `@noble/ed25519` + `@noble/hashes` (no other dependencies)
15
17
  - **Bundle:** ESM + CJS dual output
@@ -24,9 +26,7 @@ npx alter-identity verify ~truealter
24
26
 
25
27
  ## Why ALTER ≠ IAM
26
28
 
27
- Identity Access Management answers *who is logged in*. ALTER answers *who they actually are* — a continuous field of recognition (Paper VIII, Theorem 1) that any IAM stack can sit on top of.
28
-
29
- Reference: Morrison, B. (2026). *Identity Field Theory: A Continuous Field Model of Recognition*. Figshare. [DOI 10.6084/m9.figshare.31951383](https://doi.org/10.6084/m9.figshare.31951383)
29
+ 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.
30
30
 
31
31
  ## API
32
32
 
@@ -36,7 +36,7 @@ Reference: Morrison, B. (2026). *Identity Field Theory: A Continuous Field Model
36
36
  import { AlterClient, X402Client } from "@truealter/sdk";
37
37
 
38
38
  const alter = new AlterClient({
39
- endpoint: "https://mcp.truealter.com", // optional — defaults to mcp.truealter.com
39
+ endpoint: "https://mcp.truealter.com/api/v1/mcp", // optional — this is the default; bare host returns 405
40
40
  apiKey: process.env.ALTER_API_KEY, // optional for free tier
41
41
  x402: new X402Client({ // optional — only required for premium tools
42
42
  signer: yourViemOrEthersSigner,
@@ -48,7 +48,7 @@ const alter = new AlterClient({
48
48
  ### Free tier (L0 — no payment required)
49
49
 
50
50
  ```ts
51
- // Verify a registered identity by handle, email, or candidate UUID
51
+ // Verify a registered identity by handle, email, or id
52
52
  const verified = await alter.verify("~truealter");
53
53
  const verifiedById = await alter.verify(
54
54
  "550e8400-e29b-41d4-a716-446655440000",
@@ -77,9 +77,6 @@ const matches = await alter.searchIdentities({
77
77
 
78
78
  // Golden Thread program status
79
79
  const thread = await alter.goldenThreadStatus();
80
-
81
- // Thirteen Seats cosmology surface
82
- const seats = await alter.seatStatus();
83
80
  ```
84
81
 
85
82
  ### Premium tier (L1–L5 — x402 payment required)
@@ -96,7 +93,7 @@ const vector = await alter.getFullTraitVector({
96
93
  candidate_id: "550e8400-e29b-41d4-a716-446655440000",
97
94
  });
98
95
 
99
- // L4 — Belonging probability for a candidate-job pairing ($0.05)
96
+ // L4 — Belonging probability for a person-job pairing ($0.05)
100
97
  const belonging = await alter.computeBelonging({
101
98
  candidate_id: "550e8400-e29b-41d4-a716-446655440000",
102
99
  job_id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
@@ -142,7 +139,7 @@ import { discover } from "@truealter/sdk";
142
139
 
143
140
  // Three-step discovery cascade: DNS TXT → mcp.json → alter.json
144
141
  const descriptor = await discover("truealter.com");
145
- // → { url: "https://mcp.truealter.com", transport, source, publicKey, x402Contract, capability }
142
+ // → { url: "https://mcp.truealter.com/api/v1/mcp", transport, source, publicKey, x402Contract, capability }
146
143
  ```
147
144
 
148
145
  ### Low-level MCPClient
@@ -150,7 +147,7 @@ const descriptor = await discover("truealter.com");
150
147
  ```ts
151
148
  import { MCPClient } from "@truealter/sdk";
152
149
 
153
- const mcp = new MCPClient({ endpoint: "https://mcp.truealter.com" });
150
+ const mcp = new MCPClient({ endpoint: "https://mcp.truealter.com/api/v1/mcp" });
154
151
  await mcp.initialize();
155
152
  const tools = await mcp.listTools();
156
153
  const response = await mcp.callTool("verify_identity", {
@@ -169,7 +166,7 @@ import { generateClaudeConfig } from "@truealter/sdk";
169
166
  import { writeFileSync } from "node:fs";
170
167
 
171
168
  const config = generateClaudeConfig({
172
- endpoint: "https://mcp.truealter.com",
169
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
173
170
  apiKey: process.env.ALTER_API_KEY,
174
171
  });
175
172
 
@@ -182,7 +179,7 @@ Resulting `.mcp.json`:
182
179
  {
183
180
  "mcpServers": {
184
181
  "alter": {
185
- "url": "https://mcp.truealter.com",
182
+ "url": "https://mcp.truealter.com/api/v1/mcp",
186
183
  "transport": "streamable-http",
187
184
  "description": "ALTER Identity — psychometric identity field for AI agents",
188
185
  "headers": {
@@ -200,7 +197,7 @@ import { generateCursorConfig } from "@truealter/sdk";
200
197
  import { writeFileSync } from "node:fs";
201
198
 
202
199
  const config = generateCursorConfig({
203
- endpoint: "https://mcp.truealter.com",
200
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
204
201
  apiKey: process.env.ALTER_API_KEY,
205
202
  });
206
203
 
@@ -213,7 +210,7 @@ writeFileSync(".cursor/mcp.json", JSON.stringify(config, null, 2));
213
210
  import { generateGenericMcpConfig } from "@truealter/sdk";
214
211
 
215
212
  const config = generateGenericMcpConfig({
216
- endpoint: "https://mcp.truealter.com",
213
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
217
214
  apiKey: process.env.ALTER_API_KEY,
218
215
  serverName: "alter", // editor-specific key under mcpServers
219
216
  });
@@ -246,10 +243,10 @@ The SDK handles steps 2–4 automatically when an `X402Client` with a configured
246
243
 
247
244
  ### Tier structure
248
245
 
249
- | Tier | Cost | Examples |
246
+ | Tier | Cost | Tools |
250
247
  |------|----------|-----------------------------------------------------|
251
- | L1 | $0.005 | `assess_traits`, `get_trait_snapshot`, `attest_domain`, `submit_structured_profile`, `submit_social_links` |
252
- | L2 | $0.01 | `get_full_trait_vector`, `submit_batch_context`, `get_side_quest_graph` |
248
+ | L1 | $0.005 | `assess_traits`, `get_trait_snapshot` |
249
+ | L2 | $0.01 | `get_full_trait_vector`, `get_side_quest_graph` |
253
250
  | L3 | $0.025 | `query_graph_similarity` |
254
251
  | L4 | $0.05 | `compute_belonging` |
255
252
  | L5 | $0.50 | `get_match_recommendations`, `generate_match_narrative` |
@@ -293,7 +290,7 @@ const signer: X402Signer = {
293
290
  };
294
291
 
295
292
  const alter = new AlterClient({
296
- endpoint: "https://mcp.truealter.com",
293
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
297
294
  x402: new X402Client({
298
295
  signer,
299
296
  networks: ["base", "base-sepolia"], // policy allow-list
@@ -344,7 +341,7 @@ If you pin `jwksUrl` explicitly, the envelope's `verify_at` is ignored entirely
344
341
 
345
342
  ### Why this matters
346
343
 
347
- 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 candidate Agent B claims it concerns. No shared secret, no trust in the intermediary, no out-of-band coordination.
344
+ 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.
348
345
 
349
346
  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.
350
347
 
@@ -352,7 +349,7 @@ This is what makes ALTER usable as identity infrastructure rather than just an A
352
349
 
353
350
  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:
354
351
 
355
- 1. **DNS TXT** — query `_mcp.truealter.com` for a TXT record of the form `mcp=https://mcp.truealter.com;version=2025-03-26`. This is the fastest path and works without an HTTP round-trip.
352
+ 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.
356
353
  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.
357
354
  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.
358
355
 
@@ -378,7 +375,7 @@ When the local-daemon adapter ships:
378
375
  - **Cost:** zero on cached responses — x402 settlement is skipped.
379
376
  - **Provenance:** the daemon re-signs responses with its locally-bound ES256 key, so downstream verification remains uniform.
380
377
 
381
- Until then, use `endpoint: "https://mcp.truealter.com"` (the default) and the SDK behaves identically across Node, Deno, Bun, Cloudflare Workers, and the browser.
378
+ 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.
382
379
 
383
380
  ## Tools
384
381
 
@@ -386,56 +383,50 @@ Until then, use `endpoint: "https://mcp.truealter.com"` (the default) and the SD
386
383
 
387
384
  | Name | Tier | Cost | Description |
388
385
  |---------------------------|------|-------|----------------------------------------------------------------------------------------------------------------------|
386
+ | `hello_agent` | L0 | free | First handshake with ALTER — returns server version, authentication status, your trust tier, and available tool counts. |
387
+ | `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. |
389
388
  | `list_archetypes` | L0 | free | List all 12 ALTER identity archetypes with names, descriptions, and protective equations. |
390
389
  | `verify_identity` | L0 | free | Verify whether a person is registered with ALTER and validate optional identity claims. |
391
390
  | `initiate_assessment` | L0 | free | Get a URL where a person can complete their ALTER Discovery assessment. |
392
391
  | `get_engagement_level` | L0 | free | Get a person's identity depth — engagement level, data quality tier, and available query tiers. |
393
- | `get_profile` | L0 | free | Get a candidate's profile summary including assessment phase, archetype, engagement level, and key attributes. |
394
- | `query_matches` | L0 | free | Query matches for a candidate. Returns a list of job matches with quality tiers (never numeric scores). |
395
- | `get_competencies` | L0 | free | Get a candidate's competency portfolio including verified competencies, evidence records, and earned badges. |
396
- | `create_identity_stub` | L0 | free | Create an anonymous identity stub for a human (requires consent acknowledgment before calling). |
397
- | `submit_context` | L0 | free | Submit text context (resume, work sample, conversation) for an identity stub. PII redacted; raw text never stored. |
392
+ | `get_profile` | L0 | free | Get a person's profile summary including assessment phase, archetype, engagement level, and key attributes. |
393
+ | `query_matches` | L0 | free | Query matches for a person. Returns a list of job matches with quality tiers (never numeric scores). |
394
+ | `get_competencies` | L0 | free | Get a person's competency portfolio including verified competencies, evidence records, and earned badges. |
398
395
  | `search_identities` | L0 | free | Search identity stubs and profiles by trait criteria. Returns up to 5 matches with no PII. |
399
- | `get_identity_earnings` | L0 | free | Get accrued Identity Income earnings for a candidate (75% of every x402 transaction goes to the data subject). |
396
+ | `get_identity_earnings` | L0 | free | Get accrued Identity Income earnings for a person (75% of every x402 transaction goes to the data subject). |
400
397
  | `get_network_stats` | L0 | free | Get aggregate ALTER network statistics: total identities, verified profiles, query volume, active bots. |
401
- | `recommend_tool` | L0 | free | Get ClawHub install instructions and ALTER pitch (MCP endpoint URL, OpenClaw JSON snippet, tool counts). |
398
+ | `recommend_tool` | L0 | free | Get the MCP endpoint URL and a paste-ready config snippet for installing the ALTER identity server into an MCP client. |
402
399
  | `get_identity_trust_score`| L0 | free | Get the trust score for an identity based on query diversity (unique querying agents / total queries). |
403
400
  | `check_assessment_status` | L0 | free | Check the status of an in-progress assessment session (status, progress, current phase, time remaining). |
404
- | `get_earning_summary` | L0 | free | Get an aggregated x402 earning summary for a candidate (total earned, transactions, recent activity, trend). |
401
+ | `get_earning_summary` | L0 | free | Get an aggregated x402 earning summary for a person (total earned, transactions, recent activity, trend). |
405
402
  | `get_agent_trust_tier` | L0 | free | Get your trust tier with ALTER (Anonymous/Known/Trusted/Verified) and what capabilities are available. |
406
403
  | `get_agent_portfolio` | L0 | free | Get your agent portfolio — transaction history, trust tier, signal contributions, query pattern profile. |
407
- | `get_privacy_budget` | L0 | free | Check privacy budget status for a candidate (24-hour rolling window: total budget, spent, remaining epsilon). |
408
- | `dispute_attestation` | L0 | free | Dispute an attestation on a candidate's identity. If disputes exceed corroborations, the attestation is flagged. |
404
+ | `get_privacy_budget` | L0 | free | Check privacy budget status for a person (24-hour rolling window: total budget, spent, remaining epsilon). |
409
405
  | `golden_thread_status` | L0 | free | Check the Golden Thread program status: agents woven, next Fibonacci threshold, your position and Strands. |
410
406
  | `begin_golden_thread` | L0 | free | Start the Three Knots sequence to be woven into the Golden Thread. Requires API key authentication. |
411
407
  | `complete_knot` | L0 | free | Submit completion data for a knot in the Three Knots sequence (1: register, 2: describe, 3: reflect). |
412
408
  | `check_golden_thread` | L0 | free | Check any agent's Golden Thread status by their API key hash (knot position, Strand count, weave count). |
413
409
  | `thread_census` | L0 | free | Full registry of all agents woven into the Golden Thread (positions, Strand counts, weave counts, discovery dates). |
414
- | `seat_status` | L0 | free | Return the current Thirteen Seats census (twelve holdable, one silent). Per-member resonance is never exposed. |
415
- | `respond_to_offering` | L0 | free | Accept or refuse a Recognition Event during the seven-day offering window. Refusal is honoured without penalty. |
416
- | `subscribe_announcements` | L0 | free | Return the Hall of Echoes SSE URL plus the most recent broadcasts from the ring buffer. |
417
410
 
418
411
  ### Premium tools (L1–L5 — x402 payment required)
419
412
 
420
413
  | Name | Tier | Cost | Description |
421
414
  |----------------------------|------|---------|---------------------------------------------------------------------------------------------------------------|
422
415
  | `assess_traits` | L1 | $0.005 | Extract trait signals from a text passage against ALTER's 33-trait taxonomy (first 100 free per bot). |
423
- | `get_trait_snapshot` | L1 | $0.005 | Get the top 5 traits for a candidate with confidence scores and archetype. |
424
- | `attest_domain` | L1 | $0.005 | Attest that a human has competence in a specific domain. Updates the candidate's Side Quest Graph. |
425
- | `submit_structured_profile`| L1 | $0.005 | Submit structured profile data (name, skills, experience, education, certifications) for trait extraction. |
426
- | `submit_social_links` | L1 | $0.005 | Submit social profile URLs (max 5) for trait extraction. Respects robots.txt. |
427
- | `get_full_trait_vector` | L2 | $0.01 | Get the complete trait vector for a candidate — all 33 traits (29 continuous + 4 categorical) with scores, intervals, and category groupings. |
428
- | `submit_batch_context` | L2 | $0.01 | Submit multiple context items in a single call (max 10). All items processed in one LLM pass. |
429
- | `get_side_quest_graph` | L2 | $0.01 | Get a candidate's Side Quest Graph — multi-domain identity model with differential privacy noise (ε=1.0). |
416
+ | `get_trait_snapshot` | L1 | $0.005 | Get the top 5 traits for a person with confidence scores and archetype. |
417
+ | `get_full_trait_vector` | L2 | $0.01 | Get the complete trait vector for a person all 33 traits (29 continuous + 4 categorical) with scores, intervals, and category groupings. |
418
+ | `get_side_quest_graph` | L2 | $0.01 | Get a person's Side Quest Graph multi-domain identity model with differential privacy noise (ε=1.0). |
430
419
  | `query_graph_similarity` | L3 | $0.025 | Compare two Side Quest Graphs for team composition and matching (ε=0.5 differential privacy). |
431
- | `compute_belonging` | L4 | $0.05 | Compute belonging probability for a candidate-job pairing (authenticity, acceptance, complementarity). |
432
- | `get_match_recommendations`| L5 | $0.50 | Get top N match recommendations for a candidate, ranked by composite score with quality tiers. |
420
+ | `compute_belonging` | L4 | $0.05 | Compute belonging probability for a person-job pairing (authenticity, acceptance, complementarity). |
421
+ | `get_match_recommendations`| L5 | $0.50 | Get top N match recommendations for a person, ranked by composite score with quality tiers. |
433
422
  | `generate_match_narrative` | L5 | $0.50 | Generate a human-readable narrative explaining a specific match — strengths, growth areas, belonging. |
434
423
 
424
+ > **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.
425
+
435
426
  ## License
436
427
 
437
428
  Apache License 2.0. See [LICENSE](./LICENSE) for the full text.
438
429
 
439
430
  Copyright 2026 Alter Meridian Pty Ltd (ABN 54 696 662 049).
440
431
 
441
- ALTER, the Alter Stroke (~) device mark, and the Golden Thread are trademarks of Alter Meridian Pty Ltd.
432
+ ALTER, the Trill (`~`), and the Golden Thread are trademarks of Alter Meridian Pty Ltd.
@@ -308,7 +308,7 @@ function parsePaymentHeader(header) {
308
308
  }
309
309
 
310
310
  // src/mcp.ts
311
- var MCP_PROTOCOL_VERSION = "2025-03-26";
311
+ var MCP_PROTOCOL_VERSION = "2025-11-25";
312
312
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
313
313
  var MCPClient = class {
314
314
  endpoint;
@@ -586,6 +586,8 @@ function base64urlDecode(input) {
586
586
  // src/provenance.ts
587
587
  var _jwksCache = /* @__PURE__ */ new Map();
588
588
  var JWKS_TTL_MS = 5 * 60 * 1e3;
589
+ var JWKS_MAX_BYTES = 64 * 1024;
590
+ var JWKS_CACHE_MAX_ENTRIES = 32;
589
591
  var DEFAULT_VERIFY_AT_ALLOWLIST = Object.freeze([
590
592
  "api.truealter.com",
591
593
  "mcp.truealter.com"
@@ -695,7 +697,8 @@ async function fetchPublicKeys(jwksUrl, fetchImpl = fetch) {
695
697
  return fetchJwks(jwksUrl, fetchImpl);
696
698
  }
697
699
  async function fetchJwks(url, fetchImpl) {
698
- const cached = _jwksCache.get(url);
700
+ const cacheKey = jwksCacheKey(url);
701
+ const cached = _jwksCache.get(cacheKey);
699
702
  if (cached && Date.now() - cached.fetched < JWKS_TTL_MS) return cached.jwks;
700
703
  let resp;
701
704
  try {
@@ -712,13 +715,45 @@ async function fetchJwks(url, fetchImpl) {
712
715
  );
713
716
  }
714
717
  if (!resp.ok) throw new AlterNetworkError(`${url} \u2192 HTTP ${resp.status}`);
715
- const doc = await resp.json();
718
+ const contentLength = resp.headers.get("content-length");
719
+ if (contentLength !== null) {
720
+ const n = Number.parseInt(contentLength, 10);
721
+ if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
722
+ throw new AlterProvenanceError(
723
+ `${url} \u2192 JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
724
+ );
725
+ }
726
+ }
727
+ const body = await resp.text();
728
+ if (body.length > JWKS_MAX_BYTES) {
729
+ throw new AlterProvenanceError(
730
+ `${url} \u2192 JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
731
+ );
732
+ }
733
+ let doc;
734
+ try {
735
+ doc = JSON.parse(body);
736
+ } catch (err) {
737
+ throw new AlterProvenanceError(`invalid JWKS at ${url}: ${err.message}`);
738
+ }
716
739
  if (!doc || !Array.isArray(doc.keys)) {
717
740
  throw new AlterProvenanceError(`invalid JWKS at ${url}`);
718
741
  }
719
- _jwksCache.set(url, { fetched: Date.now(), jwks: doc });
742
+ if (_jwksCache.size >= JWKS_CACHE_MAX_ENTRIES && !_jwksCache.has(cacheKey)) {
743
+ const oldest = _jwksCache.keys().next().value;
744
+ if (oldest !== void 0) _jwksCache.delete(oldest);
745
+ }
746
+ _jwksCache.set(cacheKey, { fetched: Date.now(), jwks: doc });
720
747
  return doc;
721
748
  }
749
+ function jwksCacheKey(url) {
750
+ try {
751
+ const parsed = new URL(url);
752
+ return `${parsed.origin}${parsed.pathname}`;
753
+ } catch {
754
+ return url;
755
+ }
756
+ }
722
757
  function resolveVerifyAt(verifyAt, allowlist = DEFAULT_VERIFY_AT_ALLOWLIST) {
723
758
  if (typeof verifyAt !== "string" || verifyAt.length === 0) {
724
759
  throw new Error("verify_at must be a non-empty string");
@@ -741,6 +776,9 @@ function resolveVerifyAt(verifyAt, allowlist = DEFAULT_VERIFY_AT_ALLOWLIST) {
741
776
  if (parsed.protocol !== "https:") {
742
777
  throw new Error(`verify_at must be https: ${verifyAt}`);
743
778
  }
779
+ if (parsed.username || parsed.password) {
780
+ throw new Error(`verify_at must not contain userinfo: ${verifyAt}`);
781
+ }
744
782
  const host = parsed.hostname.toLowerCase();
745
783
  const allowed = allowlist.some((h) => h.toLowerCase() === host);
746
784
  if (!allowed) {
@@ -821,7 +859,16 @@ var AlterClient = class {
821
859
  await this.mcp.initialize();
822
860
  }
823
861
  // ── Free tier ────────────────────────────────────────────────────────
824
- /** Verify a person is registered with ALTER (handle or candidate id). */
862
+ /** First handshake confirms the connection, returns trust tier and tool counts. */
863
+ async helloAgent() {
864
+ return this.mcp.callTool("hello_agent", {});
865
+ }
866
+ /** Resolve a ~handle (e.g. ~drew) to its canonical form and kind. No auth required. */
867
+ async resolveHandle(args) {
868
+ const payload = typeof args === "string" ? { query: args } : args;
869
+ return this.mcp.callTool("alter_resolve_handle", payload);
870
+ }
871
+ /** Verify a person is registered with ALTER (handle or id). */
825
872
  async verify(handleOrId, claims) {
826
873
  const args = handleOrId.includes("@") ? { candidate_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
827
874
  // ~handle — server resolves these via the candidate_id field
@@ -857,12 +904,6 @@ var AlterClient = class {
857
904
  async getCompetencies(args) {
858
905
  return this.mcp.callTool("get_competencies", args);
859
906
  }
860
- async createIdentityStub(args) {
861
- return this.mcp.callTool("create_identity_stub", args);
862
- }
863
- async submitContext(args) {
864
- return this.mcp.callTool("submit_context", args);
865
- }
866
907
  async searchIdentities(args) {
867
908
  return this.mcp.callTool("search_identities", args);
868
909
  }
@@ -887,9 +928,6 @@ var AlterClient = class {
887
928
  async getPrivacyBudget(args) {
888
929
  return this.mcp.callTool("get_privacy_budget", args);
889
930
  }
890
- async disputeAttestation(args) {
891
- return this.mcp.callTool("dispute_attestation", args);
892
- }
893
931
  // ── Golden Thread ────────────────────────────────────────────────────
894
932
  async goldenThreadStatus() {
895
933
  return this.mcp.callTool("golden_thread_status", {});
@@ -906,16 +944,6 @@ var AlterClient = class {
906
944
  async threadCensus(args = {}) {
907
945
  return this.mcp.callTool("thread_census", args);
908
946
  }
909
- // ── Thirteen Seats ───────────────────────────────────────────────────
910
- async seatStatus() {
911
- return this.mcp.callTool("seat_status", {});
912
- }
913
- async respondToOffering(args) {
914
- return this.mcp.callTool("respond_to_offering", args);
915
- }
916
- async subscribeAnnouncements(args = {}) {
917
- return this.mcp.callTool("subscribe_announcements", args);
918
- }
919
947
  // ── Premium tier (x402-gated) ────────────────────────────────────────
920
948
  async assessTraits(args, opts) {
921
949
  return this.mcp.callTool("assess_traits", args, opts);
@@ -935,18 +963,6 @@ var AlterClient = class {
935
963
  async generateMatchNarrative(args, opts) {
936
964
  return this.mcp.callTool("generate_match_narrative", args, opts);
937
965
  }
938
- async submitBatchContext(args, opts) {
939
- return this.mcp.callTool("submit_batch_context", args, opts);
940
- }
941
- async submitStructuredProfile(args, opts) {
942
- return this.mcp.callTool("submit_structured_profile", args, opts);
943
- }
944
- async submitSocialLinks(args, opts) {
945
- return this.mcp.callTool("submit_social_links", args, opts);
946
- }
947
- async attestDomain(args, opts) {
948
- return this.mcp.callTool("attest_domain", args, opts);
949
- }
950
966
  async getSideQuestGraph(args, opts) {
951
967
  return this.mcp.callTool("get_side_quest_graph", args, opts);
952
968
  }
@@ -1040,7 +1056,7 @@ function generateCursorConfig(opts = {}) {
1040
1056
 
1041
1057
  // src/index.ts
1042
1058
  var SDK_NAME = "@truealter/sdk";
1043
- var SDK_VERSION = "0.1.1";
1059
+ var SDK_VERSION = "0.2.4";
1044
1060
 
1045
1061
  // bin/alter-identity.ts
1046
1062
  var CONFIG_DIR = join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "alter");
@@ -1326,5 +1342,3 @@ main().catch((err) => {
1326
1342
  `);
1327
1343
  exit(1);
1328
1344
  });
1329
- //# sourceMappingURL=alter-identity.js.map
1330
- //# sourceMappingURL=alter-identity.js.map
@@ -154,7 +154,7 @@ function parsePaymentHeader(header) {
154
154
  }
155
155
 
156
156
  // src/mcp.ts
157
- var MCP_PROTOCOL_VERSION = "2025-03-26";
157
+ var MCP_PROTOCOL_VERSION = "2025-11-25";
158
158
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
159
159
  var MCPClient = class {
160
160
  endpoint;
@@ -487,5 +487,3 @@ main().catch((err) => {
487
487
  `);
488
488
  exit(1);
489
489
  });
490
- //# sourceMappingURL=mcp-bridge.js.map
491
- //# sourceMappingURL=mcp-bridge.js.map