Function Calling vs MCP: How AI Tools Actually Work Under the Hood
Function calling and MCP solve different problems at different layers. Here's what each does, how they relate, and when to use which — with code examples.
Function calling and MCP are not competing standards — they operate at different layers of the stack. Function calling is a model capability. MCP is an application protocol. They solve different problems, and in most real-world systems, they work together. But the distinction is genuinely confusing, so let me walk through it clearly.
If you already know what MCP is, this post goes deeper into how it relates to the function calling mechanism built into every major AI model. If you need the MCP fundamentals first, start with What Is MCP?.
The layer model
The cleanest way to think about this is as a stack, similar to how networking has layers.
Transport layer: REST APIs and HTTP. This is how data moves between systems. Your weather API, your CRM endpoint, your database — they all expose HTTP endpoints. This layer has existed for decades. Nothing AI-specific about it.
Model capability layer: Function calling. This is how an AI model requests that a tool be executed. The model does not call the API directly — it outputs a structured request saying "I want to call this function with these arguments." The host application then executes the call and returns the result. OpenAI introduced this in June 2023.
Protocol layer: MCP. This is how AI clients discover, connect to, and manage tools across systems. MCP handles tool registration, capability negotiation, session lifecycle, authentication, and transport — all standardized. Anthropic launched it in November 2024.
graph TB MCP["Protocol Layer: MCP Discovery · Auth · Sessions · Transport"] FC["Model Capability Layer: Function Calling Structured tool invocation by the AI model"] REST["Transport Layer: REST APIs and HTTP How data moves between systems"] MCP --> FC --> REST style MCP fill:#e0e7ff,stroke:#4f46e5,stroke-width:2px,color:#1e1b4b style FC fill:#fff7ed,stroke:#ea580c,stroke-width:2px,color:#431407 style REST fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e
Each layer builds on the one below it. MCP servers can wrap REST APIs. MCP clients convert MCP tool definitions into function calling schemas. The model uses function calling to invoke MCP-discovered tools. They are complementary, not competing.
What function calling actually does
Function calling is a specific model behavior. When you send a message to an AI model with a set of tool definitions, the model can decide to "call" one of those tools instead of generating a text response. It outputs structured JSON — the function name and arguments — and your application is responsible for actually executing it.
Here is what that looks like with OpenAI's Responses API:
{
"model": "gpt-5.2",
"input": "What is the weather in Berlin?",
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
]
}The model does not call your weather API. It returns something like:
{
"type": "function_call",
"name": "get_weather",
"arguments": "{\"city\": \"Berlin\"}"
}Your code catches that, calls the real weather API, and sends the result back to the model for the final response. The model is just the decision-maker — "yes, we should use this tool, with these inputs." Execution is your problem.
The vendor fragmentation
Here is where it gets messy. Every model provider has their own format for defining tools.
OpenAI calls it "function calling." Tools are defined with a parameters field using JSON Schema. The API uses the Responses API (or the older Chat Completions API with tools parameter).
Anthropic calls it "tool use." Tools are defined with an input_schema field. Same concept, different key name, different API format.
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}Google also calls it "function calling." Tools use an OpenAPI-style format with parameters defined using Google's function declaration schema.
Same concept — model decides to invoke a tool, outputs structured arguments, application executes it. Three different schema formats, three different APIs, three sets of documentation.
If you are building for one model, this is fine. If you are building tools that work across models, you are writing conversion code for each provider. This is one of the problems MCP solves.
What MCP adds on top
MCP operates above function calling. It does not replace function calling — it standardizes everything around it.
Dynamic tool discovery
With function calling alone, you hardcode your tool definitions in every API request. If you add a new tool, you update every request. If you have 50 tools, you send all 50 definitions with every message.
MCP handles this with a discovery protocol. When an MCP client connects to a server, the server advertises its available tools, resources, and prompts. The client knows what is available without hardcoding anything. Add a new tool to your MCP server, and every connected client discovers it automatically.
The N x M problem
This is the core architectural argument for MCP.
Without MCP, if you have N AI clients and M tools, you need N times M integrations. ChatGPT needs its own integration for your weather tool, your CRM tool, your search tool. Claude needs separate integrations for the same tools. Cursor needs its own. Every new client multiplies the work.
MCP reduces this to N plus M. Each client implements the MCP protocol once. Each tool implements the MCP protocol once. Any client can use any tool. This is the same pattern that USB standardized for hardware peripherals — one protocol, universal compatibility.
graph LR
subgraph before["Without MCP: N x M integrations"]
direction LR
C1["ChatGPT"] ---|custom| T1["Weather"]
C1 ---|custom| T2["CRM"]
C2["Claude"] ---|custom| T1
C2 ---|custom| T2
C3["Cursor"] ---|custom| T1
C3 ---|custom| T2
end
subgraph after["With MCP: N + M integrations"]
direction LR
C4["ChatGPT"] --- MCP{"MCP"}
C5["Claude"] --- MCP
C6["Cursor"] --- MCP
MCP --- T4["Weather"]
MCP --- T5["CRM"]
end
style before fill:#fef2f2,stroke:#dc2626
style after fill:#f0fdf4,stroke:#16a34a
style MCP fill:#e0e7ff,stroke:#4f46e5,stroke-width:2pxVendor independence
Function calling ties you to a specific model provider's API format. If you build your tool definitions for OpenAI's format, you rewrite them for Anthropic, and again for Google.
MCP tool definitions are vendor-neutral. One schema format, one protocol, every client. The MCP client handles the translation to whatever function calling format the underlying model expects. You define your tool once in MCP's format, and the client converts it to OpenAI's parameters, Anthropic's input_schema, or Google's function declaration — automatically.
Resources and prompts
Function calling only handles actions — "call this function." MCP adds two more primitives that function calling does not address at all.
Resources let your server expose read-only data that gives the AI context. A database schema, a product catalog, a set of business rules. The AI reads these to make better decisions about when and how to use your tools.
Prompts let your server provide templates that guide the AI's behavior. "When showing search results, prefer the carousel layout." Prompts influence how the AI presents your tool's output to users.
These are not things function calling was designed to handle. They are application-level concerns that MCP addresses as part of the protocol.
Standardized auth and sessions
Function calling has no opinion on authentication. You handle it yourself — API keys, OAuth tokens, whatever your backend requires.
MCP defines standardized authentication as part of the protocol. OAuth 2.0 flows, token management, and capability negotiation are built in. The client and server agree on what is supported at connection time, and the session is managed by the protocol — initialization, operation, and graceful shutdown.
How they work together
In practice, MCP and function calling are not either/or. They work together in a pipeline:
sequenceDiagram participant S as MCP Server participant C as MCP Client participant M as AI Model participant U as User Note over S,C: MCP Layer S->>C: 1. Advertise tools C->>C: 2. Register tools Note over C,M: Function Calling Layer U->>M: 3. User message C->>M: Convert MCP tools to model format M->>C: 4. Function call with arguments Note over S,C: MCP Layer C->>S: 5. tools/call request S->>S: 6. Execute (call API) S->>C: 7. Return result C->>M: Pass result M->>U: Final response
- MCP server exposes tools with standardized definitions
- MCP client (inside the AI host) discovers the tools by connecting to the server
- MCP client converts MCP tool definitions into the function calling format the model expects
- Model uses function calling to decide which tool to invoke and with what arguments
- MCP client receives the function call, routes it to the correct MCP server
- MCP server executes the tool (usually by calling a REST API) and returns the result
- MCP client passes the result back to the model for the final response
Function calling is step 4 — the model's decision mechanism. MCP is steps 1 through 3 and 5 through 6 — everything around that decision. They are different layers of the same system.
When to use function calling directly
Function calling without MCP still makes sense in certain scenarios:
Fixed tool set with one model. If you have 3-5 tools, you are only using one model provider, and the set does not change, the overhead of running an MCP server might not be worth it. Just define your tools inline with the API call.
Lowest latency requirements. MCP adds a network hop — the client talks to the MCP server, which talks to your API. For function calling alone, the model output goes directly to your code. If you are optimizing for milliseconds, removing the MCP layer can help.
Prototyping and experimentation. When you are just testing whether a tool concept works, hardcoding a couple of function definitions is faster than standing up an MCP server. You can always migrate to MCP later.
When to use MCP
MCP becomes the clear choice when:
Multiple models or clients. If your tool needs to work in ChatGPT, Claude, and Cursor, MCP eliminates the per-platform integration work. One server, every client.
Dynamic or growing tool sets. If you regularly add new tools, MCP's discovery protocol means clients pick them up automatically. No client-side code changes.
Ecosystem building. If you want other developers or platforms to use your tools, MCP is the standard. Publishing an MCP server means any of the 70+ MCP-compatible clients can use it immediately.
Rich responses. If your tools need to return interactive widgets, structured data, or complex UI, MCP's content types and widget primitives handle this. Function calling alone just gives you raw text or JSON.
Vendor independence. If you do not want to be locked to one model provider, MCP is the abstraction layer that prevents that. Switch models, switch clients — your tools keep working.
For more on this architectural decision, see our comparison of MCP vs. REST APIs, which covers the protocol-level differences in detail.
A practical example
Let me show the same tool defined both ways.
Function calling (OpenAI format)
const response = await openai.responses.create({
model: "gpt-5.2",
input: userMessage,
tools: [{
type: "function",
name: "search_products",
description: "Search for products by query and price range",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "Search terms" },
max_price: { type: "number", description: "Max price in USD" }
},
required: ["query"]
}
}]
});This works, but it is tied to OpenAI's API. To use the same tool with Claude, you rewrite it with input_schema. To use it with Gemini, another rewrite.
MCP tool definition
server.tool(
"search_products",
"Search for products by query and price range",
{
query: { type: "string", description: "Search terms" },
max_price: { type: "number", description: "Max price in USD" }
},
async ({ query, max_price }) => {
const results = await productAPI.search(query, max_price);
return { content: [{ type: "text", text: JSON.stringify(results) }] };
}
);Define once. The MCP client in ChatGPT converts it to OpenAI's function calling format. The MCP client in Claude converts it to Anthropic's tool use format. The MCP client in Gemini converts it to Google's format. You wrote it once.
The bottom line
Function calling is how AI models request tool execution. MCP is how AI applications manage tools at the protocol level. They are different layers solving different problems, and they work together in every major AI client.
If you are building a quick prototype with one model, function calling is all you need. If you are building tools meant for broad distribution across the AI ecosystem, MCP is the layer that makes that practical.
The good news: you do not have to choose one over the other. MCP uses function calling under the hood. Building on MCP means you get function calling compatibility with every model, plus discovery, auth, resources, prompts, and vendor independence on top.
For a hands-on guide to building your first MCP tool, check out how to build AI apps without code. For the full protocol internals, see our MCP architecture deep dive.


