ArticleAI Architecture

MCP vs Function Calling: When You Need an MCP Server, and Where Consent Lives

Function calling and MCP both let a model ask for a tool. They differ in who runs the code, who asks the user and who checks permission. Locate those three things before you build a server.

Last reviewed: 2026-10-13

A glass cube with a small module inside, linked by glowing cyan and amber paths through a glass gate to a row of separate server blocks on a dark platform.
This illustration compares an in-process function handler with an MCP server: the application controls dispatch in the first design; the host and server share consent and access-control responsibilities in the second.

Two answers to the same question

When a model needs live data or needs to change something, it cannot act by itself. It produces a structured request, and some other component decides whether to execute it. Function calling defines how a model requests a function and receives its result; the Model Context Protocol (MCP) standardizes how an application discovers and calls capabilities on a server. They can work together: your application can translate a function call into an MCP tool call. Here we compare an in-process handler with a separate MCP server.

The common mistake runs in two directions. One is wrapping a function that only one application will ever call in a separate MCP server, adding a process, a transport and a deployment without a clear need for protocol interoperability or isolation. The other is treating an MCP server like a local helper and skipping the consent and authorization work the protocol assigns to specific roles. Both mistakes come from not knowing where each responsibility lives.

Function calling: your application is the executor

In the OpenAI function calling guide, the flow has five steps: you send a request with tool definitions, the model returns a tool call, your application executes code with that input, you send the output back, and the model responds or asks for more calls. The guide is explicit: when the model calls a function, you must execute it and return the result. The call item carries a call_id, the function name and JSON-encoded arguments.

Your application controls dispatch between receiving the call and returning the output. The handler can run in process or call another service, using the credentials appropriate to that service. Function calling does not require a particular process boundary. In the in-process design below, the permission check, confirmation dialog and audit record can sit in the same codebase. The model can also request more than one call in a turn, so your loop must handle a list, not a single call.

Strict mode in that guide requires additionalProperties: false on every object and every property listed as required; optional fields are expressed by allowing null. As of 2026-10-13, the Responses API tries to normalise schemas to strict when you omit the flag, falling back to non-strict mode if the schema cannot be made compatible; Chat Completions stays non-strict by default. Strict mode makes the arguments match the schema. It does not decide whether this user may refund this order. That check remains yours.

A documentation-based sketch, not executed:

// In-process handler: same process, same credentials as the app
async function handleToolCall(call, session) {
  if (call.name !== 'refund_order') throw new Error('unknown tool');
  const args = JSON.parse(call.arguments);
  await assertCanRefund(session.user, args.order_id);   // authorization
  await confirmWithUser(session, 'refund_order', args); // consent
  return refundOrder(args.order_id, args.amount_cents);
}

MCP: a separate server executes, the host governs

The MCP architecture has three roles. The host is the application the user interacts with. It creates one client per server, and each client keeps one connection to one server. Servers expose tools, resources and prompts and can be local processes or remote services. The host's listed responsibilities include enforcing security policies and consent requirements and handling user authorization decisions. A design principle is that servers should not be able to read the whole conversation or see into other servers.

The tools page of the 2026-07-28 specification defines the wire contract. A client discovers tools with tools/list and invokes one with tools/call; the server runs the tool and returns content, optional structuredContent, and isError for execution failures the model can correct. In this separate-server design, the server executes the tool in a different process and possibly on a different machine. The protocol defines roles and messages; it does not by itself guarantee process isolation.

That split moves responsibilities rather than removing them. The specification says that, for trust and safety, there SHOULD always be a human in the loop with the ability to deny tool invocations, while noting that the protocol does not mandate a specific interaction model. Its security section lists four things servers MUST do: validate all tool inputs, implement proper access controls, rate limit tool invocations and sanitize tool outputs. Clients SHOULD, among other things, prompt for confirmation on sensitive operations, show tool inputs to the user before calling the server, validate results before passing them to the model, set timeouts and log tool usage. Clients MUST consider tool annotations untrusted unless they come from trusted servers.

The same tool as an MCP handler, also documentation-based and not executed:

// MCP server handler: separate process, its own credentials
async function handleToolCall(call, request) {
  if (call.name !== 'refund_order') throw new Error('unknown tool');
  const args = validateRefundInput(call.arguments);        // MUST validate
  await assertCanRefund(request.caller, args.order_id);    // access control
  await rateLimit(request.caller, 'refund_order');         // MUST rate limit
  return sanitize(await refundOrder(args.order_id, args.amount_cents));
}
// Consent is not here: the host asks the user before tools/call is sent.

The pair differs in one place on purpose. In the first sketch, consent and authorization sit next to each other because one process owns both. In the second, the server enforces access control and the host retains its own authorization and consent responsibilities; neither side can assume the other did its part. For the identity and token questions that follow, read Secure MCP Server Authorization; this article does not repeat that guidance.

Hosted connectors add a third position

Some model APIs can act as the MCP client for you. Anthropic's MCP connector lets the Messages API connect to remote MCP servers without a separate client. As of 2026-10-13 it is a beta feature, enabled with the mcp-client-2025-11-20 header; the page also documents a newer mcp-client-2026-09-15 header that adds tool-list pinning. Its stated limitations matter for the decision: only tool calls are supported from the MCP feature set, and the server must be publicly exposed over HTTP, so a local STDIO server cannot be connected directly.

In this arrangement the vendor platform runs the client loop, and tool use appears in the response as mcp_tool_use and mcp_tool_result blocks. Your application still obtains and refreshes the OAuth token and passes it as authorization_token. You can enable or disable individual tools in the toolset configuration. A tool exposed this way therefore needs server-side access control from day one, because there is no in-process check in front of it.

A decision checklist

QuestionPoints to function callingPoints to an MCP server
Who calls the tool?One application you ownSeveral hosts, editors or agents
Where is the data?Already reachable from the app processBehind a boundary you want to keep separate
Who holds the credentials?The app's own service identityA server that must check each caller
Who asks the user?Your UI, in the same code pathEach host, which you may not control
Release cycleShips with the appVersioned independently of its clients

These rows are our recommendation, not rules from either specification. If most answers fall in the left column, keep the function in process: the protocol would add a deployment unit and a second place to enforce policy. If several fall on the right, a server earns its cost, but only if you implement the four server MUSTs and verify that every host you support actually offers the denial path the specification recommends.

A mixed design is common and legitimate. An application can keep low-risk read functions in process and reach a shared capability, such as a ticketing system used by several agents, through MCP. In that case, record per tool where execution, consent and authorization live, and treat a schema change as a contract change: the tool schema versioning guide covers replay and rollout. For orchestration across several agents, see the multi-agent and tool use guide.

Pitfalls to check in review

Do not read a valid schema as permission. Strict mode and inputSchema describe shape. An order_id that parses can still belong to another customer.

Do not trust tool descriptions or annotations from a server you have not vetted. The specification says clients must treat annotations as untrusted unless they come from trusted servers, so a server labelling a tool as read-only does not make it read-only.

Do not assume consent happened elsewhere. A server cannot infer that the host showed a confirmation from a tool call alone. For an irreversible operation, require a separate approval step or a verifiable approval record bound to the user and the exact action. A confirmation argument generated by the model is not proof of user consent, and the server's own access control remains mandatory.

Do not move a function into a server only to share code. A library or an internal HTTP endpoint shares code with fewer moving parts. Choose MCP when you need the protocol's discovery and multi-host reach.

Sources

Checked: 2026-10-13. Vendor feature status and beta headers can change; recheck the connector page before relying on it. Code sketches are based on documentation and were not executed.