Most MCP posts explain that a server connects an AI assistant to tools. That is true, but it skips the engineering question that matters in Claude Code: what exactly is the agent allowed to discover, what is it allowed to execute, and what must stay behind a human review boundary?

The pguso/mcp-from-scratch repository is useful because it does not hide those boundaries inside an SDK. It walks through JSON-RPC, stdio framing, lifecycle state, tools/list, tools/call, resources, prompts, notifications, sampling, and agent loops in plain Node.js. Read it as a protocol inspection lab, not as a production template.

The useful part is the boundary map

The repository's root README says modules 01-10 avoid npm dependencies, with only a root package.json entry for ESM. That detail matters because it removes the usual SDK layer between the reader and the protocol. Module 02 shows JSON-RPC request, response, error, and notification shapes; module 03 moves those messages over stdio; module 04 adds the initialize handshake and READY state; modules 05 and 06 split tools/list from tools/call. For a Claude Code user, that sequence is the real article: discovery is not execution, a listed tool is not yet a trusted tool, and a READY session is not the same thing as a safe session. A normal README summary would say the repo teaches MCP; the stronger use is to turn its module order into a review checklist.

Run lifecycle before tool invocation

Start with git clone https://github.com/pguso/mcp-from-scratch.git, then run the lifecycle client from the repo root with node 04-lifecycle/src/client.js. The run file expects a premature echo call to be rejected before initialize, then an initialize response with protocolVersion, serverInfo, and capabilities, then READY after notifications/initialized. That makes a good Claude Code gate because an MCP server that accepts ordinary calls before the handshake is not just buggy; it is blurring session state before tool policy can apply. After that, run the tools milestone, such as node 06-tools-call/src/client.js, and compare the result with 06-tools-call/run-local.md. If the server can answer tools/list but tools/call hangs, returns unbounded output, or collapses JSON-RPC errors into successful tool results, keep it out of a project-scoped Claude Code config.

Use a five-check audit path

The audit path is short enough to run before a server enters .mcp.json. First, clone the repo and confirm you can run from the repository root, because several modules rely on sibling paths. Second, run node 04-lifecycle/src/client.js and mark the server failed if pre-initialize calls are accepted or READY is reached without notifications/initialized. Third, run tools/list through the module 05 or 06 client and reject vague tool names, missing inputSchema fields, or descriptions that hide side effects. Fourth, run tools/call with a harmless tool and record whether errors are returned as JSON-RPC errors or MCP tool results with isError semantics. Fifth, only then translate the command into Claude Code with claude mcp add --transport stdio <name> -- <command> [args...], check /mcp, and keep the server pending until the command, env vars, output bounds, and external network behavior are reviewed.

Translate the lab into Claude Code settings

Claude Code gives you operational controls after the protocol audit passes. Use claude mcp add --transport stdio <name> -- <command> [args...] for a local server, then inspect it with claude mcp list, claude mcp get <name>, and /mcp inside Claude Code. For a team-shared server, prefer project scope and commit only the command shape, safe args, and documented env placeholders; keep personal tokens out of the repository. For a one-machine experiment, use local or user scope so an unfinished server does not become team policy. The advanced check is to compare the Claude Code config against the lab output: the command must start the same server you tested, the server must expose the same tool names you reviewed, and the output budget must be small enough that one MCP result cannot flood the context.

Treat discovery and execution differently

MCP makes tools discoverable through tools/list, then callable through tools/call. Those two protocol methods deserve different policy in Claude Code. Discovery should be cheap and read-only: name, description, inputSchema, and enough metadata for the model to decide whether a call is plausible. Execution is where filesystem access, API calls, database writes, and long outputs can leak into the session. Claude Code's MCP docs put this into operational terms with claude mcp add, .mcp.json, local/user/project scopes, the /mcp status panel, and pending approval for project-scoped servers. The adoption rule is simple: let Claude Code discover narrowly named tools, but require separate review for the command, env vars, output limit, and failure behavior behind each call.

Use stdio for local trust, not remote trust

The repo's early modules use stdio, which is the right first transport for a local learning server because the process communicates through stdin and stdout instead of opening a network surface. Claude Code's docs also describe local stdio servers and note that the spawned server receives CLAUDE_PROJECT_DIR, which is useful for project-relative behavior but also a permission boundary to review. The mistake would be to read a successful local stdio lab as proof that the same server is ready for remote HTTP usage. Remote MCP needs authentication, scoped credentials, request logging, rate limits, and prompt-injection review when the server fetches external content. Skip this path for production if the only thing you have tested is a local node .../src/client.js demo.

When to use the repo and when to skip it

Use mcp-from-scratch when you are about to install or write an MCP server and you cannot explain the lifecycle, registry, and result-shaping path without hand-waving. It is also useful when a Claude Code workflow keeps failing around tool names, schema mismatch, or calls that hang, because the repo gives you small modules to isolate each layer. Skip it when the task is just to connect a known server and move on; official SDKs and Claude Code's claude mcp add path are faster for that. Also skip copying the repo as a production base unless you add the parts it explicitly is not trying to teach: auth, audit logs, permissions, retries, and release discipline. The best outcome is not a from-scratch server in production; it is a sharper review of the server you were already planning to trust.

Use mcp-from-scratch as a protocol audit lab before giving Claude Code new tools. Do not treat it as a production shortcut.

Practical takeaway

Clone the repo, run node 04-lifecycle/src/client.js, run the tools-call module, then write down three separate decisions: what tools/list may reveal, what tools/call may execute, and what output or external data should stop the tool before it reaches Claude Code. Convert the server into .mcp.json or claude mcp add --transport stdio ... only after claude mcp list, claude mcp get <name>, and /mcp show the expected server, reviewed tool names, safe env handling, prompt-injection surface, and output limits.