Tutorial 2: Build an MCP server with toolschema init¶
Time: 15 minutes
Prerequisites: Tutorial 1, uv, FastMCP (via toolschema[fastmcp])
This tutorial scaffolds a production-ready MCP server where schemas are generated once by toolschema and registered on FastMCP — no double generation.
Scaffold a project¶
uv run toolschema init my-mcp-server
cd my-mcp-server
uv sync
This creates:
my-mcp-server/
├── pyproject.toml
├── README.md
├── claude_desktop_config.example.json
└── src/my_mcp_server/
├── tools.py # define tools here
└── __main__.py # FastMCP server entrypoint
Smoke test locally¶
uv run python -m my_mcp_server --check
Expected output:
{"result": "Hello, toolschema!"}
{"result": 42}
Start the stdio MCP server¶
uv run python -m my_mcp_server
The server speaks MCP over stdin/stdout — ready for Claude Desktop, Cursor, or any MCP client.
Add a custom tool¶
Edit src/my_mcp_server/tools.py:
@tool
def search(query: str, limit: int = 10) -> list[dict]:
"""Search documents."""
return [{"id": "1", "title": query}]
Register in src/my_mcp_server/__main__.py:
from my_mcp_server.tools import add, greet, search
register_tool(mcp, schema(search), search)
Verify:
uv run python -m my_mcp_server --check
uv run toolschema inspect my_mcp_server.tools:search --format mcp
Claude Desktop¶
See Claude Desktop setup guide for full instructions.
Quick config snippet (claude_desktop_config.json):
{
"mcpServers": {
"my-mcp-server": {
"command": "uv",
"args": ["run", "python", "-m", "my_mcp_server"],
"cwd": "C:\\path\\to\\my-mcp-server"
}
}
}
Restart Claude Desktop after saving.
Why toolschema + FastMCP?¶
| Approach | Schema source |
|---|---|
@mcp.tool() only |
FastMCP generates schema internally |
toolschema + register_tool() |
toolschema generates once; FastMCP executes |
Same function can also export OpenAI / LangChain formats without rewriting.