- The server reads JSON-RPC messages from
stdinand writes JSON-RPC messages tostdout. - Each message is a single JSON-RPC request, notification, or response.
- Messages are delimited by newlines, and MUST NOT contain embedded newlines.
- The server MAY write UTF-8 strings to
stderrfor any logging purposes including informational, debug, and error messages. - The client MAY capture, forward, or ignore the server’s
stderroutput and SHOULD NOT assumestderroutput indicates error conditions. - The server MUST NOT write anything to its
stdoutthat is not a valid MCP message. - The client MUST NOT write anything to the server’s
stdinthat is not a valid MCP message.
stderr, shutdown
by closing the stream, process restart) need channel-specific equivalents.
Sending Messages
The client sends messages by writing JSON-RPC requests and notifications to the server’sstdin, one message per line. The client MUST NOT write
JSON-RPC responses.
Receiving Messages
The client reads server messages fromstdout, one message per line. All
messages share this single channel; there are no per-request streams.
The server writes three kinds of messages:
- Responses to client requests, correlated by JSON-RPC
id. - Notifications that relate to an in-flight request, such as
notifications/progressandnotifications/message. - Notifications delivered for an active
subscriptions/listenrequest. Clients MUST correlate these using theio.modelcontextprotocol/subscriptionIdfield in_meta; seeSubscriptionsListenRequest.
stdout.
Server-to-client interactions are carried in
InputRequiredResult replies; see
Multi Round-Trip Requests.
Request Metadata
All request metadata for the stdio transport is carried inline in the JSON-RPC message body. The protocol version, client identity, and per-request capabilities live in_meta.io.modelcontextprotocol/*;
the method name and arguments live where JSON-RPC puts them. There is no
header layer.
Cancellation
To cancel an in-flight request, the client MUST send anotifications/cancelled notification referencing the request’s ID. Because
stdio is a single shared bidirectional channel, there is no per-request stream
to close. Servers SHOULD stop work on a cancelled request as soon as
practical and MUST NOT send any further messages for it. See
Cancellation for the full rules.
Shutdown
The client SHOULD initiate shutdown by:- Closing the input stream to the child process (the server).
- Waiting for the server to exit.
- If the server does not exit within a reasonable time, forcibly terminating the process using the mechanism appropriate for the operating system.
SIGTERM
to SIGKILL. On Windows, where POSIX signals are not available, clients can
use TerminateProcess
or Job Objects.
Servers SHOULD exit promptly when their standard input is closed or reads
return end-of-file. This is the primary graceful-shutdown signal and the only
portable one, so honoring it reduces the need for forced termination.
The server MAY initiate shutdown by closing its output stream to the
client and exiting.
Unexpected Termination
If the server process exits unexpectedly, the client SHOULD restart it. Because the protocol is stateless, any in-flight requests are simply lost and the client can retry them against the fresh process. Activesubscriptions/listen streams must also be
re-established after restart.
Backward Compatibility
A client that supports both modern (per-request-metadata) MCP versions and a legacy version that requires aninitialize handshake SHOULD probe with
server/discover before sending any other request,
setting its preferred modern version in _meta. The probe has three
possible outcomes:
- The server returns a
DiscoverResult: the server is modern. Select a mutually supported version fromsupportedVersionsand continue. - The server returns a recognized modern JSON-RPC error such as
UnsupportedProtocolVersionError: the server is modern but does not support the requested version. Use one of the versions in its advertisedsupportedlist. Do not fall back toinitialize. - The server returns any other error, or does not respond within a
reasonable timeout: the server is legacy. Fall back to the
initializehandshake.
initialize requests with implementation-defined
errors (commonly -32601 or -32602) or not at all.
A client that only supports modern versions does not need to probe, but
probing is still RECOMMENDED: some legacy servers do not validate that a
request arrives after initialize and would process an era-ambiguous method
(such as tools/call) under legacy semantics. Probing yields a
deterministic failure instead.
See Versioning: Backward Compatibility for the era model
and a compatibility matrix for implementors.