Picking your tools and scaffolding the PKB project.
MCP Servers Module 1
The official mcp Python package ships two layered APIs. Pick the layer that matches how much control you actually need.
mcp.server.Server)Full control. You hand-roll every capability handler — list_tools, call_tool, list_resources, and so on — and own the registration plumbing yourself.
Verbose, but nothing is hidden. You see every request shape the protocol gives you.
mcp.server.fastmcp.FastMCP)Decorator-based and opinionated. Slap @mcp.tool() on a function and the SDK derives the schema, registers the handler, and wires it into the protocol for you.
Less boilerplate, fewer foot-guns, faster iteration.
hello toolThe same one-argument tool, written both ways:
Low-level (mcp.server.Server) | FastMCP |
|---|---|
|
|
Same wire behaviour. Same Inspector view. Wildly different amounts of code.
| Need | Pick |
|---|---|
| Quick prototype, ergonomic decorators | FastMCP |
| Fine-grained capability control / custom transports | Low-level |
| Teaching the protocol | Low-level (this course uses both — FastMCP from L05) |
The shape we'll use for the PKB server across the whole course:
pkb/
pyproject.toml
pkb/
__init__.py
server.py ← entry point
tools.py ← tool implementations
resources.py ← resource handlers
prompts.py ← prompt templates
tests/
test_tools.py
One package, one entry point, one file per MCP primitive. Boring on purpose — it stays easy to grep as the server grows.
pyproject.toml essentials[project]
name = "pkb"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["mcp[cli]>=1.0.0", "pydantic>=2.0.0"]
[project.scripts]
pkb = "pkb.server:main"
Enough to pip install -e . from the project root and then run pkb straight from your shell — that's the script entry point doing its job.
mcp dev workflowmcp dev pkb/server.py
One command launches your server and the Inspector pointed at it. Your inner loop becomes: edit a tool, save, restart, re-run the call in the Inspector, read the JSON, repeat. No client config, no Claude Desktop reload — just the protocol and your code.
from pydantic import BaseModel
class SearchArgs(BaseModel):
query: str
limit: int = 10
FastMCP introspects Pydantic models (and plain type hints) to generate the JSON Schema that goes on the wire for tool arguments. Type errors get caught before the LLM ever sees them — the SDK refuses bad input at the protocol boundary.
The modelcontextprotocol/servers repo is the canonical collection. Each server lives in its own small package, with one clear job:
filesystem — safe, sandboxed file access (read, write, list under allowed roots).fetch — HTTP GET as a tool, with markdown extraction.sqlite — query and inspect a SQLite database.github — issues, PRs, code search against the GitHub API.Reading their source is the fastest way to learn idiomatic MCP — everything you'll build in this course is a variation on a pattern that lives there.
mcp-server-fetchThe smallest interesting reference server. Tool registration is just this much:
server = Server("mcp-fetch")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [Tool(name="fetch", description="...", inputSchema=Fetch.model_json_schema())]
@server.call_tool()
async def call_tool(name, arguments):
url = arguments["url"]
content, prefix = await fetch_url(url, ...)
return [TextContent(type="text", text=prefix + content)]
Source: github.com/modelcontextprotocol/servers/tree/main/src/fetch. One list_tools, one call_tool, a Pydantic model for the args. That's the whole shape.
import pytest
from mcp.shared.memory import create_connected_server_and_client_session
@pytest.mark.asyncio
async def test_search_returns_results():
async with create_connected_server_and_client_session(server) as (_, client):
result = await client.call_tool("pkb.search", {"query": "hello"})
assert result.content
The SDK ships an in-memory transport that wires a client and server through a pair of queues. No subprocesses, no sockets, no real I/O — but real protocol semantics (initialize, capability negotiation, request/response). Fast, deterministic, CI-friendly.
In this lesson's lab you scaffold the project from scratch — but a reference copy already lives in the repo at mcp-servers/sample-server/python/. It ships:
pyproject.toml wired up exactly like Slide 6.server.py (Plan A Task 4) with the imports and main() stub in place.tools.py, resources.py, prompts.py ready to be filled in.Lesson 04 puts the first real tool into server.py end-to-end. This lesson is about getting the runway built.
modelcontextprotocol/servers is your idiom dictionary; copy patterns from there before inventing your own.