Skip to main content

Why Hardcoding Tool Calls Is the New Technical Debt

· 5 min read
TheMCPGuy
MCP Developer & Educator

Technical debt visualised as tangled cables and custom adapters

Every time you write a bespoke AI tool integration without a standard, you're making a promise to your future self: I will maintain this forever, or I will rewrite it later.

You're not going to maintain it. And "later" has a way of arriving at the worst possible moment.

What Hardcoded Tool Calls Look Like

Here's a pattern you've probably written, or will write soon:

def call_ai_with_tools(user_message):
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": user_message}],
tools=[
{
"type": "function",
"function": {
"name": "search_customers",
"description": "Search customers",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
}
}
}
}
]
)

if response.choices[0].message.tool_calls:
tool_call = response.choices[0].message.tool_calls[0]
if tool_call.function.name == "search_customers":
args = json.loads(tool_call.function.arguments)
results = db.search_customers(args["query"])
# ... feed results back to model

This code works. Right now. For this model. In this application.

The debt isn't visible yet. The debt appears when:

  • You want the same tool in a different application → copy-paste, diverge forever
  • You switch from GPT-4 to Claude → the tool definition format is different, rewrite
  • You want to use a specialised AI client (Cursor, Claude Desktop) → they can't use your custom function definitions, rewrite
  • A colleague builds the same tool for their project → you now have two implementations of search_customers, slowly diverging

This is technical debt in its purest form: a decision that makes today faster at the cost of tomorrow's flexibility.

The Root Cause: Integration Without a Standard

The underlying problem isn't that your code is bad. It's that you're implementing the integration layer from scratch, with no standard to ensure interoperability.

Every AI provider has slightly different function-calling APIs:

  • OpenAI: tools array with type: "function" wrappers
  • Anthropic: tools array with different schema structure
  • Google: function_declarations in a different format altogether

Every AI client has different integration mechanisms:

  • Claude Desktop: process-based, reads config JSON
  • Cursor: extension-based, different config format
  • Your custom chat UI: whatever you built last year

Without a standard, you're writing N × M integrations, where N is the number of tools and M is the number of clients/models. Every combination is custom. Every combination is technical debt.

MCP as the Technical Debt Cure

MCP changes the equation. Instead of N × M integrations, you write N + M.

Write your search_customers Tool in an MCP server once. It works with every MCP-compatible AI client: Claude Desktop, Cursor, GitHub Copilot, any agent framework that implements the spec. Write your client integration once (or just configure a supported client). It works with every MCP server.

The math is simple. The implications are significant.

"But I'm Moving Fast Right Now"

The most common objection: "MCP is extra complexity I don't need yet."

Let me challenge that framing. Writing a custom tool integration isn't "moving fast." It's taking on debt with a high interest rate. You will pay that debt: when you switch models, when you add a second client, when a colleague needs the same tool, when you need to audit every place your database is accessed by an AI.

MCP is not extra complexity. It's upfront complexity in exchange for long-term simplicity. It's the architectural equivalent of writing a test, it costs time now, saves time continuously.

The developers who say "we'll standardise later" are the same developers who rewrite their entire data access layer after three years of accumulated debt. Standardising later always costs more than standardising now.

The One Valid Exception

If you're building a one-off prototype that will be thrown away after a demo, hardcode everything. Life's short. Ship the prototype.

The moment "prototype" becomes "this is what we're building on," you're in the technical debt danger zone. The prototype's tool integration doesn't get deleted, it becomes the foundation. And foundations matter.

The Migration Path

Already have custom tool integrations? Migration to MCP is mechanical, not creative:

  1. Extract your tool's execution logic into a standalone class
  2. Wrap it in an MCP server with the appropriate Tool definition
  3. Replace your custom function-calling code with an MCP client connection
  4. Delete the old integration code

For most tools, this is a day's work. For a large system with many tools, a week. The payoff is proportional to how many clients and models you eventually want to support.

A Standard Is Not a Constraint, It's a Superpower

I want to end with a mindset shift.

Standards feel like constraints when you first encounter them. You have to learn the protocol. You have to implement a server. You can't just hack a function call directly.

But standards are how ecosystems are built. HTTP felt like overhead until the entire web was built on it. USB-C felt like a forced migration until you stopped carrying five chargers.

MCP is the point where AI tool integration stops being a proprietary, application-specific concern and becomes a shared, ecosystem-level capability. Every server you build contributes to that ecosystem. Every server someone else builds is a server you might not need to build.

Build MCP servers. Not because it's easier today, it might not be. Because it's the only thing that doesn't become technical debt tomorrow.


Ready to start? The Java SDK course takes you from "what even is an MCP server" to production-grade deployment. The MCP Fundamentals course gives you the theory first, if you prefer to understand before you build.