You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
rmcp Display strings vary (Transport closed, channel closed, unexpected end of stream). Tokenize instead of listing one more substring.
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
**Do not implement code.** Execute the cases below, record pass/fail, stop.
6
+
**Shipped:**`c09e569` (retry + FK guard), `54d0de2` (literal `transport closed`). Matcher now also normalizes rmcp Display variants (camelCase / punctuation).
7
+
8
+
---
9
+
10
+
## Results (Aug 20, 2026)
11
+
12
+
| Case | Result |
13
+
| ---- | ------ |
14
+
| A bind FK |**PASS** — `invalid_argument` + `mcpmux_list_feature_sets`; 38/74 counts unchanged |
15
+
| B stdio reconnect |**PASS** — `wakatime` / `wakatime_wakatime_summaries`. Kill child → invoke succeeded. Log: `trigger=transport_closed`, `reconnect_fresh completed ok=true`. Live error: `MCP call failed: Transport closed`|
16
+
| C HA idle |**SKIPPED**|
17
+
| D unmatched |**SKIPPED** — grant-layer "did you mean", never reached classifier |
18
+
19
+
Confounders: Tauri-watch rebuild wiped inbound sessions (`POST /mcp` → 404 until MCP reload). After reload, 6 unpinned roots required one `mcpmux_set_workspace_root` to *this* repo before invoke was possible. That pin was session disambiguation, not the reconnect path.
7
20
8
21
---
9
22
10
23
## Answer first: new sessions in different roots?
11
24
12
-
**No. Do not open a new Cursor chat, do not switch workspace roots, do not call `mcpmux_set_workspace_root`.**
25
+
**No. Do not open a new Cursor chat, do not switch workspace roots, do not call `mcpmux_set_workspace_root` to "fix" a closed connection.**
13
26
14
-
That was the old workaround. It opens a fresh *inbound* rmcp session and hides the outbound-pool bug. Using it here makes Case B/C inconclusive.
27
+
That was the old workaround. It opens a fresh *inbound* rmcp session and hides the outbound-pool bug. Using it as the Case B/C recovery makes those cases inconclusive.
15
28
16
29
| Action | Use for this test? |
17
30
| --- | --- |
18
31
| Same Cursor chat, same workspace (`/Users/joe/Desktop/Repos/Personal/mcp-mux`) |**Yes — required**|
19
-
| Reload Cursor MCP tools (once, before starting, if the gateway just rebuilt) | Yes, once |
20
-
| New chat / different root / `set_workspace_root`|**No** (confounds reconnect) |
32
+
| Reload Cursor MCP tools (once, if the gateway just rebuilt) | Yes, once, *before* Case B/C |
33
+
|`set_workspace_root` to this repo after reload, **only if**`mcpmux_list_servers` says multiple roots are unpinned | Yes — otherwise nothing is `ready`|
34
+
|`set_workspace_root` after a transport-closed invoke |**No**|
21
35
| Bind a real FeatureSet onto another repo |**No** (Case A uses a fake UUID; no write) |
22
36
23
-
Bindings are keyed by exact `workspace_root` + `machine_id`, not by chat. This repo already has two rows for the same path (Gondor vs Rohan), both on FeatureSet `All`. Stay here.
37
+
Bindings are keyed by exact `workspace_root` + `machine_id`, not by chat. Stay in this repo.
1. Debug gateway is the listener on `:45818` (ancestor = `launchd`, not Cursor Helper). Health: `curl -sf http://127.0.0.1:45818/health` → `{"status":"ok","version":"0.5.0"}`.
57
-
2. Cursor MCP `user-mcpmux` points at `http://localhost:45818/mcp`. Reload tools **once** if the binary was just rebuilt, then do not reload again.
58
-
3. Only call mux via `user-mcpmux` (`mcpmux_search_tools` → `mcpmux_get_tool_schema` if needed → `mcpmux_invoke_tool`). No direct backend MCP servers.
59
-
4. Do not run `pnpm dev:stop` / rebuild mid-test (evicts the pool and invalidates Case B/C).
70
+
1. Debug gateway is the listener on `:45818`. Health: `curl -sf http://127.0.0.1:45818/health` → `{"status":"ok",…}`.
71
+
2. Cursor MCP `user-mcpmux` points at `http://localhost:45818/mcp`. Reload tools **once** if the binary was just rebuilt, then do not reload again mid-case.
72
+
3. Only call mux via `user-mcpmux`. No direct backend MCP servers.
73
+
4. Do not run `pnpm dev:stop` / rebuild mid-test (evicts the pool *and* all inbound sessions).
74
+
5. If `mcpmux_list_servers` reports several unpinned roots, pin `/Users/joe/Desktop/Repos/Personal/mcp-mux` once, then start Case A/B.
**Goal:** a nonexistent `feature_set_id` returns `invalid_argument`, not `FOREIGN KEY constraint failed`, and the DB does not grow.
66
81
67
82
1. Snapshot counts (query above). Call them `B0` / `J0`.
68
-
2. Call `mcpmux_bind_current_workspace` with `feature_set_id` = `00000000-0000-0000-0000-00000000dead` (or any other unused UUID). Do **not** approve anything — the guard runs before consent.
83
+
2. Call `mcpmux_bind_current_workspace` with `feature_set_id` = `00000000-0000-0000-0000-00000000dead`. Do **not** approve anything — the guard runs before consent.
69
84
3.**Pass** if the tool error JSON has `"error":"invalid_argument"` and the message contains `mcpmux_list_feature_sets`.
70
85
4.**Fail** if the message contains `FOREIGN KEY`, `internal_error`, or `constraint`.
## Case B — reconnect after a killed stdio child (~5 min)
84
92
85
93
**Goal:** a transport-closed error triggers `reconnect_fresh` (not OAuth `reconnect_instance`) and the retry succeeds. Same session.
86
94
87
-
Pick a **stdio** server that is already invokable under `All`. Cheap options on this machine: `wakatime`, `markitdown`, `chrome-devtools`. Confirm first:
95
+
Pick a **stdio** server that is `ready`under `All`. Cheap options: `wakatime`, `markitdown`, `chrome-devtools`.
If `server_readiness` is not `ready`, pick another stdio id from that browse, or `mcpmux_list_servers` and take one with `ready`.
101
+
If `server_readiness` is not `ready`, pick another stdio id, or pin this root first (see Preconditions).
94
102
95
-
1. Invoke a cheap read-only tool once so the instance is live. Example (only if search returned it): `mcpmux_invoke_tool``server_id=wakatime``tool=wakatime_wakatime_summaries` with explicit `start`/`end`dates for today. Any successful call is enough.
103
+
1. Invoke a cheap read-only tool once so the instance is live. `wakatime`: `mcpmux_invoke_tool``server_id=wakatime``tool=wakatime_wakatime_summaries` with explicit `start`/`end` for today (qualified name; bare `wakatime_summaries` can fail invoke).
96
104
2. Note the time (`date -u +%H:%M:%S`).
97
105
3. Kill **only the child**, not McpMux:
98
106
99
107
```bash
100
-
# find the stdio child (example: wakatime). Do not kill target/debug/mcpmux.
101
108
pgrep -lf wakatime
102
109
# then: kill <child-pid>
110
+
# Do not kill target/debug/mcpmux. Do not `pkill -f mcpmux`.
103
111
```
104
112
105
-
If you cannot identify a safe child, stop and report INCONCLUSIVE. Do not `pkill -f mcpmux`.
106
-
107
113
4. In **this same chat**, invoke the same tool again. Do not call `set_workspace_root`.
108
-
5.**Pass** if the invoke succeeds (or returns a normal tool error, not `-32000` / `Connection closed`).
114
+
5.**Pass** if the invoke succeeds (or a normal tool error, not raw `Transport closed` / `-32000` to the agent).
-`backend call_tool failed` with `trigger=transport_closed` (or `auth` only if the error was actually 401)
124
+
-`backend call_tool failed` with `trigger=transport_closed` (stdio kill has been `error=mcp call failed: transport closed`)
119
125
-`reconnect attempted after call_tool failure` with `ok=true`
120
-
-`reconnect_fresh completed` with `ok=true` (this is the new path; `reconnect_instance` / `Reconnecting instance ... after OAuth` must **not** be the line for this failure)
126
+
-`reconnect_fresh completed` with `ok=true` (`reconnect_instance` / OAuth must **not** be the line for this failure)
121
127
122
-
**Fail:** invoke returns `MCP error -32000: Connection closed` to the agent, or the log shows OAuth reconnect for a stdio kill.
128
+
**Fail:** invoke returns `Transport closed` / `-32000` / `Connection closed` to the agent, or `trigger=unmatched`, or OAuth reconnect for a stdio kill.
123
129
124
130
---
125
131
126
132
## Case C — original HA idle (optional, 15–20 min)
127
133
128
-
**Goal:**reproduce the reported HTTP shape against `home-assistant-new`.
134
+
**Goal:** the reported HTTP shape against `home-assistant-new`.
2. Invoke one cheap read-only HA tool. Confirm success.
132
-
3. Wait 15–20 minutes. Do not invoke that server. Do not reload MCP. Do not re-pin the workspace. Other mux tools are fine.
133
-
4. Invoke the **same** HA tool again in this chat.
134
-
5.**Pass / fail / log checks** are identical to Case B, except `server_id=home-assistant-new` and the error string historically was `MCP error -32000: Connection closed`.
136
+
Same as B except `server_id=home-assistant-new`, wait 15–20 min with no traffic to that server, and the historical error was `MCP error -32000: Connection closed`.
135
137
136
138
If you cannot wait, mark Case C SKIPPED and rely on B.
137
139
@@ -142,7 +144,7 @@ If you cannot wait, mark Case C SKIPPED and rely on B.
142
144
**Goal:** a failure that is neither auth nor transport-closed is not swallowed.
143
145
144
146
1.`mcpmux_invoke_tool` against a ready server with a tool name that does not exist, e.g. `server_id=wakatime``tool=definitely_not_a_real_tool`.
145
-
2.**Pass** if the raw error comes back to the caller (not a silent success, not a reconnect).
147
+
2.**Pass** if the raw error comes back (not a silent success, not a reconnect).
146
148
3. If the failure is classified unmatched, the log line is `trigger=unmatched` and there is **no**`reconnect attempted after call_tool failure` for that call.
147
149
148
150
A permission / not-found error that never hits the backend is also acceptable — note it as "never reached classifier" rather than fail.
@@ -172,7 +174,7 @@ Case B stdio reconnect: PASS | FAIL | INCONCLUSIVE | SKIPPED
0 commit comments