Function Calling vs. MCP: The Direct Request vs. The Conversational Protocol

Param Harrison
8 min read

Share this post

In our previous posts, we've built agents that can reason and even critique their own work (see our self-critique guide and ReAct agents guide). But to be truly useful, our agents need to interact with the real world—they need to call APIs, query databases, or run code.

This post is for you if you're building an agent and need to decide how to connect it to its tools. This is a critical architectural choice that will impact your app's flexibility and scalability.

Today, we'll break down the two main patterns for tool use:

  1. Function Calling: The classic, "one-shot" request.
  2. Model Context Protocol (MCP): A newer, "conversational" standard for dynamic tool interaction.

The core problem: Connecting an isolated AI to the real world

An LLM is a brilliant "brain in a jar." To do anything useful, it needs "hands and eyes"—tools to interact with the world. The question is, how do we build the "nervous system" that connects the brain to the hands?

Function Calling is a direct, one-way nerve. The brain sends a single, explicit command ("Move hand").

MCP is a rich, two-way nerve. The brain can have a conversation with the hand ("What can you feel? Can you get a better grip?").

graph TD
    A[AI Model] -->|Function Calling: One-shot request| B[Tool/Function]
    B -->|Response| A
graph TD
    C[AI Model] <-->|MCP: Ongoing Conversation| D[MCP Server/Tool]

The difference isn't just technical—it's about control, flexibility, and scalability.

1. Function Calling: The "Direct Request" method

Function Calling (or "Tool Calling" in OpenAI's API) is the most common and straightforward method. The LLM's "brain" is in charge. It parses the user's prompt, decides it needs a tool, and generates a JSON object that perfectly matches the tool's "schema" or "contract" you defined.

  • Philosophy: "The LLM knows what it needs. Let it ask for it directly."
  • Developer Experience (DX): Simple and explicit. You define your functions (tools) in your code, describe them to the LLM (e.g., in a JSON schema), and write an agent loop to execute the tool call when the LLM requests it.
  • Best For: Straightforward tasks where the model can get all the info it needs in a single, one-off call.

How it works in an agent

This flow is a simple, "one-shot" request-response.

sequenceDiagram
    participant User
    participant Your_App
    participant LLM
    participant Tool_API
    
    User->>Your_App: "What's the weather in Paris?"
    activate Your_App
    Your_App->>LLM: "User asked: '...weather in Paris?'"
    activate LLM
    LLM-->>Your_App: Call: `get_weather(city="Paris")`
    deactivate LLM
    
    Your_App->>Tool_API: Run get_weather("Paris")
    activate Tool_API
    Tool_API-->>Your_App: "18°C, cloudy"
    deactivate Tool_API
    
    Your_App->>LLM: "Tool Result: 18°C, cloudy"
    activate LLM
    LLM-->>Your_App: "The weather in Paris is 18°C and cloudy."
    deactivate LLM
    
    Your_App-->>User: "The weather in Paris is 18°C and cloudy."
    deactivate Your_App

The Code:

Here's a basic OpenAI-style function calling example in Python.

from openai import OpenAI

client = OpenAI() # Assumes OPENAI_API_KEY is set

# 1. Define your tool's "contract"
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather in a given city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "The city, e.g., Paris"},
                },
                "required": ["city"],
            },
        },
    }
]

# 2. Let the LLM decide to call the tool
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
)

# 3. Check if the LLM wanted to call a tool
tool_calls = response.choices[0].message.tool_calls
if tool_calls:
    tool_call = tool_calls[0]
    if tool_call.function.name == "get_weather":
        # 4. Execute the function (we'll mock it here)
        weather_data = {"temp": 18, "condition": "cloudy"}
        
        # 5. Feed the result *back* to the LLM
        messages.append(response.choices[0].message) # Add the AI's tool call
        messages.append(
            {
                "role": "tool", 
                "tool_call_id": tool_call.id, 
                "name": "get_weather",
                "content": str(weather_data)
            }
        )
        
        # 6. Get the final, natural-language answer
        final_response = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
        print(final_response.choices[0].message.content)

Observation: This is efficient for simple agents. The logic is clear: Ask -> LLM Decides -> Execute -> LLM Answers. It's great for static tools that don't change.

Think About It: Function calling puts the burden on the LLM to get the call right the first time. What happens if the tool's schema changes? You have to update your prompt. What if the tool needs clarification? You have to build a complex, manual loop, which adds latency.

2. MCP: The "Conversational Context" protocol

MCP (Model Context Protocol) is a newer, standardized protocol designed for AI models to interact with external "servers" that provide tools, data, or context dynamically.

  • Philosophy: "Tools aren't static items on a list. They are services the model can have an ongoing conversation with."
  • Developer Experience (DX): More setup upfront. You need to build or use an "MCP Server" for your tools. But once you do, it's highly scalable and flexible.
  • Best For: Complex agents that need dynamic tool discovery (i.e., "what tools are available right now?") or real-time, streaming data.

How it works in an agent

In this model, your agent (the MCP Client) connects to an MCP Server. The AI model can then "browse" the available tools on that server, make requests, and even receive streamed updates from the tool.

The Code:

This example shows the concept of an MCP server/client.

# --- 1. The MCP Server (The Tool Provider) ---
# This runs as a separate service
from mcp_server import MCPServer, tool

server = MCPServer(host='0.0.0.0', port=8000)

@tool(
    name="get_weather",
    description="Get weather for a city",
    parameters={"city": {"type": "string", "required": True}}
)
def get_weather(city: str):
    # This could be a real, live API call
    return {"temp": 18, "condition": "cloudy", "city": city}

# server.run() # This would start the server

# --- 2. Your Agent (The MCP Client) ---
from mcp_client import MCPClient
from your_llm import LLMWrapper # e.g., OpenAI or Claude

# The client connects to the LLM *and* the tool servers
client = MCPClient(
    llm=LLMWrapper(model="claude-3.5-sonnet"),
    servers=["http://localhost:8000"] # Connects to our weather server
)

# The client handles the discovery, calling, and response loop
response = client.query("What's the weather in Paris?")
print(response)

Observation: This is a much more powerful and decoupled architecture. Your agent's code doesn't need to know the get_weather function. It just knows how to talk to an MCP server, which tells the agent that get_weather is available. If you add a new tool (get_stock_price) to the server, the agent discovers it automatically, without you needing to update your agent's prompt.

Head-to-head comparison

Aspect Function Calling (Tool Calling) MCP (Model Context Protocol)
Interaction Style One-shot request/response Ongoing "conversation" with updates
Tool Discovery Static (defined in the prompt/API call) Dynamic (browses servers)
"Who is in charge?" The LLM (it decides what to call) The Application (it controls the protocol)
Best For Simple, static, one-off tool calls Complex, dynamic, or streaming tool use
Complexity Low setup, but high maintenance Higher setup, but low maintenance

When to use what: Scenarios and recommendations

Scenario 1: "I need a simple chatbot to answer questions about the weather or my calendar."

  • Choice: Function Calling.
  • Reason: Your tools are fixed and well-defined. The interaction is a simple, one-shot "question -> tool call -> answer." MCP would be overkill.

Scenario 2: "My agent needs to monitor a live stock feed and notify me if a stock price changes."

  • Choice: MCP.
  • Reason: This is a streaming and real-time data problem. Function Calling is a "pull" model (the LLM asks). MCP supports a "push" model, where the tool server can send updates to the model, making it truly conversational.

Scenario 3: "I'm building a multi-agent system (like in our agent framework comparison) where many agents need to share a common set of tools."

  • Choice: MCP.
  • Reason: You can build one MCP tool server (e.g., company-tools-server) and have all your agents connect to it. This standardizes how tools are accessed across your entire organization.

Challenge for you

  1. Use Case: You are building an agent that books flights. The user asks, "Find me a flight to NYC."
  2. The Problem: The find_flight tool needs to stream back results as they come in, because prices change in real-time.
  3. Your Task: Which pattern, Function Calling or MCP, would be the only choice for this, and why?

Key takeaways

  • Function Calling is simple and direct: Use it when your tools are static and your interactions are one-shot request/response patterns
  • MCP enables dynamic tool discovery: Use it when tools change frequently, when you need streaming updates, or when multiple agents need to share the same tool infrastructure
  • The choice impacts scalability: Function Calling requires updating prompts when tools change; MCP allows tools to be discovered dynamically
  • Both patterns solve different problems: Function Calling for simplicity, MCP for flexibility and enterprise-scale tool management
  • Consider your team structure: If you have multiple agents or teams building tools, MCP's standardized protocol can reduce coordination overhead

For more on building production AI systems, check out our AI Bootcamp for Software Engineers.

Share this post

Continue Reading

Weekly Bytes of AI — Newsletter by Param

Technical deep-dives for engineers building production AI systems.

Architecture patterns, system design, cost optimization, and real-world case studies. No fluff, just engineering insights.

Unsubscribe anytime. We respect your inbox.