MCP vs Function Calling for Live Web Data in Agents
Use function calling when your agent needs one or two known endpoints and you want control over what the model sees. Use MCP when the agent should pick from many sources, or when the client already speaks MCP. Both end in the same HTTP call at the same price. The difference is who writes and maintains the tool definitions: you, or the server.
How do MCP and function calling differ for web data?
Function calling is a model feature: you send tool definitions with each request, the model returns a structured call, and your code runs it. MCP is a protocol on top of that idea: a server publishes its tools, and any compatible client lists and calls them. The model still sees tools with a name, a description and a JSON schema either way.
The MCP specification defines this as tools/list for discovery and tools/call for invocation, with each tool carrying a name, a description and an inputSchema. Function calling, as described in Anthropic's tool use docs, uses the same three pieces under an input_schema key.
So the real question isn't which one the model understands. Both look identical to it. The question is where the definitions live, who keeps them correct, and what happens to the result before the model reads it.
What does the same endpoint look like both ways?
The same endpoint, GET /v1/google-maps/simple-search, can be wired as a hand-written function schema or as a call through the MCP server, and both reach the same URL. Its only required parameter is query; optional ones include limit, page, hl and gl. It costs 1 credit per successful call. The Google Maps API page lists all 10 endpoints.
As a function schema
You write the schema, the handler and the loop. This example uses the Anthropic Python SDK and requests. The handler returns only the fields the agent needs, all of which appear in the recorded response from this endpoint.
import json
import os
import requests
from anthropic import Anthropic
API = "https://api.perceptlayer.com"
tools = [
{
"name": "search_places",
"description": "Search Google Maps for businesses matching a text query, "
"for example 'restaurants in new york'. Returns name, rating, "
"review count, address and business_id for each place.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "What to search for"},
"limit": {"type": "integer", "description": "Maximum places to return"},
},
"required": ["query"],
},
}
]
def search_places(query, limit=5):
r = requests.get(
f"{API}/v1/google-maps/simple-search",
params={"query": query, "limit": limit},
headers={"X-API-Key": os.environ["API_KEY"]},
timeout=30,
)
if not r.ok:
return {"status": r.status_code, "error": r.json().get("error")}
keep = ("name", "rating", "review_count", "full_address", "business_id")
return [{k: item.get(k) for k in keep} for item in r.json()["items"]]
client = Anthropic()
messages = [{"role": "user", "content": "Find well-rated Italian restaurants in Manhattan."}]
while True:
resp = client.messages.create(
model="claude-sonnet-5-5", max_tokens=1024, tools=tools, messages=messages
)
if resp.stop_reason != "tool_use":
break
messages.append({"role": "assistant", "content": resp.content})
results = []
for block in resp.content:
if block.type == "tool_use":
out = search_places(**block.input)
results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(out)}
)
messages.append({"role": "user", "content": results})
print(next(b.text for b in resp.content if b.type == "text"))
Failed calls come back as a JSON error object with a code, a message and a request_id, and they cost nothing, so the handler passes that object to the model rather than raising.
As an MCP tool
There's no schema to write. The two paths also serve different clients, so there are two ways to show it. For your own agent loop, the Anthropic API can attach a remote MCP server through its MCP connector, which replaces the tools list, the handler and the while loop above:
import os
from anthropic import Anthropic
client = Anthropic()
resp = client.beta.messages.create(
model="claude-sonnet-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Find well-rated Italian restaurants in Manhattan."}],
mcp_servers=[
{
"type": "url",
"url": "https://api.perceptlayer.com/mcp",
"name": "perceptlayer",
"authorization_token": os.environ["API_KEY"],
}
],
tools=[{"type": "mcp_toolset", "mcp_server_name": "perceptlayer"}],
betas=["mcp-client-2025-11-20"],
)
print(next(b.text for b in resp.content if b.type == "text"))
The connector is a beta feature, so confirm the parameter names and the betas value against its docs before copying this. We haven't run this snippet against the server. With the connector, the API makes the tool calls for you, which means you give up the handler where function calling trims results.
In an interactive client such as Claude Code, one command registers the server instead:
claude mcp add --transport http perceptlayer https://api.perceptlayer.com/mcp --header "Authorization: Bearer $API_KEY"
The server at https://api.perceptlayer.com/mcp uses Streamable HTTP and exposes three tools. search_endpoints finds endpoints in the catalogue, get_endpoint returns one endpoint's details, and call_endpoint takes an endpoint ID plus that endpoint's parameters and returns its JSON response. Given those tools, the expected flow is to search for a Google Maps endpoint, read its details with get_endpoint, then call it. The MCP docs section covers the setup per client.
We haven't recorded a test of this flow, so treat it as the expected behaviour from the tool design, not a measurement. If the model does make lookup calls first, they spend no credits, but they do add turns, which matters in the next section.
How much setup effort does each approach take?
Function calling is faster for the first endpoint only if you already have an agent loop. You write a schema, a handler, error handling and the loop above. MCP is one command, but the model may need to look up an endpoint's details before it can call it (we haven't tested that flow).
The effort scales differently. A second function-calling endpoint, say GET /v1/google-maps/reviews with its required business_id and optional sort, limit and pages, means another schema and another handler. A tenth means ten. With MCP, the tenth endpoint costs the same as the first: the model would look it up through the same three tools.
If your client already supports MCP, as Claude Code does, the server path has no code at all. If you're building your own loop with a framework, the LangChain custom tool tutorial shows the function route end to end. For a fuller MCP walkthrough, see MCP Server for Web Scraping: Tools Agents Can Call and Claude MCP Web Search.
Which approach uses fewer tokens?
Neither wins outright; they spend tokens in different places. Tool definitions count as input tokens on every request (Anthropic, tool use overview, 2026): the tools parameter carries tool names, descriptions and schemas. So the size of your tool list is a recurring cost on each turn.
With function calling, you pay per endpoint you expose. The example above sends one tool with two properties (query and limit), and you can write a tight description. Ten schemas means ten tools, and every one is sent on every turn whether the agent uses it or not.
With MCP against this server, the list stays at three tools whatever the catalogue size. By count, function calling sends fewer tools up to two endpoints, ties at three and sends more from four on. Count isn't tokens, since descriptions and schemas differ in size, so the method below measures the real figure. The cost moves to turns: any lookups before the first call, and then the endpoint response in the context. We haven't benchmarked token totals, so we won't give a number.
Here is a method you can reproduce. It is a method, not a result: we haven't run it, so this article publishes no usage.input_tokens figures.
- Send the same prompt twice with the Messages API, once with the one-schema
toolslist from the example and once with the three MCP tools, copied from the server'stools/listresponse. - Read
usage.input_tokensfrom each response and compare the first turn. - Run the agent to completion and sum
usage.input_tokensandusage.output_tokensacross all turns. - Repeat with ten schemas against the same three MCP tools to see where the lines cross.
The second lever is result shaping. In the function version above, the handler keeps five fields per place: name, rating, review_count, full_address and business_id. Each item in the recorded simple-search response has 28 top-level fields, so the handler drops 23 of them, including coordinates, photos_sample with its long photo URLs, owner fields, phone_numbers, an order_link and an attributes object. With function calling, you cut that down in your handler. Over MCP, call_endpoint returns the endpoint's JSON response. simple-search also takes an optional fields string, but how it behaves isn't documented, so check a real response before counting on it to trim output.
A field count isn't a token count, and we haven't measured either side. What the recorded item does show is that the payload is far wider than the five fields the agent needs, and that it repeats for every place returned. On a wide endpoint like this one, that suggests shaping can matter at least as much as the tool list, but measure both with the method above before deciding which to optimise first.
What does it cost to run?
Running costs are the same for both approaches, because both end in the same HTTP call. A successful call costs 1 credit through function calling or call_endpoint, and failed calls return X-Credits-Used: 0. Lookups through search_endpoints and get_endpoint cost nothing, so the only extra spend MCP adds is model tokens.
A worked example: an agent that makes 10,000 data calls a month spends 10,000 calls × 1 credit = 10,000 credits. That equals the Starter pack's 10,000 credits, and purchased credits last 12 months. Every account also gets 1,000 free credits a month after email verification, with no credit card, which would cover the first 1,000 of those calls. See pricing for what credits cost.
The service is prepaid. When the balance runs out, calls return insufficient_credits (402) and nothing more is debited. That is a hard ceiling on a runaway agent in either approach. Each successful response carries X-Credits-Remaining, which your function handler can read; over MCP, call_endpoint reports credits used and remaining. See AI Agent API Cost Control for budget patterns, and Pricing for the packs.
What breaks over time, and who fixes it?
With function calling, the schema is yours, so drift is yours. If you copy a parameter list into a schema today and the endpoint gains an optional parameter, the model won't know it exists until you edit the schema. Wrong or missing required entries produce invalid_parameters (422) errors the model then has to recover from.
With MCP, the server publishes the tools, and get_endpoint returns an endpoint's details, so you don't hand-copy parameter lists into schemas. You write nothing per new endpoint. We haven't tested how quickly a catalogue change reaches the model, so check that if drift matters to you. The trade-off is less control: you can't reword a description to steer the model, and you depend on the client's handling of the server.
Retries are the same in both. Rate-limited (429) and temporarily unavailable (503) responses carry a Retry-After header with the seconds to wait. Requests have no idempotency key, so a call whose reply was lost to a client timeout may have succeeded and been billed. Put that logic in your handler or client, not in the model's hands; Retry Web API Calls Without Paying Twice shows how.
Comparison: MCP vs function calling
In short, function calling puts definitions, result shaping and turn count in your hands, while MCP hands definition upkeep to the server and keeps the tool list at three tools. Credit cost per successful call is identical. The table sets the two side by side on the points covered above.
| Function calling | MCP server | |
|---|---|---|
| Who writes tool definitions | You, per endpoint | The server |
| Tool list sent to the model | One schema per endpoint you expose | Three tools, any endpoint |
| Setup for one endpoint | Schema, handler, loop | One command in an MCP client |
| Setup for ten endpoints | Ten schemas and handlers | Same as one |
| Extra turns | None | Possible lookups before the first call (untested) |
| Result shaping | In your handler, before the model | call_endpoint returns the JSON response; fields exists on simple-search, behaviour undocumented |
| Parameter drift | Your job to track | Server publishes the tools; get_endpoint returns an endpoint's details |
| Cost per successful call | 1 credit | 1 credit |
| Cost of lookups | Not applicable | 0 credits |
| Best control over prompts | Full | Limited |
Which should you pick for your use case?
Pick by how many endpoints the agent uses and who controls the client: function calling for a few known endpoints where you want control, MCP for open-ended source choice or an MCP-native client. Since the credit price is the same either way, cost shouldn't decide it. Here is a plain verdict for five common cases.
- A fixed workflow with one to three endpoints (for example, search a place, then fetch its reviews): function calling. Small schemas, no lookup turns by design, trimmed results.
- An agent that must choose among many sources: MCP. Writing and updating dozens of schemas isn't worth it when three tools cover the catalogue.
- Interactive use in Claude Code or another MCP client: MCP. There's no code to write, and you can ask for data in plain language.
- High-volume, latency-sensitive pipelines: function calling. You avoid any possible lookup turns and control the payload size exactly. If no model needs to choose, call the HTTP endpoint directly from code and skip tools altogether.
- Prototyping before you know which sources matter: MCP first, then move the endpoints you settle on into functions.
The two aren't exclusive. You can expose a handful of hand-written functions for the hot path and attach the MCP server for everything else.
Whichever you choose, start with a real call. Get a free API key, then run simple-search in the playground on the Google Maps API page to see the response your tool will return. Authentication details are in the docs.
APIs in this post
Frequently asked questions
Is MCP better than function calling?
Neither is better in general. They carry the same kind of tool definition to the model. MCP moves definition upkeep to the server and keeps the tool list short; function calling gives you control over descriptions, results and turn count. For one to three endpoints, function calling is usually simpler. For many sources, MCP is.
Does MCP cost more credits than calling the API directly?
No. A successful call_endpoint spends the same credits as the endpoint's price, which is 1 credit for nearly all endpoints, including every Google Maps endpoint. search_endpoints and get_endpoint only look up the catalogue and spend nothing. Failed calls cost nothing either.
Can I use both in one agent?
Yes. One workable setup is hand-written functions for the two or three endpoints the agent calls constantly, plus the MCP server for occasional lookups of other sources. Just avoid exposing the same endpoint through both paths, since the model may pick either and you could see duplicate calls on your balance.
What happens when an MCP or function call fails?
The API returns an HTTP status and a JSON body with error.code, error.message and error.request_id; codes include invalid_parameters (422), rate_limited (429) and insufficient_credits (402). The failed call is not billed. Pass the error back to the model so it can correct parameters, and handle Retry-After in code. See Errors.