TimoBy Amotion AI

MCP Server Development: CCDV-F study guide

CCDV-F · Tools and MCPs, topic weight 2.1% of the exam

MCP Server Development is a topic in Domain 8, Tools and MCPs (10.6% of the CCDV-F exam), and carries 2.1% on its own. It tests whether you can build a Model Context Protocol server: choose between a tool, a resource and a prompt for each capability, pick the right transport, and deploy the server so Claude applications can connect to it. Configuring existing servers in Claude Code is covered in CCAR-F 2.4.

What the official guide covers

The Claude Certified Developer Foundations exam guide (version 1.0, effective July 2026) describes this topic as MCP server development practices:

What the guide listsWhat it means in practice
Server authoringWrite the server with an official MCP SDK (Python, TypeScript and others) and declare its tools, resources and prompts
DeploymentShip it as a local process the client launches over stdio, or as a remote service over Streamable HTTP
Integration with Claude applicationsConnect it from Claude Code, Claude Desktop or the Agent SDK
MCP resources, tools and promptsThe three building blocks a server offers, each controlled by a different party
Communication patterns (stdio, sockets, client vs server)Who starts whom, and how messages travel: standard streams, HTTP, or the same framing over a socket

Client, server and host

An MCP server is a program that exposes capabilities through the protocol. The host application, such as Claude Code, runs an MCP client for each server it connects to. The client sends JSON-RPC requests such as tools/list and tools/call; the server answers. The same server works with any MCP client, which is why one server can serve several Claude applications.

MCP does not change how Claude calls tools. The host lists the server's tools, hands them to Claude as ordinary tool definitions and routes each call to the server; the tool_use and tool_result loop is the same. What changes is who writes and maintains the tools.

The three building blocks

Building blockWho controls itProtocol methodsUse it for
ToolsThe model: Claude decides when to call themtools/list, tools/callActions and queries that take parameters
ResourcesThe application decides what to read and includeresources/list, resources/read, resources/templates/listRead-only context such as files, schemas and docs, each with a URI
PromptsThe user invokes them explicitlyprompts/list, prompts/getReusable templates for common workflows

Resources can be fixed (inventory://skus) or templates with parameters (inventory://sku/{sku}). In Claude Code, prompts appear as /mcp__<server>__<prompt> commands and resources are referenced with @<server>:<protocol>://<path>.

A server in Python

The current Python SDK (uv add "mcp[cli]") builds a server from type hints and docstrings with the MCPServer class. Older code imports FastMCP from mcp.server.fastmcp; the decorators work the same way.

import logging
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError

logging.basicConfig(level=logging.INFO)          # logging writes to stderr
logger = logging.getLogger(__name__)

mcp = MCPServer("inventory")
STOCK = {"SKU-100": 42, "SKU-200": 0}

@mcp.tool()
def check_stock(sku: str) -> str:
    """Return units in stock for one SKU. Use for availability questions about a
    single product. SKUs look like SKU-100; read inventory://skus for the list."""
    if sku not in STOCK:
        raise ToolError(f"Unknown SKU {sku!r}. Read inventory://skus for valid SKUs.")
    logger.info("stock check for %s", sku)
    return f"{sku}: {STOCK[sku]} units"

@mcp.resource("inventory://skus")
def list_skus() -> str:
    """Every SKU the warehouse tracks."""
    return "\n".join(STOCK)

@mcp.resource("inventory://sku/{sku}", mime_type="application/json")
def sku_detail(sku: str) -> dict:
    """Stock level for one SKU, as JSON."""
    return {"sku": sku, "units": STOCK.get(sku, 0)}

@mcp.prompt()
def restock_plan(sku: str) -> str:
    """Draft a restock plan for one SKU."""
    return f"Check stock for {sku} with check_stock, then draft a restock plan with quantities."

if __name__ == "__main__":
    mcp.run()   # stdio by default

The {sku} placeholder makes the second resource a template: the SDK passes the value from the URI to the function, and a return value that is not a string is converted to JSON. mime_type tells the client how to parse it.

Raising ToolError returns a result marked as an error, and Claude sees your message. Do not return an error string as a normal result, because Claude reads it as success. In TypeScript, the SDK's McpServer class registers tools with registerTool and a Zod input schema, and connects through StdioServerTransport.

stdio or Streamable HTTP

stdioStreamable HTTP
Who starts the serverThe client launches it as a subprocessIt runs on its own and can serve many clients
How messages travelNewline-delimited JSON-RPC on stdin and stdoutEach client message is an HTTP POST to one MCP endpoint, such as /mcp; the reply is one JSON object or an SSE stream for that request
Loggingstderr only; stdout must carry nothing but MCP messagesNormal logging is fine
SecurityRuns with the local user's permissionsValidate the Origin header, bind to 127.0.0.1 when local, and authenticate every connection
Good forPersonal tools, local files, wrapping a CLIShared team services and remote deployment

In the Python SDK, mcp.run(transport="streamable-http") serves the endpoint at /mcp on 127.0.0.1 port 8000 by default; transport options go to run(), not to the constructor. The older HTTP+SSE transport is deprecated; new servers should use Streamable HTTP.

The stdio framing (one JSON-RPC message per line) is not tied to standard streams. The specification says it works unchanged over Unix domain sockets or TCP, and custom transports over a byte stream should reuse it.

Test, deploy and connect

  1. Test with the MCP Inspector: uv run mcp dev server.py with the Python SDK's CLI, or npx @modelcontextprotocol/inspector python server.py, opens a browser client where you can list and call tools, read resources and get prompts.
  2. Connect a local server to Claude Code: claude mcp add --transport stdio inventory -- python /path/to/server.py. Everything after -- is the command that starts the server.
  3. Connect a remote server: claude mcp add --transport http inventory https://mcp.example.com/mcp.
  4. Use it from an agent: pass the server in the Agent SDK's mcp_servers option. Its tools appear as mcp__inventory__check_stock.

Where the configuration is stored, and how to keep tokens out of .mcp.json, is CCAR-F 2.4 territory. One deployment trap belongs here: a stdio server in a committed .mcp.json is only a launch command. Each teammate's Claude Code starts its own copy, so each machine needs the runtime and the server's dependencies, and each copy runs with that person's access. It looks shared but is not. A service the whole team should reach in one place runs over Streamable HTTP. In interactive sessions, Claude Code also asks each person to approve project-scoped servers before first use.

Know what each client supports

Not every MCP client uses all three building blocks. Claude Code offers resources through @ mentions and prompts as slash commands. The Claude API's MCP connector, which lets a Messages API request call a server directly, reaches only remote servers over HTTP, not stdio, and of the protocol's features supports only tool calls. If API applications will reach your server through the connector, anything they need must be a tool; resources and prompts need a client you run yourself with the MCP SDK. The connector's mcp_toolset can also enable only some of a server's tools, or defer loading their definitions.

The server is the privileged component

An MCP server usually holds the credential to the system behind it, so it is the part a misled agent would most like to reach. Scope that credential to the actions the tools need, not the account's full rights. Return only the fields Claude needs. Log each call with the caller and the result. Content the server returns from user-written records is untrusted data for whatever step reads it next.

Which design fits

SituationChooseWhy
Several Claude applications need the same capabilityAn MCP serverBuild once, maintain in one place, connect many
Claude must act or query with parametersA toolModel-controlled, takes input
The app should load context such as a schema without tool callsA resourceApplication-controlled, read-only
Users repeat the same multi-step requestA promptUser-invoked template
Only you use it, on your machinestdioNo network service to secure
A team shares it, or it runs in the cloudStreamable HTTP with authenticationOne service, many clients
API apps will call the server through the MCP connectorRemote HTTP server, capabilities exposed as toolsThe connector supports tool calls only
One Agent SDK app needs a function in-processThe SDK's in-process server (create_sdk_mcp_server)No separate process to deploy
Every developer in the organisation needs the serverStreamable HTTP, deployed through Claude Code's managed MCP configurationAdmins set it once for every user

Rules that decide exam answers

  • Never write to stdout in a stdio server. A stray print() corrupts the JSON-RPC stream. Log to stderr.
  • Match the building block to who controls it. Model: tool. Application: resource. User: prompt.
  • Remote means authenticate and check Origin. Local HTTP servers bind to localhost, not 0.0.0.0.
  • Report tool failures as error results. Raise the SDK's tool error; do not return an error string as success.
  • Build once, connect many. Reuse across applications is the main reason to choose a server over per-app tool code.
  • Check what the client supports. The API's MCP connector reaches remote servers only, and only their tools.

Where it appears in the exam

Tools and MCPs is Domain 8, 10.6% of the exam, and MCP Server Development is 2.1% of scored items, so expect about one question in a 53-item exam. The guide's own Domain 8 sample is about sharing one capability across several Claude applications. Expect servers that fail to connect, choices between tools, resources and prompts, and local versus remote deployment.

Two sample questions

These are original Timo practice questions. They are not official exam questions.

Question 1

A developer's stdio MCP server starts without errors, but Claude Code reports connection failures as soon as Claude calls one of its tools. The tool handler contains print(f"querying {sku}") for debugging. What is the cause?

Answer: B. In a stdio server, stdout carries only MCP messages, so any other output breaks the protocol. A mixes up the two transports. C is wrong because the SDK accepts plain functions. D would need an HTTP server, which this is not.

Question 2

A team is building an MCP server for its data warehouse. They want Claude to have the table schemas without spending tool calls exploring, and analysts to start a standard monthly report from a menu. Queries need parameters. Which design fits?

Answer: A. Each capability goes to the block whose controller matches: the application loads schemas, the user starts the report, and the model runs queries. B makes Claude spend calls on lookups. C bloats every tool description and moves the workflow outside the server. D swaps the roles of the two blocks.

Build exercise

  1. Install the Python SDK and save the inventory server above as server.py.
  2. Run it under the MCP Inspector. Call check_stock with a valid and an invalid SKU, read inventory://skus, and get the restock_plan prompt.
  3. Add it to Claude Code with claude mcp add --transport stdio. Ask a stock question, reference the resource with @, and run the prompt as a slash command. Then add a print() to the tool and watch the connection fail.
  4. Remove the print(), switch to mcp.run(transport="streamable-http"), and connect with claude mcp add --transport http inventory http://127.0.0.1:8000/mcp.

Practise this topic

Sources