Solutions — Homework 02
Lesson 02 · MCP Servers · worked solutions
Worked answers for the three homework tasks, with the JSON-RPC methods that actually appear on the wire, the request bodies you should have hand-crafted, and the reasoning behind each transport choice. Compare against your own solution — and pay attention to the common mistakes callouts, because they map directly to misreadings of the spec that I see most often.
Solution to Task 1 — Lifecycle phase mapping
Mapping the four MCP session phases to the JSON-RPC traffic you saw in Inspector during Lab 02:
| Phase | Methods / notifications on the wire |
|---|---|
| Connect | (no JSON-RPC; subprocess spawn for stdio, or socket dial for HTTP) |
| Initialize |
initialize (request/response, client→server then server→client),notifications/initialized (notification, client→server)
|
| Operate |
tools/list, tools/call,
resources/list, resources/read,
prompts/list, prompts/get;
possibly sampling/createMessage (server→client);
possibly notifications/progress, notifications/cancelled
|
| Close | (no JSON-RPC; pipe-closed for stdio, or socket-closed for HTTP) |
Two of the four phases (Connect and Close) carry no JSON-RPC at all — they are pure transport concerns. That is why the spec talks about phases of the session, not phases of the protocol: the protocol only has work to do in the middle two.
notifications/initialized in
Operate because the connection “feels ready” once the
initialize response has come back. Per the spec,
notifications/initialized is part of the Initialize phase — it is the
client's signal that the handshake is complete, and the server is not allowed to assume the
session is operational until it arrives.
Solution to Task 2 — Three hand-crafted requests
Each request is a complete JSON-RPC 2.0 envelope: jsonrpc, id,
method, params. I used IDs 100, 101,
102 — your own choice of non-null strings or numbers is equally valid as long as they
are unique within the session.
Request 1 — resources/list
{"jsonrpc":"2.0","id":100,"method":"resources/list","params":{}}
Expected response shape: a result object containing a
resources array, where each entry has at minimum a uri and a
name (and usually a mimeType and a human-readable
description). For the L02 filesystem server pointed at /tmp/mcp-demo,
you should see one entry per file in that directory, with URIs like
file:///tmp/mcp-demo/hello.txt.
Request 2 — resources/read
{"jsonrpc":"2.0","id":101,"method":"resources/read","params":{"uri":"file:///tmp/mcp-demo/hello.txt"}}
Expected response shape: a result object containing a
contents array. Each content block carries the source uri, a
mimeType, and the payload itself as either text (for textual MIME types)
or blob (base64-encoded, for binary). For hello.txt you should get a
single text block.
Request 3 — prompts/list
{"jsonrpc":"2.0","id":102,"method":"prompts/list","params":{}}
Expected response shape: depends on capability negotiation. If the
server's initialize response advertised a prompts capability, you get a
result with a prompts array. If it did not — and the official
filesystem server typically does not — you get an error object with code
-32601 ("Method not found"). That is the correct answer to
discover, not a homework failure. The whole point is to read the server's
capabilities object before calling and predict the outcome.
resources/read without the uri
parameter (or misspelling it as url) — the server returns
-32602 Invalid params. Read the error message: JSON-RPC errors are descriptive,
and the data field usually tells you exactly which parameter is missing or wrong.
Solution to Task 3 — Transport choice
Worked answer for each scenario, with the deciding factor called out explicitly:
- Scenario 1 — dev tool exposing a folder to Cursor: stdio. The client (Cursor) spawns the server as a subprocess on the same machine and owns its lifecycle entirely — no networking surface, no authentication required, and restarts are clean because killing the client tears down the server with it. Stdio is purpose-built for exactly this single-user, single-machine, single-consumer shape.
-
Scenario 2 — team-shared Slack search at
mcp.acme.internal: Streamable HTTP. A remote service with multiple concurrent consumers needs a single addressable endpoint over the network, and HTTP is the right substrate for that — it gives you a natural place to bolt onAuthorizationheaders, OAuth bearer tokens, TLS, and load balancing without inventing any of it yourself. Stdio cannot serve multiple remote clients, so it is simply not a candidate here.