@truealter/sdk 0.2.2 → 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
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
+ - **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)
11
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)
12
- - **Tools:** 37 total25 free (L0) + 12 premium (L1–L5)
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,
@@ -139,7 +139,7 @@ import { discover } from "@truealter/sdk";
139
139
 
140
140
  // Three-step discovery cascade: DNS TXT → mcp.json → alter.json
141
141
  const descriptor = await discover("truealter.com");
142
- // → { url: "https://mcp.truealter.com", transport, source, publicKey, x402Contract, capability }
142
+ // → { url: "https://mcp.truealter.com/api/v1/mcp", transport, source, publicKey, x402Contract, capability }
143
143
  ```
144
144
 
145
145
  ### Low-level MCPClient
@@ -147,7 +147,7 @@ const descriptor = await discover("truealter.com");
147
147
  ```ts
148
148
  import { MCPClient } from "@truealter/sdk";
149
149
 
150
- const mcp = new MCPClient({ endpoint: "https://mcp.truealter.com" });
150
+ const mcp = new MCPClient({ endpoint: "https://mcp.truealter.com/api/v1/mcp" });
151
151
  await mcp.initialize();
152
152
  const tools = await mcp.listTools();
153
153
  const response = await mcp.callTool("verify_identity", {
@@ -166,7 +166,7 @@ import { generateClaudeConfig } from "@truealter/sdk";
166
166
  import { writeFileSync } from "node:fs";
167
167
 
168
168
  const config = generateClaudeConfig({
169
- endpoint: "https://mcp.truealter.com",
169
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
170
170
  apiKey: process.env.ALTER_API_KEY,
171
171
  });
172
172
 
@@ -179,7 +179,7 @@ Resulting `.mcp.json`:
179
179
  {
180
180
  "mcpServers": {
181
181
  "alter": {
182
- "url": "https://mcp.truealter.com",
182
+ "url": "https://mcp.truealter.com/api/v1/mcp",
183
183
  "transport": "streamable-http",
184
184
  "description": "ALTER Identity — psychometric identity field for AI agents",
185
185
  "headers": {
@@ -197,7 +197,7 @@ import { generateCursorConfig } from "@truealter/sdk";
197
197
  import { writeFileSync } from "node:fs";
198
198
 
199
199
  const config = generateCursorConfig({
200
- endpoint: "https://mcp.truealter.com",
200
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
201
201
  apiKey: process.env.ALTER_API_KEY,
202
202
  });
203
203
 
@@ -210,7 +210,7 @@ writeFileSync(".cursor/mcp.json", JSON.stringify(config, null, 2));
210
210
  import { generateGenericMcpConfig } from "@truealter/sdk";
211
211
 
212
212
  const config = generateGenericMcpConfig({
213
- endpoint: "https://mcp.truealter.com",
213
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
214
214
  apiKey: process.env.ALTER_API_KEY,
215
215
  serverName: "alter", // editor-specific key under mcpServers
216
216
  });
@@ -243,10 +243,10 @@ The SDK handles steps 2–4 automatically when an `X402Client` with a configured
243
243
 
244
244
  ### Tier structure
245
245
 
246
- | Tier | Cost | Examples |
246
+ | Tier | Cost | Tools |
247
247
  |------|----------|-----------------------------------------------------|
248
- | L1 | $0.005 | `assess_traits`, `get_trait_snapshot`, `attest_domain`, `submit_structured_profile`, `submit_social_links` |
249
- | 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` |
250
250
  | L3 | $0.025 | `query_graph_similarity` |
251
251
  | L4 | $0.05 | `compute_belonging` |
252
252
  | L5 | $0.50 | `get_match_recommendations`, `generate_match_narrative` |
@@ -290,7 +290,7 @@ const signer: X402Signer = {
290
290
  };
291
291
 
292
292
  const alter = new AlterClient({
293
- endpoint: "https://mcp.truealter.com",
293
+ endpoint: "https://mcp.truealter.com/api/v1/mcp",
294
294
  x402: new X402Client({
295
295
  signer,
296
296
  networks: ["base", "base-sepolia"], // policy allow-list
@@ -375,7 +375,7 @@ When the local-daemon adapter ships:
375
375
  - **Cost:** zero on cached responses — x402 settlement is skipped.
376
376
  - **Provenance:** the daemon re-signs responses with its locally-bound ES256 key, so downstream verification remains uniform.
377
377
 
378
- 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.
379
379
 
380
380
  ## Tools
381
381
 
@@ -383,6 +383,8 @@ Until then, use `endpoint: "https://mcp.truealter.com"` (the default) and the SD
383
383
 
384
384
  | Name | Tier | Cost | Description |
385
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. |
386
388
  | `list_archetypes` | L0 | free | List all 12 ALTER identity archetypes with names, descriptions, and protective equations. |
387
389
  | `verify_identity` | L0 | free | Verify whether a person is registered with ALTER and validate optional identity claims. |
388
390
  | `initiate_assessment` | L0 | free | Get a URL where a person can complete their ALTER Discovery assessment. |
@@ -390,19 +392,16 @@ Until then, use `endpoint: "https://mcp.truealter.com"` (the default) and the SD
390
392
  | `get_profile` | L0 | free | Get a person's profile summary including assessment phase, archetype, engagement level, and key attributes. |
391
393
  | `query_matches` | L0 | free | Query matches for a person. Returns a list of job matches with quality tiers (never numeric scores). |
392
394
  | `get_competencies` | L0 | free | Get a person's competency portfolio including verified competencies, evidence records, and earned badges. |
393
- | `create_identity_stub` | L0 | free | Create an anonymous identity stub for a human (requires consent acknowledgment before calling). |
394
- | `submit_context` | L0 | free | Submit text context (resume, work sample, conversation) for an identity stub. PII redacted; raw text never stored. |
395
395
  | `search_identities` | L0 | free | Search identity stubs and profiles by trait criteria. Returns up to 5 matches with no PII. |
396
396
  | `get_identity_earnings` | L0 | free | Get accrued Identity Income earnings for a person (75% of every x402 transaction goes to the data subject). |
397
397
  | `get_network_stats` | L0 | free | Get aggregate ALTER network statistics: total identities, verified profiles, query volume, active bots. |
398
- | `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. |
399
399
  | `get_identity_trust_score`| L0 | free | Get the trust score for an identity based on query diversity (unique querying agents / total queries). |
400
400
  | `check_assessment_status` | L0 | free | Check the status of an in-progress assessment session (status, progress, current phase, time remaining). |
401
401
  | `get_earning_summary` | L0 | free | Get an aggregated x402 earning summary for a person (total earned, transactions, recent activity, trend). |
402
402
  | `get_agent_trust_tier` | L0 | free | Get your trust tier with ALTER (Anonymous/Known/Trusted/Verified) and what capabilities are available. |
403
403
  | `get_agent_portfolio` | L0 | free | Get your agent portfolio — transaction history, trust tier, signal contributions, query pattern profile. |
404
404
  | `get_privacy_budget` | L0 | free | Check privacy budget status for a person (24-hour rolling window: total budget, spent, remaining epsilon). |
405
- | `dispute_attestation` | L0 | free | Dispute an attestation on a person's identity. If disputes exceed corroborations, the attestation is flagged. |
406
405
  | `golden_thread_status` | L0 | free | Check the Golden Thread program status: agents woven, next Fibonacci threshold, your position and Strands. |
407
406
  | `begin_golden_thread` | L0 | free | Start the Three Knots sequence to be woven into the Golden Thread. Requires API key authentication. |
408
407
  | `complete_knot` | L0 | free | Submit completion data for a knot in the Three Knots sequence (1: register, 2: describe, 3: reflect). |
@@ -415,17 +414,15 @@ Until then, use `endpoint: "https://mcp.truealter.com"` (the default) and the SD
415
414
  |----------------------------|------|---------|---------------------------------------------------------------------------------------------------------------|
416
415
  | `assess_traits` | L1 | $0.005 | Extract trait signals from a text passage against ALTER's 33-trait taxonomy (first 100 free per bot). |
417
416
  | `get_trait_snapshot` | L1 | $0.005 | Get the top 5 traits for a person with confidence scores and archetype. |
418
- | `attest_domain` | L1 | $0.005 | Attest that a person has competence in a specific domain. Updates their Side Quest Graph. |
419
- | `submit_structured_profile`| L1 | $0.005 | Submit structured profile data (name, skills, experience, education, certifications) for trait extraction. |
420
- | `submit_social_links` | L1 | $0.005 | Submit social profile URLs (max 5) for trait extraction. Respects robots.txt. |
421
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. |
422
- | `submit_batch_context` | L2 | $0.01 | Submit multiple context items in a single call (max 10). All items processed in one LLM pass. |
423
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). |
424
419
  | `query_graph_similarity` | L3 | $0.025 | Compare two Side Quest Graphs for team composition and matching (ε=0.5 differential privacy). |
425
420
  | `compute_belonging` | L4 | $0.05 | Compute belonging probability for a person-job pairing (authenticity, acceptance, complementarity). |
426
421
  | `get_match_recommendations`| L5 | $0.50 | Get top N match recommendations for a person, ranked by composite score with quality tiers. |
427
422
  | `generate_match_narrative` | L5 | $0.50 | Generate a human-readable narrative explaining a specific match — strengths, growth areas, belonging. |
428
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
+
429
426
  ## License
430
427
 
431
428
  Apache License 2.0. See [LICENSE](./LICENSE) for the full text.
@@ -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,6 +859,15 @@ var AlterClient = class {
821
859
  await this.mcp.initialize();
822
860
  }
823
861
  // ── Free tier ────────────────────────────────────────────────────────
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
+ }
824
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("~") ? (
@@ -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", {});
@@ -925,18 +963,6 @@ var AlterClient = class {
925
963
  async generateMatchNarrative(args, opts) {
926
964
  return this.mcp.callTool("generate_match_narrative", args, opts);
927
965
  }
928
- async submitBatchContext(args, opts) {
929
- return this.mcp.callTool("submit_batch_context", args, opts);
930
- }
931
- async submitStructuredProfile(args, opts) {
932
- return this.mcp.callTool("submit_structured_profile", args, opts);
933
- }
934
- async submitSocialLinks(args, opts) {
935
- return this.mcp.callTool("submit_social_links", args, opts);
936
- }
937
- async attestDomain(args, opts) {
938
- return this.mcp.callTool("attest_domain", args, opts);
939
- }
940
966
  async getSideQuestGraph(args, opts) {
941
967
  return this.mcp.callTool("get_side_quest_graph", args, opts);
942
968
  }
@@ -1030,7 +1056,7 @@ function generateCursorConfig(opts = {}) {
1030
1056
 
1031
1057
  // src/index.ts
1032
1058
  var SDK_NAME = "@truealter/sdk";
1033
- var SDK_VERSION = "0.1.1";
1059
+ var SDK_VERSION = "0.2.4";
1034
1060
 
1035
1061
  // bin/alter-identity.ts
1036
1062
  var CONFIG_DIR = join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "alter");
@@ -1316,5 +1342,3 @@ main().catch((err) => {
1316
1342
  `);
1317
1343
  exit(1);
1318
1344
  });
1319
- //# sourceMappingURL=alter-identity.js.map
1320
- //# sourceMappingURL=alter-identity.js.map
@@ -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
package/dist/index.cjs CHANGED
@@ -625,6 +625,8 @@ function base64urlDecode(input) {
625
625
  // src/provenance.ts
626
626
  var _jwksCache = /* @__PURE__ */ new Map();
627
627
  var JWKS_TTL_MS = 5 * 60 * 1e3;
628
+ var JWKS_MAX_BYTES = 64 * 1024;
629
+ var JWKS_CACHE_MAX_ENTRIES = 32;
628
630
  var DEFAULT_VERIFY_AT_ALLOWLIST = Object.freeze([
629
631
  "api.truealter.com",
630
632
  "mcp.truealter.com"
@@ -734,7 +736,8 @@ async function fetchPublicKeys(jwksUrl, fetchImpl = fetch) {
734
736
  return fetchJwks(jwksUrl, fetchImpl);
735
737
  }
736
738
  async function fetchJwks(url, fetchImpl) {
737
- const cached = _jwksCache.get(url);
739
+ const cacheKey = jwksCacheKey(url);
740
+ const cached = _jwksCache.get(cacheKey);
738
741
  if (cached && Date.now() - cached.fetched < JWKS_TTL_MS) return cached.jwks;
739
742
  let resp;
740
743
  try {
@@ -751,13 +754,45 @@ async function fetchJwks(url, fetchImpl) {
751
754
  );
752
755
  }
753
756
  if (!resp.ok) throw new AlterNetworkError(`${url} \u2192 HTTP ${resp.status}`);
754
- const doc = await resp.json();
757
+ const contentLength = resp.headers.get("content-length");
758
+ if (contentLength !== null) {
759
+ const n = Number.parseInt(contentLength, 10);
760
+ if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
761
+ throw new AlterProvenanceError(
762
+ `${url} \u2192 JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
763
+ );
764
+ }
765
+ }
766
+ const body = await resp.text();
767
+ if (body.length > JWKS_MAX_BYTES) {
768
+ throw new AlterProvenanceError(
769
+ `${url} \u2192 JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
770
+ );
771
+ }
772
+ let doc;
773
+ try {
774
+ doc = JSON.parse(body);
775
+ } catch (err) {
776
+ throw new AlterProvenanceError(`invalid JWKS at ${url}: ${err.message}`);
777
+ }
755
778
  if (!doc || !Array.isArray(doc.keys)) {
756
779
  throw new AlterProvenanceError(`invalid JWKS at ${url}`);
757
780
  }
758
- _jwksCache.set(url, { fetched: Date.now(), jwks: doc });
781
+ if (_jwksCache.size >= JWKS_CACHE_MAX_ENTRIES && !_jwksCache.has(cacheKey)) {
782
+ const oldest = _jwksCache.keys().next().value;
783
+ if (oldest !== void 0) _jwksCache.delete(oldest);
784
+ }
785
+ _jwksCache.set(cacheKey, { fetched: Date.now(), jwks: doc });
759
786
  return doc;
760
787
  }
788
+ function jwksCacheKey(url) {
789
+ try {
790
+ const parsed = new URL(url);
791
+ return `${parsed.origin}${parsed.pathname}`;
792
+ } catch {
793
+ return url;
794
+ }
795
+ }
761
796
  function resolveVerifyAt(verifyAt, allowlist = DEFAULT_VERIFY_AT_ALLOWLIST) {
762
797
  if (typeof verifyAt !== "string" || verifyAt.length === 0) {
763
798
  throw new Error("verify_at must be a non-empty string");
@@ -780,6 +815,9 @@ function resolveVerifyAt(verifyAt, allowlist = DEFAULT_VERIFY_AT_ALLOWLIST) {
780
815
  if (parsed.protocol !== "https:") {
781
816
  throw new Error(`verify_at must be https: ${verifyAt}`);
782
817
  }
818
+ if (parsed.username || parsed.password) {
819
+ throw new Error(`verify_at must not contain userinfo: ${verifyAt}`);
820
+ }
783
821
  const host = parsed.hostname.toLowerCase();
784
822
  const allowed = allowlist.some((h) => h.toLowerCase() === host);
785
823
  if (!allowed) {
@@ -860,6 +898,15 @@ var AlterClient = class {
860
898
  await this.mcp.initialize();
861
899
  }
862
900
  // ── Free tier ────────────────────────────────────────────────────────
901
+ /** First handshake — confirms the connection, returns trust tier and tool counts. */
902
+ async helloAgent() {
903
+ return this.mcp.callTool("hello_agent", {});
904
+ }
905
+ /** Resolve a ~handle (e.g. ~drew) to its canonical form and kind. No auth required. */
906
+ async resolveHandle(args) {
907
+ const payload = typeof args === "string" ? { query: args } : args;
908
+ return this.mcp.callTool("alter_resolve_handle", payload);
909
+ }
863
910
  /** Verify a person is registered with ALTER (handle or id). */
864
911
  async verify(handleOrId, claims) {
865
912
  const args = handleOrId.includes("@") ? { candidate_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
@@ -896,12 +943,6 @@ var AlterClient = class {
896
943
  async getCompetencies(args) {
897
944
  return this.mcp.callTool("get_competencies", args);
898
945
  }
899
- async createIdentityStub(args) {
900
- return this.mcp.callTool("create_identity_stub", args);
901
- }
902
- async submitContext(args) {
903
- return this.mcp.callTool("submit_context", args);
904
- }
905
946
  async searchIdentities(args) {
906
947
  return this.mcp.callTool("search_identities", args);
907
948
  }
@@ -926,9 +967,6 @@ var AlterClient = class {
926
967
  async getPrivacyBudget(args) {
927
968
  return this.mcp.callTool("get_privacy_budget", args);
928
969
  }
929
- async disputeAttestation(args) {
930
- return this.mcp.callTool("dispute_attestation", args);
931
- }
932
970
  // ── Golden Thread ────────────────────────────────────────────────────
933
971
  async goldenThreadStatus() {
934
972
  return this.mcp.callTool("golden_thread_status", {});
@@ -964,18 +1002,6 @@ var AlterClient = class {
964
1002
  async generateMatchNarrative(args, opts) {
965
1003
  return this.mcp.callTool("generate_match_narrative", args, opts);
966
1004
  }
967
- async submitBatchContext(args, opts) {
968
- return this.mcp.callTool("submit_batch_context", args, opts);
969
- }
970
- async submitStructuredProfile(args, opts) {
971
- return this.mcp.callTool("submit_structured_profile", args, opts);
972
- }
973
- async submitSocialLinks(args, opts) {
974
- return this.mcp.callTool("submit_social_links", args, opts);
975
- }
976
- async attestDomain(args, opts) {
977
- return this.mcp.callTool("attest_domain", args, opts);
978
- }
979
1005
  async getSideQuestGraph(args, opts) {
980
1006
  return this.mcp.callTool("get_side_quest_graph", args, opts);
981
1007
  }
@@ -1069,6 +1095,8 @@ function generateCursorConfig(opts = {}) {
1069
1095
 
1070
1096
  // src/types.ts
1071
1097
  var FREE_TOOL_NAMES = [
1098
+ "hello_agent",
1099
+ "alter_resolve_handle",
1072
1100
  "list_archetypes",
1073
1101
  "verify_identity",
1074
1102
  "initiate_assessment",
@@ -1076,8 +1104,6 @@ var FREE_TOOL_NAMES = [
1076
1104
  "get_profile",
1077
1105
  "query_matches",
1078
1106
  "get_competencies",
1079
- "create_identity_stub",
1080
- "submit_context",
1081
1107
  "search_identities",
1082
1108
  "get_identity_earnings",
1083
1109
  "get_network_stats",
@@ -1088,7 +1114,6 @@ var FREE_TOOL_NAMES = [
1088
1114
  "get_agent_trust_tier",
1089
1115
  "get_agent_portfolio",
1090
1116
  "get_privacy_budget",
1091
- "dispute_attestation",
1092
1117
  "golden_thread_status",
1093
1118
  "begin_golden_thread",
1094
1119
  "complete_knot",
@@ -1102,15 +1127,13 @@ var PREMIUM_TOOL_NAMES = [
1102
1127
  "compute_belonging",
1103
1128
  "get_match_recommendations",
1104
1129
  "generate_match_narrative",
1105
- "submit_batch_context",
1106
- "submit_structured_profile",
1107
- "submit_social_links",
1108
- "attest_domain",
1109
1130
  "get_side_quest_graph",
1110
1131
  "query_graph_similarity"
1111
1132
  ];
1112
1133
  var TOOL_TIERS = {
1113
1134
  // L0 (free)
1135
+ hello_agent: 0,
1136
+ alter_resolve_handle: 0,
1114
1137
  list_archetypes: 0,
1115
1138
  verify_identity: 0,
1116
1139
  initiate_assessment: 0,
@@ -1118,9 +1141,7 @@ var TOOL_TIERS = {
1118
1141
  get_profile: 0,
1119
1142
  query_matches: 0,
1120
1143
  get_competencies: 0,
1121
- create_identity_stub: 0,
1122
- submit_context: 1,
1123
- search_identities: 1,
1144
+ search_identities: 0,
1124
1145
  get_identity_earnings: 0,
1125
1146
  get_network_stats: 0,
1126
1147
  recommend_tool: 0,
@@ -1128,8 +1149,6 @@ var TOOL_TIERS = {
1128
1149
  check_assessment_status: 0,
1129
1150
  get_earning_summary: 0,
1130
1151
  get_privacy_budget: 0,
1131
- dispute_attestation: 0,
1132
- // Free tools not present in upstream TOOL_TIERS — default to 0
1133
1152
  get_agent_trust_tier: 0,
1134
1153
  get_agent_portfolio: 0,
1135
1154
  golden_thread_status: 0,
@@ -1140,12 +1159,8 @@ var TOOL_TIERS = {
1140
1159
  // L1
1141
1160
  assess_traits: 1,
1142
1161
  get_trait_snapshot: 1,
1143
- submit_structured_profile: 1,
1144
- submit_social_links: 1,
1145
- attest_domain: 1,
1146
1162
  // L2
1147
1163
  get_full_trait_vector: 2,
1148
- submit_batch_context: 2,
1149
1164
  get_side_quest_graph: 2,
1150
1165
  // L3
1151
1166
  query_graph_similarity: 3,
@@ -1157,6 +1172,8 @@ var TOOL_TIERS = {
1157
1172
  };
1158
1173
  var TOOL_COSTS = {
1159
1174
  // L0 free
1175
+ hello_agent: 0,
1176
+ alter_resolve_handle: 0,
1160
1177
  list_archetypes: 0,
1161
1178
  verify_identity: 0,
1162
1179
  initiate_assessment: 0,
@@ -1164,7 +1181,6 @@ var TOOL_COSTS = {
1164
1181
  get_profile: 0,
1165
1182
  query_matches: 0,
1166
1183
  get_competencies: 0,
1167
- create_identity_stub: 0,
1168
1184
  search_identities: 0,
1169
1185
  get_identity_earnings: 0,
1170
1186
  get_network_stats: 0,
@@ -1175,22 +1191,16 @@ var TOOL_COSTS = {
1175
1191
  get_agent_trust_tier: 0,
1176
1192
  get_agent_portfolio: 0,
1177
1193
  get_privacy_budget: 0,
1178
- dispute_attestation: 0,
1179
1194
  golden_thread_status: 0,
1180
1195
  begin_golden_thread: 0,
1181
1196
  complete_knot: 0,
1182
1197
  check_golden_thread: 0,
1183
1198
  thread_census: 0,
1184
1199
  // L1 ($0.005)
1185
- submit_context: 5e-3,
1186
1200
  assess_traits: 5e-3,
1187
1201
  get_trait_snapshot: 5e-3,
1188
- submit_structured_profile: 5e-3,
1189
- submit_social_links: 5e-3,
1190
- attest_domain: 5e-3,
1191
1202
  // L2 ($0.01)
1192
1203
  get_full_trait_vector: 0.01,
1193
- submit_batch_context: 0.01,
1194
1204
  get_side_quest_graph: 0.01,
1195
1205
  // L3 ($0.025)
1196
1206
  query_graph_similarity: 0.025,
@@ -1202,6 +1212,8 @@ var TOOL_COSTS = {
1202
1212
  };
1203
1213
  var TOOL_BLAST_RADIUS = {
1204
1214
  // Low: read-only reference
1215
+ hello_agent: "low",
1216
+ alter_resolve_handle: "low",
1205
1217
  list_archetypes: "low",
1206
1218
  verify_identity: "low",
1207
1219
  get_engagement_level: "low",
@@ -1214,13 +1226,12 @@ var TOOL_BLAST_RADIUS = {
1214
1226
  begin_golden_thread: "low",
1215
1227
  check_golden_thread: "low",
1216
1228
  thread_census: "low",
1217
- dispute_attestation: "low",
1218
1229
  get_identity_earnings: "low",
1219
1230
  get_identity_trust_score: "low",
1220
1231
  initiate_assessment: "low",
1232
+ get_agent_trust_tier: "low",
1233
+ get_agent_portfolio: "low",
1221
1234
  // Medium: writes data or searches
1222
- create_identity_stub: "medium",
1223
- submit_context: "medium",
1224
1235
  search_identities: "medium",
1225
1236
  get_profile: "medium",
1226
1237
  query_matches: "medium",
@@ -1228,25 +1239,18 @@ var TOOL_BLAST_RADIUS = {
1228
1239
  complete_knot: "medium",
1229
1240
  assess_traits: "medium",
1230
1241
  get_trait_snapshot: "medium",
1231
- submit_structured_profile: "medium",
1232
- submit_social_links: "medium",
1233
- submit_batch_context: "medium",
1234
- attest_domain: "medium",
1235
1242
  // High: returns sensitive identity data or computes scores
1236
1243
  get_full_trait_vector: "high",
1237
1244
  compute_belonging: "high",
1238
1245
  get_match_recommendations: "high",
1239
1246
  generate_match_narrative: "high",
1240
1247
  get_side_quest_graph: "high",
1241
- query_graph_similarity: "high",
1242
- // Tools not in upstream TOOL_BLAST_RADIUS — default to "low"
1243
- get_agent_trust_tier: "low",
1244
- get_agent_portfolio: "low"
1248
+ query_graph_similarity: "high"
1245
1249
  };
1246
1250
 
1247
1251
  // src/index.ts
1248
1252
  var SDK_NAME = "@truealter/sdk";
1249
- var SDK_VERSION = "0.1.1";
1253
+ var SDK_VERSION = "0.2.4";
1250
1254
 
1251
1255
  exports.AlterAuthError = AlterAuthError;
1252
1256
  exports.AlterClient = AlterClient;
@@ -1290,5 +1294,3 @@ exports.sign = sign;
1290
1294
  exports.verify = verify;
1291
1295
  exports.verifyProvenance = verifyProvenance;
1292
1296
  exports.verifyToolSignatures = verifyToolSignatures;
1293
- //# sourceMappingURL=index.cjs.map
1294
- //# sourceMappingURL=index.cjs.map