Write your first MCP server in 30 minutes
An MCP server isn't magic: a handful of functions, cleanly described. Here you build your first one and plug it into your coding agent.
Most developers talk about MCP like it's a protocol monster. It isn't. At its core, an MCP server is a handful of functions you describe well enough for a coding agent to call them. If you've ever built a REST route, you can do this. In the next 30 minutes you'll wire your first tool into an agent — and you'll understand why that beats yet another clever prompt.
Why MCP at all?
A coding agent without tools is a very well-read intern with access to nothing. It can talk about your database but not look inside it. It can describe a deployment but not trigger one. MCP — the Model Context Protocol — is the standardized socket you use to give the agent real capabilities: query a database, call an internal API, read a ticket system.
The point is the standardization. You write the tool once, and it works in Claude Code, in Cursor, and in any other MCP client. No glue code per agent.
The smallest server that does something
We'll build a server with one tool: it reads the open tickets from a (here simulated) source. Python, using the official SDK.
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("ticket-tools")
@mcp.tool()
def list_open_tickets(assignee: str) -> list[dict]:
"""List open tickets for a person.
Args:
assignee: Username, e.g. "nico". Required.
"""
# In reality: a DB query or API call. Hard-coded here.
tickets = [
{"id": 142, "title": "Login breaks on empty password", "assignee": "nico"},
{"id": 187, "title": "Invoice PDF missing VAT line", "assignee": "mara"},
]
return [t for t in tickets if t["assignee"] == assignee]
if __name__ == "__main__":
mcp.run()
That's the whole server. FastMCP handles the protocol plumbing; you only write the function. Install and run:
pip install "mcp[cli]"
python server.py
Wire it into the agent
Now you connect the server to Claude Code. In your MCP config, you register it as a process:
{
"mcpServers": {
"ticket-tools": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}
Restart the agent, and the tool list_open_tickets shows up in its tool list. From now on you can ask: "Which open tickets does nico have?" — and the agent calls your function instead of guessing.
The thing nobody mentions: the docstring is your API
Here's the part that decides between success and frustration — and that no quickstart spells out. The agent never sees your code. It sees only the tool name, the parameters, and the docstring. That's your entire interface to the model.
A vague docstring leads to vague calls: the agent calls the tool at the wrong time, invents parameters, or ignores it. Treat the description like API docs for a junior who can never ask a follow-up question:
- Say when the tool should be used, not just what it does.
- Describe every parameter with format and example.
- Be honest about the limits ("open tickets only, no closed ones").
The second rule, learned the painful way: never hand out more permission than the tool needs. An MCP tool that can DELETE on your production DB because that was convenient is a time bomb. Read-only is the default. Write access gets its own narrowly scoped tool — so you can see in the agent log exactly what happened.
From toy to real tool
You replace the hard-coded list with your real data access — a query, an API call, a filesystem read. The pattern stays the same: tightly scoped functions, clearly described, minimally permissioned. That's exactly how we build the tools at anvil-coder that let 58+ agents take a ticket all the way to tested, reviewed code — each tool does one thing, and you can trace every step in the log.
The takeaway you walk away with today: an MCP server isn't infrastructure, it's well-described functions. Build the smallest one that solves a real pain — and write the docstring as if your deployment depended on it. Because it does.
Want to see a whole swarm of tools and agents take a ticket all the way to merge? Take a look at anvil-coder.