MCP vs Function Calling: What Developers Need to Know
MCP vs Function Calling: What Developers Need to Know
Quick Answer: They're not competitors — they're different layers. Function calling is a model API feature: you pass a
toolsarray with JSON schemas, the model returns structured calls, your code executes them. MCP (Model Context Protocol) is an open protocol that standardizes where those tools come from: reusable tool servers that any MCP host (Claude, ChatGPT, Cursor, VS Code, your own agent) can discover and use. Under the hood, MCP hosts translate MCP tools into function calls. Building one app with 3 tools? Plain function calling is simpler. Sharing tools across multiple apps, agents, or desktop hosts? MCP wins — and in 2026 it's the de facto standard, with every major model provider and IDE shipping native support.
On This Page
- What Function Calling Actually Is
- What MCP Actually Is
- The N×M Integration Problem
- Architecture Comparison
- When Plain Function Calling Is Simpler
- When MCP Wins
- Code-Level Contrast
- How They Compose
- Adoption Landscape in 2026
- Frequently Asked Questions
What Function Calling Actually Is
Function calling (also called "tool use") is a feature of a model provider's API. You send the model a list of tool definitions — name, description, JSON Schema parameters — alongside the conversation. The model doesn't execute anything; it returns a structured message saying "call get_weather with {"city": "Austin"}". Your application code executes the function, appends the result to the conversation, and calls the model again.
Every major API supports it with minor dialect differences: OpenAI's tools array, Anthropic's tools with input_schema, Gemini's function declarations. The loop is always the same:
- Send messages + tool schemas
- Model responds with tool call(s)
- Your code runs the function
- Send the result back
- Model produces the final answer (or more tool calls)
The critical property: the tools live inside your application. They're defined in your codebase, executed by your process, invisible to any other app.
What MCP Actually Is
The Model Context Protocol, open-sourced by Anthropic in November 2024 and governed openly since, is a client-server protocol — JSON-RPC 2.0 over stdio or Streamable HTTP — that standardizes how AI applications connect to external capabilities. Think "USB-C for AI tools," or more precisely, what the Language Server Protocol did for editor-language integrations.
An MCP server is a small program exposing three primitive types:
- Tools — executable functions (query a database, create a ticket, run a search)
- Resources — readable data (files, schemas, documents) the host can load as context
- Prompts — reusable prompt templates the user can invoke
An MCP host (Claude Desktop, Claude Code, ChatGPT, Cursor, VS Code Copilot, or your own agent built on any framework) connects a client to each configured server, calls tools/list to discover what's available at runtime, and invokes tools with tools/call. The server doesn't know or care which host is calling it, or which model that host is running.
The key property is the mirror image of function calling: the tools live outside any single application, behind a standard interface any host can consume.
The N×M Integration Problem
Why does a protocol matter at all? Because of integration math.
Say your company has 4 AI surfaces — a customer-facing chatbot, an internal Slack agent, Claude Desktop for the ops team, Cursor for engineering — and 6 systems they should all reach: Postgres, GitHub, Jira, Salesforce, an internal wiki, and a billing API.
With app-local function calling, that's 4 × 6 = 24 bespoke integrations, each with its own schema definitions, auth handling, error mapping, and bit-rot. Every new AI app re-implements the same six connectors; every API change breaks four codebases.
With MCP, you build 6 servers, once. Each of the 4 hosts connects to all of them through the same protocol: N + M instead of N × M. The Jira server your platform team hardened — auth, rate limits, input validation — is the same one your chatbot, your Slack agent, and every engineer's IDE use.
"MCP did to agent tooling what LSP did to language tooling: it turned an O(N×M) mess into an O(N+M) ecosystem, and the long tail of integrations appeared almost overnight." — The Pragmatic Engineer, Q1 2026
Architecture Comparison
| Dimension | Function Calling | MCP |
|---|---|---|
| What it is | Model API feature | Open client-server protocol (JSON-RPC 2.0) |
| Tool discovery | Static — hardcoded in your app at build time | Dynamic — tools/list at runtime; hosts get notified when tool sets change |
| Transport | None (in-process function dispatch) | stdio (local) or Streamable HTTP (remote) |
| Statefulness | Stateless schemas per request | Stateful sessions; supports subscriptions, sampling, elicitation |
| Auth | Whatever your app implements | OAuth 2.1 standardized for remote servers (June 2025 spec) |
| Reusability | Zero — tools are app-locked | Any MCP host can use any MCP server |
| Model coupling | Tied to one provider's tool-call dialect | Model-agnostic; host translates for its model |
| Distribution | Ship your app | Publish server via npm/PyPI/registry; users add a config entry |
| Beyond tools | Tools only | Tools + resources + prompts + sampling |
Photo by AltumCode on Unsplash
When Plain Function Calling Is Simpler
MCP is not a default. Plain function calling is the right call when:
- Single application, few tools. One backend service with 3-8 tools that nothing else will ever use? A protocol layer adds a server process, transport, and lifecycle management for zero payoff.
- Latency-critical paths. In-process function dispatch has no IPC or HTTP hop. For high-QPS production inference where every millisecond counts, local tools win.
- Tight coupling is a feature. When tools need your app's internal state — the user's session, an open DB transaction, request-scoped permissions — passing that context through a protocol boundary is awkward. In-process tools just close over it.
- You control the whole stack and it's small. A weekend project or a single-purpose product does not need ecosystem interoperability.
Rule of thumb: if the tool has exactly one consumer, keep it a function. Extract it to an MCP server the day a second consumer shows up — the refactor is mechanical because the JSON Schema shapes are nearly identical.
When MCP Wins
- Shared tool servers. The moment two or more AI surfaces need the same capability, MCP's N+M math takes over.
- Desktop and IDE hosts you don't control. You cannot add a
toolsarray to Claude Desktop, ChatGPT, or Cursor — but all of them load MCP servers. If you want your tool inside their host, MCP is the only door. - Ecosystem distribution. Publishing one server puts your product in reach of every MCP host user. That's why Stripe, GitHub, Cloudflare, Notion, Supabase, and thousands of others ship official servers.
- Org-level governance. Central platform teams can harden one server — auth, audit logging, rate limits — instead of policing tool code scattered across every team's agents. See the OWASP guidance on LLM tool security for why centralizing this matters.
- Dynamic toolsets. MCP hosts discover tools at runtime, so a server can expose different tools per user, plan tier, or environment without redeploying any host.
The decision, condensed:
| Scenario | Reach for |
|---|---|
| Single app, 3–8 tools nothing else will use | Function calling |
| Latency-critical, high-QPS inference path | Function calling |
| Tools that close over request-scoped app state (sessions, transactions) | Function calling |
| Two or more AI surfaces need the same capability | MCP |
| Your tool must load into Claude, ChatGPT, Cursor, or VS Code | MCP |
| Ecosystem distribution or org-level governance of tool access | MCP |
| Toolsets that vary per user, plan tier, or environment | MCP |
Code-Level Contrast
Function calling — the tool is an in-process array entry (OpenAI-style):
tools = [{
"type": "function",
"function": {
"name": "get_invoice",
"description": "Fetch an invoice by ID",
"parameters": {
"type": "object",
"properties": {"invoice_id": {"type": "string"}},
"required": ["invoice_id"],
},
},
}]
resp = client.chat.completions.create(model="gpt-5.2", messages=msgs, tools=tools)
call = resp.choices[0].message.tool_calls[0]
result = get_invoice(**json.loads(call.function.arguments)) # you execute
MCP — the same tool as a standalone server any host can load (Python SDK):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("billing")
@mcp.tool()
def get_invoice(invoice_id: str) -> dict:
"""Fetch an invoice by ID."""
return db.fetch_invoice(invoice_id)
if __name__ == "__main__":
mcp.run() # stdio transport; hosts connect via config
A host config entry (Claude Desktop / Claude Code style) is all a consumer needs:
{ "mcpServers": { "billing": { "command": "python", "args": ["billing_server.py"] } } }
Notice what didn't change: the JSON Schema for parameters, the description, the function body. What changed is who can call it — everything, instead of one app.
How They Compose
The most misunderstood point: MCP doesn't replace function calling — it feeds it. Models don't speak MCP. The host does the translation:
- Host connects to configured MCP servers, calls
tools/list - Host converts each MCP tool definition into its model's native function-calling schema
- Model emits an ordinary tool call
- Host routes it back through
tools/callon the right server - Result returns to the model as a normal tool result
So the accurate mental model is a pipeline: MCP standardizes tool supply; function calling is how the model consumes them. This is also why "MCP vs function calling benchmarks" are mostly nonsense — every MCP tool invocation is a function call plus transport overhead (typically single-digit milliseconds on stdio, one HTTP round-trip for remote servers).
One real cost to watch: context bloat. Every connected server's tool schemas land in the model's context. Hosts in 2026 mitigate this with tool filtering, lazy loading, and search-over-tools, but connecting 15 servers with 200 combined tools will still degrade tool-selection accuracy. Curate ruthlessly — our agent context engineering guide covers this in depth.
Adoption Landscape in 2026
| Milestone | Status (mid-2026) |
|---|---|
| Anthropic (Claude, Claude Code, Claude Desktop) | Native since 2024; MCP originator |
| OpenAI (ChatGPT, Agents SDK, Responses API) | Adopted March 2025; remote MCP in ChatGPT connectors |
| Google DeepMind (Gemini API/SDK) | MCP support confirmed April 2025, shipped in SDK |
| Microsoft (VS Code Copilot, Windows AI Foundry) | Native MCP client + Windows-level integration |
| IDE hosts (Cursor, Windsurf, Zed, JetBrains) | All ship MCP clients |
| Public MCP servers | Tens of thousands; official registry live since late 2025 |
| Spec governance | Open spec at modelcontextprotocol.io; OAuth 2.1, Streamable HTTP, elicitation added through 2025-2026 revisions |
The practical upshot for developers in 2026: function calling is table stakes knowledge for any LLM feature, and MCP is table stakes for anything meant to plug into the broader agent ecosystem. Learn the loop first, the protocol second — and remember they're the same tools wearing different transport layers.
Related Reads
Key Takeaways
- Function calling is an API feature where you define tools in your app’s code (JSON schemas) and the model returns structured calls for your app to execute—ideal for single-app, low-latency, or tightly coupled use cases.
- MCP (Model Context Protocol) is an open JSON-RPC 2.0 protocol that standardizes tool discovery via external servers, enabling N+M integrations (one server for many hosts) instead of N×M bespoke integrations—critical for shared tools, desktop/IDE hosts, or ecosystem distribution.
- Use plain function calling when building a single app with 3–8 tools, latency-sensitive paths, or tools that need app-internal state (e.g., sessions, DB transactions); refactor to MCP only when a second consumer emerges.
- MCP wins for shared tool servers, desktop/IDE hosts (Claude, Cursor, VS Code), org-wide governance (centralized auth/auditing), or dynamic toolsets (per-user/per-tier)—publish one server to reach every MCP-compatible host.
- MCP and function calling compose: hosts translate MCP tool definitions into the model’s native function-calling schema at runtime, so MCP standardizes tool supply while function calling handles model consumption—benchmarks comparing them directly are misleading.
- In 2026, MCP is the de facto standard for cross-app tooling, with native support in every major model provider and IDE; learn function calling first for core LLM features, then MCP for ecosystem interoperability.
Frequently Asked Questions
Is MCP a replacement for function calling?
No. Function calling is how a model requests tool execution; MCP is how applications discover and connect to tool servers. MCP hosts translate MCP tool definitions into the model's native function-calling format at runtime. Every MCP tool invocation ultimately travels through function calling.
Does MCP work with OpenAI models or only Claude?
MCP is model-agnostic and OpenAI adopted it officially in March 2025 — the Agents SDK, Responses API, and ChatGPT connectors all support MCP servers. Gemini, Microsoft Copilot, Cursor, and effectively every major host followed. The server you write doesn't know or care which model calls it.
Is MCP slower than plain function calling?
Marginally. A local stdio server adds single-digit milliseconds of IPC; a remote server adds one HTTP round-trip. That's noise compared to LLM inference time (hundreds of ms to seconds). The bigger performance concern is context bloat from over-connecting servers, which hurts tool-selection accuracy more than latency.
When should I build an MCP server instead of app-local tools?
When a tool has (or will soon have) more than one consumer, when you want your product usable from hosts you don't control (Claude, ChatGPT, Cursor), or when a platform team needs centralized auth and auditing for tool access. For a single app with a handful of private tools, plain function calling is simpler and faster to ship.
How do MCP servers handle authentication?
Local stdio servers typically read credentials from environment variables in the host config. Remote servers use OAuth 2.1, standardized in the June 2025 spec revision — the host runs the authorization flow and attaches tokens per session. Treat every server as an attack surface: validate inputs, scope tokens minimally, and log tool calls.



Comments
Sign in to join the conversation
No comments yet. Be the first to share your thoughts!