Claude Certification Guides
CCAR-F/Domain 29 min read

Lesson 2.4

Building and Configuring MCP Servers

How to implement an MCP server with the official SDKs, run it over stdio or HTTP, test it, and expose it to clients safely.

Building an MCP server is mostly ordinary programming: you write functions, describe them, and let an SDK handle the protocol. The architecture decisions sit around the edges: which transport, how credentials reach the server, how results are shaped for a model, and how the server is tested before a host depends on it. The exam tests the SDK patterns and these surrounding decisions rather than protocol internals.

A minimal server in Python

The official Python SDK ships FastMCP, which turns decorated functions into tools, resources and prompts. Type hints and docstrings become the schema and description.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("inventory")

@mcp.tool()
def get_stock(sku: str, warehouse: str = "main") -> str:
    """Return current stock level for a SKU. Call when the user asks
    whether an item is available or how many are left."""
    qty = lookup(sku, warehouse)
    return f"{sku}: {qty} units in {warehouse}"

@mcp.resource("inventory://warehouses")
def warehouses() -> str:
    """List of warehouse codes, one per line."""
    return "\n".join(all_warehouse_codes())

@mcp.prompt()
def stock_report(region: str) -> str:
    return f"Produce a stock summary for region {region} using get_stock."

if __name__ == "__main__":
    mcp.run(transport="stdio")

The docstring is the tool description the model will read, so write it with the same care as any tool description: what it does, when to call it, what it returns. Because this is a stdio server, nothing may be printed to stdout; use logging configured to stderr.

The same server in TypeScript

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "inventory", version: "1.0.0" });

server.registerTool("get_stock",
  { description: "Return current stock level for a SKU. Call when the user asks about availability.",
    inputSchema: { sku: z.string(), warehouse: z.string().default("main") } },
  async ({ sku, warehouse }) => ({
    content: [{ type: "text", text: `${sku}: ${await lookup(sku, warehouse)} units` }],
  }));

await server.connect(new StdioServerTransport());

Zod schemas generate the JSON Schema. Handlers return a content array (text, images or embedded resources) and may set isError: true to report a failure the model should read.

Choosing and switching transport

The code above runs over stdio. Switching to Streamable HTTP means mounting the server in an HTTP framework and swapping the transport object; the tool code does not change. Decide by deployment:

  • stdio when the server runs on the same machine as the host, needs local filesystem access, or is distributed as a package the host launches (npx, uvx).
  • Streamable HTTP when multiple users or hosts share one server, when the server sits behind a gateway, or when OAuth is needed.

Stateless HTTP mode, where each request is independent, scales horizontally and suits serverless deployment; stateful mode keeps per-session state and supports server-initiated notifications.

Credentials and configuration

Servers should read secrets from the environment, never from tool arguments. For stdio servers the host passes environment variables in its configuration; Claude Code's .mcp.json supports ${VAR} expansion so that the committed file contains a placeholder rather than the secret. Remote servers use OAuth or a bearer token supplied by the host. Keep the server's own permissions minimal: a read-only database user for a reporting server, a scoped token for a GitHub server.

Shaping results for a model

Return what the model needs and no more. Convert large JSON to a compact table or summary, paginate lists with an explicit "more available" marker, and include units and timestamps. Hosts cap the size of tool results (Claude Code, for example, warns and truncates very large outputs), so a server that returns a megabyte of rows will be cut off regardless. Declare an outputSchema when consumers need structured results, and return structuredContent alongside the text.

Testing

The MCP Inspector is a browser tool that launches or connects to a server, lists its primitives, and lets you call tools with arbitrary arguments and see raw messages. Use it before wiring the server into a host; it isolates server bugs from host configuration bugs. Add unit tests for handler functions independent of the protocol, and an integration test that spins up the server over stdio and performs initialize and tools/list.

Common defects

SymptomLikely cause
Host reports the server failed to startPrinting to stdout in a stdio server; wrong command or missing dependency
Model never calls the toolDescription lacks trigger conditions; tool name too generic
Model passes malformed argumentsMissing property descriptions or types; no enum for closed sets
Tool errors surface as crashesExceptions escaping instead of isError: true results
Works locally, fails remotelyAuth not configured; HTTP transport expecting a session the client did not create

Exam focus

  • FastMCP (Python) and McpServer (TypeScript) turn annotated functions into tools; docstrings and descriptions become what the model reads.
  • A stdio server must keep stdout clean; all logging goes to stderr.
  • Tool code is transport-independent; choose stdio for local single-user servers and Streamable HTTP for shared or remote ones.
  • Secrets reach servers via environment variables or OAuth, never via tool arguments; .mcp.json supports ${VAR} placeholders.
  • Return concise, model-friendly results; hosts truncate oversized tool output.
  • Test with the MCP Inspector before configuring the server in a host, to separate server bugs from configuration bugs.

Check yourself

5 questions

  1. 1.A developer runs: claude mcp add myserver npx -y my-mcp-server --port 3000. Claude Code complains about an unknown option --port. What is the fix?

  2. 2.Where is a project-scoped MCP server configuration stored so that it can be shared with the team through version control?

  3. 3.A reviewer finds an Authorization header with a live bearer token hard-coded in a committed .mcp.json. Which design is correct?

  4. 4.A tool handler inside an MCP server encounters a downstream failure. How should it report the problem so the model can recover?

  5. 5.Before wiring a newly written MCP server into Claude, a developer wants to list its tools and call them interactively to verify behaviour. Which tool is designed for this?