Lifecycle and capabilities
Explain MCP roles and lifecycle and trace a tool-discovery and tool-call exchange under a pinned protocol version.
THE IDEA
Lifecycle and capabilities
Under revision 2026-07-28, MCP is stateless: there is no initialize handshake. Every request carries the protocol version and the client's capabilities in its _meta field, and the server accepts or rejects each request on its own. An unsupported version gets error -32022 listing the versions the server supports; a client may call server/discover first to learn them. Older revisions open with an initialize exchange instead, so always check which revision a server speaks. The messages are JSON-RPC 2.0.
Revision 2026-07-28 is stateless: there is no initialize handshake. Each request carries its own protocol version and client capabilities in _meta, and the server accepts or rejects it on its own. This is a teaching simulator of that behaviour; real servers add more.
Predict first. Under revision 2026-07-28, what must every request carry so the server needs no earlier state?
Chapter context
MCP defines a protocol for applications to connect models with external capabilities. It standardizes exchanges; it does not grant a server universal authority or make every discovered tool appropriate for every user request.
TRY IT YOURSELF
Live lab
Protocol revision 2026-07-28 is stateless: there is no initialize handshake. Every request carries its protocol version and client capabilities in _meta, and the server accepts or rejects each one on its own.
TRY IT YOURSELF
Versions and capabilities
Protocol revision 2026-07-28 is stateless: there is no initialize handshake. Every request carries its protocol version and client capabilities in _meta, and the server accepts or rejects each one on its own.
Discover the server, trigger the unsupported-version error, retry successfully, and see what a request without _meta gets.
This simulated server speaks protocol revision 2026-07-28. Edit the JSON freely, then send it.
- Call server/discover and read the supported versions
- Send a wrong protocol version and read the -32022 error
- Retry with the supported version and succeed
- Send a request without _meta and read the -32602 error
Exercises
Part 1. Do it hands-on
Match errors to codes by hand, then write the request builder and the envelope checker.
Revision 2026-07-28 is stateless: there is no initialize handshake. Each request carries its own protocol version and client capabilities in _meta, and the server accepts or rejects it on its own. This is a teaching simulator of that behaviour; real servers add more.
- To do: A request carries protocolVersion "1900-01-01", which the demo server does not support. The JSON-RPC error code?
- To do: A request has no _meta at all. The demo server answers with which code?
- To do: The request text is not valid JSON. Code?
- To do: In revision 2026-07-28 must the server remember an earlier handshake to handle a request? Type yes or no
Part 2. Do it in code
- make_request(id, method, params=None, version=VERSION, caps=None): the request dict: jsonrpc "2.0", the id, the method, and params that include `_meta` with the protocol version and client capabilities (keys VKEY and CKEY; caps defaults to {}). Extra params are kept.
- error_response(id, code, message, data=None): {"jsonrpc": "2.0", "id": id, "error": {"code", "message"[, "data"]}}.
- check_envelope(text): validate a request text and return the error response dict, or the parsed request if it is fine. Not JSON → code -32700 (id None). Not an object, jsonrpc not "2.0" or method not a string → -32600. A method named "initialize" → -32601 (the demo server speaks the stateless revision). _meta missing the version or the capabilities (the capabilities must be an object) → -32602 with the missing keys named in the message. A version other than VERSION → -32022 with data {"supported": [VERSION], "requested": <what was sent>}.
- handle(text): run check_envelope; if it returned an error, return it. For server/discover return {"jsonrpc", "id", "result": {"resultType": "complete", "supportedVersions": [VERSION], "capabilities": {"tools": {}}}}; any other method → -32601. A request without an id is a notification: return None.
Both the live lab and the exercises must be solved to finish this lesson.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Under revision 2026-07-28, what must every request carry so the server needs no earlier state?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
The MCP specification revision examined here is 2026-07-28. Request envelopes use JSON-RPC 2.0, an ID for response correlation, a method, and method-specific parameters that include _meta with the required io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities. A successful result carries resultType, here "complete". Do not treat this date as a claim about every installed client or SDK.
Worked example
A tools/call request with id 7 names add, supplies a=2,b=3 and carries the protocol version and client capabilities in _meta. The server runs the permitted operation and answers with the same id and a content array holding the text 5. The offline example below checks envelope fields and arithmetic; a production server must also implement discovery, schema validation, errors and transport behavior.
Reference code
The following example uses the Python standard library and does not call a remote service.
meta={'io.modelcontextprotocol/protocolVersion':'2026-07-28','io.modelcontextprotocol/clientCapabilities':{}}
request={'jsonrpc':'2.0','id':7,'method':'tools/call','params':{'name':'add','arguments':{'a':2,'b':3},'_meta':meta}}
args=request['params']['arguments']
total=args['a']+args['b']
response={'jsonrpc':'2.0','id':request['id'],'result':{'resultType':'complete','content':[{'type':'text','text':str(total)}],'isError':False}}
assert request['method'] == 'tools/call'
assert request['params']['_meta']['io.modelcontextprotocol/protocolVersion'] == '2026-07-28'
assert response['id'] == 7
assert response['result']['content'][0]['text'] == '5'
assert response['result']['isError'] is False
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
MCP is not a language model or an automatic permission system. Resource, prompt and tool capabilities differ. Do not assume an initialize handshake: that belongs to revisions before 2026-07-28. Protocol versions and SDK releases must be matched when turning this draft into runnable setup instructions.