What you will be able to do
- Build a stdio server that keeps stdout for protocol messages and sends logs to stderr
- Describe how Streamable HTTP carries requests, notifications and responses over one POST endpoint
- Secure a locally or remotely deployed HTTP server with Origin checks, localhost binding and authentication
- Add stdio, HTTP, SSE and WebSocket servers to Claude Code and diagnose their status
1.stdio: the server as a child process
The simplest transport runs the server as a child process of the client. The server reads JSON-RPC messages from stdin and writes JSON-RPC messages to stdout. Each message is a single request, notification or response, messages are separated by newlines, and a message must not contain embedded newlines. On stdout the server may send responses to client requests (matched by JSON-RPC id), notifications about an in-flight request such as notifications/progress, and notifications for an active subscriptions/listen request.
The key rule is that stdout belongs to the protocol. The server must not write anything to stdout that is not a valid MCP message, and the client must not write anything else to the server's stdin. Diagnostics go to stderr. The server may write UTF-8 logging there, including informational, debug and error messages. The client may capture, forward or ignore stderr, and should not treat output there as a sign of an error. Shutdown is also client-driven: the client closes the child process's input stream, waits for it to exit, and forcibly terminates it if it does not exit within a reasonable time.
That line is not a valid MCP message, so it breaks the rule that stdout carries only protocol messages. The client reads stdout as newline-delimited JSON-RPC and will get a line it cannot parse. Send the banner to stderr instead. The server may log there, and the client will not treat it as an error.
The stdio page also describes how a client works out which protocol generation a server speaks. If the server returns a DiscoverResult, it is modern, and the client picks a version from supportedVersions. If it returns a recognised error such as UnsupportedProtocolVersionError, it is still modern, and the client must choose one of the versions the server advertises rather than falling back. Any other error, or no response within a reasonable timeout, marks the server as legacy, and the client falls back to the initialize handshake.
Sources1
2.Streamable HTTP: one endpoint, one POST per message
For remote servers, the protocol uses Streamable HTTP. The server exposes a single HTTP endpoint, the MCP endpoint, which accepts POST. The client sends every JSON-RPC request or notification as its own POST, and the body is always a single request or notification, never a response. The client's Accept header must list both application/json and text/event-stream. If the body is a notification, the server returns 202 Accepted with no body, or an HTTP error status if it rejects it. If the body is a request, the server replies either with one JSON object or with a Server-Sent Events stream for that request. The stream may carry related notifications such as notifications/progress before the final response, which should end the stream.
The 2026-07-28 revision removes the GET stream endpoint and protocol-level sessions. The server must not send independent JSON-RPC requests on a response stream. Server-to-client interactions such as sampling, elicitation and list-roots are embedded as input requests inside an InputRequiredResult. This changes the 2025-03-26 through 2025-11-25 versions, where servers could send such requests on SSE streams. Long-lived change notifications arrive on the response stream of a subscriptions/listen request.
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weatherMcp-Method is required on every request. Mcp-Name, taken from params.name or params.uri, is required on tools/call, resources/read and prompts/get. A tool author can also mirror a parameter into a header by adding an x-mcp-header annotation to it in the inputSchema. For example, a region property annotated "Region" is sent as Mcp-Param-Region: us-west1. The annotation is allowed only on string, integer and boolean parameters (not number) that are reachable from the schema root through properties keys alone.
A team wants to connect their agent to a third-party analytics MCP server. The provider's documentation gives a hosted endpoint URL, `https://analytics.example.com/mcp`, rather than a local command to run. Which configuration best matches this integration pattern?
Correct answer: A — Set the server's "type" to "http" and provide the given URL
- A. Correct. A documented URL indicates a remote, cloud-hosted server, so the server should be configured with "type": "http" (or "sse") and the given URL.
- B. Incorrect. stdio is for local processes launched with a command; there is no local binary to run here.
- C. Incorrect. An SDK MCP server is for custom in-process tools you write yourself, not for connecting to an already-hosted third-party server.
- D. Incorrect. "command"/"args" configure a stdio server that spawns a local process; a URL cannot be passed as an argument to achieve HTTP connectivity.
Sources2
3.Deploying a server locally or remotely
A server can run as a local process or a remote service, and the transport follows from that choice. A stdio server is launched on the user's machine. An HTTP server listens on a network interface, which is where most deployment risk comes from. The Streamable HTTP rules cover three points. Servers must validate the Origin header on all incoming connections to prevent DNS rebinding attacks, and must respond with HTTP 403 Forbidden if the header is present and invalid. When running locally, servers should bind only to 127.0.0.1 rather than 0.0.0.0. And servers should implement proper authentication for every connection.
On the client side, Claude Code sends credentials to an authenticated remote server as HTTP headers that you set when you add the server:
# Example with Bearer token
claude mcp add --transport http secure-api https://api.example.com/mcp \
--header "Authorization: Bearer your-token"Sources2
4.Connecting a server to Claude Code
Claude Code is an MCP host. Once it is connected to your server, Claude can use it for tasks such as querying a database or implementing an issue from a tracker, and a server can even act as a channel that pushes messages into the session. The documentation shows how to identify what you have been given. A URL such as https://mcp.example.com/mcp means the server is remote. A launch command such as npx -y @example/mcp-server means it runs on your machine. An mcpServers JSON block is configuration written for another client's settings file.
# Basic syntax
claude mcp add [options] <name> -- <command> [args...]
# Real example: Add Airtable server
claude mcp add --env AIRTABLE_API_KEY=YOUR_KEY --transport stdio airtable \
-- npx -y airtable-mcp-server| Server shape | How to add it | Config type |
|---|---|---|
| Local launch command | claude mcp add --transport stdio <name> -- <command> [args...] | No type field; an entry without a type is read as stdio |
| Remote HTTP endpoint | claude mcp add --transport http <name> <url> | "type": "http" |
| Remote SSE endpoint | claude mcp add --transport sse <name> <url> | "type": "sse" |
| WebSocket endpoint | claude mcp add-json with a wss:// url | "type": "ws" |
When you paste an mcpServers block through claude mcp add-json, give every url entry an explicit type. Server names may contain only letters, numbers, hyphens and underscores. After adding servers, claude mcp list shows all configured servers, claude mcp get <name> shows details, claude mcp remove <name> removes one, and /mcp inside Claude Code shows status. Common states include: a project-scoped server from .mcp.json that is pending your approval, a server rejected by a disabledMcpjsonServers entry (shown only in claude mcp get), and a server disabled for the project through disabledMcpServers, which you can turn back on from /mcp. Claude Code also warns about hidden leading or trailing whitespace in config values, which often comes from a pasted token, and about the same server name defined in more than one scope with different endpoints.
For authoring, Claude Code offers a helper: install the mcp-server-dev plugin with /plugin install mcp-server-dev@claude-plugins-official, then run /mcp-server-dev:build-mcp-server. These sources cover connecting servers to Claude Code only. They do not describe how the Agent SDK loads or authenticates MCP servers, so that is outside this lesson.
A developer configures an MCP server named `github` that exposes a `list_issues` tool. To auto-approve only this specific tool, without granting access to any other tool on the server, which entry should be added to `allowedTools`?
Correct answer: A — mcp__github__list_issues
- A. Correct. MCP tool names follow the pattern mcp__{server_name}__{tool_name}, so this fully qualified name approves only list_issues from the github server.
- B. Incorrect. This omits the required "mcp__" prefix and does not match the naming convention Claude uses for MCP tools.
- C. Incorrect. This wildcard would approve every tool on the github server, not just list_issues.
- D. Incorrect. The server and tool segments are reversed and missing the "mcp__" prefix, so it will not match the actual tool name.
Sources3
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Writing debug output to stdout from a stdio server is harmless, because the client ignores lines it does not recognise.Why is that wrong?
stdout is reserved for protocol messages. Logs belong on stderr, which the client may capture, forward or ignore.
Covered in stdio: the server as a child process
2.A locally run HTTP MCP server should listen on 0.0.0.0 so every client on the machine can reach it.Why is that wrong?
Local servers should bind only to 127.0.0.1, validate the Origin header against DNS rebinding, and authenticate every connection.
Covered in Deploying a server locally or remotely
3.An mcpServers entry that has only a url field will be connected to Claude Code as a remote HTTP server.Why is that wrong?
Claude Code treats an entry with no type as stdio, so a url-only entry fails. Add "type": "http", "sse" or "ws" to match the endpoint.
Covered in Connecting a server to Claude Code
Practise it for real
Connect a remote MCP server to Claude Code, inspect it with the management commands, then remove it.
1.Run: claude mcp add --transport http notion https://mcp.notion.com/mcp
Why: The server is given as a URL, so it is remote and needs the http transport rather than a stdio launch command.
You should see: Claude Code records a server named notion.
2.Run: claude mcp list
Why: This lists every configured server along with any warnings, such as hidden whitespace or the same name defined in more than one scope.
You should see: notion appears among the configured servers.
3.Run: claude mcp get notion
Why: This shows details for one server, including states that claude mcp list does not show, such as a rejected .mcp.json server.
You should see: The notion entry is shown with its details.
4.Start claude and run /mcp
Why: The /mcp panel is where you check server status from inside Claude Code and re-enable disabled servers.
You should see: notion is listed in the panel with its current status.
5.Run: claude mcp remove notion
Why: Removing test servers keeps you from having the same name defined in several scopes with different endpoints later.
You should see: notion no longer appears in claude mcp list.
Stuck? Get a nudge
If the URL-based entry you paste through claude mcp add-json fails to connect, check whether it has a type field.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“The server reads JSON-RPC messages from stdin and writes JSON-RPC messages to stdout.”
↩︎ stdio: the server as a child process“Messages are delimited by newlines, and MUST NOT contain embedded newlines.”
↩︎ stdio: the server as a child process“SHOULD NOT assume stderr output indicates error conditions”
↩︎ stdio: the server as a child process“The server returns any other error, or does not respond within a reasonable timeout: the server is legacy.”
↩︎ stdio: the server as a child process“The server MUST NOT write anything to its stdout that is not a valid MCP message.”
↩︎ Exam trap 1 - 2.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.”
↩︎ Streamable HTTP: one endpoint, one POST per message“The server MUST NOT send independent JSON-RPC requests on this stream.”
↩︎ Streamable HTTP: one endpoint, one POST per message“Removal of protocol-level sessions.”
↩︎ Streamable HTTP: one endpoint, one POST per message“Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks.”
↩︎ Deploying a server locally or remotely“Servers SHOULD implement proper authentication for all connections.”
↩︎ Deploying a server locally or remotely“When running locally, servers SHOULD bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0).”
↩︎ Exam trap 2 - 3.https://code.claude.com/docs/en/mcpOfficial docs
“A launch command such as npx -y @example/mcp-server: the server runs on your machine.”
↩︎ Connecting a server to Claude Code“an MCP server can also act as a channel that pushes messages into your session”
↩︎ Connecting a server to Claude Code“Pending approval (run `claude` to approve): a project-scoped server from .mcp.json”
↩︎ Connecting a server to Claude Code“Claude Code reads an entry with no type as a stdio server, so a url entry without a type fails.”
↩︎ Exam trap 3