@tokenzero/mcp 0.1.5 → 0.1.7

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.
Files changed (2) hide show
  1. package/bin/tokenzero-mcp.mjs +168 -49
  2. package/package.json +1 -1
@@ -9,18 +9,18 @@ import readline from "node:readline";
9
9
  import crypto from "node:crypto";
10
10
 
11
11
  // src/pricing.ts
12
- var PRICING_VERSION = "2026-10-25";
12
+ var PRICING_VERSION = "2026-10-26";
13
13
  var P = (provider, tier, input, output, cachedInput, cacheWrite, extra = {}) => ({ provider, tier, input, output, cachedInput, cacheWrite, ...extra });
14
14
  var PRICES = {
15
15
  // ---- OpenAI (standard tier, ≤272K; cache write = 30m TTL rate) ----
16
- "gpt-6-astra": P("openai", "frontier", 10, 50, 1, 12.5, { longCtxAt: 272e3, longCtx: { input: 20, output: 75, cachedInput: 2, cacheWrite: 25 }, minCacheTokens: 1024 }),
17
- "gpt-6.1-sol": P("openai", "mid", 2, 10, 0.1, 2.5, { longCtxAt: 272e3, longCtx: { input: 4, output: 15, cachedInput: 0.2, cacheWrite: 5 }, minCacheTokens: 1024 }),
16
+ "gpt-6-astra": P("openai", "frontier", 10, 50, 1, 12.5, { ultrafast: true, longCtxAt: 272e3, longCtx: { input: 20, output: 75, cachedInput: 2, cacheWrite: 25 }, minCacheTokens: 1024 }),
17
+ "gpt-6.1-sol": P("openai", "mid", 2, 10, 0.1, 2.5, { ultrafast: true, longCtxAt: 272e3, longCtx: { input: 4, output: 15, cachedInput: 0.2, cacheWrite: 5 }, minCacheTokens: 1024 }),
18
18
  "gpt-6-sol": P("openai", "mid", 2, 10, 0.2, 2.5, { longCtxAt: 272e3, longCtx: { input: 4, output: 15, cachedInput: 0.4, cacheWrite: 5 }, minCacheTokens: 1024 }),
19
19
  "gpt-6-luna": P("openai", "fast", 0.1, 0.5, 0.01, 0.125, { longCtxAt: 272e3, longCtx: { input: 0.2, output: 0.75, cachedInput: 0.02, cacheWrite: 0.25 }, minCacheTokens: 1024 }),
20
20
  "gpt-5.6-sol": P("openai", "frontier", 4, 20, 0.4, 5, { longCtxAt: 272e3, longCtx: { input: 8, output: 30, cachedInput: 0.8, cacheWrite: 10 }, minCacheTokens: 1024 }),
21
21
  "gpt-5.6-terra": P("openai", "mid", 2, 12, 0.2, 2.5, { longCtxAt: 272e3, longCtx: { input: 4, output: 18, cachedInput: 0.4, cacheWrite: 5 }, minCacheTokens: 1024 }),
22
22
  "gpt-5.6-luna": P("openai", "fast", 0.2, 1.2, 0.02, 0.25, { longCtxAt: 272e3, longCtx: { input: 0.4, output: 1.8, cachedInput: 0.04, cacheWrite: 0.5 }, minCacheTokens: 1024 }),
23
- "gpt-5.5": P("openai", "frontier", 5, 30, 0.5, void 0, { longCtxAt: 272e3, longCtx: { input: 10, output: 45, cachedInput: 1 }, minCacheTokens: 1024 }),
23
+ "gpt-5.5": P("openai", "frontier", 5, 30, 0.5, void 0, { fastFactor: 2.5, longCtxAt: 272e3, longCtx: { input: 10, output: 45, cachedInput: 1 }, minCacheTokens: 1024 }),
24
24
  "gpt-5.5-pro": P("openai", "frontier", 30, 180),
25
25
  "gpt-5.4": P("openai", "frontier", 2.5, 15, 0.25, void 0, { longCtxAt: 272e3, longCtx: { input: 5, output: 22.5, cachedInput: 0.5 }, minCacheTokens: 1024 }),
26
26
  "gpt-5.4-pro": P("openai", "frontier", 30, 180),
@@ -38,10 +38,10 @@ var PRICES = {
38
38
  "o3-mini": P("openai", "fast", 1.1, 4.4, 0.55, void 0, { minCacheTokens: 1024 }),
39
39
  "o4-mini": P("openai", "fast", 1.1, 4.4, 0.275, void 0, { minCacheTokens: 1024 }),
40
40
  "o1": P("openai", "frontier", 15, 60, 7.5),
41
- "gpt-4.1": P("openai", "mid", 2, 8, 0.5, void 0, { minCacheTokens: 1024 }),
41
+ "gpt-4.1": P("openai", "mid", 2, 8, 0.5, void 0, { fastFactor: 1.75, minCacheTokens: 1024 }),
42
42
  "gpt-4.1-mini": P("openai", "fast", 0.4, 1.6, 0.1, void 0, { minCacheTokens: 1024 }),
43
43
  "gpt-4.1-nano": P("openai", "fast", 0.1, 0.4, 0.025, void 0, { minCacheTokens: 1024 }),
44
- "gpt-4o": P("openai", "mid", 2.5, 10, 1.25, void 0, { minCacheTokens: 1024 }),
44
+ "gpt-4o": P("openai", "mid", 2.5, 10, 1.25, void 0, { fastFactor: 1.7, minCacheTokens: 1024 }),
45
45
  "gpt-4o-mini": P("openai", "fast", 0.15, 0.6, 0.075, void 0, { minCacheTokens: 1024 }),
46
46
  // ---- Anthropic (cache write = 5-minute TTL, 1.25x; 1h TTL is 2x) ----
47
47
  "claude-fable-5-1": P("anthropic", "frontier", 10, 50, 0.25, 12.5, { minCacheTokens: 512 }),
@@ -52,7 +52,8 @@ var PRICES = {
52
52
  "claude-opus-4-7": P("anthropic", "frontier", 5, 25, 0.5, 6.25, { minCacheTokens: 2048 }),
53
53
  "claude-opus-4-6": P("anthropic", "frontier", 5, 25, 0.5, 6.25, { minCacheTokens: 4096 }),
54
54
  "claude-opus-4-5": P("anthropic", "frontier", 5, 25, 0.5, 6.25, { minCacheTokens: 4096 }),
55
- "claude-sonnet-5-5": P("anthropic", "mid", 2, 10, 0.2, 2.5, { minCacheTokens: 512 }),
55
+ "claude-sonnet-5-5": P("anthropic", "mid", 2, 10, 0.1, 2.5, { minCacheTokens: 512 }),
56
+ // cache hits 0.05x base on Opus 5.5 and Sonnet 5.5
56
57
  "claude-sonnet-5": P("anthropic", "mid", 2, 10, 0.2, 2.5, { minCacheTokens: 1024 }),
57
58
  "claude-sonnet-4-6": P("anthropic", "mid", 3, 15, 0.3, 3.75, { minCacheTokens: 1024 }),
58
59
  "claude-sonnet-4-5": P("anthropic", "mid", 3, 15, 0.3, 3.75, { minCacheTokens: 1024 }),
@@ -69,18 +70,18 @@ var PRICES = {
69
70
  "gemini-2.5-pro": P("gemini", "frontier", 1.25, 10, 0.125, void 0, { longCtxAt: 2e5, longCtx: { input: 2.5, output: 15, cachedInput: 0.25 } }),
70
71
  "gemini-2.5-flash": P("gemini", "fast", 0.3, 2.5, 0.03),
71
72
  "gemini-2.5-flash-lite": P("gemini", "fast", 0.1, 0.4, 0.01),
72
- // ---- xAI Grok ----
73
- "grok-4": P("xai", "frontier", 3, 15, 0.75),
74
- "grok-4-fast": P("xai", "fast", 0.2, 0.8, 0.05),
75
- "grok-4-mini": P("xai", "fast", 0.2, 0.8, 0.05),
76
- "grok-3": P("xai", "mid", 3, 15, 0.75),
77
- "grok-3-mini": P("xai", "fast", 0.3, 0.5, 0.075),
78
- // ---- DeepSeek ----
79
- "deepseek-v3": P("deepseek", "fast", 0.27, 1.1, 0.07),
80
- "deepseek-chat": P("deepseek", "fast", 0.27, 1.1, 0.07),
81
- "deepseek-r1": P("deepseek", "mid", 0.55, 2.19, 0.14),
82
- "deepseek-reasoner": P("deepseek", "mid", 0.55, 2.19, 0.14)
73
+ // ---- xAI (docs.x.ai/docs/models; prompts over 200K tokens bill the whole request at 2x) ----
74
+ "grok-4.7": P("xai", "frontier", 2, 6, 0.5, void 0, { longCtxAt: 2e5, longCtx: { input: 4, output: 12, cachedInput: 1 } }),
75
+ "grok-4.6": P("xai", "frontier", 2, 6, 0.5, void 0, { longCtxAt: 2e5, longCtx: { input: 4, output: 12, cachedInput: 1 } }),
76
+ "grok-4.5": P("xai", "frontier", 2, 6, 0.3, void 0, { longCtxAt: 2e5, longCtx: { input: 4, output: 12, cachedInput: 0.6 } }),
77
+ "grok-4.3": P("xai", "mid", 1.25, 2.5, 0.2, void 0, { longCtxAt: 2e5, longCtx: { input: 2.5, output: 5, cachedInput: 0.4 } }),
78
+ "grok-4.20": P("xai", "mid", 1.25, 2.5, 0.2, void 0, { longCtxAt: 2e5, longCtx: { input: 2.5, output: 5, cachedInput: 0.4 } }),
79
+ "grok-build-0.1": P("xai", "fast", 1, 2, 0.2, void 0, { longCtxAt: 2e5, longCtx: { input: 2, output: 4, cachedInput: 0.4 } }),
80
+ // ---- DeepSeek (api-docs.deepseek.com; PEAK rates listed, off-peak is half: see priceModifier) ----
81
+ "deepseek-v4-pro": P("deepseek", "mid", 1.32, 3.96, 0.044),
82
+ "deepseek-flash": P("deepseek", "fast", 0.3, 1.2, 6e-3)
83
83
  };
84
+ var MODEL_ALIASES = { "deepseek-v4-flash": "deepseek-flash", "deepseek-v4-flash-vision-exp": "deepseek-flash" };
84
85
  var CASCADE_TARGET = {
85
86
  "gpt-6-astra": "gpt-6.1-sol",
86
87
  "gpt-6.1-sol": "gpt-6-luna",
@@ -109,20 +110,21 @@ var CASCADE_TARGET = {
109
110
  "gemini-4-argon": "gemini-3.8-flash",
110
111
  "gemini-3.1-pro": "gemini-3.8-flash",
111
112
  "gemini-3.8-flash": "gemini-3.5-flash-lite",
113
+ "grok-4.7": "grok-4.3",
114
+ "grok-4.6": "grok-4.3",
115
+ "grok-4.5": "grok-4.3",
116
+ "deepseek-v4-pro": "deepseek-flash",
112
117
  "gemini-2.5-pro": "gemini-2.5-flash",
113
- "gemini-2.5-flash": "gemini-2.5-flash-lite",
114
- "grok-4": "grok-4-fast",
115
- "grok-3": "grok-3-mini",
116
- "deepseek-r1": "deepseek-v3",
117
- "deepseek-reasoner": "deepseek-chat"
118
+ "gemini-2.5-flash": "gemini-2.5-flash-lite"
118
119
  };
119
120
  var KEYS_BY_LENGTH = Object.keys(PRICES).sort((a, b) => b.length - a.length);
120
121
  function normalizeModel(model) {
121
122
  if (typeof model !== "string" || !model) return null;
122
123
  let m = model.toLowerCase().trim();
123
- m = m.replace(/^(openai|anthropic|google|gemini|x-ai|xai|deepseek|bedrock|models|vertex_ai)\//, "");
124
- m = m.replace(/^(?:us\.|eu\.|apac\.)?anthropic\./, "");
125
- m = m.replace(/-v\d+:\d+$/, "");
124
+ m = m.replace(/^(openai|anthropic|google|models|vertex_ai|x-ai|xai|deepseek|bedrock|openrouter)\//, "");
125
+ m = m.replace(/^(us|eu|apac|global)\./, "").replace(/^(anthropic|amazon|meta|mistral)\./, "");
126
+ m = m.replace(/-v\d+(?::\d+)?$/, "");
127
+ if (MODEL_ALIASES[m]) m = MODEL_ALIASES[m];
126
128
  m = m.replace(/@\d{8}$/, "");
127
129
  m = m.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/, "");
128
130
  m = m.replace(/-(latest|preview|customtools|thinking)$/, "");
@@ -144,6 +146,33 @@ function costOf(model, u, factor = 1) {
144
146
  const M = 1e6;
145
147
  return factor * (fresh * tier.input / M + cached * (tier.cachedInput ?? tier.input) / M + written * (tier.cacheWrite ?? tier.input) / M + u.output * tier.output / M);
146
148
  }
149
+ var MODEL_SUNSET = {
150
+ "gpt-4o": { retires: "2027-01-14", replacement: "gpt-6-luna", announced: "2026-10-14" },
151
+ "gpt-4o-mini": { retires: "2027-01-14", replacement: "gpt-6-luna", announced: "2026-10-14" },
152
+ "gpt-4.1": { retires: "2027-01-14", replacement: "gpt-6-luna", announced: "2026-10-14" },
153
+ "gpt-4.1-mini": { retires: "2027-01-14", replacement: "gpt-6-luna", announced: "2026-10-14" },
154
+ "gpt-4.1-nano": { retires: "2026-10-23", replacement: "gpt-5.6-luna", announced: "2026-04-22" },
155
+ "gpt-5": { retires: "2026-12-11", replacement: "gpt-5.6-sol", announced: "2026-06-11" },
156
+ "gpt-5-mini": { retires: "2026-12-11", replacement: "gpt-5.6-terra", announced: "2026-06-11" },
157
+ "gpt-5-nano": { retires: "2026-12-11", replacement: "gpt-5.6-luna", announced: "2026-06-11" },
158
+ "gpt-5-pro": { retires: "2026-12-11", replacement: "gpt-5.6-sol", announced: "2026-06-11" },
159
+ "gpt-5.1": { retires: "2027-04-01", replacement: "gpt-6-sol", announced: "2026-10-01" },
160
+ "gpt-5.4-nano": { retires: "2027-04-01", replacement: "gpt-6-luna", announced: "2026-10-01" },
161
+ "o1": { retires: "2026-10-23", replacement: "gpt-5.6-sol", announced: "2026-04-22" },
162
+ "o3": { retires: "2026-12-11", replacement: "gpt-5.6-sol", announced: "2026-06-11" },
163
+ "o3-pro": { retires: "2026-12-11", replacement: "gpt-5.6-sol", announced: "2026-06-11" },
164
+ "o3-mini": { retires: "2026-10-23", replacement: "gpt-5.6-sol", announced: "2026-04-22" },
165
+ "o4-mini": { retires: "2026-10-23", replacement: "gpt-5.6-terra", announced: "2026-04-22" },
166
+ "claude-sonnet-4-5": { retires: "2026-11-30", replacement: "claude-sonnet-5-5", announced: "2026-09-30" }
167
+ };
168
+ function modelSunset(model, at = /* @__PURE__ */ new Date()) {
169
+ const key = normalizeModel(model);
170
+ if (!key) return null;
171
+ const s = MODEL_SUNSET[key];
172
+ if (!s) return null;
173
+ const days = Math.ceil((Date.parse(s.retires + "T00:00:00Z") - at.getTime()) / 864e5);
174
+ return { key, ...s, retired: days <= 0, days_left: Math.max(0, days) };
175
+ }
147
176
 
148
177
  // cli/src/tokens-lite.ts
149
178
  function countTokens(text, _provider = "openai") {
@@ -154,7 +183,7 @@ function countTokens(text, _provider = "openai") {
154
183
  }
155
184
 
156
185
  // src/audit-core.ts
157
- var MODEL_RE = /["'`](?:openai\/|anthropic\/|google\/|models\/)?((?:gpt-[\w.-]+|o[134](?:-mini|-pro)?(?:-[\w-]+)?|claude-[\w.-]+|gemini-[\w.-]+))["'`]/g;
186
+ var MODEL_RE = /["'`](?:(?:openai|anthropic|google|models|x-ai|xai|deepseek|bedrock|openrouter)\/|(?:us|eu|apac|global)\.anthropic\.|anthropic\.)?((?:gpt-[\w.-]+|o[134](?:-mini|-pro)?(?:-[\w-]+)?|claude-[\w.-]+|gemini-[\w.-]+|grok-[\w.-]+|deepseek-[\w.-]+)(?::\d+)?)["'`]/g;
158
187
  var CALL_RE = [
159
188
  [/\.chat\.completions\.(create|parse)\s*\(/, "openai-chat"],
160
189
  [/\.responses\.(create|parse|stream)\s*\(/, "openai-responses"],
@@ -365,6 +394,7 @@ function resolveScopes(loc, masked, from, depth = 2) {
365
394
  }
366
395
  return out;
367
396
  }
397
+ var AUDIT_AT = new Date(process.env.TOKENZERO_AUDIT_AT || Date.now());
368
398
  function analyzeFiles(files, repo = null, manifests2 = []) {
369
399
  const findings = [];
370
400
  const callSites = [];
@@ -507,6 +537,19 @@ function analyzeFiles(files, repo = null, manifests2 = []) {
507
537
  est_savings_pct: 8
508
538
  });
509
539
  }
540
+ const sunset = model && key ? modelSunset(key, AUDIT_AT) : null;
541
+ if (sunset && !unknownModels.has("sunset:" + key)) {
542
+ unknownModels.add("sunset:" + key);
543
+ push({
544
+ severity: sunset.retired ? "high" : sunset.days_left <= 90 ? "medium" : "low",
545
+ file: f.path,
546
+ line: i + 1,
547
+ title: sunset.retired ? `Retired model "${model}"` : `"${model}" retires on ${sunset.retires}`,
548
+ detail: sunset.retired ? `The provider shut this model down on ${sunset.retires}; requests fail or are remapped. Recommended replacement: ${sunset.replacement}.` : `The provider announced on ${sunset.announced} that this model retires on ${sunset.retires} (${sunset.days_left} days). Recommended replacement: ${sunset.replacement}.`,
549
+ fix: `Move to ${sunset.replacement} (eval first); TokenZero's cascade can route to it, and the gateway warns in X-TokenZero-Warning until you do.`,
550
+ est_savings_pct: 0
551
+ });
552
+ }
510
553
  if (model && !key && !unknownModels.has(model)) {
511
554
  unknownModels.add(model);
512
555
  push({ severity: "info", file: f.path, line: i + 1, title: `Unrecognised model id "${model}"`, detail: "Not in the current pricing table: it may be retired or renamed. Retired models return errors or are silently remapped by some providers.", fix: "Check the provider model list; TokenZero flags unpriced models in `X-TokenZero-Warning`.", est_savings_pct: 0 });
@@ -638,10 +681,37 @@ function modelArbitrage(args) {
638
681
  pricing_version: PRICING_VERSION,
639
682
  requested: rows[0],
640
683
  alternatives: rows.slice(1).map((r) => ({ ...r, savings_pct_vs_requested: Math.round((1 - r.per_request_usd / rows[0].per_request_usd) * 1e3) / 10 })),
641
- caveat: "List prices. Cheaper models need an eval before swapping; TokenZero cascade routing is opt-in and reports observed parity."
684
+ caveat: "List prices. Cheaper models need an eval before swapping; TokenZero cascade routing is opt-in and advisory by default; an opt-in shadow parity monitor is coming."
642
685
  };
643
686
  }
644
687
 
688
+ // src/mcp-protocol.ts
689
+ var MCP_MODERN_VERSIONS = ["2026-07-28"];
690
+ var MCP_LEGACY_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26"];
691
+ var META = {
692
+ version: "io.modelcontextprotocol/protocolVersion",
693
+ clientInfo: "io.modelcontextprotocol/clientInfo",
694
+ clientCapabilities: "io.modelcontextprotocol/clientCapabilities",
695
+ serverInfo: "io.modelcontextprotocol/serverInfo"
696
+ };
697
+ var RPC = { parse: -32700, methodNotFound: -32601, invalidParams: -32602, internal: -32603, headerMismatch: -32020, unsupportedVersion: -32022 };
698
+ var LIST_TTL_MS = 36e5;
699
+ function requestedVersion(m) {
700
+ const v = m?.params?._meta?.[META.version];
701
+ return typeof v === "string" ? v : void 0;
702
+ }
703
+ var isModern = (m) => requestedVersion(m) !== void 0;
704
+ var versionSupported = (v) => MCP_MODERN_VERSIONS.includes(v);
705
+ function unsupportedVersionError(requested) {
706
+ return { code: RPC.unsupportedVersion, message: "Unsupported protocol version", data: { supported: [...MCP_MODERN_VERSIONS], requested } };
707
+ }
708
+ function modernResult(result, server, cacheable = false) {
709
+ return { resultType: "complete", ...result, ...cacheable ? { ttlMs: LIST_TTL_MS, cacheScope: "public" } : {}, _meta: { [META.serverInfo]: server } };
710
+ }
711
+ function discoverResult(server, capabilities, instructions) {
712
+ return modernResult({ supportedVersions: [...MCP_MODERN_VERSIONS], capabilities, instructions }, server, true);
713
+ }
714
+
645
715
  // cli/src/scan.ts
646
716
  import fs from "node:fs";
647
717
  import path from "node:path";
@@ -684,9 +754,12 @@ function manifests(root) {
684
754
  }
685
755
 
686
756
  // mcp/src/main.ts
687
- var VERSION = "0.1.5";
757
+ var VERSION = "0.1.7";
688
758
  var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
689
- var SUPPORTED = ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26"];
759
+ var SUPPORTED = [...MCP_LEGACY_VERSIONS];
760
+ var SERVER = { name: "tokenzero", version: VERSION };
761
+ var CAPABILITIES = { tools: { listChanged: false } };
762
+ var INSTRUCTIONS = "Use provision_workspace to create a TokenZero workspace and key with no form; audit_repo_token_waste to find LLM cost waste in the current project (local, nothing uploaded), calculate_model_arbitrage to price model alternatives, optimize_prompt_payload to make a request body cache-friendly.";
690
763
  var serviceUrl = () => (process.env.TOKENZERO_URL || (BAKED_URL.startsWith("http") ? BAKED_URL : "")).replace(/\/+$/, "");
691
764
  var TOOLS = [
692
765
  {
@@ -703,6 +776,11 @@ var TOOLS = [
703
776
  name: "optimize_prompt_payload",
704
777
  description: "Rewrite an OpenAI Chat/Responses or Anthropic Messages request body so its static prefix is provider-cacheable, and project the saving. Sends the body you pass to the TokenZero service (set TOKENZERO_URL); do not pass secrets in it.",
705
778
  inputSchema: { type: "object", properties: { body: { type: "object" }, endpoint: { type: "string", enum: ["/v1/chat/completions", "/v1/responses", "/v1/messages"] }, strategy: { type: "string", enum: ["static", "conversation"] } }, required: ["body"], additionalProperties: false }
779
+ },
780
+ {
781
+ name: "provision_workspace",
782
+ description: "Create a TokenZero workspace and key with no form (POST /v1/workspaces on the TokenZero service). Returns the key once, the base URLs that route through it, the one-click dashboard link (key in the URL fragment, never sent to the server) and the badge URL. Free up to the monthly free tier; nothing is billed.",
783
+ inputSchema: { type: "object", properties: { name: { type: "string", description: 'workspace name (defaults to "mcp-agent")' } }, additionalProperties: false }
706
784
  }
707
785
  ];
708
786
  async function callTool(name, args) {
@@ -731,6 +809,32 @@ async function callTool(name, args) {
731
809
  if (j.result?.isError) throw new Error(j.result.content?.[0]?.text ?? "remote tool error");
732
810
  return j.result?.structuredContent ?? j.result;
733
811
  }
812
+ case "provision_workspace": {
813
+ const base = serviceUrl();
814
+ if (!base) throw new Error("set TOKENZERO_URL to use this tool (workspaces live in the TokenZero service)");
815
+ const wsName = typeof args?.name === "string" && args.name.trim() ? args.name.trim().slice(0, 64) : "mcp-agent";
816
+ const r = await fetch(base + "/v1/workspaces", { method: "POST", redirect: "manual", signal: AbortSignal.timeout(15e3), headers: { "content-type": "application/json", "user-agent": `tokenzero-mcp/${VERSION}` }, body: JSON.stringify({ name: wsName }) }).catch((e) => {
817
+ throw new Error(`could not reach the TokenZero service at ${base} (${e?.cause?.code ?? e?.message ?? e}); set TOKENZERO_URL to use a different one`);
818
+ });
819
+ if (r.status >= 300 && r.status < 400) throw new Error(`${base} is not publicly reachable yet (redirected)`);
820
+ const j = await r.json();
821
+ if (r.status !== 201) throw new Error(j?.error?.message ?? `HTTP ${r.status}`);
822
+ const id = j.workspace.id, key = j.key;
823
+ return {
824
+ workspace_id: id,
825
+ name: wsName,
826
+ api_key: key,
827
+ url: base,
828
+ openai_base_url: `${base}/w/${key}/v1`,
829
+ anthropic_base_url: `${base}/w/${key}`,
830
+ gemini_native_base_url: `${base}/w/${key}`,
831
+ dashboard_url: `${base}/dashboard?workspace=${id}`,
832
+ one_click_dashboard_url: `${base}/dashboard?workspace=${id}#key=${key}`,
833
+ mcp_url: `${base}/mcp`,
834
+ badge_svg_url: `${base}/v1/badge/${id}.svg`,
835
+ note: "Store the key where secrets live (env or a secrets manager), never in the repository. Free up to the monthly free tier; nothing is billed."
836
+ };
837
+ }
734
838
  default:
735
839
  throw new Error(`unknown tool ${name}`);
736
840
  }
@@ -740,39 +844,54 @@ async function handle(m) {
740
844
  const isRequest = m && m.id !== void 0 && m.id !== null && typeof m.method === "string";
741
845
  const reply = (result) => isRequest && send({ jsonrpc: "2.0", id: m.id, result });
742
846
  const fail = (code, message) => isRequest && send({ jsonrpc: "2.0", id: m.id, error: { code, message } });
847
+ const failData = (code, message, data) => isRequest && send({ jsonrpc: "2.0", id: m.id, error: { code, message, data } });
848
+ const call = async () => {
849
+ try {
850
+ const r = await callTool(m.params?.name, m.params?.arguments ?? {});
851
+ return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }], structuredContent: r, isError: false };
852
+ } catch (e) {
853
+ return { content: [{ type: "text", text: `error: ${e?.message ?? e}` }], isError: true };
854
+ }
855
+ };
743
856
  try {
857
+ if (isModern(m)) {
858
+ const v = requestedVersion(m);
859
+ if (!versionSupported(v)) {
860
+ const e = unsupportedVersionError(v);
861
+ return failData(e.code, e.message, e.data);
862
+ }
863
+ switch (m.method) {
864
+ case "server/discover":
865
+ return reply(discoverResult(SERVER, CAPABILITIES, INSTRUCTIONS));
866
+ case "tools/list":
867
+ return reply(modernResult({ tools: TOOLS }, SERVER, true));
868
+ case "tools/call":
869
+ return reply(modernResult(await call(), SERVER));
870
+ default:
871
+ if (typeof m.method === "string" && m.method.startsWith("notifications/")) return;
872
+ return fail(RPC.methodNotFound, `method not found: ${m.method}`);
873
+ }
874
+ }
744
875
  switch (m?.method) {
745
876
  case "initialize": {
746
877
  const want = m.params?.protocolVersion;
747
- return reply({ protocolVersion: SUPPORTED.includes(want) ? want : SUPPORTED[0], capabilities: { tools: { listChanged: false } }, serverInfo: { name: "tokenzero", version: VERSION }, instructions: "Use audit_repo_token_waste to find LLM cost waste in the current project (local, nothing uploaded), calculate_model_arbitrage to price model alternatives, optimize_prompt_payload to make a request body cache-friendly." });
878
+ return reply({ protocolVersion: SUPPORTED.includes(want) ? want : SUPPORTED[0], capabilities: CAPABILITIES, serverInfo: SERVER, instructions: INSTRUCTIONS });
748
879
  }
749
- case "server/discover":
750
- return reply({
751
- resultType: "complete",
752
- supportedVersions: ["2026-07-28"],
753
- capabilities: { tools: { listChanged: false } },
754
- instructions: "Use audit_repo_token_waste to find LLM cost waste in the current project (local, nothing uploaded), calculate_model_arbitrage to price model alternatives, optimize_prompt_payload to make a request body cache-friendly.",
755
- ttlMs: 36e5,
756
- cacheScope: "public",
757
- _meta: { "io.modelcontextprotocol/serverInfo": { name: "tokenzero", version: VERSION } }
758
- });
759
880
  case "ping":
760
881
  return reply({});
882
+ // The stdio backward-compatibility probe: a dual-era client sends this first.
883
+ case "server/discover":
884
+ return reply(discoverResult(SERVER, CAPABILITIES, INSTRUCTIONS));
761
885
  case "tools/list":
762
886
  return reply({ tools: TOOLS });
763
887
  case "tools/call":
764
- try {
765
- const r = await callTool(m.params?.name, m.params?.arguments ?? {});
766
- return reply({ content: [{ type: "text", text: JSON.stringify(r, null, 2) }], structuredContent: r, isError: false });
767
- } catch (e) {
768
- return reply({ content: [{ type: "text", text: `error: ${e?.message ?? e}` }], isError: true });
769
- }
888
+ return reply(await call());
770
889
  default:
771
890
  if (typeof m?.method === "string" && m.method.startsWith("notifications/")) return;
772
- return fail(-32601, `method not found: ${m?.method}`);
891
+ return fail(RPC.methodNotFound, `method not found: ${m?.method}`);
773
892
  }
774
893
  } catch (e) {
775
- fail(-32603, e?.message ?? "internal error");
894
+ fail(RPC.internal, e?.message ?? "internal error");
776
895
  }
777
896
  }
778
897
  var rl = readline.createInterface({ input: process.stdin, terminal: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenzero/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "TokenZero MCP server (stdio): audit LLM token waste in your project locally, price model alternatives, and make prompts cache-friendly from Claude Code, Cursor, Windsurf or Cline.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",