prerequisite
HTTP basics
Requests and responses, methods, headers, status codes, and streamed responses: the parts of HTTP a remote MCP server uses.
Before this
Nothing beyond first-year college math. This is a starting page.
Why you need this
A local MCP server talks to its host through pipes. A remote server, one running on another machine, talks over HTTP, the same protocol your browser uses to load web pages. Every remote MCP message is an HTTP request, and every answer is an HTTP response, sometimes one that arrives in pieces. To read a remote server's logs, debug a failed connection, or understand what a load balancer can see, you need to read HTTP at the level of its actual text.
The idea
HTTP is a request and response protocol. A client opens a network connection to a server, sends one request, and gets back one response. That is the whole conversation.
URLs
A URL names where to send a request. Its parts:
https://api.example.com:8443/mcp?units=metric
| Part | Here | Meaning |
|---|---|---|
| scheme | https |
Which protocol; https is HTTP over an encrypted connection |
| host | api.example.com |
Which machine |
| port | 8443 |
Which program on that machine; defaults to 443 for https, 80 for http |
| path | /mcp |
Which thing on that server |
| query | units=metric |
Extra name=value pairs, after ? |
Methods
The request's method says what kind of action it is. GET asks for something and carries no body. POST sends data in a body for the server to act on. There are others (PUT, DELETE, PATCH), but MCP uses POST for every message.
Requests and responses as text
An HTTP/1.1 message is text with a fixed layout:
- A start line. For a request: method, path, version (
POST /mcp HTTP/1.1). For a response: version, status code, reason (HTTP/1.1 200 OK). - Headers, one per line, each
Name: value. Header names ignore upper and lower case. - One blank line, which marks the end of the headers.
- An optional body: the actual data.
Lines end with two characters, carriage return and line feed, written \r\n in code.
A few headers matter everywhere:
| Header | Sent by | Meaning |
|---|---|---|
Host |
client | Which site, since one machine can host many |
Content-Type |
both | The body's format, such as application/json |
Content-Length |
both | The body's size in bytes, so the reader knows where it ends |
Accept |
client | Which formats the client can read back |
Authorization |
client | Credentials, such as Bearer plus a token (see OAuth basics) |
Applications may add their own headers. MCP adds MCP-Protocol-Version, Mcp-Method, and Mcp-Name, which Transports explains.
Status codes
The three-digit status code says how it went. The first digit is the family:
| Family | Meaning | Examples |
|---|---|---|
| 2xx | Success | 200 OK; 202 Accepted (received, nothing to send back) |
| 3xx | Look elsewhere | 307 Temporary Redirect |
| 4xx | The client's request was wrong | 400 Bad Request (malformed or invalid), 401 Unauthorized (no valid credentials: log in), 403 Forbidden (credentials fine, but not allowed), 404 Not Found (nothing at that path), 405 Method Not Allowed (path exists, wrong method) |
| 5xx | The server failed | 500 Internal Server Error, 503 Service Unavailable |
The difference between 401 and 403 trips people up: 401 means "I don't know who you are", 403 means "I know who you are, and no".
Streaming with server-sent events
Usually a response has a Content-Length and arrives whole. Sometimes the server wants to send several things over time on one response: progress updates, then a final answer. Server-sent events (SSE) is a simple format for that. The response has Content-Type: text/event-stream, stays open, and carries events, each a few field: value lines ended by a blank line. The data: field holds the payload. Since the server does not know the total length in advance, HTTP/1.1 sends such a body in chunks, each preceded by its size in hexadecimal, ending with a chunk of size 0.
Here is part of a real stream, from the server in Transports, reporting progress before its answer:
HTTP/1.1 200 OK
date: Fri, 02 Oct 2026 21:35:32 GMT
server: uvicorn
content-type: text/event-stream
cache-control: no-cache, no-transform
x-accel-buffering: no
Transfer-Encoding: chunked
Connection: close
84
event: message
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"p1","progress":1,"total":2}}
...
0
84 is hexadecimal for 132, the length in bytes of the chunk that follows. The ... stands for a second progress event and the final result, each in its own chunk.
HTTP is stateless
Each request stands alone. The server is not required to remember anything about earlier requests from the same client, and with many copies of a server behind a load balancer (a front door that spreads requests across machines), consecutive requests may reach different copies. Anything that must carry over, such as who you are, travels in every request, usually in a header. The 2026-07-28 revision of MCP works the same way.
Worked example
This script writes one HTTP POST by hand, byte for byte, sends it to an MCP server listening on port 8765 of the same computer (the one built in Transports), and prints both directions.
# post_once.py: send one HTTP POST by hand and print exactly what goes each way.
import json
import socket
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "/mcp"
body = json.dumps({
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "add", "arguments": {"a": 2, "b": 3},
"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}},
},
}, separators=(",", ":")).encode("utf-8")
head = (
f"POST {path} HTTP/1.1\r\n"
"Host: 127.0.0.1:8765\r\n"
"Content-Type: application/json\r\n"
"Accept: application/json, text/event-stream\r\n"
"MCP-Protocol-Version: 2026-07-28\r\n"
"Mcp-Method: tools/call\r\n"
"Mcp-Name: add\r\n"
f"Content-Length: {len(body)}\r\n"
"Connection: close\r\n"
"\r\n"
).encode("ascii")
with socket.create_connection(("127.0.0.1", 8765)) as s:
s.sendall(head + body)
reply = b""
while chunk := s.recv(65536):
reply += chunk
print((head + body).decode())
print()
print(reply.decode())
python post_once.py printed the request:
POST /mcp HTTP/1.1
Host: 127.0.0.1:8765
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: add
Content-Length: 209
Connection: close
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add","arguments":{"a":2,"b":3},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}
and the response:
HTTP/1.1 200 OK
date: Fri, 02 Oct 2026 21:43:32 GMT
server: uvicorn
content-length: 224
content-type: application/json
Connection: close
{"jsonrpc":"2.0","id":1,"result":{"content":[{"text":"5","type":"text"}],"isError":false,"resultType":"complete","structuredContent":{"result":5},"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"adder","version":""}}}}
Read it part by part:
| Piece | Request | Response |
|---|---|---|
| Start line | POST /mcp HTTP/1.1: send data to path /mcp |
HTTP/1.1 200 OK: it worked |
| Body format | Content-Type: application/json |
content-type: application/json |
| Body size | Content-Length: 209 |
content-length: 224 |
| Blank line | ends the headers | ends the headers |
| Body | a JSON-RPC request to add 2 and 3 | a JSON-RPC result whose answer is 5 |
The 209 is the byte count of the body exactly as sent: the script computed it with len(body) after encoding. Get it wrong and the server either waits for bytes that never come or cuts the body short.
The same request to a path the server does not serve, python post_once.py /nope, came back:
HTTP/1.1 404 Not Found
date: Fri, 02 Oct 2026 21:43:29 GMT
server: uvicorn
content-length: 9
content-type: text/plain; charset=utf-8
Connection: close
Not Found
Note the HTTP status said 404 before the MCP layer ever saw the body. Errors can come from either layer, and you read the status code first.
In a server's life
- Build and connect (stage 3). The Streamable HTTP transport is one URL that accepts POST, answers with JSON or an SSE stream, and adds MCP headers; see Transports.
- Secure it (stage 4). Remote servers check an
Authorizationheader and answer 401 when it is missing; see OAuth basics and Authorization for remote servers. - Test and ship (stage 5). Statelessness is what lets a server run as many copies behind a load balancer; see Deploying remote servers.
Common mistakes
- Reading only the body. A tool returns nothing and you blame your code, when the status line said
401 Unauthorized. Check the status first. - Wrong
Content-Type. The same JSON body sent to the example server withContent-Type: text/plaincame back400 Bad Requestwith the bodyInvalid Content-Type header; the JSON was never parsed. - Missing the blank line. A hand-built request with no empty line after the headers makes the server wait for more headers until it times out.
- Confusing 401 and 403. Logging in again does not fix a 403; the account needs permission.
- A proxy that buffers streams. Progress events arrive all at once at the end. Streaming servers send
X-Accel-Buffering: no(visible in the stream above) to ask common proxies not to hold the response.
Cost
Each HTTP request pays for a network round trip, from under a millisecond on the same machine to a large fraction of a second across the world, plus headers on every request, a few hundred bytes in the example above. Opening a new encrypted connection costs extra round trips, which is why clients keep connections open and reuse them. An SSE stream holds one connection open for as long as the work takes, which matters when a server has thousands of clients at once.
Going further
- The HTTP Semantics standard, RFC 9110, for the full list of methods, headers, and status codes.
- HTTP/2, which sends the same requests and responses in a binary, multiplexed form.
- The server-sent events section of the HTML standard.
- Using
curl -vto watch the start lines and headers of any request.
Leads to
- prerequisiteOAuth basicsAccess tokens, scopes, the authorization code flow with PKCE, issuers and audiences: the vocabulary MCP authorization is built from.
- techniqueTransports: stdio and Streamable HTTPThe two ways messages travel: a local child process over stdin and stdout, or a remote endpoint over HTTP POST with optional streamed responses, plus long-lived subscriptions.