How to give an AI agent real tools with MCP
Model Context Protocol lets an agent call an API as a tool without you writing an integration. What MCP actually is, how to connect one, and why tool descriptions decide whether it works.
An agent without tools can only tell you what it already knows. Model Context Protocol is the standard that lets it do things — call an API, read a page, look something up — without you writing a bespoke integration for every model and every client.
What MCP actually is
A small JSON-RPC protocol with three operations that matter: the client asks the server what tools it has (tools/list), the model picks one, and the client asks the server to run it (tools/call).
That is genuinely the whole thing. The value is not the protocol's cleverness — it is that Claude, Cursor, and other clients all speak it, so one server works everywhere instead of one integration per client.
Connecting one
Every API on Twotic publishes an MCP server. Point a client at it:
{
"mcpServers": {
"web-extract": {
"type": "http",
"url": "https://twotic.dev/api/mcp/web-extract",
"headers": { "Authorization": "Bearer tk_live_your_key" }
}
}
}Restart the client and the endpoints appear as callable tools. There is no SDK to install and no glue code to maintain.
The Authorization header is your normal API key. Agent calls are metered exactly like HTTP calls — same free allowance, same per-request price, same entry in your usage log. There is no separate agent tier, which also means there is no separate bill to reconcile.
Why tool descriptions decide everything
Here is the part most MCP guides skip, and it is the part that determines whether your agent works.
When a model chooses a tool, the description is the only thing it sees. Not your documentation, not your README, not the parameter names you were proud of. One paragraph per tool, and it picks from those.
So a description like this is close to useless:
Extracts content from a web page.
It says what the tool does but nothing about when it is the right choice. Give a model six tools described that way and it picks semi-randomly.
Compare:
Fetch a web page and return its main article content as clean Markdown, stripped of navigation, ads and scripts. Choose this whenever you need to READ the content of a URL — to summarise it, answer questions about it, or store it for retrieval. Requires url. Use the links tool instead if you only need the page's outbound URLs.The second sentence is the one doing the work. It draws the boundary against the sibling tool, which is precisely the decision the model has to make.
Write descriptions as a set, not one at a time
This follows directly. A tool description's value is almost entirely contrast — how it differs from the tools sitting next to it. Write them one at a time and you get six competent paragraphs that are individually fine and collectively useless, because none of them says why it rather than its neighbour.
Write them together, then read them as a group and ask: if I only had these, could I choose correctly? If two could be swapped without the model noticing, neither is doing its job.
Give the agent the ability to read
The single most useful tool for most agents is one that turns a URL into readable text:
{ "url": "https://example.com/post", "format": "markdown" }The agent gets clean Markdown instead of a wall of HTML, which matters more than it sounds. On a typical page, navigation, scripts and boilerplate are most of the bytes — feed that in raw and most of the agent's context window is spent on markup it will never use.
Watch what the agent spends
An agent that reads several pages per task can consume a context window quickly. Checking before you commit is cheap:
curl -X POST "https://twotic.dev/v1/token-counter/count" -H "Authorization: Bearer $TWOTIC_KEY" -H "Content-Type: application/json" -d '{"text": "<extracted text>", "model": "claude-sonnet-4"}'
# Branch on fitsInContext before sending anything to the model.const { inputTokens, fitsInContext } = (await fetch(
"https://twotic.dev/v1/token-counter/count",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TWOTIC_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text: extracted, model: "claude-sonnet-4" }),
}
).then((r) => r.json())) as { inputTokens: number; fitsInContext: boolean }
if (!fitsInContext) {
// Summarise or chunk before handing it to the model.
}const { inputTokens, fitsInContext } = await fetch(
"https://twotic.dev/v1/token-counter/count",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TWOTIC_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text: extracted, model: "claude-sonnet-4" }),
}
).then((r) => r.json())
if (!fitsInContext) {
// Summarise or chunk before handing it to the model.
}import os
import requests
result = requests.post(
"https://twotic.dev/v1/token-counter/count",
headers={"Authorization": f"Bearer {os.environ['TWOTIC_KEY']}"},
json={"text": extracted, "model": "claude-sonnet-4"},
).json()
if not result["fitsInContext"]:
# Summarise or chunk before handing it to the model.
...Practical notes
Give an agent few tools, not many. Every tool is a choice it can get wrong. A focused set of five outperforms a sprawling twenty.
Expect it to call things you did not anticipate. That is the point of an agent, and it is also why per-call pricing and a prepaid balance are a good fit — the balance is a hard ceiling on how surprised you can be.
Read the usage log after the first real run. It shows which tools were actually reached for and how often, which is the fastest way to find a description that is over- or under-triggering.
Do not expose a tool that writes something irreversible without a confirmation step in your own application. MCP has no notion of "are you sure".
When MCP is the wrong answer
If your application always makes the same call in the same order, you do not need an agent or a protocol — call the API directly. MCP earns its place when the model should decide what to call, based on something you cannot predict when you write the code. Reaching for it in a fixed pipeline adds a decision point and a failure mode to something that was already deterministic.
Authentication: OAuth or an API key
There are two ways to authorise a remote MCP server, and the right one depends on who is connecting rather than on which is technically nicer.
An API key is a header. You paste Authorization: Bearer tk_live_… into the client's config and the server bills that key. It is the right choice for anything running unattended — a script, a backend service, a CI job — because there is no human present to approve a browser prompt.
OAuth is the flow you have already used if you have connected an app to a Google account: the client opens a browser, you sign in, you see what is being requested, you approve, and the client receives a token it can refresh on its own. It is the right choice for a person connecting a desktop client, because it means nobody is copying a long-lived secret between applications — and it is the only one that works when a client offers no way to set a header, which several do not.
The handshake that makes this automatic is worth understanding, because it explains why some clients need only a URL. A client connects with no credentials, receives a 401 carrying a WWW-Authenticate header that points at the server's metadata document, follows that pointer to discover the authorisation endpoints, registers itself, and starts the flow — all without anyone configuring anything. That is why pasting a bare URL into a client that supports the flow simply works.
Two things to check regardless of which you pick:
- Scope the connection to one API where the client allows it. A token that can call everything is a token that can spend everything.
- Revoke from your side, not theirs. Removing a connection from your dashboard invalidates its tokens immediately, without rotating a key that other integrations still depend on.
What you can build with this
A research agent that reads real pages. Connect content extraction and let the agent fetch, extract and summarise. The step that makes it good is the extraction: an agent reading raw HTML spends most of its context on navigation and gets worse answers for more money.
An internal assistant with exactly the tools your team needs. The value of MCP is not any single server, it is that capabilities compose. Connect two or three and the agent starts doing multi-step work you did not explicitly program.
A validation agent for messy data. Point it at a spreadsheet of addresses or URLs and let it check each one and report. This is unglamorous, boring, and the kind of task people put off for months because doing it by hand is miserable.
Your own MCP server for something only you have. The protocol is simple enough to implement in an afternoon: a tool list, and a call handler. If you have an internal system with an API, wrapping it is usually a smaller job than the first integration you would otherwise write.
If you build one thing, make it the smallest useful one, and pay attention to the tool descriptions. They matter more than the implementation.
Related reading
- MCP documentation — the exact configuration for Claude, ChatGPT, Cursor and the rest.
- How to control LLM API costs — agent loops are the fastest way to spend unexpectedly, and this covers bounding them.
Frequently asked
Should I connect an agent with OAuth or an API key?+
OAuth for a person using a desktop client — nothing long-lived gets copied between applications and it can be revoked without rotating a key. An API key for anything unattended, such as a script or a CI job, where no one is present to approve a browser prompt.
Why does pasting just a URL work in some clients?+
The client connects without credentials, gets a 401 pointing at the server's metadata document, discovers the authorisation endpoints from it, registers itself and starts the OAuth flow. Nothing needs configuring because each step tells the client where the next one is.
Do MCP calls cost more than HTTP calls?+
No. They run through the same gateway at the same per-request price, draw on the same free monthly allowance, and appear in the same usage log. There is no agent tier and no markup.
Which clients support MCP?+
Claude, Cursor, Windsurf and a growing set of others. They all take the same three fields — type, url and headers — under their own configuration key, so one server configuration works across them.
Can I stop an agent spending too much?+
Your prepaid balance is the ceiling — an agent cannot spend credit you have not added, and calls return HTTP 402 once it runs out. Each API also has a per-minute rate limit that applies to agent calls exactly as it does to any other.