@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.
Files changed (2) hide show
  1. package/dist/server.js +32 -13
  2. 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
- `η_mem = 0.65 (typical real-world memory utilization)`,
16014
- `η_compute = 0.50 (typical compute utilization)`,
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}× multiplier` : "disabled"}`,
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, input.batchSize);
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, input.batchSize, input.contextTokens, quant.bytesPerParam);
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 batch = 2 × ${model.layers} layers × ${model.kvHeads} KV heads × ${model.headDim} head_dim × 2 bytes × ${input.contextTokens} tokens × ${input.batchSize} batch = ${kvPerRequest.toFixed(2)} GB`,
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 * (input.utilization / 100);
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: `${input.utilization}%`,
16145
- throughput: `${fmtTokens(sh.aggregateTokensPerSec)} tok/s`
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 ${input.utilization}% utilization with ${input.gpuCount}× ${gpu?.name || input.gpu}, self-hosting breaks even at ${Math.round(breakEven).toLocaleString("en-US")} requests/day.`
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 ${input.utilization}% utilization`,
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.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",