@tokenzero/mcp 0.2.10 → 0.2.12

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.
@@ -9,7 +9,7 @@ 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-28";
12
+ var PRICING_VERSION = "2026-11-01";
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) ----
@@ -23,6 +23,9 @@ var PRICES = {
23
23
  "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 }),
24
24
  // Daybreak cyber model: access needs OpenAI approval; 400K window
25
25
  "gpt-5.6-cyber": P("openai", "frontier", 12.5, 75, 1.25, 15.625, { longCtxAt: 272e3, longCtx: { input: 25, output: 112.5, cachedInput: 2.5, cacheWrite: 31.25 }, minCacheTokens: 1024 }),
26
+ // 2026-11-01 (developers.openai.com/api/docs/pricing): Astra for Law at Astra rates (promo), Daybreak gpt-5.5-cyber.
27
+ "gpt-6-astra-law": P("openai", "frontier", 10, 50, 1, void 0, { minCacheTokens: 1024 }),
28
+ "gpt-5.5-cyber": P("openai", "frontier", 12.5, 75),
26
29
  "gpt-5.3-codex": P("openai", "mid", 1.75, 14, 0.175, void 0, { minCacheTokens: 1024 }),
27
30
  "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 }),
28
31
  "gpt-5.5-pro": P("openai", "frontier", 30, 180),
@@ -42,6 +45,13 @@ var PRICES = {
42
45
  "o3-mini": P("openai", "fast", 1.1, 4.4, 0.55, void 0, { minCacheTokens: 1024 }),
43
46
  "o4-mini": P("openai", "fast", 1.1, 4.4, 0.275, void 0, { minCacheTokens: 1024 }),
44
47
  "o1": P("openai", "frontier", 15, 60, 7.5),
48
+ "o1-pro": P("openai", "frontier", 150, 600),
49
+ // Legacy models still on the pricing page: audited repos use them, so receipts and findings price them correctly
50
+ // instead of falling back to the nearest prefix row (gpt-4o-2024-05-13 is $5/$15, not gpt-4o's $2.50/$10).
51
+ "gpt-4o-2024-05-13": P("openai", "mid", 5, 15),
52
+ "gpt-4-turbo": P("openai", "frontier", 10, 30),
53
+ "gpt-4-0613": P("openai", "frontier", 30, 60),
54
+ "gpt-3.5-turbo": P("openai", "fast", 0.5, 1.5),
45
55
  "gpt-4.1": P("openai", "mid", 2, 8, 0.5, void 0, { fastFactor: 1.75, minCacheTokens: 1024 }),
46
56
  "gpt-4.1-mini": P("openai", "fast", 0.4, 1.6, 0.1, void 0, { minCacheTokens: 1024 }),
47
57
  "gpt-4.1-nano": P("openai", "fast", 0.1, 0.4, 0.025, void 0, { minCacheTokens: 1024 }),
@@ -85,7 +95,7 @@ var PRICES = {
85
95
  "deepseek-v4-pro": P("deepseek", "mid", 1.32, 3.96, 0.044),
86
96
  "deepseek-flash": P("deepseek", "fast", 0.3, 1.2, 6e-3)
87
97
  };
88
- var MODEL_ALIASES = { "deepseek-v4-flash": "deepseek-flash", "deepseek-v4-flash-vision-exp": "deepseek-flash" };
98
+ var MODEL_ALIASES = { "deepseek-v4-flash": "deepseek-flash", "deepseek-v4-flash-vision-exp": "deepseek-flash", "deepseek-v4.1-flash": "deepseek-flash", "deepseek-v4.1-flash-1017": "deepseek-flash", "deepseek-v4-pro-0813": "deepseek-v4-pro", "gpt-4-turbo-2024-04-09": "gpt-4-turbo", "gpt-4": "gpt-4-0613", "gpt-3.5-turbo-0125": "gpt-3.5-turbo" };
89
99
  var CASCADE_TARGET = {
90
100
  "gpt-6-astra": "gpt-6.1-sol",
91
101
  "gpt-6.1-sol": "gpt-6.1-terra",
@@ -123,6 +133,7 @@ var CASCADE_TARGET = {
123
133
  "gemini-2.5-flash": "gemini-2.5-flash-lite"
124
134
  };
125
135
  var KEYS_BY_LENGTH = Object.keys(PRICES).sort((a, b) => b.length - a.length);
136
+ var DISTINCT_VARIANT = /^(instruct|audio|realtime|search|transcribe|tts|image|embedding|vision|computer-use|deep-research)\b/;
126
137
  function normalizeModel(model) {
127
138
  if (typeof model !== "string" || !model) return null;
128
139
  let m = model.toLowerCase().trim();
@@ -130,11 +141,12 @@ function normalizeModel(model) {
130
141
  m = m.replace(/^(us|eu|apac|global)\./, "").replace(/^(anthropic|amazon|meta|mistral|openai)\./, "");
131
142
  m = m.replace(/-v\d+(?::\d+)?$/, "");
132
143
  if (MODEL_ALIASES[m]) m = MODEL_ALIASES[m];
144
+ if (PRICES[m]) return m;
133
145
  m = m.replace(/@\d{8}$/, "");
134
146
  m = m.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/, "");
135
147
  m = m.replace(/-(latest|preview|customtools|thinking)$/, "");
136
148
  if (PRICES[m]) return m;
137
- for (const k of KEYS_BY_LENGTH) if (m.startsWith(k + "-") || m === k) return k;
149
+ for (const k of KEYS_BY_LENGTH) if (m === k || m.startsWith(k + "-") && !DISTINCT_VARIANT.test(m.slice(k.length + 1))) return k;
138
150
  return null;
139
151
  }
140
152
  var isFreeModel = (model) => typeof model === "string" && (/:free$/i.test(model.trim()) || /^openrouter\/free$/i.test(model.trim()));
@@ -920,12 +932,58 @@ function manifests(root) {
920
932
  });
921
933
  }
922
934
 
935
+ // src/events-types.ts
936
+ var EVENTS_EXTENSION = "io.modelcontextprotocol/events";
937
+ var EVENT_ERR = { invalidParams: -32602, notFound: -32023, forbidden: -32024, resourceExhausted: -32025, unsupported: -32026 };
938
+ var keyNote = "Needs a workspace key (x-tokenzero-key or TOKENZERO_KEY).";
939
+ var EVENT_TYPES = [
940
+ {
941
+ name: "spend.threshold_crossed",
942
+ description: `Fires once per period when the workspace's metered LLM spend through TokenZero crosses a dollar threshold: wake an agent on a cost spike. ${keyNote}`,
943
+ delivery: ["poll"],
944
+ inputSchema: { type: "object", properties: { usd: { type: "number", exclusiveMinimum: 0, description: "threshold in USD of actual (post-savings) spend" }, window: { type: "string", enum: ["day", "month"], default: "day", description: "UTC calendar period the spend accumulates over" } }, required: ["usd"] },
945
+ payloadSchema: { type: "object", properties: { workspace: { type: "string" }, window: { type: "string" }, period: { type: "string" }, threshold_usd: { type: "number" }, spend_usd: { type: "number" }, crossed_at: { type: "string", format: "date-time" }, request_id: { type: "string" }, dashboard_url: { type: "string" } } }
946
+ },
947
+ {
948
+ name: "savings.daily_report",
949
+ description: `One event per completed UTC day with traffic: requests, list-price baseline, actual spend and signed savings. ${keyNote}`,
950
+ delivery: ["poll"],
951
+ inputSchema: { type: "object", properties: {} },
952
+ payloadSchema: { type: "object", properties: { workspace: { type: "string" }, date: { type: "string", format: "date" }, requests: { type: "integer" }, cache_hits: { type: "integer" }, baseline_usd: { type: "number" }, actual_usd: { type: "number" }, savings_usd: { type: "number" }, savings_pct: { type: "number" } } }
953
+ },
954
+ {
955
+ name: "upstream.auth_failed",
956
+ description: `Fires when a provider rejects the BYOK key a call carried (401/403), with the fix and a keyless sandbox link. ${keyNote}`,
957
+ delivery: ["poll"],
958
+ inputSchema: { type: "object", properties: { provider: { type: "string", description: "only this provider (openai, anthropic, gemini, ...)" } } },
959
+ payloadSchema: { type: "object", properties: { workspace: { type: "string" }, request_id: { type: "string" }, provider: { type: "string" }, status: { type: "integer" }, model: { type: ["string", "null"] }, at: { type: "string", format: "date-time" }, fix: { type: "string" }, keyless_sandbox: { type: "string" } } }
960
+ },
961
+ {
962
+ name: "pricing.changed",
963
+ description: "Fires when a list price in TokenZero's curated, provider-verified pricing table changes (one event per model and field). No key needed.",
964
+ delivery: ["poll"],
965
+ inputSchema: { type: "object", properties: { models: { type: "array", items: { type: "string" }, description: "only these model ids" } } },
966
+ payloadSchema: { type: "object", properties: { model: { type: "string" }, field: { type: "string", enum: ["input", "output", "cachedInput", "cacheWrite", "added", "removed"] }, old: { type: ["number", "null"] }, new: { type: ["number", "null"] }, unit: { type: "string" }, pricing_version: { type: "string" } } }
967
+ }
968
+ ];
969
+
923
970
  // mcp/src/main.ts
924
- var VERSION = "0.2.10";
971
+ var VERSION = "0.2.12";
925
972
  var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
926
973
  var SUPPORTED = [...MCP_LEGACY_VERSIONS];
927
974
  var SERVER = { name: "tokenzero", version: VERSION };
928
- var CAPABILITIES = { tools: { listChanged: false }, prompts: { listChanged: false } };
975
+ var CAPABILITIES = { tools: { listChanged: false }, prompts: { listChanged: false }, extensions: { [EVENTS_EXTENSION]: {} } };
976
+ var LEGACY_CAPABILITIES = { tools: { listChanged: false }, prompts: { listChanged: false }, experimental: { [EVENTS_EXTENSION]: {} } };
977
+ var listEvents = () => ({ events: EVENT_TYPES.map((e) => ({ ...e, delivery: [...e.delivery] })) });
978
+ async function pollEvents(params) {
979
+ const base = serviceUrl();
980
+ if (!base) return { error: { code: EVENT_ERR.unsupported, message: "Unsupported", data: { reason: "set TOKENZERO_URL to poll events" } } };
981
+ const key = process.env.TOKENZERO_KEY ?? "";
982
+ const r = await fetch(base + "/v1/events/poll", { method: "POST", redirect: "manual", signal: AbortSignal.timeout(2e4), headers: { "content-type": "application/json", "user-agent": `tokenzero-mcp/${VERSION}`, ...key ? { "x-tokenzero-key": key } : {} }, body: JSON.stringify({ name: params?.name, arguments: params?.arguments, cursor: params?.cursor ?? null, maxAgeMs: params?.maxAgeMs, maxEvents: params?.maxEvents }) });
983
+ const j = await r.json().catch(() => null);
984
+ if (r.ok && j && Array.isArray(j.events)) return { result: j };
985
+ return { error: j?.error && typeof j.error.code === "number" ? j.error : { code: RPC.internal, message: `events/poll failed: HTTP ${r.status}` } };
986
+ }
929
987
  var PROMPTS = [
930
988
  { name: "cut-my-llm-bill", title: "Cut my LLM bill", description: "Find token waste in this project (scanned locally, nothing uploaded) and show the top fixes with estimated savings. No API key needed.", arguments: [{ name: "repo", description: "owner/name of a public GitHub repo instead of the local project", required: false }] },
931
989
  { name: "price-my-workload", title: "Price my LLM workload", description: "Price a workload across models at list prices, with the cheaper sibling and the effect of prompt caching. No API key needed.", arguments: [{ name: "model", description: "model id, e.g. gpt-6.1-sol or claude-sonnet-5-5", required: true }, { name: "requests_per_month", description: "monthly request volume (default 10000)", required: false }] },
@@ -965,7 +1023,7 @@ var TOOLS = [
965
1023
  name: "calculate_model_arbitrage",
966
1024
  title: "Price a workload across models",
967
1025
  annotations: { title: "Price a workload across models", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
968
- description: `No API key needed; only \`model\` is required (defaults 2,000 tokens in and 400 out). Price a workload at list prices (table ${PRICING_VERSION}) for the requested model, its cheaper sibling and any extra models, with and without prompt caching. Runs locally.`,
1026
+ description: `No API key needed; only \`model\` is required (defaults 2,000 tokens in and 400 out). Price a workload at list prices for the requested model, its cheaper sibling and any extra models, with and without prompt caching. Runs locally.`,
969
1027
  inputSchema: { type: "object", properties: { model: { type: "string", description: "model id, e.g. gpt-6.1-sol or claude-sonnet-5-5" }, input_tokens: { type: "number", description: "per request; default 2000" }, output_tokens: { type: "number", description: "per request; default 400" }, requests_per_month: { type: "number" }, static_tokens: { type: "number", description: "portion of input_tokens that is a fixed prefix (system + tools)" }, compare_to: { type: "array", items: { type: "string" } } }, required: ["model"], additionalProperties: false }
970
1028
  },
971
1029
  {
@@ -1092,6 +1150,16 @@ async function handle(m) {
1092
1150
  return reply(modernResult(promptMessages(String(m.params?.name ?? ""), m.params?.arguments), SERVER));
1093
1151
  case "tools/call":
1094
1152
  return reply(modernResult(await call(), SERVER));
1153
+ case "events/list":
1154
+ return reply(modernResult(listEvents(), SERVER, true));
1155
+ case "events/poll": {
1156
+ const r = await pollEvents(m.params);
1157
+ return r.result ? reply({ ...r.result, resultType: "complete", _meta: modernResult({}, SERVER)._meta }) : failData(r.error.code, r.error.message, r.error.data);
1158
+ }
1159
+ case "events/stream":
1160
+ case "events/subscribe":
1161
+ case "events/unsubscribe":
1162
+ return failData(EVENT_ERR.unsupported, "Unsupported", { feature: "deliveryMode", value: m.method === "events/stream" ? "push" : "webhook", supported: ["poll"] });
1095
1163
  default:
1096
1164
  if (typeof m.method === "string" && m.method.startsWith("notifications/")) return;
1097
1165
  return fail(RPC.methodNotFound, `method not found: ${m.method}`);
@@ -1100,7 +1168,7 @@ async function handle(m) {
1100
1168
  switch (m?.method) {
1101
1169
  case "initialize": {
1102
1170
  const want = m.params?.protocolVersion;
1103
- return reply({ protocolVersion: SUPPORTED.includes(want) ? want : SUPPORTED[0], capabilities: CAPABILITIES, serverInfo: SERVER, instructions: INSTRUCTIONS });
1171
+ return reply({ protocolVersion: SUPPORTED.includes(want) ? want : SUPPORTED[0], capabilities: LEGACY_CAPABILITIES, serverInfo: SERVER, instructions: INSTRUCTIONS });
1104
1172
  }
1105
1173
  case "ping":
1106
1174
  return reply({});
@@ -1115,6 +1183,21 @@ async function handle(m) {
1115
1183
  return reply(promptMessages(String(m.params?.name ?? ""), m.params?.arguments));
1116
1184
  case "tools/call":
1117
1185
  return reply(await call());
1186
+ case "events/list":
1187
+ return reply(listEvents());
1188
+ case "events/poll": {
1189
+ const r = await pollEvents(m.params);
1190
+ if (r.result) {
1191
+ const { resultType, ...rest } = r.result;
1192
+ void resultType;
1193
+ return reply(rest);
1194
+ }
1195
+ return failData(r.error.code, r.error.message, r.error.data);
1196
+ }
1197
+ case "events/stream":
1198
+ case "events/subscribe":
1199
+ case "events/unsubscribe":
1200
+ return failData(EVENT_ERR.unsupported, "Unsupported", { feature: "deliveryMode", value: m.method === "events/stream" ? "push" : "webhook", supported: ["poll"] });
1118
1201
  default:
1119
1202
  if (typeof m?.method === "string" && m.method.startsWith("notifications/")) return;
1120
1203
  return fail(RPC.methodNotFound, `method not found: ${m?.method}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tokenzero/mcp",
3
3
  "mcpName": "sh.tokenzero/mcp",
4
- "version": "0.2.10",
4
+ "version": "0.2.12",
5
5
  "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.",
6
6
  "type": "module",
7
7
  "license": "Apache-2.0",