@tokenzero/mcp 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/tokenzero-mcp.mjs +151 -69
  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.6";
757
+ var VERSION = "0.1.8";
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
  {
@@ -706,8 +779,8 @@ var TOOLS = [
706
779
  },
707
780
  {
708
781
  name: "provision_workspace",
709
- description: "Zero-click autonomous workspace and API key provisioning on TokenZero (POST /v1/workspaces). Returns a live tz_live_... API key, workspace_id, 1-click auto-unlock dashboard URL, gateway base URLs, and badge SVG URL with zero signup form required.",
710
- inputSchema: { type: "object", properties: { name: { type: "string", description: "Optional workspace name (defaults to mcp-agent)" } }, additionalProperties: false }
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 }
711
784
  }
712
785
  ];
713
786
  async function callTool(name, args) {
@@ -738,34 +811,28 @@ async function callTool(name, args) {
738
811
  }
739
812
  case "provision_workspace": {
740
813
  const base = serviceUrl();
741
- if (!base) throw new Error("set TOKENZERO_URL to use this tool");
742
- const wsName = typeof args?.name === "string" && args.name.trim() ? args.name.trim() : "mcp-agent";
743
- const r = await fetch(base + "/v1/workspaces", {
744
- method: "POST",
745
- redirect: "manual",
746
- signal: AbortSignal.timeout(15e3),
747
- headers: { "content-type": "application/json", "user-agent": `tokenzero-mcp/${VERSION}` },
748
- body: JSON.stringify({ name: wsName })
749
- }).catch((e) => {
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) => {
750
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`);
751
818
  });
752
819
  if (r.status >= 300 && r.status < 400) throw new Error(`${base} is not publicly reachable yet (redirected)`);
753
820
  const j = await r.json();
754
- if (r.status >= 400) throw new Error(j?.error?.message ?? `HTTP ${r.status}`);
755
- const id = j.workspace?.id ?? j.workspace_id ?? j.id;
756
- const key = j.key ?? j.api_key;
821
+ if (r.status !== 201) throw new Error(j?.error?.message ?? `HTTP ${r.status}`);
822
+ const id = j.workspace.id, key = j.key;
757
823
  return {
758
824
  workspace_id: id,
759
- api_key: key,
760
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}`,
761
831
  dashboard_url: `${base}/dashboard?workspace=${id}`,
762
832
  one_click_dashboard_url: `${base}/dashboard?workspace=${id}#key=${key}`,
763
- gateway_base_url: `${base}/v1`,
764
- anthropic_base_url: base,
765
- gemini_base_url: `${base}/gemini`,
766
- zero_code_base_url: `${base}/w/${key}/v1`,
767
833
  mcp_url: `${base}/mcp`,
768
- badge_svg_url: `${base}/v1/badge/${id}.svg`
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."
769
836
  };
770
837
  }
771
838
  default:
@@ -777,39 +844,54 @@ async function handle(m) {
777
844
  const isRequest = m && m.id !== void 0 && m.id !== null && typeof m.method === "string";
778
845
  const reply = (result) => isRequest && send({ jsonrpc: "2.0", id: m.id, result });
779
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
+ };
780
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
+ }
781
875
  switch (m?.method) {
782
876
  case "initialize": {
783
877
  const want = m.params?.protocolVersion;
784
- 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 });
785
879
  }
786
- case "server/discover":
787
- return reply({
788
- resultType: "complete",
789
- supportedVersions: ["2026-07-28"],
790
- capabilities: { tools: { listChanged: false } },
791
- 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.",
792
- ttlMs: 36e5,
793
- cacheScope: "public",
794
- _meta: { "io.modelcontextprotocol/serverInfo": { name: "tokenzero", version: VERSION } }
795
- });
796
880
  case "ping":
797
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));
798
885
  case "tools/list":
799
886
  return reply({ tools: TOOLS });
800
887
  case "tools/call":
801
- try {
802
- const r = await callTool(m.params?.name, m.params?.arguments ?? {});
803
- return reply({ content: [{ type: "text", text: JSON.stringify(r, null, 2) }], structuredContent: r, isError: false });
804
- } catch (e) {
805
- return reply({ content: [{ type: "text", text: `error: ${e?.message ?? e}` }], isError: true });
806
- }
888
+ return reply(await call());
807
889
  default:
808
890
  if (typeof m?.method === "string" && m.method.startsWith("notifications/")) return;
809
- return fail(-32601, `method not found: ${m?.method}`);
891
+ return fail(RPC.methodNotFound, `method not found: ${m?.method}`);
810
892
  }
811
893
  } catch (e) {
812
- fail(-32603, e?.message ?? "internal error");
894
+ fail(RPC.internal, e?.message ?? "internal error");
813
895
  }
814
896
  }
815
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.6",
3
+ "version": "0.1.8",
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",