MCP and Function Calling are not the same thing

A commonly conflated concept: Function Calling is a capability of the model itself, MCP is the protocol connecting model and tools. Clarifies their real differences in tool discovery, security boundary, and portability, and how Spring AI unifies the two.

MCP and Function Calling are not the same thing

These two terms are often used as synonyms, but they sit at completely different layers in an AI application.

Conflating them directly leads to wrong architecture choices — e.g. agonizing over “should I switch from Function Calling to MCP,” when the two don’t conflict at all.

One-line distinction

  • Function Calling: a capability the model itself has (what) — the model can judge “I need to call an external function” while generating a reply
  • MCP: the protocol connecting model and tools (how) — defines how tools are discovered, called, and parameterized

An analogy: Function Calling is like the function-call mechanism in a programming language, MCP is like the HTTP protocol. Would you ask “use HTTP or use function calls”? No, because they solve problems at different layers.

Positioning comparison:

DimensionFunction CallingMCP
Essencea feature of the model APIapplication-layer communication protocol
Defined bymodel vendors (OpenAI, Anthropic, etc.)Anthropic’s open standard
Scopea single model and its defined toolsany AI app and any tool
Standardizationinconsistent across vendorsunified standard, model-agnostic
Tool managementtool definitions sent with every requesttools deployed independently, discovered dynamically

Four real differences

1. Tool discovery mechanism

Function Calling: the full tool definitions must be stuffed in with every request.

response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "What's the weather in Beijing today?"}],
    functions=functions,          # must be passed every time
    function_call="auto"
)

When there are few tools it doesn’t matter. When you have fifty tools, every request carries fifty schemas, and both token cost and latency climb noticeably.

MCP: tools are independently deployed services; the client pulls the tool list after connecting. Tool definitions don’t consume conversation context, and can be added/removed dynamically — add a new tool and every connected client can use it immediately, no code change.

2. Security boundary

This is the one I think is most easily underestimated.

Function Calling’s security depends entirely on how the application implements it. The model says “call read_file('/etc/passwd')” — whether your code executes it and whether there’s access control is entirely the application layer’s responsibility.

MCP bakes the security boundary into the protocol: the MCP Server itself declares what resources it can access, and the client can see and authorize them at connection time. Wiring up local filesystem access via MCP, permissions are Server-side controlled — the model can’t bypass them with a prompt.

Read together with the earlier sandbox article for clarity — MCP solves “what should the tool be allowed to access,” the sandbox solves “how to isolate code execution when it runs,” and the two are complementary.

3. Portability

The most annoying practical problem with Function Calling is inconsistent vendor formats. The same tool, you have to maintain three definitions:

// OpenAI format
{ "type": "function", "function": { "name": "get_weather", "description": "...", "parameters": {...} } }

// Anthropic Claude format
{ "name": "get_weather", "description": "...", "input_schema": {...} }

// Google Gemini format
{ "function_declarations": [{ "name": "get_weather", "description": "...", "parameters": {...} }] }

To support three model vendors at once, you write three sets. MCP is model-agnostic: write once, any MCP-supporting client can use it.

4. Resource management

Function Calling is stateless; each call is independent.

An MCP Server can hold state (DB connections, file handles, session context), which means tools can share resources and call each other to form workflows, instead of starting from zero every time.

How Spring AI unifies the two

If you use the Java stack, Spring AI has already wrapped this layer of difference away — which also precisely shows the two are complementary, not mutually exclusive:

@Service
public class ToolService {

    // 1. Define the tool (supports both Function Calling and MCP)
    @Tool(name = "get_weather", description = "Get weather information")
    public String getWeather(@ToolParam(description = "City name") String city) {
        return weatherService.getWeather(city);
    }

    // 2. Configure the MCP connection
    @Bean
    public McpClient mcpClient() {
        return McpClient.builder()
            .serverUrl("http://localhost:8080/mcp")
            .build();
    }

    // 3. Use the unified API
    public String chatWithTools(String query) {
        return chatClient.prompt()
            .user(query)
            .tools("get_weather")   // unified reference, no distinction of underlying mechanism
            .call()
            .content();
    }
}

Note that last .tools("get_weather") — you don’t need to care whether this tool is a local Function Calling or a remote MCP Server; the framework handles it.

This is the best proof that “the two don’t conflict”: in the same app, you can have local tools go through Function Calling and remote tools go through MCP, called with the same codebase.

When to use which

My criteria:

Function Calling is enough when:

  • Fewer than ten tools
  • Tools are private to the app, not meant for others
  • No need to keep state across sessions
  • Only integrating with one model vendor

You should go MCP when:

  • Tools need to be used by others (this is MCP’s core value — a tool you write, others and others’ AIs can connect directly)
  • Many tools, and sending full definitions every time already hurts cost and latency
  • Strict permission boundaries are needed (e.g. accessing local files, databases)
  • You need to integrate multiple models
  • Tools have dependencies and need to form a workflow

A concrete scenario

Suppose you want the AI to access the local filesystem.

Pure Function Calling approach: define read_file, write_file two functions; your code receives the path parameter and reads/writes directly. The risk is — the model could easily be induced to read /etc/passwd or your SSH private key, and if your implementation has no path whitelist, it runs bare.

MCP approach: spin up a filesystem MCP Server, and in the Server config declare it only allows access to ~/Documents/workspace. No matter how the model is induced, the protocol layer blocks it.

The difference isn’t “can it be implemented” — in Function Calling you could also hand-write a whitelist. The difference is that MCP turns this into standard configuration, instead of every developer implementing it themselves.

Summary

  • Function Calling is a model capability, MCP is a connection protocol; they’re not at the same layer
  • General direction: inside the app, few tools → Function Calling; opening to the outside, many tools, need permission control → MCP
  • Java stack: directly use Spring AI’s @Tool + McpClient to call both mechanisms uniformly

The earlier piece “Write an MCP server” covers how to turn your tools into an MCP Server; this piece covers its relationship with Function Calling. Reading them together is clearer.

Qimiaogan This work is licensed under CC BY-NC-SA. Please credit the source and author when reposting.

Contents