AI Development

MCP in Production: What It Actually Solves, Where It Breaks, and When a Plain Function Wins

MCP is a distribution protocol, not a capability upgrade. Here's the rule we use to decide whether a tool belongs in an MCP server or stays a plain function in your agent loop — and the context tax nobody budgets for.

KAKabir AnandLead Developer
10 min read
Abstract flat-vector illustration of one hub connected to several distinct client shapes

The first question every team asks about MCP is the wrong one: "should we build an MCP server for this?" The right one is "who else needs this tool, and do they live in our codebase?" MCP doesn't make an agent more capable. It makes a capability portable. If nothing is going to consume that portability, you've added a process, a transport, and a schema layer to call a function you already had.

We've shipped MCP servers for four clients this year — an invoicing back office, a logistics dispatcher, an internal design-system query tool, and one that we deleted three weeks in and replaced with twelve lines of TypeScript. That last one taught us more than the other three.

MCP solves distribution, not capability

Before MCP, every agent-to-system integration was bespoke: your app wired tools one way, Claude Code wired them another, a teammate's IDE didn't have them at all. Each new client meant re-implementing auth, re-describing schemas, re-testing edge cases. MCP collapses that N×M problem into N+M — one server, many clients, one place where the contract lives.

One server, many consumers — and the cost of that

MCP serverone implementationClaude CodeYour agentEditor pluginCI runnercontext taxpaid per client
Every client that connects re-reads the whole tool manifest — the cost scales with consumers, not with calls.

What an MCP server actually moves out of your codebase

BeforeEach app re-implements auth, schemas, rate limits
One MCP serverOwns the contract, the credentials, the retries
Any MCP client connectsYour product, Claude Code, a teammate's IDE
Fix ships onceEvery client gets it without a release
  • The win is operational, not cognitive— the model doesn’t reason better because a tool arrived over MCP. It reasons exactly as well as it would with the same schema passed inline.
  • Credentials stop travelling — the server holds the API key and the scope. Clients get a tool name, not a token, which is the single strongest argument for MCP in a regulated environment.
  • Versioning becomes possible — you can deprecate a tool, change its shape, or add a guardrail without shipping four client releases in lockstep.
  • Discovery is the underrated part — a new engineer points their editor at the server and immediately has the same twelve tools the production agent has, with the same descriptions.

Where it breaks: you pay for every tool on every turn

Tool definitions are prompt. They're serialized into the request on every single turn, whether the model uses them or not. Our logistics client connected three MCP servers, exposed 40 tools between them, and burned 31% of their prompt budget on schemas before the user had typed anything. Latency went up, cost went up, and — worse — tool selection accuracy went down, because eleven of those tools had overlapping descriptions.

The fix isn't a bigger context window. It's fewer, coarser tools. We stopped mirroring our REST endpoints one-to-one and started designing tools around the task the agent is actually trying to complete.

servers/billing/tools.ts
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2 
3// One task-shaped tool replaced six CRUD wrappers.
4server.registerTool("resolve_invoice", {
5  description: "Find an invoice by number or customer name, then credit it or mark it paid.",
6  inputSchema: {
7    query: z.string(),
8    action: z.enum(["credit", "mark_paid"]),
9  },
10}, async ({ query, action }): Promise<ToolResult> => {
11  const invoice = await findInvoice(query);
12  if (!invoice) return text("No match. Ask the user for the invoice number.");
13  return text(await apply(invoice, action));
14});
The failure message is part of the schema

Returning "No match. Ask the user for the invoice number." instead of a 404 cut our retry loops roughly in half. An MCP tool's error strings are read by a model, not a human — write them as instructions for what to do next, not as status reports.

The rule: MCP for other people's clients, functions for your own loop

Here's the decision we make on every tool now, and it takes about thirty seconds. If the only caller is the agent loop you control, in the same repo, in the same deploy — it's a plain function. You get type safety, a stack trace, a debugger, and zero serialization. The moment a second, independently-deployed client needs it, or the credential can't be in that client's process, it earns an MCP server.

  • 1Count the clients. One caller in one codebase means a function. Two or more independently-shipped consumers means a server — that's the whole test, and it's right most of the time.
  • 2Check where the secret lives. If a tool needs a credential you wouldn't put in the calling process, MCP is the cheap boundary. This overrides the client count.
  • 3Look at the return payload. Tools that return large or unbounded results (a full table, a whole file tree) belong behind a server that can paginate and truncate, because a plain function will happily blow your context window.
  • 4Then trim ruthlessly. Cap what any one agent sees at roughly a dozen tools; if you're past that, you're modelling your API surface instead of the agent's job.

31%

of prompt budget spent on tool schemas before we trimmed the server list

40 → 9

tools exposed to the logistics agent after redesigning around tasks

1 of 4

MCP servers we built this year that should have stayed a plain function

We stopped asking whether a tool could be an MCP tool and started asking who the second client is. If we couldn't name one, we deleted the server.

Kabir Anand, Lead Developer

Key Takeaways

  • MCP is a distribution and credential boundary — it makes a capability portable, it does not make the model better at using it.
  • Every connected tool costs prompt tokens on every turn; three servers and 40 tools measurably degraded both cost and tool-selection accuracy.
  • Design tools around the task the agent is completing, not around your REST endpoints — one coarse tool beats six thin CRUD wrappers.
  • Default to a plain function when you own the only caller; promote it to an MCP server when a second independent client appears or the credential can't live in-process.

Where to start

Take the agent you already have in production and log the prompt-token split between tool schemas and actual conversation for a week. If schemas are over a fifth of it, you have a trimming problem, not an MCP problem — and no amount of protocol will fix that. Once the tool list is honest, pick the single integration that two teams are duplicating and make that your first server. One real consumer on day one beats a platform nobody connects to.

Keep Reading

More from the blog

The Engineering Partner You Can Build On

Reliable software takes an experienced team that owns delivery end to end. Here’s the track record behind ours.

11+

Years Building Custom Software

320+

Projects Delivered Across Web, Mobile & AI

85%

Repeat Client Rate

12+

Countries Served Worldwide

Trusted by startups and enterprises worldwide

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.