From an empty server to a callable tool, in one sitting.
MCP Servers Module 2
pkb.echoBy the end of this lesson the PKB server exposes a single tool — pkb.echo(message: str) -> str — that returns the message right back to the caller. Both the MCP Inspector and Claude Desktop can call it, and you'll watch the round-trip from both sides.
It's a deliberately tiny tool. The point isn't what echo does; the point is that you wire up everything around it — project, entry point, client config — without any moving parts you can't see.
Every tool the protocol exposes carries the same four pieces of metadata:
tools/call.TextContent.This is exactly what the tools/list response from Lesson 02 returned — one entry per tool, with those four fields.
@mcp.tool()
def echo(message: str) -> str:
"""Echo the message back."""
return message
FastMCP introspects the function's type hints and docstring to build the schema. The parameter name and type become the inputSchema; the docstring becomes the tool description; the function name becomes the tool name. Four metadata fields, derived from four pieces of Python you'd be writing anyway.
pkb/server.pyfrom mcp.server.fastmcp import FastMCP
mcp = FastMCP("pkb", version="0.1.0")
@mcp.tool()
def echo(message: str) -> str:
"""Echo the message back."""
return message
def main() -> None:
mcp.run()
This replaces the placeholder server.py you scaffolded in Lesson 03 — same file, same main() entry point, but now there's an actual tool behind it.
{
"mcpServers": {
"pkb": { "command": "pkb" }
}
}
The pkb binary came from [project.scripts] in pyproject.toml (set up in Plan A Task 4) — pip install -e . drops it onto your PATH. Restart Claude Desktop after editing the config and PKB shows up in the tools panel, ready to call.
"hello world".Claude Desktop showing the pkb.echo tool call expanded, with arguments {"message": "hello world"} and the returned text block.
mcp dev pkb/server.py
echo from the tools list in the left rail.message="ping" from the arguments form.TextContent block come back — the same payload Claude Desktop would have received, just rendered as raw JSON.Recap the Lesson 02 lifecycle for this one call:
initialize; the server replied with its capabilities.tools/list — the response included an entry for echo with its schema.tools/call with {name: "echo", arguments: {message: "ping"}}.[{type: "text", text: "ping"}].Same JSON-RPC envelopes you traced by hand in Lesson 02 — just produced by your code instead of read from a transcript.
If you raise from inside the tool function, FastMCP wraps the exception in a JSON-RPC error response — the client sees a structured failure, not a crashed server. We'll dig into schema validation and structured errors properly in Lesson 05.
pip install -e . after editing — Claude Desktop keeps running the old pkb binary from the last install, and your new tool never shows up.mcpServers is read at startup, not live-reloaded. No restart, no PKB in the panel.mcpServers expose a tool with the same name, the client has to disambiguate, and you'll see confusing behaviour until you rename one.[project.scripts] connect the dots — the JSON config points at a binary that pip install -e . put on your PATH.