Skip to main content
AgentsUse

Guide

MCP vs Direct API Integration: When to Use Each to Connect an Agent to a Tool

Published 2026-11-07 · 14 min read

Direct function calling or an MCP server: how each one hands tools to an agent, what each costs in control and upkeep, and the four questions that pick between them.

Published
2026-11-07
Reading time
14 min read
Sections
11
Sources
3

An agent that cannot touch a tool can only talk. Sooner or later you have to connect one, and there are two established ways to do it. You can define functions in your own code and let the model call them through the provider's function calling, with your code running each call. Or you can plug the agent into an MCP server that advertises its tools, and let any compatible host discover and call them. Both end with the model picking a tool and your systems doing the work. The difference is who owns each part of that path, and the wrong choice shows up as maintenance, not as a failed demo.

Direct function calling: you define it, your code runs it

In direct function calling, you describe each function to the model as a JSON Schema: a name, a description of when to use it, and the shape of its arguments. The model reads the descriptions, decides a call would help, and returns a structured call with the arguments filled in. That is all the model does. Your code parses the call, runs the real function, and sends the result back so the model can answer or call again. OpenAI's function calling guide lays out the loop in five steps: make a request with tools the model could call, receive a tool call, execute code on your side with the call's input, send the result back, and receive the final response or more calls.

Three properties of this model matter for the decision. First, the schema lives in your code, next to the function it describes, so the two cannot drift apart without someone noticing. Second, authentication is whatever your code already does: the function runs with your credentials, your database connection, your session, and no extra handshake. Third, you own the whole failure path. When a call fails, your code decides whether to retry, what to tell the model, and whether a human needs to see it. OpenAI's guide also notes the costs: function definitions count against the model's context limit and are billed as input tokens, and it suggests keeping the number of functions available at the start of a turn under 20, deferring the rest with tool search where the model supports it.

  • Full control of execution. Your code runs the function, so you set timeouts, retries, rate limits, and logging exactly as you like.
  • No extra process or protocol. There is no server to run, no discovery step, and no second schema to keep in sync.
  • Private by default. The tool never leaves your codebase, so nothing about it is visible to other hosts or users.
  • Approval where you put it. The call reaches your code before anything happens, so a confirmation step is a few lines, not a protocol feature.

MCP: the server advertises, any host can call

MCP flips the ownership. A server publishes the tools it offers, and any host that speaks the protocol can use them. The client asks the server for its tool list with tools/list, passes each tool's name, description, and input schema to the model, and when the model picks one, sends a tools/call request with the arguments. The messages are JSON-RPC 2.0, and the model only ever sees the name, the description, and the schema, so those three fields are documentation written for a machine. The current specification, revision 2026-07-28, made the protocol stateless: there is no initialize handshake and no session to hold, each request carries its own protocol version and client capabilities, and a server can answer any request on any instance behind a plain load balancer.

Two transports carry the messages. Local servers run over stdio, launched as a subprocess with no network and no authentication layer in the protocol: the server inherits whatever credentials its environment gives it. Remote servers use Streamable HTTP, one POST per message to a single endpoint, with OAuth 2.1 for sign-in. The 2026-07-28 revision also made tool lists cacheable, with cache hints on list responses, and hardened authorization: clients should identify themselves with a Client ID Metadata Document, and Dynamic Client Registration is deprecated. The specification's own security section sets the baseline for both sides: servers must validate all tool inputs, enforce access controls, rate limit calls, and sanitize outputs, while clients should prompt for confirmation on sensitive operations, show the inputs before calling, and log tool use for audit.

  • Discovery instead of hand-writing. A new host learns the tools by asking, so adding a second or third client means configuration, not a rewrite.
  • One implementation, many hosts. The same server can serve a coding agent, a chat assistant, and a scheduled job, each seeing the tools its credentials allow.
  • Someone else maintains the connector. When a service ships an official MCP server, its team owns the schema, the versioning, and the breakage.
  • Scoped access per caller. A remote server can filter its tool list by the token presented, so a caller only sees the tools it is allowed to use.

The same call, two different owners

Put the same tool, say looking up an order, behind both models and the differences become concrete. With direct calling, the schema is a literal in your request, the code that runs it is your function, the credentials are your session, and the approval check is your if statement. With MCP, the schema comes from the server's tools/list response, the code that runs it lives in the server process, the credentials are a token the client presents, and the approval check is whatever the host shows the user before it sends tools/call. Nothing about the model's job changes: it reads descriptions and picks. Everything about yours does.

  • Who writes the schema: you, in your code, for direct calling; the server author, for MCP. A bad description hurts both, but only one of them can you fix yourself.
  • Who runs the code: your application, for direct calling; the server process, for MCP. A local stdio server runs on the user's machine with their permissions; a remote server runs wherever its operator put it.
  • Who holds the credentials: your code, for direct calling; the server's environment for stdio, or a per-user OAuth token for a remote server, for MCP.
  • Who versions it: you, by deploying your code, for direct calling; the server operator, for MCP. Your agent gets the server's new tools when it re-lists them, which is convenient until a rename breaks your prompts.
  • Where approval happens: in your code before execution, for direct calling; in the host before it sends the call, for MCP, with the server able to filter the list by scope on top.
  • What the model pays: tool definitions consume context either way, and MCP adds a discovery round trip plus the cost of the list itself. Cacheable lists in the 2026-07-28 revision take some of that back.

Four questions that decide it

The choice is a decision about ownership, not about technology. Four questions settle it in most cases, and they are worth answering in order, before you write any integration code.

  • Whose tool is it? If the tool touches your data, your business rules, or your private systems, direct calling keeps the logic where you can see it. If the tool belongs to a service someone else runs, MCP lets their server do the talking.
  • How many hosts need it? One app, one host: direct calling is less moving parts. Two or more hosts, or a team sharing the same tools: MCP earns its keep, because the second integration is configuration instead of a rewrite.
  • Who approves the action? If approval logic is custom, if it depends on your own state, or if it must run before anything touches the database, your code is the natural place and direct calling keeps it there. If the host's confirmation prompt plus the server's scope filtering is enough, MCP covers it.
  • How often does it change? A tool that changes weekly is cheaper to version inside your own deploy. A tool that changes on someone else's schedule is cheaper to consume as a server, because their team ships the updates and your client re-discovers them.

If the four questions split two and two, pick the model that owns the riskiest part. A write action with custom approval rules is safer direct, even if three hosts will call it, because you can wrap a direct function in a server later, while a bad approval decision is hard to undo. A read-only lookup used by five hosts is a natural MCP tool even if you wrote the server yourself, because the cost of the server is paid once and the reuse is real.

When direct calling wins

  • The tool wraps your own data or business logic, and the function already exists in your codebase.
  • Exactly one host will ever call it, and it will only ever run in your application.
  • Approval, retry, or error handling needs your own state: the user's session, the current transaction, a feature flag.
  • Latency matters and the extra hop to a server process buys nothing.
  • The tool must never be visible outside your system: internal admin actions, unreleased features, anything with credentials baked in.

When MCP wins

  • A service you use ships an official MCP server, and you would rather consume its tools than wrap its API by hand.
  • Two or more hosts need the same tools: a coding agent and a chat assistant, or a scheduled job and an interactive one.
  • A team shares tools across machines, and everyone should get the same list from the same server instead of copying schemas.
  • The tool list itself is dynamic: tools appear or disappear by permission, by plan, or by environment, and discovery handles that for you.
  • You want the option to swap hosts later without rewriting the integration: the server stays, the client changes.

The hybrid most teams land on

Few teams pick one model for everything. The usual shape is direct calling for the actions you own and MCP for the services you borrow. Your agent calls your own functions for your data, and talks to MCP servers for the ticketing system, the search API, and the docs host. The two models compose cleanly: a direct function can itself open an MCP client and call a server, and an MCP server can wrap your own API so other hosts can reach it. Integration platforms sit in the middle: they host MCP servers for services that do not ship their own, with per-session credentials, so a team gets the discovery benefit without running a server per service.

That hybrid is also the migration path. Start direct, because it is the fastest way to get a working agent. When a second host needs the same tool, wrap the function in an MCP server instead of copying the integration. The function does not change; only the doorway does. What does change is worth naming: the schema moves from your code to the server, authentication moves from your session to a token, discovery replaces hardcoding, and approval moves from your code to the host's prompt plus the server's scope filter. If any of those four moves makes you uneasy, keep that tool direct.

A worked hypothetical: the CRM decision

Here is a hypothetical example, an invented scenario rather than a report of real events. A small support team has an internal CRM with three actions an agent needs: look up a customer, list open tickets, and add a note to a ticket. The first version is direct: three functions in the support app, each a few lines over the existing database, with a confirmation step before adding a note. It ships in a day, and the approval logic reuses the app's own permission checks. Months later the team adopts a coding agent that should also look up customers while fixing bugs. Copying the three functions into the coding agent's setup would duplicate the schema, the credentials, and the approval logic, and the copies would drift. So the team wraps the CRM actions in a small MCP server instead: the same three functions, the same database, but now advertised through tools/list over a local stdio server. The chat assistant and the coding agent both point at it. The note-adding tool still asks for confirmation, but now the host asks, and the server only lists that tool for users whose token carries the support scope. Nothing about the CRM changed. The doorway did, and with it the number of hosts that could walk through.

What a tool call looks like over MCP

It helps to see one real call. Under the 2026-07-28 revision, a remote tool call is a single self-contained POST, with the method and the tool name in headers so a gateway can route on them without parsing the body. The example below comes from the protocol's own release announcement, with the tool name and the search term kept as written there.

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"},
  "_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}

Compare that with the direct version of the same moment: your code receives a tool call naming a function and its arguments, runs the function, and sends the result back. The shape of the work is the same. The difference is the address it was sent to, and who was responsible for what arrived.

What to check before you commit

  • Read the tool descriptions as the model will: name, description, schema, nothing else. If a human could not use the tool from that description alone, the model will guess, and the guess will be wrong in the expensive direction.
  • Name the approval owner for every write. Direct: your code. MCP: the host's prompt, the server's scope filter, or both. A write with no named approver is a write waiting for an incident.
  • Decide what a failed call does. Direct: your code retries or tells the model. MCP: the server returns isError and the host decides whether to show it, retry, or escalate. Write the policy down before the first failure, not after.
  • Plan for the server going away. A direct function fails with your code. An MCP server fails with a process that is not yours, a network that is not yours, or a version you did not choose. Know which of your tools stop working in each case.
  • Watch the context bill. Tool definitions cost input tokens in both models. Count the tools the model actually sees at the start of a turn, and defer or scope the rest.
  • Version the seam, not just the code. For direct calling that is your deploy. For MCP it is the server's revision and the client's supported versions, so a rename or a removed tool shows up in a test before it shows up in production.

Mistakes both sides make

  • Exposing every function to every turn. The model chooses worse with fifty tools than with ten, and the context cost is real either way. Keep the working set small and load the rest on demand.
  • Trusting descriptions you did not write. An MCP server's tool description is the whole contract the model sees. A vague or misleading description produces confident, wrong calls, and the server cannot fix what the model never understood.
  • Skipping approval on writes because reads worked fine. Reads are cheap to get wrong. Writes are not, and neither model approves them for you unless you build it in.
  • Treating MCP as a security boundary by itself. The protocol gives you discovery, transport, and a place to put OAuth. It does not give you authorization policy, audit, or rate limits unless the server implements them, and the specification says so in its own security section.
  • Rewriting working direct integrations to MCP for fashion. If one host calls three stable functions, the server adds a process, a version to track, and a failure mode, and buys nothing.

From the AgentsUse catalog

Published profiles for the tools and frameworks this guide discusses, with setup, limits, and maintenance signals checked against primary sources.

Continue in the Harness pillar

The pattern pages behind this guide: the failure each one prevents, the mechanism, and the implementation checklist.

Frequently asked questions

Can I use MCP and direct function calling in the same agent?

Yes, and most teams do. Direct calling covers the actions you own, such as your own data and business logic, while MCP covers the services you borrow, such as a ticketing system or a search API with an official server. A direct function can itself open an MCP client and call a server, so the two models compose instead of competing.

Does MCP replace function calling?

No. MCP is a way to deliver tools to a model; function calling is how a model asks for a tool. A host that speaks MCP still turns the server's advertised tools into the same kind of call, and then runs it or hands it to the server. OpenAI's own platform treats MCP servers as one of its built-in tool types, sitting next to function tools rather than replacing them.

Is MCP slower than calling an API directly?

It adds a hop: a subprocess call for a local stdio server, or one HTTP POST per message for a remote server, plus a discovery round trip to list the tools. The 2026-07-28 revision made those lists cacheable, which takes some of that back. For most tools the extra cost is small next to the model call itself, but the only answer that holds up for your tool is to measure it, not to assume it.

What if the service I need has no MCP server?

Call its API directly with function calling, use an integration platform that hosts an MCP server for it, or wrap its API in a small server yourself with one of the official SDKs. The wrapper is usually thin: it translates the service's API into tools with names, descriptions, and schemas, and then any compatible host can use it. Start with the direct version and wrap it only when a second host needs the same tools.

How do I keep an MCP server from doing something dangerous?

The same way you would with any tool, plus the pieces the protocol gives you. The specification says servers must validate inputs, enforce access controls, rate limit calls, and sanitize outputs, and clients should prompt for confirmation on sensitive operations, show the inputs before calling, and log tool use. On top of that, a remote server can filter its tool list by the caller's token, so the model never sees a tool it is not allowed to use. None of that replaces an approval step on writes.

Should a beginner start with MCP or direct calling?

Direct calling, for your own first tools. The schema sits next to the code it describes, the credentials are the ones your app already has, and there is no server to run or version. Move to MCP when a second host needs the same tool, or when a service you rely on ships an official server that is better maintained than anything you would write by hand.

Sources

Keep reading

Accounts and the planned features are not open yet. See what is planned. The catalog and the browser workspace are free and need no account.