@tokcalc/mcp-server 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/dist/server.js +56 -17
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -16010,11 +16010,11 @@ function handleEstimateCapacity(input) {
|
|
|
16010
16010
|
memory: result.confidence.totalVramNeededGb
|
|
16011
16011
|
},
|
|
16012
16012
|
assumptions: [
|
|
16013
|
-
|
|
16014
|
-
|
|
16013
|
+
`η_mem = 0.65 (typical real-world memory utilization)`,
|
|
16014
|
+
`η_compute = 0.50 (typical compute utilization)`,
|
|
16015
16015
|
`KV cache in FP16 (2 bytes per value)`,
|
|
16016
16016
|
`Engine: ${input.engine} (affects efficiency factors)`,
|
|
16017
|
-
`Continuous batching: ${input.continuousBatching ? `${input.continuousBatchingMultiplier}
|
|
16017
|
+
`Continuous batching: ${input.continuousBatching ? `${input.continuousBatchingMultiplier}× multiplier` : "disabled"}`,
|
|
16018
16018
|
`These are planning estimates, not deployment guarantees`
|
|
16019
16019
|
],
|
|
16020
16020
|
catalogVersion: "0.3.0"
|
|
@@ -16089,19 +16089,24 @@ function handleRecommendTopology(input) {
|
|
|
16089
16089
|
const quant = QUANT_MAP[input.quantization];
|
|
16090
16090
|
if (!model || !quant)
|
|
16091
16091
|
return { error: "Unknown model or quantization" };
|
|
16092
|
-
const kvPerRequest = computeKVCacheGb(model, input.contextTokens,
|
|
16092
|
+
const kvPerRequest = computeKVCacheGb(model, input.contextTokens, 1);
|
|
16093
|
+
const kvPerBatch = computeKVCacheGb(model, input.contextTokens, input.batchSize);
|
|
16093
16094
|
const results = GPUS.map((g) => {
|
|
16094
16095
|
const rec = recommendTopology(model, g, input.contextTokens, input.batchSize, quant.bytesPerParam);
|
|
16095
|
-
const maxConcurrent = computeMaxConcurrency(model, g,
|
|
16096
|
+
const maxConcurrent = rec.neededGpus > 0 ? computeMaxConcurrency(model, g, rec.neededGpus, input.contextTokens, quant.bytesPerParam) : 0;
|
|
16096
16097
|
return {
|
|
16097
16098
|
gpuId: g.id,
|
|
16098
16099
|
gpuName: g.name,
|
|
16099
16100
|
vramGb: g.vramGb,
|
|
16100
16101
|
topology: rec.topology,
|
|
16101
16102
|
neededGpus: rec.neededGpus,
|
|
16103
|
+
totalVramGb: +(g.vramGb * rec.neededGpus).toFixed(0),
|
|
16104
|
+
vramNeededGb: +(model.paramsB * quant.bytesPerParam + kvPerBatch).toFixed(1),
|
|
16102
16105
|
fits: rec.fits,
|
|
16103
16106
|
maxConcurrentUsers: maxConcurrent,
|
|
16107
|
+
maxConcurrentBatchesAtContext: Math.floor(maxConcurrent / Math.max(input.batchSize, 1)),
|
|
16104
16108
|
kvPerRequestGb: +kvPerRequest.toFixed(2),
|
|
16109
|
+
kvPerBatchGb: +kvPerBatch.toFixed(2),
|
|
16105
16110
|
reason: rec.reason
|
|
16106
16111
|
};
|
|
16107
16112
|
}).filter((r) => r.fits).slice(0, 5);
|
|
@@ -16110,11 +16115,12 @@ function handleRecommendTopology(input) {
|
|
|
16110
16115
|
model: { name: model.name, paramsB: model.paramsB, activeParamsB: model.activeParamsB, isMoE: model.isMoE },
|
|
16111
16116
|
context: { tokens: input.contextTokens, label: fmtContext(input.contextTokens), batchSize: input.batchSize },
|
|
16112
16117
|
recommendations: results,
|
|
16113
|
-
formula: `KV per
|
|
16114
|
-
assumptions: [`Single GPU unless TP needed`, `KV cache in FP16`, `Model weights + KV × batchSize must fit in total VRAM`, `Catalog version: 0.3.0`]
|
|
16118
|
+
formula: `KV per request = 2 × ${model.layers} layers × ${model.kvHeads} KV heads × ${model.headDim} head_dim × 2 bytes × ${input.contextTokens} tokens = ${kvPerRequest.toFixed(2)} GB; × ${input.batchSize} batch = ${kvPerBatch.toFixed(2)} GB`,
|
|
16119
|
+
assumptions: [`Single GPU unless TP needed`, `KV cache in FP16`, `Model weights + KV × batchSize must fit in total VRAM`, `maxConcurrentUsers is concurrent single-slot requests at this context; concurrent batches = maxConcurrentUsers ÷ ${input.batchSize}`, `Catalog version: 0.3.0`]
|
|
16115
16120
|
};
|
|
16116
16121
|
}
|
|
16117
16122
|
function handleEstimateApiVsSelfHost(input) {
|
|
16123
|
+
const utilizationPct = input.utilization * 100;
|
|
16118
16124
|
const sh = calculate({
|
|
16119
16125
|
modelId: input.model,
|
|
16120
16126
|
gpuId: input.gpu,
|
|
@@ -16125,8 +16131,16 @@ function handleEstimateApiVsSelfHost(input) {
|
|
|
16125
16131
|
outputTokens: input.outputTokens
|
|
16126
16132
|
});
|
|
16127
16133
|
const gpu = GPU_MAP[input.gpu];
|
|
16134
|
+
const model = MODEL_MAP[input.model];
|
|
16135
|
+
if (!sh.vramFits) {
|
|
16136
|
+
return {
|
|
16137
|
+
error: `Self-host infeasible: ${model?.name ?? input.model} at ${input.quantization} on ${input.gpuCount}× ${gpu?.name ?? input.gpu} needs ${sh.totalVramNeededGb.toFixed(1)} GB but only ${((gpu?.vramGb ?? 0) * input.gpuCount).toFixed(0)} GB is available.`,
|
|
16138
|
+
suggestion: `Increase gpuCount, lower the quantization (e.g. fp8/int4), or pick a smaller model. Break-even is undefined until the config fits.`,
|
|
16139
|
+
feasibility: { fits: false, vramNeededGb: +sh.totalVramNeededGb.toFixed(2), vramAvailableGb: +((gpu?.vramGb ?? 0) * input.gpuCount).toFixed(0) }
|
|
16140
|
+
};
|
|
16141
|
+
}
|
|
16128
16142
|
const effGpuPrice = (gpu?.usdPerHour ?? 0) * input.gpuCount;
|
|
16129
|
-
const effTokens = sh.aggregateTokensPerSec *
|
|
16143
|
+
const effTokens = sh.aggregateTokensPerSec * input.utilization;
|
|
16130
16144
|
const selfHostCostPerM = effTokens > 0 ? effGpuPrice / 3600 / effTokens * 1e6 : Infinity;
|
|
16131
16145
|
const selfHostMonthly = effGpuPrice * 730;
|
|
16132
16146
|
const apiCostPerRequest = input.inputTokens / 1e6 * input.apiInputPrice + input.outputTokens / 1e6 * input.apiOutputPrice;
|
|
@@ -16141,8 +16155,10 @@ function handleEstimateApiVsSelfHost(input) {
|
|
|
16141
16155
|
costPerMillionTokens: +selfHostCostPerM.toFixed(2),
|
|
16142
16156
|
monthlyInfraUsd: +selfHostMonthly.toFixed(2),
|
|
16143
16157
|
monthlyTotalUsd: +selfHostMonthlyTotal.toFixed(2),
|
|
16144
|
-
utilization: `${
|
|
16145
|
-
|
|
16158
|
+
utilization: `${utilizationPct.toLocaleString("en-US")}%`,
|
|
16159
|
+
utilizationNote: "Share of peak fleet throughput actually sold/utilized.",
|
|
16160
|
+
throughput: `${fmtTokens(sh.aggregateTokensPerSec)} tok/s`,
|
|
16161
|
+
effectiveThroughput: `${fmtTokens(effTokens)} tok/s at ${utilizationPct.toLocaleString("en-US")}% utilization`
|
|
16146
16162
|
},
|
|
16147
16163
|
api: {
|
|
16148
16164
|
costPerMillionTokens: input.apiOutputPrice,
|
|
@@ -16152,10 +16168,11 @@ function handleEstimateApiVsSelfHost(input) {
|
|
|
16152
16168
|
breakEven: {
|
|
16153
16169
|
requestsPerDay: Math.round(breakEven),
|
|
16154
16170
|
reached: meetsVolume,
|
|
16155
|
-
explanation: `At ${
|
|
16171
|
+
explanation: `At ${utilizationPct.toLocaleString("en-US")}% utilization with ${input.gpuCount}× ${gpu?.name || input.gpu}, self-hosting breaks even at ${Math.round(breakEven).toLocaleString("en-US")} requests/day.`,
|
|
16172
|
+
requestsPerDayLabel: Math.round(breakEven).toLocaleString("en-US")
|
|
16156
16173
|
},
|
|
16157
16174
|
assumptions: [
|
|
16158
|
-
`Self-host throughput: ${fmtTokens(sh.aggregateTokensPerSec)} tok/s at ${
|
|
16175
|
+
`Self-host peak throughput: ${fmtTokens(sh.aggregateTokensPerSec)} tok/s; effective at ${utilizationPct.toLocaleString("en-US")}% utilization = ${fmtTokens(effTokens)} tok/s`,
|
|
16159
16176
|
`GPU price: $${effGpuPrice}/hr`,
|
|
16160
16177
|
`API pricing: $${input.apiInputPrice}/M input, $${input.apiOutputPrice}/M output`,
|
|
16161
16178
|
`730 hours/month`,
|
|
@@ -16286,11 +16303,23 @@ var TOOL_DEFINITIONS = [
|
|
|
16286
16303
|
inputSchema: formatInputSchema(GetMlperfBenchmarksSchema)
|
|
16287
16304
|
}
|
|
16288
16305
|
];
|
|
16306
|
+
var SERVER_VERSION = "0.2.4";
|
|
16307
|
+
function withProvenance(result) {
|
|
16308
|
+
return {
|
|
16309
|
+
...result,
|
|
16310
|
+
provenance: {
|
|
16311
|
+
serverVersion: SERVER_VERSION,
|
|
16312
|
+
catalogVersion: "0.3.0",
|
|
16313
|
+
buildStamp: `tokcalc-mcp/${SERVER_VERSION}`
|
|
16314
|
+
}
|
|
16315
|
+
};
|
|
16316
|
+
}
|
|
16289
16317
|
function createMcpServer() {
|
|
16290
|
-
const server = new Server({ name: "tokcalc", version:
|
|
16318
|
+
const server = new Server({ name: "tokcalc", version: SERVER_VERSION }, {
|
|
16291
16319
|
capabilities: {
|
|
16292
16320
|
tools: {}
|
|
16293
|
-
}
|
|
16321
|
+
},
|
|
16322
|
+
instructions: `tokcalc MCP server ${SERVER_VERSION} — LLM serving capacity planner. ` + `Use list_models / list_gpus to discover canonical IDs before calling ` + `estimate_capacity, compare_gpus, recommend_topology, or ` + `estimate_api_vs_self_host. All estimates are planning projections, not ` + `deployment guarantees. Every response carries provenance.serverVersion.`
|
|
16294
16323
|
});
|
|
16295
16324
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16296
16325
|
tools: TOOL_DEFINITIONS
|
|
@@ -16341,16 +16370,25 @@ function createMcpServer() {
|
|
|
16341
16370
|
isError: true
|
|
16342
16371
|
};
|
|
16343
16372
|
}
|
|
16373
|
+
const isHandlerError = !!result && typeof result === "object" && "error" in result;
|
|
16374
|
+
const stamped = withProvenance(result);
|
|
16344
16375
|
return {
|
|
16345
16376
|
content: [
|
|
16346
|
-
{ type: "text", text: JSON.stringify(
|
|
16377
|
+
{ type: "text", text: JSON.stringify(stamped, null, 2) }
|
|
16347
16378
|
],
|
|
16348
|
-
structuredContent:
|
|
16379
|
+
structuredContent: stamped,
|
|
16380
|
+
...isHandlerError ? { isError: true } : {}
|
|
16349
16381
|
};
|
|
16350
16382
|
} catch (error) {
|
|
16351
16383
|
return {
|
|
16352
16384
|
content: [
|
|
16353
|
-
{
|
|
16385
|
+
{
|
|
16386
|
+
type: "text",
|
|
16387
|
+
text: JSON.stringify({
|
|
16388
|
+
error: error instanceof Error ? error.message : String(error),
|
|
16389
|
+
provenance: { serverVersion: SERVER_VERSION, buildStamp: `tokcalc-mcp/${SERVER_VERSION}` }
|
|
16390
|
+
}, null, 2)
|
|
16391
|
+
}
|
|
16354
16392
|
],
|
|
16355
16393
|
isError: true
|
|
16356
16394
|
};
|
|
@@ -16359,5 +16397,6 @@ function createMcpServer() {
|
|
|
16359
16397
|
return server;
|
|
16360
16398
|
}
|
|
16361
16399
|
export {
|
|
16400
|
+
SERVER_VERSION,
|
|
16362
16401
|
createMcpServer
|
|
16363
16402
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokcalc/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"mcpName": "io.github.stevecrates489-commits/tokcalc",
|
|
5
5
|
"description": "tokcalc MCP server — open-source LLM serving capacity planner for AI agents. v0.2.1: stdio + stateless HTTP transport with bearer API key auth + KV-backed rate limiting + public hosted endpoint. 7 read-only tools: estimate_capacity, compare_gpus, recommend_topology, estimate_api_vs_self_host, list_models, list_gpus, get_mlperf_benchmarks.",
|
|
6
6
|
"type": "module",
|