{"slug": "your-mcp-server-s-tool-calls-are-silently-burning-money-here-s-a-40-line-budget", "title": "Your MCP server's tool calls are silently burning money. Here's a 40-line budget guard.", "summary": "A developer published a 40-line JavaScript budget guard for MCP servers that caps per-agent tool calls by time and token budget, addressing runaway LLM tool invocations that the author says can turn a $12 job into $47. The ToolCallBudgetGuard class tracks time used, tokens consumed and cost per agent, emits budgetExceeded and callRecorded events, and wraps client.callTool calls so agents are blocked once limits are hit.", "body_md": "Your MCP server's tool calls are silently burning money. Here's a 40-line budget guard.\n\nYou have 3 agents running in parallel, each calling the same expensive LLM tool 50 times per minute. The first agent finishes its batch and stops. The second and third agents keep calling until their 10-minute time budget expires. By then you've spent $47 on a job that should have cost $12.\n\nThis happens because MCP tool calls are fire-and-forget: you send the request, you get a result, and you never track how much you've spent. No per-call budget, no time limit, no per-agent cap.\n\nHere's a 40-line budget guard that stops runaway tool calls in their tracks.\n\nAn MCP server exposes a tool like this:\n\n```\n{\n  \"name\": \"llm_generate\",\n  \"description\": \"Generate text using an LLM\",\n  \"inputSchema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"prompt\": { \"type\": \"string\" },\n      \"model\": { \"type\": \"string\", \"enum\": [\"gpt-4\", \"claude-3-5-sonnet\"] }\n    }\n  }\n}\n```\n\nYour agent calls it like this:\n\n``` js\nconst result = await client.callTool({\n  name: \"llm_generate\",\n  arguments: { prompt: \"Write a Python script to...\" }\n});\n```\n\nNo budget, no time limit, no per-agent cap. If the agent loops 100 times, you pay for 100 calls.\n\nWrap the tool call in a budget guard that tracks:\n\n``` js\n// budget-guard.js\nimport { EventEmitter } from \"events\";\n\nclass ToolCallBudgetGuard {\n  constructor(options = {}) {\n    this.timeBudget = options.timeBudget || 10 * 60 * 1000; // 10 minutes\n    this.tokenBudget = options.tokenBudget || 10_000;\n    this.costPerToken = options.costPerToken || 0.00003; // GPT-4 ~$3/1M tokens\n    this.budgets = new Map(); // agentId -> { timeUsed, tokensUsed, startTime }\n    this.emitter = new EventEmitter();\n  }\n\n  // Get or create a budget for an agent\n  getBudget(agentId) {\n    if (!this.budgets.has(agentId)) {\n      this.budgets.set(agentId, {\n        timeUsed: 0,\n        tokensUsed: 0,\n        startTime: Date.now()\n      });\n    }\n    return this.budgets.get(agentId);\n  }\n\n  // Check if a tool call is allowed\n  canCall(agentId, estimatedTokens = 0) {\n    const budget = this.getBudget(agentId);\n    const timeUsed = Date.now() - budget.startTime;\n    const tokensUsed = budget.tokensUsed + estimatedTokens;\n\n    const timeExceeded = timeUsed > this.timeBudget;\n    const tokensExceeded = tokensUsed > this.tokenBudget;\n\n    if (timeExceeded || tokensExceeded) {\n      this.emitter.emit(\"budgetExceeded\", {\n        agentId,\n        reason: timeExceeded ? \"time\" : \"tokens\",\n        timeUsed,\n        tokensUsed\n      });\n      return false;\n    }\n\n    return true;\n  }\n\n  // Record a successful tool call\n  recordCall(agentId, tokens = 0, cost = 0) {\n    const budget = this.getBudget(agentId);\n    budget.timeUsed = Date.now() - budget.startTime;\n    budget.tokensUsed += tokens;\n    budget.cost += cost;\n\n    this.emitter.emit(\"callRecorded\", {\n      agentId,\n      tokens,\n      cost,\n      timeUsed: budget.timeUsed,\n      tokensUsed: budget.tokensUsed\n    });\n  }\n\n  // Get current budget usage\n  getUsage(agentId) {\n    const budget = this.budgets.get(agentId);\n    if (!budget) return null;\n    return {\n      timeUsed: Date.now() - budget.startTime,\n      tokensUsed: budget.tokensUsed,\n      cost: budget.cost\n    };\n  }\n}\n\nexport default ToolCallBudgetGuard;\npython\nimport ToolCallBudgetGuard from \"./budget-guard.js\";\n\nconst guard = new ToolCallBudgetGuard({\n  timeBudget: 10 * 60 * 1000, // 10 minutes\n  tokenBudget: 10_000,\n  costPerToken: 0.00003 // GPT-4\n});\n\n// Listen for budget exceeded events\nguard.on(\"budgetExceeded\", ({ agentId, reason, timeUsed, tokensUsed }) => {\n  console.error(`[${agentId}] Budget exceeded: ${reason}`);\n  console.error(`  Time used: ${Math.round(timeUsed / 1000)}s`);\n  console.error(`  Tokens used: ${tokensUsed}`);\n});\n\n// Wrap your tool call\nasync function callLLM(agentId, prompt, model = \"gpt-4\") {\n  // Estimate tokens (simplified: 4 chars per token for English)\n  const estimatedTokens = Math.ceil(prompt.length / 4);\n\n  // Check budget before calling\n  if (!guard.canCall(agentId, estimatedTokens)) {\n    throw new Error(`Tool call blocked: budget exceeded for ${agentId}`);\n  }\n\n  // Call the LLM\n  const result = await openai.chat.completions.create({\n    model,\n    messages: [{ role: \"user\", content: prompt }]\n  });\n\n  // Record the call\n  guard.recordCall(agentId, result.usage.total_tokens, result.usage.total_tokens * guard.costPerToken);\n\n  return result.choices[0].message.content;\n}\n\n// Use it in your agent\nconst agent1 = callLLM(\"agent-1\", \"Write a Python script...\");\nconst agent2 = callLLM(\"agent-2\", \"Write a Python script...\");\nconst agent3 = callLLM(\"agent-3\", \"Write a Python script...\");\n```\n\n`length / 4`\n`llm_generate`) separately from others.\nRunaway tool calls are a silent budget killer. A 40-line guard stops them before they burn you.\n\n*This article is a teaching piece. For a production-ready MCP tool-call budgeting pack, see the linked resources above.*", "url": "https://wpnews.pro/news/your-mcp-server-s-tool-calls-are-silently-burning-money-here-s-a-40-line-budget", "canonical_source": "https://dev.to/renev3408/your-mcp-servers-tool-calls-are-silently-burning-money-heres-a-40-line-budget-guard-5gkl", "published_at": "2026-10-19 02:07:56+00:00", "updated_at": "2026-10-19 02:19:01.071630+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools", "ai-infrastructure"], "entities": ["MCP", "ToolCallBudgetGuard", "GPT-4", "Claude 3.5 Sonnet"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/your-mcp-server-s-tool-calls-are-silently-burning-money-here-s-a-40-line-budget", "markdown": "https://wpnews.pro/news/your-mcp-server-s-tool-calls-are-silently-burning-money-here-s-a-40-line-budget.md", "text": "https://wpnews.pro/news/your-mcp-server-s-tool-calls-are-silently-burning-money-here-s-a-40-line-budget.txt", "jsonld": "https://wpnews.pro/news/your-mcp-server-s-tool-calls-are-silently-burning-money-here-s-a-40-line-budget.jsonld"}}