Chapter DMCP in codePage 3 of 8

MCP in code

Implement the happy path

Build one complete vertical slice: build an MCP client that connects to a local stdio server, discovers tools, and calls one read-only tool as a bounded, testable system whose behavior does not depend on trusting plausible model output.

~14 minHappy path

Before you start

Why this matters

Before coding, write the single observable result this page must add to the previous page. Then name one failure that the implementation must reject. For MCP in code, use the running project—not a toy explanation—as your test: an MCP client that connects to a local stdio server, discovers tools, and calls one read-only tool. Predict what the CLI, test, or HTTP client will observe when the page is complete.

1Learn the idea

Read

Build one complete vertical slice

Implement the narrowest successful flow first: load validated config, accept one known fixture, perform one external or local operation, validate the result, and print or return the domain artifact. Avoid retries and clever abstractions until this path is observable.

Keep orchestration code boring. Dependencies are passed in, network calls are isolated in an adapter, and domain logic remains testable without credentials. The example uses a placeholder base URL because providers differ; set it to the documented endpoint of the provider you actually use.

For this project, execute will use the current @modelcontextprotocol/sdk Client with a StdioClientTransport, then listTools and callTool. Use native fetch or the current official SDK, but isolate that choice in one file. Check HTTP status before parsing JSON, check the content type when appropriate, and parse the response into a capability snapshot and validated MCP tool result. The expected terminal observation is: The client discovers echo, calls it once, prints “hello”, and closes the transport.

Run the fixture from a tiny CLI and print a stable JSON summary rather than an entire provider response. A healthy run includes terminal status, elapsed milliseconds, and a safe result identifier. It must not print credentials, hidden instructions, or sensitive source content. Once this path works, commit the fixture conceptually as your regression oracle before adding retries.

Read

Implementation

export async function run(input: unknown, deps: Dependencies) {
  const started = performance.now();
  const validated = deps.parseInput(input);
  const raw = await deps.execute(validated); // use the current @modelcontextprotocol/sdk Client with a StdioClientTransport, then listTools and callTool
  const value = deps.parseOutput(raw);
  return { value, durationMs: Math.round(performance.now() - started) };
}

The code is intentionally provider-neutral at the adapter boundary. AI_BASE_URL and model names are environment placeholders, not invented services. If the topic uses MCP, install the current official SDK and verify its exported paths against its release documentation. If a provider offers an official SDK, it can replace fetch inside execute without changing the domain contract. Always inspect the real provider’s documented request and response fields before connecting credentials.

Read

Debug the boundary

Debug from deterministic code outward. First print the parsed configuration with secret values replaced by [set]. Next run the parser against a local fixture. Then run the orchestration with a fake dependency. Only after those pass should you inspect the real boundary. Capture status, content type, request ID, elapsed time, and a redacted response shape. Do not “fix” malformed data with a permissive cast; save it as a failing fixture and decide whether the contract or adapter is wrong.

For this chapter, the fastest diagnostic is to assert that the client trusts only configured servers and intersects discovered tools with a local allowlist. When that assertion fails, stop before downstream work. Expected behavior is concrete: The client discovers echo, calls it once, prints “hello”, and closes the transport. A different but plausible sentence is not enough if the structural and policy checks fail.

Read

Verification notes

  • One fixture runs end to end.
  • The external response is validated before domain use.
  • Expected output and terminal state are visible.
  • Run npm run typecheck and npm test without provider credentials.
  • Record expected terminal behavior beside the fixture so future changes remain reviewable.

A passing test is necessary but does not establish production quality. Review the fixture set for realistic distributions, stale assumptions, tenant boundaries, and expensive edge cases. When outputs are probabilistic, assert schemas, citations, chosen capabilities, stop reasons, and task rubric scores. Reserve exact string assertions for deterministic adapters and protocol framing.

Checking tutor…

Continue learning · glossary & guides
  • Can you point to the executable check proving that the client trusts only configured servers and intersects discovered tools with a local allowlist?
  • Does malformed or unauthorized data stop before external or privileged work?
  • Is the observable success criterion exactly this clear: The client discovers echo, calls it once, prints “hello”, and closes the transport.
  • Glossary: tool · Glossary: structured output · Cheatsheet: production ops signals
  • Previous · Next