What you will be able to do
- Describe the roles of host, client, and server in the MCP architecture
- Explain how a server advertises its features through capability declaration
- Define a tool with a valid inputSchema and return text or structured results
- Expose resources and resource templates, and secure file:// resources
- Publish parameterised prompts and return the correct error codes
- Choose between the stdio and Streamable HTTP communication patterns and keep a stdio server's stdout clean
Key concept
MCP server primitives — An MCP server is a small, focused program that offers three kinds of capability to a host application: tools the model can call, resources that supply context, and prompts that act as reusable message templates. The host keeps control of the conversation, and the server only answers the requests it receives.
1.Host, client, and server
MCP involves three roles, and it helps to keep them apart. The host is the application the user works in, such as Claude Code or your own agent. It creates and manages several client instances, enforces security policies and consent, handles authorization decisions, and coordinates the LLM integration. Each client lives inside the host and holds exactly one connection to one server. It attaches the protocol version and capabilities to every request, routes messages in both directions, and keeps servers isolated from each other. The server exposes tools, resources, and prompts, and it can run as a local process or as a remote service.
The spec gives a few design principles for this split. Servers should be extremely easy to build, because the host does the complex orchestration. They should be composable, so several focused servers can be combined. And they should not be able to read the whole conversation or see into other servers. A server receives only the context it needs, and the full conversation history stays with the host.
Checkpoint 1 of 5· Match them up
Match each responsibility to the MCP role that owns it
Tap a term, then the definition that fits it.
The host orchestrates and owns consent. Each client is a one-to-one connection to a single server. The server only provides primitives.
“Communicates with exactly one server”Source: modelcontextprotocol.io
Sources1
2.Declaring capabilities
Features are negotiated, not assumed. A server declares capabilities such as tool support, resource subscriptions, and prompt templates. A client declares its own capabilities, such as support for sampling and elicitation. Both sides must then respect what was declared for the rest of the interaction. In practice, any feature your server implements has to be advertised in its capabilities. A server that registers tool handlers but never declares tools cannot have those tools invoked.
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}With listChanged: true, the server is promising to send notifications/tools/list_changed when its tool set changes, so the client knows to fetch the list again. Resources and prompts use the same pattern under their own capability keys.
Checkpoint 2 of 5· Check yourself
A server implements a tools/call handler but leaves tools out of its capabilities. What does the spec say?
Capability negotiation is binding. A server that has not declared tool capabilities cannot have its tools invoked.
“Tool invocation requires the server to declare tool capabilities”Source: modelcontextprotocol.io
Capabilities describe what a server offers. The communication pattern describes how its messages travel, and the two are independent. The first pattern is stdio, used for local servers. The client launches the server as a child process, and the server reads JSON-RPC messages from stdin and writes JSON-RPC messages to stdout. Each message is a single JSON-RPC request, notification, or response. Messages are delimited by newlines and must not contain embedded newlines. Claude Code connects this way, as in claude mcp add --transport stdio airtable -- npx -y airtable-mcp-server. There the command after -- is the process Claude Code spawns. An SDK server does the same by connecting a StdioServerTransport.
Because stdout carries the protocol, a stdio server must never print anything else to it, and the client must never send anything to stdin that is not a valid MCP message. A stray print or console.log corrupts the stream. Log to stderr instead, which the spec allows for any logging purposes. The client may capture, forward, or ignore that output, and it should not assume stderr output means an error. To shut down, the client closes the input stream to the child process and waits for the server to exit. If the server does not exit in a reasonable time, the client forcibly terminates it.
The second pattern is Streamable HTTP, used for remote servers. Here the server is a standalone service that exposes a single HTTP endpoint accepting POST, and the client sends every JSON-RPC request or notification as its own POST. The server answers with either one JSON object or an SSE stream. Claude Code reaches such a server with claude mcp add --transport http <name> <url>. The older sse transport and a ws WebSocket entry are also accepted in Claude Code configuration. In both patterns the client opens the connection and the server answers requests. The difference is whether the client starts the server process (stdio) or connects to one that is already running (HTTP).
3.Tools: model-callable functions
A tool definition has a unique name, an optional display title, a description, an inputSchema, an optional outputSchema, and optional annotations and icons. Tool names should be 1–128 characters, are treated as case-sensitive, should use only ASCII letters, digits, _, -, and ., and should be unique within a server. getUser, DATA_EXPORT_v2 and admin.tools.list are all valid names.
inputSchema defaults to JSON Schema 2020-12 when no $schema field is present. For a tool with no parameters, the recommended schema is { "type": "object", "additionalProperties": false }. Clients find tools with tools/list, which is paginated with a cursor, and run them with tools/call, passing name and arguments. A result carries a content array, which can hold text, image, audio, resource_link, or embedded resource items, plus an isError flag. If you declare an outputSchema, the server must also return structuredContent that conforms to it.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}Some tools create state that later calls refer to by handle, for example a create_basket call that returns a basket_id. For these tools, state the retention policy in the creation tool's description. When a call uses an expired or unknown handle, return a tool execution error so the model can recover. On the host side, the spec expects a human in the loop: the UI should make exposed tools visible, show when a tool is invoked, and ask the user to confirm operations.
Checkpoint 3 of 5· Fill the gap
Which method name completes this request to run a tool?
{
"jsonrpc": "2.0",
"id": 2,
"method": " ? ",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}Clients discover tools with tools/list and run one with tools/call, passing name and arguments.
Source: modelcontextprotocol.ioSources6
4.Resources: context identified by URI
A resource is data identified by a uri. It also has a name and, optionally, a title, description, mimeType, size, and icons. Clients list resources with resources/list and fetch them with resources/read. Each item in the returned contents holds either text or base64 blob data. For parameterised access, resources/templates/list returns URI templates such as file:///{path}. Annotations tell the client how to use a resource: audience (user and/or assistant), priority from 0.0 to 1.0, and lastModified.
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///project/src/main.rs"
}
}The resources capability has two optional flags. listChanged means the server will send a notification when the set of resources changes. subscribe means it supports update notifications for individual resources requested through subscriptions/listen. Security falls on the server. It must validate every resource URI, must encode binary data properly, should apply access controls to sensitive resources, and must sanitize file paths when serving file:// resources. If a resource does not exist, the server returns error -32602 with the URI in data.
Checkpoint 4 of 5· Check yourself
Your server exposes project files through the template file:///{path}. Which requirement does the spec make specifically for this?
Serving file:// resources requires path sanitization so that a crafted path cannot escape the intended directory. The other options are optional features or are wrong.
“Servers MUST sanitize file paths to prevent directory traversal attacks when serving file:// resources”Source: modelcontextprotocol.io
Sources7
5.Prompts: reusable message templates
A prompt is a named template with optional arguments, each of which has a name, a description, and a required flag. A client lists prompts with prompts/list and fills one in with prompts/get. The server returns a list of messages. Each message has a role of user or assistant and content that can be text, image, audio, a resource link, or an embedded resource.
{
"jsonrpc": "2.0",
"id": 2,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"code": "def hello():\n print('world')"
}
}
}Servers should validate prompt arguments before processing them. An unknown prompt name or a missing required argument returns -32602 (Invalid params), and internal failures return -32603. The table below puts the three primitives side by side.
| Primitive | Discover | Use | Change notification |
|---|---|---|---|
| Tools | tools/list | tools/call | notifications/tools/list_changed |
| Resources | resources/list, resources/templates/list | resources/read | notifications/resources/list_changed, notifications/resources/updated |
| Prompts | prompts/list | prompts/get | notifications/prompts/list_changed |
Checkpoint 5 of 5· Check yourself
A client calls prompts/get for code_review but leaves out the required code argument. Which error should the server return?
Missing required arguments and invalid prompt names both map to -32602. Code -32603 is reserved for internal server errors.
“Missing required arguments: -32602 (Invalid params)”Source: modelcontextprotocol.io
Sources8
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A tool with no parameters can set inputSchema to null or leave it out.Why is that wrong?
inputSchema must always be a valid JSON Schema object. For a tool with no parameters, the recommended form is an object type with additionalProperties: false.
Covered in Tools: model-callable functions
2.Implementing a handler is enough for clients to use a feature, so capability declaration is optional.Why is that wrong?
Server features must be advertised in the server's capabilities, and tool invocation specifically requires the tools capability to be declared.
Covered in Declaring capabilities
3.A stdio server can print debug output to stdout, since the client simply ignores text it does not recognise.Why is that wrong?
stdout carries the protocol, so anything that is not a valid MCP message breaks the stream. Logging belongs on stderr.
Covered in Declaring capabilities
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Full conversation history stays with the host”
↩︎ Host, client, and server“Can be local processes or remote services”
↩︎ Host, client, and server“Servers declare capabilities like tool support, resource subscriptions, and prompt templates”
↩︎ Declaring capabilities“Expose resources, tools and prompts via MCP primitives”
↩︎ Key concept“Tool invocation requires the server to declare tool capabilities”
↩︎ Exam trap 2“Communicates with exactly one server”
↩︎ Checkpoint“Tool invocation requires the server to declare tool capabilities”
↩︎ Checkpoint - 2.
“The server reads JSON-RPC messages from stdin and writes JSON-RPC messages to stdout.”
↩︎ Declaring capabilities“Messages are delimited by newlines, and MUST NOT contain embedded newlines.”
↩︎ Declaring capabilities“The server MAY write UTF-8 strings to stderr for any logging purposes including informational, debug, and error messages.”
↩︎ Declaring capabilities“Closing the input stream to the child process (the server).”
↩︎ Declaring capabilities“The server MUST NOT write anything to its stdout that is not a valid MCP message.”
↩︎ Exam trap 3 - 3.https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-httpOfficial docs
“The server exposes a single HTTP endpoint (the MCP endpoint) that accepts POST.”
↩︎ Declaring capabilities - 4.https://code.claude.com/docs/en/channels-referenceOfficial docs
“Stdio connection: connects to Claude Code over stdin/stdout. This is standard for any MCP server.”
↩︎ Declaring capabilities - 5.https://code.claude.com/docs/en/mcpOfficial docs
“claude mcp add --transport http <name> <url>”
↩︎ Declaring capabilities - 6.
“Servers MUST provide structured results that conform to this schema.”
↩︎ Tools: model-callable functions“should return a tool execution error that says so, so the model can recover by creating a new one”
↩︎ Tools: model-callable functions“Present confirmation prompts to the user for operations, to ensure a human is in the loop”
↩︎ Tools: model-callable functions“MUST be a valid JSON Schema object (not null)”
↩︎ Exam trap 1“MUST be a valid JSON Schema object (not null)”
↩︎ Prediction - 7.
“whether the server supports resource-specific update notifications for resources requested through subscriptions/listen using the resourceSubscriptions filter”
↩︎ Resources: context identified by URI“uri: Unique identifier for the resource”
↩︎ Resources: context identified by URI“Servers MUST sanitize file paths to prevent directory traversal attacks when serving file:// resources”
↩︎ Checkpoint - 8.
“arguments: Optional list of arguments for customization”
↩︎ Prompts: reusable message templates“Servers SHOULD validate prompt arguments before processing”
↩︎ Prompts: reusable message templates“Missing required arguments: -32602 (Invalid params)”
↩︎ Checkpoint