remote-access-mcp 2.4.4 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ # Transport Compatibility
2
+
3
+ Why the server speaks three MCP dialects, and the exact bugs that forced
4
+ each one. This file exists so nobody "simplifies" it away.
5
+
6
+ ## The three dialects
7
+
8
+ | # | Dialect | Who speaks it | Handshake | Replies |
9
+ |---|---|---|---|---|
10
+ | 1 | Streamable HTTP, **stateless** | ChatGPT, Grok | POST initialize | inside the POST response; session id ignored |
11
+ | 2 | Streamable HTTP, **stateful** | Claude (new SDK), most reference clients | POST initialize → `Mcp-Session-Id` response header | every request carries the id; GET opens a server→client notification stream |
12
+ | 3 | **Legacy SSE** (2024-11-05) | Claude's connector UI auto-selects it when the URL ends in `/sse` | **GET** → `event: endpoint` frame announces the POST URL + sessionId | replies ride the long-lived GET stream; POSTs are 202 |
13
+
14
+ All three are served on **both** endpoint paths (`/mcp` and `/sse`, plus any
15
+ configured aliases) — the URL does not determine the dialect; the request
16
+ shape does.
17
+
18
+ ## Dispatch rules (server/app.ts)
19
+
20
+ ```
21
+ POST with Mcp-Session-Id:
22
+ known session → that session's StreamableHTTP transport
23
+ unknown session → 404 "Session not found" (client re-initializes)
24
+ POST initialize (no id) → new stateful session, id in response header
25
+ POST anything else (no id) → stateless throwaway transport (ChatGPT/Grok)
26
+ GET with Mcp-Session-Id → streamable notification stream for that session
27
+ GET without id → legacy SSE handshake (event: endpoint …)
28
+ POST /<token>/<path>/messages?sessionId=… → legacy SSE post-back leg
29
+ DELETE with id → session teardown (204)
30
+ ```
31
+
32
+ `normalizeAccept()` widens every Accept header to
33
+ `application/json, text/event-stream` (GET → `text/event-stream`) before
34
+ the SDK sees it. The SDK enforces the spec strictly and answers 406 to
35
+ `*/*` or single-type Accepts; real clients send all of those.
36
+
37
+ Responses are **SSE-framed** (`enableJsonResponse: false`) — the spec's
38
+ reference behavior, and the framing every dialect we've seen accepts.
39
+ Plain-JSON mode was tried; Claude's connector silently dropped it.
40
+
41
+ ## The incident log (each of these shipped and hurt)
42
+
43
+ 1. **No session id** → Claude retried initialize every ~60s and reported
44
+ "Couldn't reach <server>". Fix: stateful sessions (v2.2.0),
45
+ `sessions.ts` store with 30-min idle TTL, 200 cap, per-token binding.
46
+ 2. **406 on honest Accepts** → clients sending `*/*` or
47
+ `text/event-stream` alone got rejected by the SDK's strict check.
48
+ Fix: normalizeAccept (v2.2.1).
49
+ 3. **`Unknown SSE event: endpoint`** → the legacy transport was bolted on
50
+ by hijacking **every** GET; a stateful client opening its notification
51
+ stream received the legacy `event: endpoint` frame and aborted its whole
52
+ TaskGroup. Fix: dispatch GETs by the Mcp-Session-Id header (v2.2.2/3).
53
+ 4. **Plain-JSON responses dropped** → initialize answered 200 +
54
+ `application/json`, Claude read it and silently gave up. Fix: SSE
55
+ framing everywhere (v2.2.4). Diagnosed with a byte-logging wire proxy:
56
+ the client's exact headers/body made it obvious Claude got a *valid*
57
+ reply and still walked away — only the framing differed.
58
+
59
+ ## Verification (do not trust, run)
60
+
61
+ The byte-exact Claude simulation lives in git history
62
+ (`python-httpx/0.28.1` + `clientInfo.name: "Anthropic"`); the pinned suite
63
+ covers the matrix:
64
+
65
+ - `tests/sessions.test.ts` — stateful handshake, routing, hijack-refusal,
66
+ DELETE teardown, stateless fallback
67
+ - `tests/get-dispatch.test.ts` — GET with id never emits `endpoint`; GET
68
+ without id always does
69
+ - `tests/legacy-sse.test.ts` — full 2024-11-05 handshake over `/sse` and
70
+ `/mcp`, sessionId-less POST-back → 400, unknown → 404, cross-token → 403
71
+ - `tests/accept-compat.test.ts` — six Accept variants all initialize
72
+
73
+ ## Edge / proxy notes (production config)
74
+
75
+ - nginx: `proxy_buffering off`, `proxy_request_buffering off`,
76
+ `proxy_set_header Connection ""`, `chunked_transfer_encoding on`,
77
+ `gzip off`, `add_header X-Accel-Buffering no`, read/send timeouts 3600s —
78
+ a legacy SSE stream idles between tool calls; the default 60s proxy
79
+ timeouts kill it. The gateway sends `: keepalive` comments every 15s so
80
+ intermediate hops don't reap the stream.
81
+ - Cloudflare proxies all three dialects fine (verified from the edge).
82
+ - Quick tunnels on filtered networks can register but not carry traffic —
83
+ `ramcp tunnel` self-verifies and warns instead of handing out a dead URL
84
+ (that's environment, not protocol; http2-over-TCP is forced for the same
85
+ reason — QUIC is blocked on some ISPs).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-access-mcp",
3
- "version": "2.4.4",
3
+ "version": "3.0.1",
4
4
  "description": "Turn any Linux server into an AI-agent-accessible machine via MCP. ChatGPT, Claude, and Grok connect over HTTPS and control files, shell, git, and more — securely and with zero Python dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,9 @@
12
12
  "README.md",
13
13
  "README.fa.md",
14
14
  "LICENSE",
15
- "install.sh"
15
+ "install.sh",
16
+ "AGENTS.md",
17
+ "docs/ai"
16
18
  ],
17
19
  "engines": {
18
20
  "node": ">=20.18.1"