Capstone: build a stdio MCP server
Write a working MCP server from scratch that discovers, lists and calls a tool over stdio.
- Read newline-delimited JSON-RPC requests from stdin
- Answer server/discover, tools/list and tools/call correctly
- Return the right kind of error for every failure
Time to build the whole thing. Your server will speak MCP over stdio: one JSON-RPC request per line on stdin, one response per line on stdout. In practice you would use an official SDK, which handles this plumbing for you - but writing it once by hand shows exactly what the SDK does, and makes every bug you meet later easier to understand.
Your server must handle:
| Request | Response |
|---|---|
any request missing a required _meta field | error -32602 “Missing required _meta” |
server/discover | supportedVersions: ["2026-07-28"], capabilities: {"tools": {}} |
tools/list | the add tool definition |
tools/call for add with integer a and b | the sum as text content, structuredContent: {"sum": …}, isError: false |
tools/call for add with bad arguments | a tool execution error, isError: true |
tools/call for an unknown tool | error -32602 “Unknown tool: NAME” |
| any other method | error -32601 “Method not found” |
Try it
Try the add tool
Before you code it, play the server: send arguments to add and look at the response each one deserves. Your server should produce the same kinds of answers.
The tool, as tools/list describes it
{
"name": "add",
"description": "Add two integers.",
"inputSchema": {
"type": "object",
"properties": {
"a": {
"type": "integer"
},
"b": {
"type": "integer"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
}The server’s responseSuccess
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Added 2 and 3."
}
],
"isError": false
}
}Key takeaways
A stdio server is a loop: read a line, handle the request, write one JSON line.
Check
_metafirst, dispatch onmethod, and validate arguments before doing anything.Unknown methods →
-32601; unknown tools or missing_meta→-32602; bad tool arguments →isError: true.
Lesson quiz
5 questions · pass with 4 correct · up to 50 XP
Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.
Practice: write Python
Write Python in the editor and run it against sample inputs. Python runs locally in your browser using a WebAssembly runtime.
Build the server
Complete handle so the server answers every request as described in the table above, printing exactly one JSON line per request with the respond helper.
- Error messages:
Missing required _meta,Unknown tool: NAME,Method not found. - For bad
addarguments, the text isInvalid arguments: a and b must be integers(arguments must be exactlyaandb, both integers, not booleans). - For a successful
add, return{"content": [{"type": "text", "text": "SUM"}], "structuredContent": {"sum": SUM}, "isError": false}.
- Discover, list and add
- Every kind of error
- Tool list
Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.
Questions about this lesson
Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.
Loading posts…