Install FastMCP
FastMCP is the fastest way to create MCP-compatible tools. Install it:
pip install fastmcp
The Code
Create a file called my_tools.py:
from fastmcp import FastMCP
mcp = FastMCP(name="MyTools")
# Sample data β swap this with your real database
USERS = {
"u001": {"name": "Alice Chen", "role": "engineer", "team": "platform"},
"u002": {"name": "Bob Park", "role": "designer", "team": "growth"},
"u003": {"name": "Carol Silva", "role": "engineer", "team": "infra"},
}
@mcp.tool
def lookup_user(user_id: str) -> dict:
"""Look up an employee by their user ID. Returns name, role, and team."""
if user_id not in USERS:
return {"error": f"No user found with ID '{user_id}'"}
return USERS[user_id]
@mcp.tool
def list_team_members(team: str) -> list[dict]:
"""List all employees on a given team."""
members = [
{"id": uid, **info}
for uid, info in USERS.items()
if info["team"] == team
]
return members if members else [{"error": f"No members found for team '{team}'"}]
if __name__ == "__main__":
mcp.run()
That's it. Two custom tools in 30 lines.
What's Happening Here
FastMCP(name="MyTools") creates your MCP server. The name identifies your tool collection when agents connect to it.
@mcp.tool is the decorator that registers a function as a callable tool. FastMCP automatically:
- Uses the function name (
lookup_user) as the tool name
- Uses the docstring as the tool description (this is what the AI reads to decide when to call it)
- Generates an input schema from your type annotations (
user_id: str)
- Validates parameters before your function runs
Return types matter. The -> dict and -> list[dict] annotations tell FastMCP what schema to advertise. Agents use this to parse your response correctly.
Docstrings are critical. The AI agent reads your docstring to decide whether this tool solves its current problem. Write them like you're explaining the function to a coworker: short, specific, and focused on what it returns.
Run It
Start the server:
python my_tools.py
FastMCP launches a local MCP server using stdio transport by default. Any MCP-compatible client (Claude Desktop, Cursor, or your own agent) can now discover and call lookup_user and list_team_members.
To connect from Claude Desktop, add this to your config:
{"mcpServers":{"my-tools":{"command":"python","args":["my_tools.py"]}}}
Make It Your Own
The sample uses a dictionary, but swap USERS with anything:
-
Database query: Replace the dict lookup with
sqlite3 or sqlalchemy
-
API call: Use
httpx to hit your internal service
-
File search: Read from local logs or config files
The pattern stays the same: decorate a function, add type hints, write a clear docstring. FastMCP handles the rest.
One Gotcha
Your function signature cannot use *args or **kwargs. FastMCP needs explicit parameters to generate the JSON schema that agents use. Every parameter must have a type annotation.
Next Steps
You now have a working MCP server with custom tools. From here you can:
- Add more tools to the same server (just add more
@mcp.tool functions)
- Use
Annotated types to add parameter descriptions for better agent behavior
- Deploy it as an HTTP server with
mcp.run(transport="streamable-http")
If you want your custom tools to run as part of a managed agent workflow without setting up infrastructure, Nebula connects to MCP servers and lets your agents call custom tools on a schedule.
This is part of the AI Agent Quick Tips series. Follow for more bite-sized agent tutorials.