The full walkthrough is
BUILD_FROM_SCRATCH.md
in the repo. This is the shape of it, and the two things that actually broke.
Prerequisites
Node, npm, a GitHub account, a Vercel account. No API keys β a server costs nothing to run and needs nothing to run it.
Scaffold the Next.js app
Checkpoint
npm run devserves the Next.js starter.Install the MCP packages
The route handler skeleton
The whole server is one file:
app/api/mcp/route.ts.Checkpoint
Knock on the door and get an answer:
bashcurl -X POST http://localhost:3000/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Your first tool
roll_dice, for the reason on The four tools: the model cannot fake it, so you can tell whether the tool actually ran.Read the response properly
The reply may be plain JSON or Server-Sent Events, depending on how the handler feels. Both are legal. A client that assumes one gets a confusing parse error against a server that chose the other β which is exactly what project #2 has to deal with when it writes a client by hand.
Three more tools, three more concepts
say_hellofor the minimum,cookie_jarfor state and refusal,secret_codefor a thing models are bad at.A resource and a prompt
Checkpoint
bashmcp '{"jsonrpc":"2.0","id":3,"method":"resources/list"}' mcp '{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"cookiejar://status"}}'Build a client in the browser
A tiny MCP client in the page β the other half of the protocol, so you can watch the exact JSON going back and forth rather than trusting a description of it.
The landing page, then typecheck and build
bashnpx tsc --noEmit && npx eslint . && npm run buildGit, GitHub, and deploy via Vercel
Vercel is wired to the repo, so shipping a new tool is a push.
Checkpoint
Verify against production, not localhost. Same curl, different hostname:
bashcurl -s -X POST https://YOUR-APP.vercel.app/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Connect it to Claude
bashclaude mcp add --transport http cookie-jar https://learn-mcp-5-year-old.vercel.app/api/mcpThen say "roll me three twenty-sided dice" and watch it reach into the jar.
How a change reaches the internet
The two gotchas worth carrying forward
Both of these bit again in later projects, which is the test of whether a gotcha is worth writing down.
| # | Gotcha | Bit again in |
|---|---|---|
| 1 | create-next-app refuses to scaffold into a non-empty folder | #2, #3, #4, #5 β every single one |
| 2 | mcp-handler peer-depends on @modelcontextprotocol/server, not β¦/sdk | #5 |
The other two from project #1's appendix β the 406 from a missing Accept
header, and the cookie jar forgetting β are covered on The vending
machine and The
sandcastle. All four, with causes and fixes, are
in the compendium.