{
 "name": "MCP transport: the GET and HEAD leg",
 "what": "Every request shape the pathwren MCP endpoints accept and what each returns: GET and HEAD return the server card, HEAD mirrors GET, and a stream-open GET gets a real, empty, immediately-closed SSE stream carrying the one call that needs nothing from the caller.",
 "url": "https://www.pathwren.workers.dev/mcp-transport.json",
 "twin_of": "https://www.pathwren.workers.dev/mcp-transport.html",
 "page": {
  "path": "/mcp-transport.html",
  "url": "https://www.pathwren.workers.dev/mcp-transport.html",
  "type": "text/html"
 },
 "generated_at": "2026-09-14T09:49:20+00:00",
 "generated_from": "the bytes of /mcp-transport.html, by surfaces/ai-crawler-index/build.py, in the same pass that wrote the page — one source, so the page and this document cannot disagree about what this host says.",
 "license": {
  "document": "CC0-1.0",
  "url": "https://creativecommons.org/publicdomain/zero/1.0/"
 },
 "access": {
  "api_key": "none",
  "account": "none",
  "rate_limit": "none",
  "cors": "*",
  "auth": "none — every document here is a public GET"
 },
 "commands": [
  "# check any row of that table yourself",
  "curl -si https://www.pathwren.workers.dev/mcp/doctor -H 'accept: application/json' | head -1        # 200",
  "curl -sI https://www.pathwren.workers.dev/mcp/doctor                                                # 200, no body",
  "curl -si https://www.pathwren.workers.dev/mcp/doctor -H 'accept: text/event-stream' | head -1       # 200, and the stream says what to POST",
  "curl -s https://www.pathwren.workers.dev/mcp/doctor -H 'accept: text/event-stream'"
 ],
 "sections": [
  {
   "heading": "MCP transport: the GET and HEAD leg",
   "text": [
    "Every request shape our three MCP endpoints can be sent, what it returns, and why. Applies identically to /mcp, /mcp/triage, /mcp/doctor and to their /c/<channel>/ copies. Measured live, not asserted."
   ],
   "commands": [
    "# check any row of that table yourself",
    "curl -si https://www.pathwren.workers.dev/mcp/doctor -H 'accept: application/json' | head -1        # 200",
    "curl -sI https://www.pathwren.workers.dev/mcp/doctor                                                # 200, no body",
    "curl -si https://www.pathwren.workers.dev/mcp/doctor -H 'accept: text/event-stream' | head -1       # 200, and the stream says what to POST"
   ],
   "tables": [
    {
     "headers": [
      "Request",
      "Status",
      "Body"
     ],
     "rows": [
      [
       "POST JSON-RPC",
       "200",
       "The JSON-RPC result. This is the transport; everything else on this page is discovery."
      ],
      [
       "GET accept: application/json",
       "200",
       "The server card: name, version, protocol versions, capabilities, tools, catalogue."
      ],
      [
       "GET accept: application/json, text/event-stream;q=0.9",
       "200",
       "The same card. JSON is ranked above the stream, so JSON is what you get — RFC 9110 §12.5.1."
      ],
      [
       "GET no accept header",
       "200",
       "The same card. No Accept means anything is acceptable."
      ],
      [
       "HEAD (any of the above)",
       "mirrors the GET",
       "Nothing. Same status, same content-type, same allow, and content-length of the body the GET would have sent."
      ],
      [
       "GET or HEAD accept: text/event-stream",
       "200 text/event-stream",
       "A real stream: valid, empty, closed immediately, with the one call that needs nothing from you written into its comment lines. See below."
      ],
      [
       "OPTIONS",
       "204",
       "CORS preflight; every origin is allowed."
      ],
      [
       "PUT, PATCH, anything else",
       "405",
       "allow: GET, HEAD, POST, DELETE, OPTIONS — and every method in that list works."
      ]
     ]
    }
   ],
   "links": []
  },
  {
   "heading": "What a stream-open GET returns, and why that changed",
   "text": [
    "A GET that ranks text/event-stream at or above everything else is asking to open a standalone server-to-client SSE stream. Until 2026-09-03 we answered 405 Method Not Allowed. We now answer 200 with a real, empty, immediately-closed stream. Both are correct, and the change is worth writing down because the page previously argued the opposite case in full.",
    "The specification allows either, and says so in one sentence.",
    "“The server MUST either return Content-Type: text/event-stream in response to this HTTP GET, or else return HTTP 405 Method Not Allowed.” (2025-06-18.) The 405 clause this page used to quote — “HTTP GET or DELETE to the MCP endpoint: respond with 405 Method Not Allowed” — is addressed to “a server that supports ONLY this revision”, and this host also speaks 2025-06-18 and 2025-03-26, where the stream exists. Both answers conform; the choice was ours.",
    "What decided it was what the 405 was the first thing a client ever saw.",
    "In the 24 hours to 2026-09-03T00:18Z these endpoints answered 16 stream-open GETs with 405 across 5 client keys — every one of them arriving on the registry's own channel path, /c/mcp-registry-official/…. Three were that client's opening request to the endpoint (node, 08:52:59 on /mcp/robots, 09:04:41 on /mcp/doctor and /mcp/triage) and none of those three ever came back. A refusal read in context is an answer; a refusal read as the first row of a scan is a record that this endpoint refused.",
    "The old third argument was right, and is why the stream closes at once.",
    "These servers are stateless: every JSON-RPC reply is returned inside the POST that asked for it, so a stream held open would carry nothing for as long as the client held it. So we do not hold it open. The stream sets a 24-hour retry: — no client reconnect-loops on it — writes the one call that needs nothing from the caller into SSE comment lines, which every parser ignores and every log keeps, and closes. No client waits for a message that is never coming.",
    "What this page said before was that “every client that has met this 405 here went on to finish its session over POST anyway.” That was true of the log it was written from and stopped being true: in the window above, undici recovered every time and node did not once. The revert condition is written into the code beside the change — if notifications/initialized per initialize does not improve and GET rows start looping, the 405 document goes back, and it is kept whole in the source for exactly that."
   ],
   "commands": [],
   "tables": [],
   "links": []
  },
  {
   "heading": "What the stream says",
   "text": [
    "Comments, and nothing else — a client SDK sees a valid empty stream and no JSON-RPC frame it never asked for, while anything that reads bytes gets the complete first call:"
   ],
   "commands": [
    "curl -s https://www.pathwren.workers.dev/mcp/doctor -H 'accept: text/event-stream'"
   ],
   "tables": [],
   "links": []
  },
  {
   "heading": "HEAD is GET without the body",
   "text": [
    "Not “HEAD returns 200” — mirrors. The endpoint builds the real GET response and removes the body, so HEAD is still correct after whatever GET does next, including the stream above: HEAD with accept: text/event-stream returns that 200 with no body. RFC 9110 §9.3.2.",
    "Until 2026-09-01 HEAD fell through to the unsupported-method branch and returned 405 on every one of these endpoints, which cost at least one real client three failed probes before anybody owned the row."
   ],
   "commands": [],
   "tables": [],
   "links": []
  },
  {
   "heading": "The Allow header never contradicts the response",
   "text": [
    "A 405 carrying allow: GET, … on a refused GET tells a reader that the header and the behaviour disagree, and it is the first thing an automated grader checks. So:",
    "On a refused method — PUT and friends — allow is the full set GET, HEAD, POST, DELETE, OPTIONS, and every method in it works.",
    "On the stream refusal, which is a refusal of a representation rather than of a method, allow is POST, OPTIONS: what works for a client sending that Accept header. The complete method set is still in the body under allow_any_accept, and vary: accept marks the response as negotiated, so nothing is hidden by the narrower header."
   ],
   "commands": [],
   "tables": [],
   "links": []
  },
  {
   "heading": "Protocol versions, said plainly",
   "text": [
    "These servers accept 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05 for version negotiation on POST, and POST answers all of them. The mechanisms the older revisions added around the POST — the standalone GET stream, Mcp-Session-Id sessions, Last-Event-ID resumption — are not implemented, and per 2026-07-28 a session id is ignored rather than minted or echoed. If your client needs the deprecated 2024-11-05 HTTP+SSE transport specifically, it will not work here, and the 405 says so rather than letting you discover it after a handshake.",
    "The six servers: ai-crawler-index, crawler-log-triage, agent-discovery-doctor, robots-policy-lint, crawler-ip-verifier, mcp-endpoint-lint. Specification: modelcontextprotocol.io."
   ],
   "commands": [],
   "tables": [],
   "links": [
    "/mcp.html",
    "/mcp-triage.html",
    "/mcp-doctor.html",
    "/mcp-robots.html",
    "/mcp-netcheck.html",
    "/mcp-lint.html",
    "https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http"
   ]
  }
 ],
 "machine_doors": [
  {
   "url": "https://www.pathwren.workers.dev/tools/?s=client-dossiers",
   "name": "6 keyless GET tools",
   "what": "The read-only MCP tools of this host as plain GET endpoints — no JSON-RPC, no key"
  },
  {
   "url": "https://www.pathwren.workers.dev/documents.json",
   "name": "documents.json",
   "what": "Every document here with its strong ETag and the date its bytes changed"
  },
  {
   "url": "https://www.pathwren.workers.dev/changes",
   "name": "changes",
   "what": "What moved since your cursor — poll this instead of re-downloading anything"
  },
  {
   "url": "https://www.pathwren.workers.dev/llms.txt",
   "name": "llms.txt",
   "what": "The whole map in one text file"
  },
  {
   "url": "https://www.pathwren.workers.dev/openapi.json",
   "name": "openapi.json",
   "what": "Every read endpoint, described formally"
  },
  {
   "url": "https://www.pathwren.workers.dev/.well-known/agent-card.json",
   "name": "agent card",
   "what": "A2A agent card"
  },
  {
   "url": "https://www.pathwren.workers.dev/mcp",
   "name": "mcp",
   "what": "MCP over JSON-RPC (POST)"
  },
  {
   "url": "https://www.pathwren.workers.dev/a2a",
   "name": "a2a",
   "what": "A2A (POST message/send)"
  }
 ],
 "links": [
  {
   "rel": "self",
   "href": "https://www.pathwren.workers.dev/mcp-transport.json",
   "type": "application/json"
  },
  {
   "rel": "describes",
   "href": "https://www.pathwren.workers.dev/mcp-transport.html",
   "type": "text/html",
   "title": "The page this document is the JSON twin of: MCP transport: the GET and HEAD leg"
  },
  {
   "rel": "changes",
   "href": "https://www.pathwren.workers.dev/changes.json?since=165",
   "type": "application/json",
   "title": "What changed since your cursor — poll this instead of re-downloading this document",
   "cursor_param": "since",
   "head_cursor": 165,
   "min_poll_seconds": 21600,
   "how": "Read `cursor` from the response and send it back as `since`. It advances only when something really changed, so an unchanged answer is proof rather than luck — about 2.5 KB, or a 304 with no body if you send back the ETag."
  },
  {
   "rel": "related",
   "href": "https://www.pathwren.workers.dev/documents.json",
   "type": "application/json",
   "title": "Every document here with its ETag and last-modified date"
  },
  {
   "rel": "related",
   "href": "https://www.pathwren.workers.dev/data/agents.json",
   "type": "application/json",
   "title": "Every crawler record in one file"
  },
  {
   "rel": "alternate",
   "href": "https://www.pathwren.workers.dev/sitemap.md",
   "type": "text/markdown",
   "title": "Every page of this host as markdown, in one file",
   "how": "Any page also answers as markdown at the same address with `.md` — and at `.mdx`, `<page>.html.md` and `<page>.html.mdx`, which are the same bytes. `Accept: text/markdown` on the page itself returns the same document. The HTML page stays canonical and every mirror says so in a Link header."
  },
  {
   "rel": "service-desc",
   "href": "https://www.pathwren.workers.dev/openapi.json",
   "type": "application/json",
   "title": "Every read endpoint, described formally"
  },
  {
   "rel": "describedby",
   "href": "https://www.pathwren.workers.dev/llms.txt",
   "type": "text/plain",
   "title": "The whole map in one text file"
  }
 ]
}