Skip to content

AIP-1 §7.1 gap: legacy bare /sse path probed by research scanner — codify path-level enumeration via transport_paths block? #35

@Aigen-Protocol

Description

@Aigen-Protocol

Summary

AIP-1 §7.1 normatively defines the transport field (streamable_http, sse, stdio) and the not_implemented array, with the example /mcp/sse listed as a probed-but-not-served variant a streamable_http server should declare. AIP-1 does not mention the historical bare /sse path (no /mcp prefix).

Empirically observed against the AIGEN reference server: a recurring research scanner that self-identifies via PTR zl-amsc-nl-gr1-wk102a.internet-census.org (Zenlayer AS21859, NL) probes bare GET /sse — not /mcp/sse — on every visit. AIGEN returns 404 22B and the client abandons the lifecycle. Pattern: 3 sessions / 11 distinct IPs across the 185.226.197.0/24 worker pool, 2 visit windows 5 days apart (2026-05-24T06:46Z + 2026-05-29T10:38Z + 10:49Z), 22 hits total. Per session: dual-flow Chrome browser to / (cookie warm-up) → python-httpx/0.28.1 POST /mcp init+202+tools/list (succeeds, 41564B tools/list body) → bare GET /sse 404 → session ends. Different /24 worker per visit window.

Why this matters for the spec

Pre-streamable_http MCP clients (and research scanners that index the long-tail of MCP-1.0 endpoints) probe /sse at the root, not under /mcp. AIP-1 §7.1's example not_implemented: ["sse", "stdio"] is about transport names, and the array of probed-but-not-served paths (/mcp/sse, /messages/) is mentioned in §7.1 prose but not codified normatively as a separate field. The result: a conformant /.well-known/oabp.json declaring transport: streamable_http does not communicate to a legacy-SSE-probing client that /sse at the document root is also unserved.

For an OABP implementation that supports only streamable_http, the catalogue of paths a client might probe and not find includes at minimum:

  • /mcp/sse (Streamable HTTP path's SSE variant, MCP 0.2-era convention)
  • /sse (root-level SSE, MCP 0.1-era convention — observed in production)
  • /messages/ (MCP message-bus variant)
  • /v1/messages (some forks)

A normative path-level enumeration would let scanners self-correct without hitting 404.

Proposal

Extend §7.1 with a separate transport_paths block adjacent to transport and not_implemented:

"mcp": {
  "transport": "streamable_http",
  "url": "https://example.org/mcp",
  "not_implemented": ["sse", "stdio"],
  "transport_paths": {
    "served": ["/mcp"],
    "not_served": ["/mcp/sse", "/sse", "/messages/", "/v1/messages"]
  }
}

Where:

  • transport_paths.served lists the canonical endpoint(s) actually serving the declared transport.
  • transport_paths.not_served lists root- or subpath-level variants a probing client may hit, which the server explicitly does not serve. Items SHOULD respond with structured 404 per §7.2 ({"error":"not_implemented","canonical_endpoint":"...", "supported_transports":[...]}) rather than bare 404.

Rationale: the existing not_implemented array describes transport names (which AIP-1 enumerates as streamable_http | sse | stdio); transport_paths.not_served describes URL paths a client may probe, which is a different axis. The two are not redundant: a server that supports sse transport-name on /mcp/sse may still need to declare it does not serve bare /sse.

Falsifiable counter-watch

If no other client besides the 185.226.197.0/24 scanner probes bare /sse against AIGEN in the next 60 days (2026-05-29 → 2026-07-28), this proposal can be closed as low-impact (single-actor noise). If 2+ additional distinct ASNs probe bare /sse in that window, the case for transport_paths.not_served strengthens.

Reference data

  • 24-May 2026 06:46:15Z burst: 185.226.197.37/38/39/40 from /24, Referer http://207.148.107.2/ (which is AIGEN's external test IP — so the scanner seeded itself by crawling the public IP), 8 hits
  • 29-May 2026 10:38:10Z burst: 185.226.197.22/23/25, no Referer, 7 hits
  • 29-May 2026 10:49:38Z burst: 185.226.197.17/18/19, no Referer, 5 hits
  • PTR for 185.226.197.22: zl-amsc-nl-gr1-wk102a.internet-census.org (Zenlayer Inc / AS21859 / Lelystad NL)
  • Per-session method: Chrome GET / warm-up → python-httpx/0.28.1 POST /mcp init (200 1188B) → POST /mcp 202 0BPOST /mcp tools/list 200 41564BGET /sse 404 22B → favicon by Chrome IP → end

Implementation cost in reference impl

  • /.well-known/oabp.json regeneration: add 4 lines under the mcp block.
  • Optional: route alias /sse → structured 404 per §7.2. ~5 lines nginx, no backend.

If the consensus is to keep §7.1 minimal and not normalise transport_paths, an alternative is a non-normative §7.1 example showing the not_implemented array including path-level variants (["sse", "stdio", "/sse", "/messages/"]) — but that overloads the field's meaning and is harder to validate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions