Understanding MCP Part 1: The Transport Layer

Like pretty much everyone else, I can't seem to escape seeing MCP everywhere. Also like alot of people, specifically the HN crowd, I was pretty skeptical in the beginning. With the added support of http streaming, OpenAI adoption and after playing around with it myself, I've started to come around.

I thought I would put together a brief primer on MCP for others looking to understand it better. I think the docs do a great job, but there were some things that didn't click for me right away. Hopefully this will help.

This piece will be focusing on the latest version of the MCP spec, ignoring backwards compatibility for now and mainly focusing on the HTTP transport not STDIO.

HTTP Streaming

For me the best way to understand MCP was to leverage something like charles to capture the traffic in order to actually understand what was going on. Unfortunately as I was writing this I couldn't find any clients that yet supported the latest version of the spec. Luckily MCP-Framework had just realeased an inspector tool that supported the latest version and I combined that with the Charles web debugging proxy to get a better understanding of what was going on.

Step 1: Initilization:

Request: 
POST /mcp HTTP/1.1
Host: localhost.charlesproxy.com:1337
Content-Length: 204
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
accept: application/json, text/event-stream
content-type: application/json
Origin: http://127.0.0.1:5173
Referer: http://127.0.0.1:5173/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"sampling":{},"roots":{"listChanged":true}},"clientInfo":{"name":"mcp-inspector","version":"0.7.2"}},"jsonrpc":"2.0","id":0}
Response: 
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID
Access-Control-Expose-Headers: Content-Type, Authorization, x-api-key, Mcp-Session-Id, Mcp-Session-Id
Access-Control-Allow-Credentials: true
Mcp-Session-Id: 222f2469-8d9e-43ba-bc9c-8ea5a2865382
Content-Type: application/json
Date: Wed, 02 Apr 2025 19:03:55 GMT
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Proxy-Connection: keep-alive
[{"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"enabled":true},"prompts":{"enabled":true}},"serverInfo":{"name":"weather-http-server","version":"0.0.1"}},"jsonrpc":"2.0","id":0}]

The key thing to note here is the Content-Type of the response. If the content type is text/event-stream then an SSE event stream will be initated if its application/json then the response will be a json object.

An SSE event stream will look like this:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID
Access-Control-Expose-Headers: Content-Type, Authorization, x-api-key, Mcp-Session-Id, Mcp-Session-Id
Access-Control-Allow-Credentials: true
Content-Type: text/event-stream
Cache-Control: no-cache
Mcp-Session-Id: b1f75815-1049-4895-bd64-78dcada962c2
Date: Wed, 02 Apr 2025 19:19:28 GMT
Transfer-Encoding: chunked
Proxy-Connection: keep-alive
: stream opened

data: {"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"enabled":true},"prompts":{"enabled":true}},"serverInfo":{"name":"weather-http-server","version":"0.0.1"}},"jsonrpc":"2.0","id":0}

: keep-alive

Step 3: Notifications/Initialized:

POST /mcp HTTP/1.1
Host: localhost.charlesproxy.com:1337
Content-Length: 54
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
accept: application/json, text/event-stream
content-type: application/json
mcp-session-id: 222f2469-8d9e-43ba-bc9c-8ea5a2865382
Origin: http://127.0.0.1:5173
Referer: http://127.0.0.1:5173/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
{"method":"notifications/initialized","jsonrpc":"2.0"}
HTTP/1.1 202 Accepted
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID
Access-Control-Expose-Headers: Content-Type, Authorization, x-api-key, Mcp-Session-Id, Mcp-Session-Id
Access-Control-Allow-Credentials: true
Date: Wed, 02 Apr 2025 19:03:55 GMT
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Proxy-Connection: keep-alive

Now the connection has been established and we can start doing more interesting things.

List tools

POST /mcp HTTP/1.1
Host: localhost.charlesproxy.com:1337
Content-Length: 58
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
accept: application/json, text/event-stream
content-type: application/json
mcp-session-id: 4f2b9597-772d-4e32-8771-eb8cad086c44
Origin: http://127.0.0.1:5173
Referer: http://127.0.0.1:5173/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
{"method":"tools/list","params":{},"jsonrpc":"2.0","id":1}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID
Access-Control-Expose-Headers: Content-Type, Authorization, x-api-key, Mcp-Session-Id, Mcp-Session-Id
Access-Control-Allow-Credentials: true
Content-Type: application/json
Date: Wed, 02 Apr 2025 19:09:18 GMT
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Proxy-Connection: keep-alive
[{"result":{"tools":[{"name":"example_tool","description":"An example tool that processes messages","inputSchema":{"type":"object","properties":{"message":{"type":"string","description":"Message to process"}}}}]},"jsonrpc":"2.0","id":1}]