`mcp-server` (HTTP transport) crashes the whole process on a second session: "Already connected to a transport"
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 28
- Forks
- 2
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
Both the CLI's standalone `mcp-server` HTTP mode and the desktop app's built-in `/mcp` endpoint (same underlying implementation — `logseq.cli.common.mcp.server`, documented as "shared between CLI and frontend") share **one** `McpServer` instance across every incoming session. The MCP TypeScript SDK's `Server`/`Protocol.connect()` only allows a single transport to be connected at a time; calling `.connect()` a second time throws an uncaught exception that kills the entire Node process — not just the offending session, every session, including any client that was already connected and working.
This reproduces reliably and does not require anything unusual on the client side. In my testing, Claude Code's own normal MCP connection flow (a `server/discover` probe + `initialize`, apparently done as a validation pass, followed by a second `initialize` for the real session) was enough to trigger it on the very first real connection attempt from a remote client — no manual misuse needed.
## Reproduction
```sh
$ node cli.mjs mcp-server --host --port 12399 -a
MCP Streamable HTTP Server started on :12399
```
Then, from any client (or manually with curl), open a session and leave it open (don't send `DELETE /mcp`):
```sh
curl -s http://:12399/mcp -X POST \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer " \
-d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"A","version":"1.0"}}}'
```
Then send a second `initialize` with no session ID, before closing the first:
```sh
curl -s http://:12399/mcp -X POST \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer " \
-d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"B","version":"1.0"}}}'
```
The server logs a successful `Initialize sessionId ...` for the second session, then crashes:
```
file:///.../node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:217
throw new Error('Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.');
^
Error: Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.
at Server.connect (.../shared/protocol.js:217:19)
at McpServer.connect (.../server/mcp.js:48:34)
at Wfa (.../@logseq/nbb-logseq/lib/nbb_core.js:352:404)
...
Node.js v24.15.0
```
Nothing catches this — the whole process exits (`Node.js v24.15.0` trailer, then dead). Any other client that was already mid-session at the time is also dropped, since the process is gone.
I also reproduced this from a real remote client (Claude Code connecting over the LAN via `claude mcp add --transport http`) with no manual test traffic involved — its own connection sequence (a `server/discover` probe + `initialize`, then a second `initialize` shortly after) was sufficient to crash it on the very first attempt.
## Root cause
`logseq.cli.commands.mcp-server/create-mcp-server` creates a single `McpServer` instance once, at process/server startup:
```clojure
(defn- create-mcp-server [{{:keys [api-server-token] :as opts} :opts} graph]
...
(let [mcp-server (cli-common-mcp-server/create-mcp-server)]
...
mcp-server))
```
`logseq.cli.common.mcp.server/handle-post-request` then reuses that *same* instance for every new session:
```clojure
(and (not session-id)
(isInitializeRequest (.-body req)))
(let [transport (StreamableHTTPServerTransport. ...)]
...
(.connect mcp-server transport) ;; <- same shared `mcp-server` every time
...)
```
The MCP SDK's `Server`/`Protocol` only supports one connected transport per instance — a fresh `McpServer` (or `Server`) needs to be created per incoming session, not shared. This is a known, recurring pitfall in other MCP server implementations that started from a singleton-server design — see [penpot/penpot#8829](https://github.com/penpot/penpot/issues/8829) and [srbhptl39/MCP-SuperAssistant#183](https://github.com/srbhptl39/MCP-SuperAssistant/issues/183) / [#194](https://github.com/srbhptl39/MCP-SuperAssistant/issues/194), which hit the identical error for the identical reason and fixed it by instantiating a new server/transport pair per connection instead of reusing one.
## Suggested fix
Create a new `McpServer` (or `Server`) instance per session in `handle-post-request`'s initialize branch, instead of reusing the single instance created at startup — mirroring the fix applied in the projects linked above. The per-session tool-registration setup (`create-mcp-api-server`/`local-tools`) would need to run once per new server instance rather than once at process startup.
This appears to already be recognized: [#13055](https://github.com/logseq/logseq/pull/13055) ("fix(electron): complete first MCP initialize SSE") describes fixing a related first-initialize SSE hang and explicitly mentions "creates a fresh `McpServer` per initialize session to prevent shared transport blocking" — the same fix this issue is asking for. That PR was **closed without merging** (draft, `merged: false`), so as far as I can tell this problem is still open in any released or mergeable state. Linking it here in case it's a useful starting point to revive rather than writing the fix from scratch.
## Impact
This makes the native `mcp-server` HTTP transport (and by extension the desktop app's built-in `/mcp` endpoint, sharing the same code) effectively unusable for more than one client, or even one client with normal reconnect/validation behavior — any overlapping session crashes the entire server for everyone connected to it, not just the second client. `--stdio` transport is unaffected, since each stdio invocation is its own isolated process by construction.
## Environment
- `@logseq/cli` 0.4.3, build `b09316a` (2026-07-13)
- Node.js v24.15.0
- macOS (Darwin), desktop app 2.0.1 (arm64)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with logseq.cli.commands.mcp-server/create-mcp-server and logseq.cli.common.mcp.server/handle-post-request, then inspect create-mcp-api-server and local-tools for per-session setup. Reproduce two overlapping HTTP initialize requests and verify that each session remains usable without terminating the Node process; no test file is named in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- clojure, node.js
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100