@tokcalc/mcp-server 0.2.2 → 0.2.3
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 +32 -13
- 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`,
|
|
@@ -16341,11 +16358,13 @@ function createMcpServer() {
|
|
|
16341
16358
|
isError: true
|
|
16342
16359
|
};
|
|
16343
16360
|
}
|
|
16361
|
+
const isHandlerError = !!result && typeof result === "object" && "error" in result;
|
|
16344
16362
|
return {
|
|
16345
16363
|
content: [
|
|
16346
16364
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
16347
16365
|
],
|
|
16348
|
-
structuredContent: result
|
|
16366
|
+
structuredContent: result,
|
|
16367
|
+
...isHandlerError ? { isError: true } : {}
|
|
16349
16368
|
};
|
|
16350
16369
|
} catch (error) {
|
|
16351
16370
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokcalc/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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",
|