Sam Austinon October 21, 202614 min read

MCP vs Function Calling: Difference and When to Use Each

MCP vs Function Calling: Difference and When to Use Each
Contents

Figure 1: MCP vs Function Calling: Difference and When to Use Each — the question is not which one wins, but where your tools should live

"Should we use MCP or function calling?" is a common question, and it is slightly the wrong one, because the two are not competitors.

Function calling is how a model asks your application to run something during a conversation. The Model Context Protocol (MCP) is a standard for how applications discover and connect to tools and data hosted elsewhere. In practice, an MCP tool reaches the model through function calling. The real decision is whether your tools should live inside your application or behind a shared, standardised interface.

Function Calling in One Minute

With function calling, you send the model a list of tool definitions (name, description, parameter schema) alongside the conversation. When the model wants a tool, it replies with a structured request instead of text. Your code runs the function and sends back the result, and the loop continues — as covered in the function calling guide earlier in this series.

Everything is local to your application:

  • You define the tools in your own code, in your provider's format.
  • You execute them in your own process, with full access to your app's state.
  • You control everything: validation, permissions, logging, retries.

Provider formats differ in detail, since OpenAI, Anthropic and Google each structure tool calls and results a little differently, so a tool wired for one provider needs adapting for another.

MCP in One Minute

MCP is an open protocol, launched by Anthropic in November 2024 and governed since 9 December 2025 by the Agentic AI Foundation under the Linux Foundation. It defines a client-server relationship:

Role What it is
Host The AI application: a desktop assistant, an IDE agent, your own agent
Client One MCP client per MCP server, run by the host
Server Exposes tools (actions the model can invoke), resources (read-only context such as files or records) and prompts (reusable templates a user can trigger)

Servers run locally over stdio or remotely over Streamable HTTP, with an OAuth 2.1-based authorization framework for remote servers.

The idea is "write the integration once, use it from any compliant host", which is why people call it the USB-C of AI tooling. A company can publish one MCP server for its product, and any MCP-capable assistant can use it. The MCP explainer covers the primitives in more depth.

A Fast-Moving Spec

MCP's specification is versioned by date and has changed meaningfully, so check which revision a tutorial or SDK targets.

Revision What it changed
March 2025 Added OAuth 2.1 authorization and replaced HTTP+SSE with Streamable HTTP
Later 2025 revisions Added elicitation (servers asking the user a structured question) and structured tool output
28 July 2026 Rebuilt the core as stateless: removed the initialization handshake and protocol-level sessions, made every request self-describing, added header-based routing, made list results cacheable, hardened authorization, introduced a formal extensions framework, and deprecated roots, sampling, logging and HTTP+SSE

SDKs have been updating to match, so confirm API details against your installed version. The MCP server guide walks through what the July 2026 revision looks like in actual Python code.

How They Fit Together

When you use MCP with a model, function calling is still doing the model-facing work. A typical flow:

  1. The host's MCP client connects to servers and lists their tools.
  2. The host translates each MCP tool definition into the model provider's function-calling format and includes them in the request.
  3. The model emits a function call.
  4. The host maps that call to an MCP tool call on the right server.
  5. The server executes and returns a result, which the host passes back to the model as the function result.

So MCP does not replace function calling: it standardises where the tools come from and how they are executed. Here is a compact bridge using the official Python SDK, with a server and a client that feeds MCP tools to a model. The SDKs have been changing with the latest spec, so treat this as the long-standing pattern and verify method names against current docs.

A minimal server:

# orders_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("orders")

ORDERS = {"A100": {"status": "shipped", "total": 59.97}}

@mcp.tool()
def lookup_order(order_id: str) -> dict:
    """Look up an order's status and total by order ID."""
    return ORDERS.get(order_id, {"error": "order not found"})

if __name__ == "__main__":
    mcp.run()          # stdio transport by default

A client that exposes those tools to a model through function calling:

import asyncio
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

MODEL = "claude-sonnet-5-5"        # check current model names

async def main():
    params = StdioServerParameters(command="python", args=["orders_server.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()                   # may differ in newer SDK versions

            # MCP tool definitions -> function-calling format
            listed = await session.list_tools()
            tools = [{"name": t.name, "description": t.description or "",
                      "input_schema": t.inputSchema} for t in listed.tools]

            client = anthropic.Anthropic()
            messages = [{"role": "user", "content": "What's the status of order A100?"}]

            for _ in range(5):                           # step limit
                resp = client.messages.create(model=MODEL, max_tokens=800,
                                              tools=tools, messages=messages)
                messages.append({"role": "assistant", "content": resp.content})
                if resp.stop_reason != "tool_use":
                    print("".join(b.text for b in resp.content if b.type == "text"))
                    return

                results = []
                for block in resp.content:
                    if block.type == "tool_use":
                        out = await session.call_tool(block.name, block.input)  # MCP call
                        text = "".join(c.text for c in out.content if c.type == "text")
                        results.append({"type": "tool_result", "tool_use_id": block.id,
                                        "content": text, "is_error": bool(out.isError)})
                messages.append({"role": "user", "content": results})

asyncio.run(main())

Look at the seam: the model only ever sees function-calling tool definitions, and the host decides whether a given call runs a local Python function or an MCP request to a server. The model does not know or care. In production hosts, the same loop gains approval prompts, allowlists, logging and limits, as the guardrails guide describes.

Comparing Them Across the Dimensions That Matter

Dimension Function calling MCP
Layer and scope A model API feature inside one conversation An integration protocol across applications and servers
Where tools live In your application code In separate servers, possibly owned by other teams or companies
Portability Tied to your app and provider format Works with any compliant host and any model the host supports
Discovery Static, defined at build time Dynamic; newer revisions make list results cacheable
Capabilities Tools only Tools, resources and prompts, plus elicitation depending on revision and client support
Auth and governance Whatever you build OAuth-based framework for remote servers, with gateways centralising policy and logging
Latency and overhead Fastest, in-process Adds a hop, though the stateless core cuts handshake overhead and eases horizontal scaling
Context cost Every definition costs tokens per request Same, times every connected server
Control and trust Everything inside your trust boundary Components you may not control
Ownership Suits an app team shipping a feature Suits a platform team publishing tools many agents consume, or a vendor publishing to customers

Context cost is the dimension people underestimate. Every tool definition consumes tokens on every request, and connecting several MCP servers can flood the context with dozens of descriptions. Some clients cap the total: VS Code, for example, enforces a hard limit of 128 tools per request across all servers. More tools often means worse tool selection, not better.

When Plain Function Calling Is the Right Choice

  • One application, a handful of tools tightly coupled to your business logic and state.
  • Tools need in-process access to objects, sessions or databases you do not want to expose over a protocol.
  • Latency and token budgets are tight, and you want the minimal tool set.
  • Strict trust requirements, where connecting third-party servers is not acceptable.
  • Batch and backend workflows where a model call is one step in a deterministic pipeline.
  • A prototype, where the quickest path to a working loop wins.

When MCP Earns Its Overhead

  • The same tools serve several hosts, agents or teams, and you would otherwise rewrite adapters for each.
  • Good servers already exist for what you need, such as source control, ticketing, documentation or databases, and you would rather configure than build.
  • You want users to connect their own tools to your agent without redeploying it.
  • You are publishing your product to AI assistants, where one MCP server reaches many clients.
  • Enterprise governance matters: centralised auth, audit logs, rate limits and policy through a gateway.
  • You need resources or prompts, not only tool calls.
  • You support multiple model providers and want tool definitions that do not depend on one provider's format.

A very common and sensible design is hybrid: core domain tools as native function calls inside the app, and integrations with external systems through MCP.

Neither: When You Don't Need Either

  • Deterministic workflows. A nightly sync does not need a model deciding anything, so call the API directly.
  • Coding agents writing code. It can be more token-efficient to let the agent call an API from code than to load many tool schemas, a pattern some teams adopt to cut the cost of large tool catalogues.
  • Agent-to-agent traffic. MCP is about agents talking to tools. Agents talking to other agents is a different layer handled by protocols like A2A, covered in the multi-agent architecture guide.
  • A single API call in a script. If nothing chooses, nothing needs a schema.

Security: Don't Treat MCP as a Safety Net

MCP standardises connectivity, not safety. As one 2026 security write-up puts it, MCP is not a security boundary. I could not keep a stable link to that write-up, so treat the phrasing as attributed rather than sourced. The main risks:

Risk What it looks like
Tool poisoning A malicious or compromised server supplies descriptions or results crafted to manipulate the model — a form of prompt injection
Rug pull A server changes tool definitions after you approved it
Confused deputy A server or tool uses your authority to do things you did not intend
Excessive permissions Broad OAuth scopes, or passing tokens through to downstream services
Supply chain Local stdio servers are programs running with your user's permissions, so installing one is like running a package from the internet
Injection through results The same prompt injection problem you have with any tool output

Practical defences:

  1. Allowlist servers and review them like dependencies. Prefer reputable, maintained, signed or pinned versions.
  2. Apply least privilege: minimal OAuth scopes, read-only where possible, and separate credentials per server.
  3. Keep secrets out of the model's context. The model should never see credentials; the server should hold them.
  4. Require user consent for high-impact actions, and keep human approval for irreversible ones.
  5. Detect definition changes by hashing or pinning tool descriptions and reviewing diffs.
  6. Sandbox local servers with containers or OS-level restrictions, and restrict network egress.
  7. Validate tool inputs and outputs on your side, since model-generated arguments can be wrong or malicious.
  8. Route remote traffic through a gateway for authentication, rate limiting and audit logs.
  9. Trim the tool set per task, which improves both security and accuracy.

A Simple Decision Flow

  1. Is a model deciding which action to take? If not, use direct API calls.
  2. Are the tools specific to one app and tightly tied to its state? Use native function calling.
  3. Do multiple hosts, teams or models need the same tools, or do ready-made servers exist? Use MCP for those.
  4. Are you exposing your product to third-party assistants? Publish an MCP server.
  5. Do you have more than a couple of dozen tools? Load them dynamically per task, whichever route you use.
  6. Do you handle sensitive data or irreversible actions? Add approval, least privilege, logging and an allowlist before anything else.

Common Misconceptions

Claim The correction
"MCP replaces function calling" It does not. Tools still reach the model through function calling, and MCP standardises how they are sourced.
"MCP makes tool use safe" It standardises connections. Safety remains your job.
"More connected servers means a smarter agent" More tools cost tokens and can confuse tool selection.
"An MCP connection is a session" Not since the July 2026 revision removed protocol-level sessions and the initialization handshake, though older tutorials still describe them.
"A server in a directory is trustworthy" Listing is not vetting.
"MCP tutorials from early 2025 are current" Transport, authorization and the session model have all changed since, so check the spec revision.

Common Pitfalls

  1. Adopting MCP for a single-app feature that three native functions would handle.
  2. Loading every available server's tools into every request.
  3. Giving servers broad OAuth scopes, or running unvetted local servers with full user permissions.
  4. Letting tool descriptions from servers go unreviewed, then discovering they changed.
  5. Treating tool results from servers as trusted instructions.
  6. Following an old tutorial's handshake and transport details against a new SDK.
  7. Skipping logging, so nobody can reconstruct what an agent did through which server.

Best Book to Read

Three books cover the parts neither protocol settles:

CoverBookDescriptionGet it
Cover of “Designing Machine Learning Systems” Designing Machine Learning Systemsby Chip Huyen how a model-serving layer fits into a real system, which is the question that decides where tools belong. View on Amazon
Cover of “Threat Modeling: Designing for Security” Threat Modeling: Designing for Securityby Adam Shostack a method for reasoning about tool poisoning, rug pulls and confused deputies before they ship. View on Amazon
Cover of “Zero Trust Networks” Zero Trust Networksby Evan Gilman and Doug Barth the mindset behind least privilege and per-request authorization for remote servers. View on Amazon

As an Amazon Associate I earn from qualifying purchases. Links on this page are affiliate links, and I may earn a commission at no extra cost to you.

Threat Modeling is the one that pays off fastest, because a single diagram of your agent's trust boundaries usually changes the tool list.

Unlock AI That Actually Works

Get lifetime access to GPT-6 Astra, Claude Fable 5.1, Gemini 3.5, Grok 4.5, and more — all in one platform. Build websites, apps, videos, content, and digital products from a single command. No monthly fees. No tool-hopping.

Click here to get GPTAstra Max now — one-time payment, lifetime access.

Frequently Asked Questions

Is MCP the same thing as function calling?

No. Function calling is the mechanism by which a model asks your application to run something during a conversation: you send tool definitions, the model returns a structured request, your code executes it and returns the result. MCP is a protocol for how applications discover and connect to tools and data hosted elsewhere. In practice an MCP tool still reaches the model through function calling, so the two operate at different layers rather than competing for the same job.

Does MCP replace function calling?

It does not. When you use MCP with a model, function calling is still doing the model-facing work. The host lists tools from its MCP servers, translates each definition into the provider's function-calling format, and sends those in the request. When the model emits a function call, the host maps it to an MCP tool call on the right server, then passes the result back as a function result. MCP standardises where the tools come from and how they are executed; it does not replace the call loop.

When should I use MCP instead of native function calling?

Use MCP when the same tools serve several hosts, agents or teams and you would otherwise rewrite adapters for each; when good servers already exist for source control, ticketing, documentation or databases and you would rather configure than build; when users should connect their own tools without a redeploy; when you are publishing your product to assistants; when enterprise governance needs centralised auth, audit logs and rate limits; or when you need resources and prompts rather than only tool calls. Most real products end up hybrid.

Does MCP make tool use secure?

No. MCP standardises connectivity, not safety. Tool poisoning, rug pulls where a server changes a definition after approval, confused-deputy problems, excessive OAuth scopes, supply chain risk from local stdio servers and prompt injection through results are all still yours to manage. As one 2026 security write-up puts it, MCP is not a security boundary. Allowlist servers, apply least privilege, keep credentials out of the model's context, require approval for irreversible actions, and validate tool inputs and outputs on your side.

Is an MCP connection a session?

Not since the 2026-07-28 revision. That revision rebuilt the core as stateless: it removed the initialization handshake and protocol-level sessions, and made every request self-describing with the protocol version and client capabilities carried in each call. Older tutorials still describe the handshake and session identifiers, which is why confirming the spec revision a tutorial targets matters more than following it closely.

How many MCP tools should I load into one request?

Fewer than you think. Every tool definition consumes tokens on every request, and connecting several servers can flood the context with dozens of descriptions. Some clients cap the total: VS Code, for example, enforces a hard limit of 128 tools per request across all servers. More tools often means worse tool selection, not better, so load them dynamically per task whatever route you choose.

Wrapping Up

Function calling is the mechanism: the model asks, your code acts. MCP is the standard for packaging and connecting tools so they work across hosts, teams and models, and it relies on function calling to reach the model. Use native function calling for tools that belong to one application, MCP for reusable integrations, third-party servers and cross-team platforms, and a hybrid of both for most real products. Whatever you choose, keep the tool set small, treat every server as untrusted until vetted, and keep humans in the loop for actions you cannot undo.

How should you start? List your tools and ask of each: does anyone besides this app need it? The ones that stay local become functions, the ones that should be shared become MCP servers, and everything gets least-privilege permissions and logging either way.

Which of your current tools would survive that question, and which are MCP servers nobody else needed?

Sources

  • MCP specification — the versioned revisions, the stateless core added on 2026-07-28, the deprecated features and the OAuth 2.1 authorization framework.
  • Python SDK repository — SDK versioning notes that accompany the spec changes.
  • The 2026 security write-up quoted as saying MCP is not a security boundary — I could not keep a stable link to it, so treat that phrase as attributed rather than sourced, and verify the risk list against your own threat model.

Specification details were checked on 21 October 2026. Protocol revisions move quickly, so confirm the revision your SDK targets before relying on any behaviour described here.

What are You Looking For?

esc