From reader to author
In b1 you watched tools/list and tools/call fly past on the wire. You never wrote a line of JSON to make that happen - and tonight you still will not. The Python SDK's FastMCP generates every one of those messages for you from ordinary Python functions. Your job is to write good functions and good docstrings; the SDK handles the protocol.
daybreak-mcp stdio server with three real tools over a seeded SQLite database, the MCP Inspector open on it, and the single most transferable server skill there is: writing the docstring and type hints that become the tool's name, description, and input schema - the interface the model reads to decide what to call.
Setup that actually works 6 min live
Three commands and a seed script. We use uv - the fast Python package manager the official quickstart standardizes on - and the mcp[cli] package, which ships both the SDK and the mcp command line tool that runs the Inspector. Run these in order; do not skip the seed step, or every tool returns empty.
LiveWhy uv, and what the seed script builds3 min▶
uv creates the project, the virtual environment, and resolves dependencies in seconds - the official Python quickstart uses it, so your setup matches the docs exactly. The mcp[cli] extra is what gives you the mcp dev command in Part 3.
- seed_daybreak.py drops any old
daybreak.dband rebuilds it: customers, products, orders, order_items, subscriptions, events - the same schema as the SQL course, so the March dip is real data, not a fixture. - The number to remember: completed revenue for 2026-03 is $150.00 with one refund, against $271.00 in 2026-02. Your tool will report exactly that in Part 3.
- Run the seed once. Re-run it any time to reset; it is idempotent.
daybreak.db does not sit next to server.py. Re-run python seed_daybreak.py and confirm the file exists.
FastMCP and your first tool 7 min live
A FastMCP server is a Python object you decorate functions onto. Instantiate it with a name, decorate a function with @mcp.tool(), and the SDK reads the function's name, its docstring, and its type hints to build the tool's name, description, and inputSchema - the exact three fields you saw in tools/list last session. You write Python; the wire format is derived.
Livequery_sales, line by line4 min▶
Here is the real tool from the running project - not a toy. Read it top to bottom: the decorator registers it, the signature declares the interface, the docstring documents it for the model, and the body just queries SQLite and returns a string. Every MCP tool result in this course is a string.
month: str- one required string argument. The model must supply it; the Inspector will show a required field.- The docstring spells out the format
(YYYY-MM). That single parenthetical stops the model guessing "March" instead of "2026-03". - Refunds are excluded from revenue by grouping on status - the March refund is why completed revenue is $150.00, not more.
LiveWriting tool descriptions the model uses well4 min▶
This is the craft point of the whole session. The docstring is not documentation garnish - it is the interface the model reads before every call. Watch the discovery message again and notice the description is the only prose the model gets:
Four rules that make descriptions the model acts on correctly:
- State the format in the description, not just the type.
(YYYY-MM)beats "a month string" every time. - Say what it returns and what it excludes. "completed revenue" tells the model refunds are already handled - it will not double-correct.
- One tool, one job. A tool that both queries and mutates confuses the model about when it is safe to call.
- Name the function like the action.
query_salesreads as a verb; the function name becomes the tool name.
The tool nobody could call. A team shipped a get_data tool with the docstring "Returns data." The model never picked it correctly because it could not tell what data or when. Renaming it query_sales and writing one sentence about the month format and the revenue definition fixed the call rate overnight - no model change, just the description.
Run it and poke it: the Inspector 6 min live
One command runs your server and opens a browser devtool wired to it - the MCP Inspector. It is the single most useful thing in your toolkit this track: it lists your tools, lets you call them with arguments, and shows the raw result, so you never guess whether a bug is in your code or on the wire.
Run uv run mcp dev server.py. The Inspector opens and connects; if it does not, the server threw on import - read the terminal.
Open the Tools tab. You should see query_sales with its description and a month field. That is your docstring and type hint, rendered.
Type 2026-03 into month and press call. Read the result string: it should say completed revenue $150.00.
Call it again with 2026-02 - $271.00. You just proved the March dip through your own server.
LiveInspector traffic vs the wire scenarios3 min▶
The stepper above is the same JSON-RPC the Inspector generates when you click "call". The Inspector is just a host that never involves a model - you play the model, choosing the tool and filling the arguments by hand. That is exactly why it is the right debugging tool: it removes the model's judgement so you can test the server in isolation.
- Tools tab = the
tools/listresponse, rendered as a form. - Calling a tool = one
tools/callrequest and its result - the log tab shows the raw JSON. - A red result with
isError:true= your tool ran and raised; a protocol error = the Inspector could not even reach the tool.
Self-studyCommon first-server failures3 min read▶
Three failures catch nearly everyone once. Recognize them and you save the afternoon:
- Path. The server cannot find
daybreak.dbbecause it was launched from a different directory. Fix:DB_PATHis built from__file__in the project, so keepserver.pyand the db together. - Unseeded db. Tools return "No orders found." Fix: run
python seed_daybreak.py. - Silent exceptions. A tool raises but you only see a generic error in the host. Fix: run in the Inspector, which surfaces the traceback - and never print to stdout in a stdio server, because stdout is the protocol channel. Use stderr or logging.
print("debug") corrupts the JSON stream and the host disconnects. Log to stderr instead.
Build query_sales end to end ★ 12 min · everyone builds
Do this live, all together. Five moves from empty folder to the March dip on screen through a server you wrote.
Seed the db. python seed_daybreak.py - confirm "33 orders".
Write the tool. Paste the FastMCP header and query_sales from Part 2 into server.py. Add if __name__ == "__main__": mcp.run() at the bottom - mcp.run() defaults to stdio.
Open the Inspector. uv run mcp dev server.py. Confirm query_sales appears in the Tools tab.
Call month=2026-03. Read the result: 2026-03: ... completed revenue $150.00.
Compare to 2026-02 ($271.00). The dip is real, and it is your server saying so.
Add two more tools ★ 10 min · build
Two tools give the server real range: top_products takes a date range with defaults, and subscription_status takes no arguments at all. Notice how the signatures change what the Inspector renders.
Paste both under query_sales, save, and restart the Inspector.
top_products shows two fields, both pre-filled with defaults - that is what the = "..." defaults do to the schema (optional, with a default).
subscription_status shows no fields and a call button - a zero-argument tool. Call it and read active vs cancelled.
Try it yourself - this week ◐ 25-35 min total
- Add a fourth tool,
customer_lookup(name: str) -> str, that returns a customer's city, plan, and signup date from thecustomerstable. Write the docstring first, before the body - practice the interface as interface. - Test a bad month: call
query_saleswith2027-01. Confirm it returns "No orders found" and not a crash - graceful empties are part of tool craft. - Deliberately break it: add a
print("hi")inside a tool, restart the Inspector, and watch the connection fail. Remove it. Now you will never do it by accident. - Rewrite one docstring to be vague ("gets sales") and one to be precise, and compare how each reads in the Tools tab. Bring the precise one to b3.
Official sources covered
This session teaches the working core of the official server-build quickstart and the server-authoring lessons across the major courses, on the actual running project. Certificates and videos stay on the official sites. This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · With FastMCP, what does a tool function's docstring become?
FastMCP derives the tools/list entry from your function: name from the function name, description from the docstring, inputSchema from the type hints. The docstring is the interface, not a comment.
2 · Your stdio server disconnects the moment a tool runs. Likeliest cause?
In stdio transport, every protocol message travels over stdout. A stray print() corrupts the JSON stream and the host drops the connection. Log to stderr instead.
3 · What is the MCP Inspector for?
The Inspector is a host that lets you play the model: pick a tool, fill arguments, see the raw result. It isolates the server so you can tell a code bug from a protocol problem.