@tokenzero/mcp 0.1.6 → 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.
- package/bin/tokenzero-mcp.mjs +151 -69
- package/package.json +1 -1
package/bin/tokenzero-mcp.mjs
CHANGED
|
@@ -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-
|
|
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.
|
|
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
|
|
73
|
-
"grok-4": P("xai", "frontier",
|
|
74
|
-
"grok-4
|
|
75
|
-
"grok-4
|
|
76
|
-
"grok-3": P("xai", "mid",
|
|
77
|
-
"grok-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"deepseek-
|
|
81
|
-
"deepseek-
|
|
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|
|
|
124
|
-
m = m.replace(/^(
|
|
125
|
-
m = m.replace(/-v\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
|
|
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
|
|
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.
|
|
757
|
+
var VERSION = "0.1.7";
|
|
688
758
|
var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
|
|
689
|
-
var SUPPORTED = [
|
|
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: "
|
|
710
|
-
inputSchema: { type: "object", properties: { name: { type: "string", description:
|
|
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
|
|
755
|
-
const id = j.workspace
|
|
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:
|
|
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
|
-
|
|
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(
|
|
891
|
+
return fail(RPC.methodNotFound, `method not found: ${m?.method}`);
|
|
810
892
|
}
|
|
811
893
|
} catch (e) {
|
|
812
|
-
fail(
|
|
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.
|
|
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",
|