Home › Study guides › CCDV-F › Domain 8 › Lesson 8.2
CCDV-F · Domain 8 · 10.6% of the exam · Lesson 8.2 · 21 min read
Building an MCP server and connecting it to Claude
How an MCP server exposes tools, resources and prompts, when to use stdio or Streamable HTTP, and how Claude Code, Desktop and the API connect to it.
Written against skill 8.2 of the official CCDV-F exam guide (Version 1.0, effective July 2026). An independent resource, not affiliated with Anthropic; the practice questions are written from scratch.
8.2.1 Why the same integration keeps getting built twice
Picture the HR department at Corvell, a food manufacturer with 1,400 staff. Two teams want Claude to answer the same questions: "How much parental leave do I get?" and "How many holiday days does employee E1042 have left?" Beatriz's HR tools team works in Claude Code and wants those answers while writing HR scripts. Kofi is adding a help assistant to the employee portal, a web app built on the Claude API. Both need the HR policy handbook and a live leave-balance lookup.
The obvious first move is for each team to write its own tool code: Kofi defines a get_leave_balance tool that calls the HR database, and Beatriz wires up something similar for Claude Code. Now one integration lives in two places, with two copies of the credentials. A bug fixed in one copy quietly survives in the other, and the third team that asks makes it three.
The Model Context Protocol (MCP) removes the copies. It is an open standard for connecting AI applications to external systems: you write the integration once, as an MCP server, and every application that speaks MCP can connect to it. The MCP documentation compares it to a USB-C port: one standard port instead of a custom cable per device. Corvell builds one HR server, both applications plug into it, and the HR tools team maintains it on its own schedule.
One integration per app, or one server for all
Without MCP
With MCP
8.2.2 Host, client and server: who talks to whom
Here is the question that trips people up: when Claude "uses an MCP server", who actually connects to it? Not Claude. The model never opens a connection, holds a credential or runs code. MCP splits the work between three roles.
- MCP host. The AI application the person works in: Claude Code, Claude Desktop, or an application you build on the Claude API.
- MCP client. A component inside the host that keeps one dedicated connection to one server. A host connected to three servers runs three clients.
- MCP server. The program you write. It exposes capabilities to clients and never talks to the model directly.
Client and server exchange JSON-RPC 2.0 messages, a small standard format for requests and responses. The client discovers what the server offers (tools/list, resources/list, prompts/list), and the host hands the tool definitions to Claude. When Claude asks for get_leave_balance, the host routes the request to the right client, which sends tools/call. The server queries the HR system, and the host puts the answer back into the conversation.
Think of a hotel concierge. The guest (Claude) says what they need: a table for two at eight. The concierge (the host) keeps a direct line (a client) to each restaurant (a server), places the call and relays the answer. The guest never phones the restaurant, and the restaurant never sees the guest's room key.
The path of one MCP tool call
get_leave_balancetools/call to its serverThis split explains a classic failure. Suppose Kofi's first attempt only adds "use the HR server at https://..." to the system prompt. No tool call ever appears, because nothing in that request is an MCP client. Something on the application side must play that role: the API's MCP connector, or a client in Kofi's own code.
8.2.3 Tools, resources and prompts: who decides
An MCP server can expose three kinds of thing, called primitives. They are easy to mix up, so learn them by one question: who decides to use it?
| Primitive | Who decides to use it | What it is | In the HR server |
|---|---|---|---|
| Tool | The model | A function Claude can ask to run, with a JSON Schema for its inputs; it may change things | get_leave_balance(employee_id) |
| Resource | The application | Read-only data at a URI, which the host loads into Claude's context | policy://leave, the leave section of the handbook |
| Prompt | The user | A reusable message template a person runs by name, often as a slash command | leave_summary, a draft reply about one employee's balance |
Walk through the HR server with that question. The leave balance is a TOOL: Claude is the one who realises, mid-conversation, that it needs E1042's balance, so the model must be able to ask for it. A lookup is still a tool when the model chooses when and with which arguments. The handbook sections are RESOURCES: each has a URI, an address such as policy://leave, and the application or its user decides which to attach. In Claude Code, Beatriz references one with an @ mention, @hr:policy://leave, the way she would attach a file. The drafting template is a PROMPT: it should run only when a person asks for it.
Two rules keep the mapping honest. Only tools are callable: Claude cannot invoke a resource or a prompt as an action, whatever the system prompt says. And a resource read must never change anything; a "read" that also approves a request is a tool in disguise, firing whenever the host loads context.
8.2.4 Writing the server with an official SDK
Nobody writes the JSON-RPC messages by hand. The official MCP software development kits (SDKs), for TypeScript, Python, C#, Go and other languages, handle discovery, message formats and transports, so you write ordinary functions. In the Python SDK the server class is MCPServer; tutorials written for earlier protocol revisions import a class called FastMCP instead, so follow the SDK docs for the version you install. Each primitive is one decorator on a plain function. Look at the three decorators and the last line: the decorators register a tool, a resource and a prompt, and run() chooses the transport. hr_system and handbook stand for your own code that reaches the HR database and the handbook store.
from mcp.server import MCPServer
mcp = MCPServer("hr")
@mcp.tool() # TOOL: Claude decides when to call it
def get_leave_balance(employee_id: str) -> str:
"""Days of annual leave an employee has left this year."""
return hr_system.leave_days(employee_id)
@mcp.resource("policy://leave") # RESOURCE: the app or the user attaches it
def leave_policy() -> str:
"""The leave section of the HR handbook: annual, parental and sick leave."""
return handbook.section("leave")
@mcp.prompt() # PROMPT: a person runs it by name
def leave_summary(employee_id: str) -> str:
"""Draft a reply about an employee's leave balance."""
return f"Check {employee_id}'s leave balance and the leave policy, then draft a reply."
if __name__ == "__main__":
mcp.run(transport="stdio") # local; "streamable-http" serves it remotely
The SDK reads everything else from the function itself. The function name becomes the tool name, the docstring becomes the description Claude reads, and the type hints become the input schema, so employee_id: str turns into a required string field. A URI with a placeholder, such as policy://{section}, makes one function a resource template for every section. Writing descriptions Claude chooses well from is a craft of its own.
Test the server before any Claude application sees it. The MCP Inspector connects to it the way a host would, with a tab each for tools, resources and prompts: npx @modelcontextprotocol/inspector uv run server.py launches your server and prints a link to open in the browser. Whatever is wrong in the Inspector will be wrong in every host.
8.2.5 Transports and deployment: stdio, HTTP and sockets
The transport is how the messages travel between client and server. It is the choice that separates a tool on one laptop from a service the whole company uses.
| Transport | How the messages travel | When to use it |
|---|---|---|
| stdio | The host launches the server as a child process and exchanges one JSON-RPC message per line over its standard input and output | A local server for one user, started by Claude Code or Claude Desktop on that machine |
| Streamable HTTP | The server runs as a service at one HTTP endpoint, such as /mcp; each client message is an HTTP POST |
A shared, remote server that many clients reach over the network, with HTTP authentication |
| SSE (Server-Sent Events) | The older HTTP transport | Deprecated: recognise it in existing setups, build new servers on Streamable HTTP |
Memorise the first two rows, local child process versus remote service; recognise SSE only as the deprecated predecessor of Streamable HTTP.
Where do sockets come in? stdio uses the pipes of a child process on the same machine; Streamable HTTP runs over a network socket. The specification defines only those two but allows custom transports, and one over a Unix socket or a TCP connection should reuse the stdio format of one JSON message per line. Claude Code also accepts WebSocket servers, a persistent two-way connection for servers that push events unprompted. Whatever the channel, the client connects and sends requests, and the server answers.
For Corvell, the choice follows from who needs the server. While Beatriz develops it, Claude Code starts it over stdio, and the server reads test credentials from environment variables. In production it must be shared, so the team runs it as a long-lived Streamable HTTP service at an address such as https://hr-mcp.corvell.example/mcp, changing one argument: mcp.run(transport="streamable-http"). A stdio process lives only as long as the session that launched it, so a back end that started one per request would pay the start-up cost every time. And since its 2026-07-28 revision, MCP is stateless: every request carries its protocol version and capabilities, so whichever copy of the server receives it can answer.
Deployment raises the security questions, and they all land on the server. It keeps the HR system's credentials in its own environment or a secrets store, never in a client or prompt. Clients prove who they are to the server with standard HTTP authentication; MCP recommends OAuth for obtaining the tokens. The server then checks what each caller may see: HR staff may look up any employee, an employee only their own balance. A system prompt saying "only show users their own data" is a request; a check in the server is a rule.
8.2.6 Connecting it to Claude Code, Claude Desktop and the API
Now the payoff: one HR server, three Claude applications, each playing host and client in its own way.
| Claude application | How you connect the server | Transports it can use | What Claude gets |
|---|---|---|---|
| Claude Code | claude mcp add, or a .mcp.json file committed to the project |
stdio, HTTP, WebSocket (SSE deprecated) | Tools, resources by @ mention, prompts as / commands |
| Claude Desktop | claude_desktop_config.json for a local server; a custom connector for a remote one |
stdio locally, remote by URL | Tools, resources and prompts |
| Claude API (MCP connector) | mcp_servers plus an mcp_toolset in tools, with a beta header |
Streamable HTTP or SSE, at a public https:// URL |
Tools only |
In Claude Code, claude mcp add --transport http hr https://hr-mcp.corvell.example/mcp connects the remote server, and claude mcp add hr -- uv run server.py launches a local stdio one. The --scope flag decides who gets it: local (the default, one project, just you), user (all your projects) or project, which writes .mcp.json at the repository root for the team to commit. Keep secrets out of that file with ${VAR} references, expanded from each developer's environment. In a session, /mcp shows each server's status and runs OAuth sign-in, and Claude sees the tool as mcp__hr__get_leave_balance.
Claude Desktop reads local servers from the mcpServers key of claude_desktop_config.json: the command and args that launch each one, with absolute paths, picked up after a restart. A remote server is added as a custom connector instead.
Kofi's portal has no built-in client, so the MCP connector does the client's job: the Messages API connects to a remote MCP server for you. Look at the two parameters that work as a pair: mcp_servers says where the server is and how to authenticate, and the mcp_toolset entry in tools switches on its tools.
response = client.beta.messages.create(
model=MODEL,
max_tokens=1024,
messages=[{"role": "user", "content": "How many leave days do I have left?"}],
mcp_servers=[{
"type": "url",
"url": "https://hr-mcp.corvell.example/mcp", # public HTTPS, not a local process
"name": "hr",
"authorization_token": employee_oauth_token, # obtained by the portal, not the model
}],
tools=[{"type": "mcp_toolset", "mcp_server_name": "hr"}], # enable this server's tools
betas=["mcp-client-2025-11-20"],
)
The portal runs the OAuth flow itself and passes the token, from which the server reads the employee's identity. The response carries mcp_tool_use and mcp_tool_result blocks: the call already happened, with no tool loop in Kofi's code. Two limits decide whether the connector fits. It supports only tools, not resources or prompts. And the server must be publicly reachable at an https:// URL, over Streamable HTTP or SSE, because Anthropic's API makes the connection; it cannot reach a local stdio server. For anything else, the portal would run its own MCP client with an MCP SDK; the Anthropic SDKs include helpers that convert its tools, prompts and resources into API formats.
8.2.7 The exam traps
Almost every mistake here is one of two things: the capability sits in the wrong place, or nobody is playing the client.
- ✗ Copying the same tool code into every application that needs it. ✓ Build one MCP server, maintained on its own, that every application connects to. Copies drift; a server is fixed once.
- ✗ Naming the server's URL in a system prompt and expecting Claude to use it. ✓ Give the application an MCP client: the connector's
mcp_serversplus anmcp_toolset, a host's configuration, or your own client. Claude connects to nothing. - ✗ Exposing everything as a tool, or letting a resource read change state. ✓ Map each capability to who triggers it: tools for model-invoked actions and lookups, resources for read-only context, prompts for user-invoked templates.
- ✗ Putting backend credentials in prompts, tool arguments or a committed
.mcp.json. ✓ The server holds its credentials and checks each caller's rights; clients authenticate with tokens, and shared configuration uses${VAR}references. - ✗ Pointing the API's MCP connector at a local stdio server, or expecting resources and prompts through it. ✓ Give the connector a remote Streamable HTTP server at a public HTTPS URL, and expect tools only; run your own MCP client for more.
- ✗ Debugging a missing MCP tool by rewording the prompt or changing the model. ✓ Check the connection first: does the client connect over the configured transport, and does discovery list the operation as a tool?
Four fixes aimed at the wrong layer, one that works
8.2.8 Put it together: build the HR server and break it
You now have every piece: the three roles, the three primitives, an SDK-built server, its transports and how each Claude application connects. To make it stick, build a small version of Corvell's server and break its connection on purpose.
One question remains, and agentic customization (8.3) answers it: when is an MCP server the right mechanism at all, compared with built-in tools, custom tools and Skills?
Key takeaways
- ✓ An MCP server packages a system's tools, data and prompts once, so every MCP-capable Claude application can use it instead of carrying its own copy.
- ✓ The host is the AI application; it runs one MCP client per server, and the client, never Claude, connects, discovers capabilities and makes the calls.
- ✓ Tools are model-controlled, resources are application-controlled read-only data at a URI, and prompts are user-invoked templates; only tools are actions Claude can call.
- ✓ An official SDK turns plain functions into primitives from their names, docstrings and type hints; test with the MCP Inspector, and keep standard output clean in a stdio server.
- ✓ stdio suits a local server launched by the host for one user, Streamable HTTP suits a shared remote service, and SSE is deprecated; Claude Code can also use WebSocket servers.
- ✓ The server holds the backend credentials and enforces per-user access, while clients authenticate to it with tokens, OAuth being the recommended way for remote servers.
- ✓ Claude Code and Claude Desktop connect through their own configuration, while the API's MCP connector reaches only public HTTPS servers and only their tools.
Check your understanding
4 questions written for this lesson, then one from the CCDV-F question bank on the same topic. Every answer option is explained, including the ones you did not pick. Nothing is stored.
18 CCDV-F questions on Domain 8, free
Every question in the bank is tagged to a domain, so you can drill 18 questions on Tools and MCPs alone, or sit the full 53-question timed simulator.
Open the CCDV-F question bank → Back to Domain 8 →
The question bank is free. It asks for an account only because the quiz engine has to store answers to score them and show which domains are weak. The questions on this page need nothing.