Lesson 3 of 5 · MCP Builder Track

MCP vs REST APIs vs Function Calling

reading

If you've been building software for any length of time, you've probably connected to a REST API. If you've worked with ChatGPT or Claude, you may have used function calling. And now there's MCP. So what's the difference, and why does it matter?

This lesson breaks down the three dominant approaches to connecting AI with external systems. By the end, you'll understand not just how they differ, but when each approach is the right choice.

The Three Approaches at a Glance

Before we dive deep, here's a high-level comparison:

| Feature              | REST APIs          | Function Calling     | MCP                    |
|----------------------|--------------------|----------------------|------------------------|
| Who calls it?        | Your app code      | The AI model         | The AI model           |
| Discovery            | Read docs manually | Hardcoded in prompt  | Dynamic at runtime     |
| Protocol             | HTTP + JSON        | Provider-specific    | JSON-RPC 2.0           |
| Standardized?        | Loosely (OpenAPI)  | No (per-provider)    | Yes (open spec)        |
| Stateful?            | Usually no         | Per-conversation     | Yes (session-based)    |
| Bidirectional?       | Request/Response   | Request/Response     | Full bidirectional     |
| Multi-provider?      | N/A                | Lock-in              | Any MCP-compatible AI  |

Let's unpack each approach in detail.


REST APIs: The Traditional Backbone

REST (Representational State Transfer) APIs have been the standard for system-to-system communication since the mid-2000s. They use HTTP methods (GET, POST, PUT, DELETE) with JSON payloads.

How REST Works with AI

When you build an AI application using REST APIs, the flow looks like this:

Concept Card
Python
import requests
import openai

# Step 1: User asks "What's the weather in Tokyo?"
user_question = "What's the weather in Tokyo?"

# Step 2: Your app code decides to call a weather API
# YOU write this logic - the AI doesn't choose this
response = requests.get(
    "https://api.weatherapi.com/v1/current.json",
    params={"key": "YOUR_API_KEY", "q": "Tokyo"}
)
weather_data = response.json()

# Step 3: You manually inject the result into the AI prompt
completion = openai.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "system", "content": f"Current weather data: {weather_data}"},
        {"role": "user", "content": user_question}
    ]
)

The Problem with REST + AI

Notice what happened: you had to write the glue code. You decided when to call the API, which API to call, how to parse the response, and how to inject it into the prompt. The AI model had zero involvement in that decision.

This creates several problems:

  1. Rigid routing logic - You must anticipate every possible user intent and map it to the right API call
  2. No AI autonomy - The model can't decide it needs additional data mid-reasoning
  3. Maintenance burden - Every new API requires new glue code, new parsing logic, new error handling
  4. No discovery - The AI doesn't know what tools are available
Tip

Use MCP vs REST APIs vs Function Calling in a low-risk branch or scratch project first. That keeps the lesson concrete without making your first attempt carry production pressure.

Python
# This is what REST + AI integration looks like at scale
# It becomes a massive if/else tree

if "weather" in user_message:
    data = call_weather_api(extract_city(user_message))
elif "stock" in user_message:
    data = call_stock_api(extract_ticker(user_message))
elif "email" in user_message:
    data = call_email_api(extract_email_params(user_message))
elif "calendar" in user_message:
    data = call_calendar_api(extract_date(user_message))
# ... this never ends
Warning

REST APIs are designed for application-to-application communication. They were never designed for AI-to-application communication. Forcing them into that role creates brittle, hard-to-maintain systems.


Function Calling: Letting the AI Choose

Function calling (also called "tool use") was a breakthrough. Instead of writing routing logic yourself, you describe available functions to the AI model, and it decides which ones to call.

How Function Calling Works

Python
import openai

# Define tools the AI can use
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

# The AI decides whether and how to call the function
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools
)

# If the AI chose to call a function:
tool_call = response.choices[0].message.tool_calls[0]
# tool_call.function.name == "get_weather"
# tool_call.function.arguments == '{"city": "Tokyo"}'

# YOU still execute it and return the result
weather = get_weather(city="Tokyo")  # Your implementation

# Send the result back to the AI
follow_up = openai.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": "What's the weather in Tokyo?"},
        response.choices[0].message,
        {"role": "tool", "tool_call_id": tool_call.id, "content": str(weather)}
    ],
    tools=tools
)

What Function Calling Got Right

This was a genuine step forward:

  • AI decides which function to call (and whether to call one at all)
  • Structured output - The AI returns well-formed JSON matching your schema
  • Multi-step reasoning - The AI can chain multiple function calls together

The Problems with Function Calling

But function calling has significant limitations:

Problem 1: PROVIDER LOCK-IN
=================================
OpenAI function calling format:
  {"type": "function", "function": {"name": "...", ...}}

Anthropic tool use format:
  {"name": "...", "input_schema": {...}}

Google Gemini format:
  {"function_declarations": [{"name": "...", ...}]}

Each provider uses a different format. Your tool definitions
are NOT portable. Switch providers? Rewrite everything.


Problem 2: STATIC DEFINITIONS
=================================
Tools are defined at the START of a conversation.
They are literally embedded in the prompt. You cannot:
  - Add new tools mid-conversation
  - Discover tools dynamically
  - Share tools between applications


Problem 3: NO STANDARD EXECUTION LAYER
=================================
The AI says "call get_weather with city=Tokyo"
But YOU must implement get_weather()
And YOU must wire the result back
There is no standard for HOW tools are executed
Info

Function calling gave AI the ability to choose which tools to use, but it did not standardize how tools are defined, discovered, or executed. Every app reimplements the execution layer from scratch.


MCP: The Complete Solution

MCP takes the best idea from function calling (let the AI choose tools) and wraps it in a complete, standardized protocol. It solves every problem we have discussed.

How MCP Works

Python
# Server side - define a tool ONCE
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Weather Service")

@mcp.tool()
async def get_weather(city: str) -> str:
    """Get current weather for a city.

    Args:
        city: The city name to look up weather for
    """
    # Your actual implementation
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://api.weatherapi.com/v1/current.json",
            params={"key": API_KEY, "q": city}
        )
        data = resp.json()
        return f"{data['current']['temp_c']}C, {data['current']['condition']['text']}"

# Any MCP-compatible AI can discover and use this tool.

What Makes MCP Fundamentally Different

Here is the key insight: MCP is not just function calling with a different syntax. It is a fundamentally different architecture.

Add One MCP vs REST APIs vs Function Calling Connection

  1. Choose one external system that would genuinely improve this lesson's workflow.
  2. Configure an MCP server or inspect the one your team already uses.
  3. Ask Claude to call exactly one tool from that server and verify the result is useful.
FUNCTION CALLING:
  App defines tools -> Embeds in prompt -> AI chooses -> App executes
  (Everything lives in YOUR application)

MCP:
  Server exposes tools -> Client discovers them -> AI chooses -> Server executes
  (Tools live in independent servers, reusable by ANY client)

Let me break down the five critical differences:

1. Dynamic Discovery

JSON
// With MCP, the AI discovers tools at RUNTIME
// Client sends:
{"jsonrpc": "2.0", "method": "tools/list", "id": 1}

// Server responds with all available tools:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "inputSchema": {
          "type": "object",
          "properties": {
            "city": {"type": "string", "description": "City name"}
          },
          "required": ["city"]
        }
      }
    ]
  }
}

No hardcoding tool definitions in prompts. The AI asks "what can you do?" and the server answers.

2. Provider Independence

MCP tool definition works with:
  - Claude Desktop
  - Cursor IDE
  - VS Code Copilot
  - Windsurf
  - Any MCP-compatible host

Function calling tool definition works with:
  - Only the specific provider you wrote it for

Write once, connect everywhere. That is the MCP promise.

3. Beyond Just Tools

Function calling gives you tools (functions the AI can call). MCP gives you three primitive types:

Python
# TOOLS - Actions the AI can take (like function calling)
@mcp.tool()
def search_database(query: str) -> str:
    """Search the product database."""
    return db.search(query)

# RESOURCES - Data the AI can read (like a file system)
@mcp.resource("config://settings")
def get_settings() -> str:
    """Application settings the AI can reference."""
    return json.dumps(app_settings)

# PROMPTS - Reusable prompt templates
@mcp.prompt()
def analyze_data(dataset: str) -> str:
    """Template for data analysis tasks."""
    return f"Analyze this dataset thoroughly: {dataset}"

Resources and prompts have no equivalent in function calling. They enable entirely new interaction patterns.

4. Stateful Sessions

REST API:       Each request is independent (stateless)
Function Call:  State lives in conversation context only
MCP:            Persistent session with the server

MCP servers can maintain state across multiple tool calls:
  Call 1: connect_to_database("production")  -> Session established
  Call 2: run_query("SELECT * FROM users")   -> Uses same connection
  Call 3: run_query("SELECT * FROM orders")  -> Still same connection

5. Bidirectional Communication

MCP is not just request/response. Servers can send notifications to clients:

JSON
// Server notifies client that available tools have changed
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

// Server can request the AI to sample (generate text)
{
  "jsonrpc": "2.0",
  "method": "sampling/createMessage",
  "params": {
    "messages": [{"role": "user", "content": "Summarize this data..."}],
    "maxTokens": 500
  }
}

No other approach offers this level of interaction.


Real-World Comparison: Building a Code Review Bot

Let us see how each approach handles the same real-world task: building an AI that reviews pull requests on GitHub.

Quick Check

What is the main benefit of using MCP vs REST APIs vs Function Calling well in Claude Code?

REST API Approach

Python
# You write ALL the orchestration logic
def review_pr(pr_number):
    # Fetch PR details
    pr = requests.get(
        f"https://api.github.com/repos/owner/repo/pulls/{pr_number}"
    ).json()

    # Fetch the diff
    diff = requests.get(pr["diff_url"]).text

    # Fetch related files for context
    files = requests.get(f"{pr['url']}/files").json()

    # Fetch previous review comments
    comments = requests.get(f"{pr['url']}/comments").json()

    # Manually construct the prompt with all this context
    prompt = f"""Review this PR:
    Title: {pr['title']}
    Diff: {diff}
    Files changed: {json.dumps(files)}
    Previous comments: {json.dumps(comments)}
    """

    # Send to AI
    review = call_ai(prompt)

    # Post the review back (more REST calls)
    requests.post(f"{pr['url']}/reviews", json={"body": review})

Lines of glue code: 50+. The AI has no autonomy.

Function Calling Approach

Python
# Better - AI decides what info it needs
tools = [
    make_tool("get_pr_details", "Get PR info", {"pr_number": "integer"}),
    make_tool("get_pr_diff", "Get the diff", {"pr_number": "integer"}),
    make_tool("get_file_content", "Read a file", {"path": "string"}),
    make_tool("post_review", "Post review", {"pr_number": "integer", "body": "string"}),
]

# Still need to implement each function yourself
# Still locked to one AI provider
# Tool definitions are static

Lines of glue code: 30. Better, but still provider-locked.

MCP Approach

JSON
// In Claude Desktop config, just add:
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "your-token"
      }
    }
  }
}

// Claude now has full GitHub access.
// It discovers and uses all GitHub tools automatically.
// No glue code. No custom implementations.

0

Lines of Glue Code

With MCP, the server handles discovery, execution, and results -- your application needs zero custom integration code

Lines of glue code: 0. The MCP server handles everything.

Tip

The GitHub MCP server is a real, production-ready server maintained by the community. When you connect it to Claude Desktop, the AI automatically discovers tools like create_pull_request, search_repositories, get_file_contents, and dozens more.


When to Use What

Each approach has its place. Here is a decision framework:

Use REST APIs When:

  • Building traditional app-to-app integrations
  • The AI does not need to make decisions about which APIs to call
  • You are building a simple, linear pipeline (fetch data then process then display)
  • You need maximum control over every API call

Use Function Calling When:

  • You are locked into a single AI provider
  • You have a small, fixed set of tools (fewer than 10)
  • You do not need tool reusability across projects
  • You are building a quick prototype

Use MCP When:

  • You want tools that work with any AI application
  • You need dynamic tool discovery
  • You are building tools that multiple teams or projects will share
  • You want bidirectional communication
  • You need stateful, session-based interactions
  • You are building production-grade AI integrations

Choosing Your Integration Approach

Do

Start with MCP for new AI integrations -- it is portable, discoverable, and future-proof

Don't

Use REST glue code for AI tool access when MCP can handle discovery and execution automatically

Decision flowchart:

Does the AI need to choose which actions to take?
  No  -> Use REST APIs
  Yes -> Do you need portability across AI providers?
           No  -> Function calling may suffice
           Yes -> Use MCP

The Convergence

Here is what is fascinating: the industry is converging on MCP. OpenAI added MCP support in March 2025. Google ADK supports MCP. Microsoft Copilot Studio supports MCP. Even function calling is evolving toward MCP compatibility.

This is not about one approach winning. It is about the industry recognizing that a standard protocol for AI-tool communication is essential, and MCP is becoming that standard.

Try This Now

Take one of your existing projects that uses REST APIs or function calling. Map out all the integration points. Then sketch how you would redesign it with MCP servers. How many lines of glue code would disappear? Which parts become reusable across projects? This exercise will cement the difference in your mind.

Key Takeaways

  • REST APIs require you to write all routing and glue code; the AI has no autonomy in choosing which APIs to call
  • Function calling lets the AI choose tools but locks you into a specific provider format with no standardization
  • MCP combines AI-driven tool selection with a standardized protocol, dynamic discovery, and stateful sessions
  • MCP goes beyond tools by adding resources (data) and prompts (templates) as first-class primitives
  • The industry is converging on MCP, with OpenAI, Google, and Microsoft all adding support
  • Use REST for simple pipelines, function calling for quick prototypes, and MCP for production-grade AI integrations