Repository navigation
Adopt unknown Streamable HTTP session ids - #25
Merged
Merged
Conversation
…restart Fixes #24. Streamable HTTP sessions live only in dnSpy's memory, so after dnSpy is closed and reopened, a client that kept its Mcp-Session-Id got 404 "Unknown Mcp-Session-Id" on every call. The spec says the client must then re-initialize, but the official TypeScript SDK (which Chatbox is built on) just throws "Error POSTing to endpoint: Unknown Mcp-Session-Id" and keeps the stale id, so the server stayed unusable until the user reconnected by hand. Chatbox's connection test passed because it opens a fresh session each time. A session carries no state here (each POST is answered inline; the session object is just the id), so POST and the standalone GET stream now adopt an id this process never issued instead of refusing it. Only an id the client explicitly ended with DELETE is still refused with 404, which keeps the spec's behaviour for real terminations. Reproduced with @modelcontextprotocol/sdk 1.30.0 against a real dnSpy restart (connect, call a tool, restart dnSpy, call again with the same client): before, the second call failed with exactly the reported error and the GET stream's reconnect got 404; after, both succeed. run-tests.ps1 step [36] covers adoption over POST and GET plus the DELETE case. E2E: net10 245 pass / 0 fail, net48 245 pass / 0 fail. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
KernelErr
pushed a commit
that referenced
this pull request
Sep 23, 2026
CLAUDE.md: keep this branch's architecture bullets and slot #25's session adoption note under McpServer.cs; the test-fixture line names [36] too. run-tests.ps1: [36] tests Streamable HTTP sessions, so it is skipped under -Headless like [23]. E2E on this tree: GUI net10 245/245, net48 245/245; headless net10 239/239, net48 239/239. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
KernelErr
added a commit
that referenced
this pull request
Sep 23, 2026
* Decouple the tools from dnSpy's GUI services behind IMcpHost Groundwork for a headless host that serves the same tools without the dnSpy window. Behaviour inside dnSpy is unchanged. - McpTools depended directly on IDocumentTreeView, IDocumentTabService and IDecompilerService. It now takes an IMcpHost with exactly what the tools use: GetDocuments / OpenDocument, the decompiler, running a mutating tool on the UI thread, and the rename hooks (ApplyRename re-sorts and redraws the tree node and rebuilds open tabs; RefreshTreeNode / RefreshDecompiledViews for the rest). DnSpyMcpHost implements it with the same dnSpy calls the tools made before, so the tree/tab code now lives in one file. - JSON-RPC dispatch (initialize / tools / resources, error codes) moves from McpServer into McpDispatcher, which has no transport code; the HTTP server delegates to it and a stdio host can too. - McpSettings.Logged exposes each log line, so a host whose stdout is the protocol channel can mirror the log to stderr. - Removes RenameClassOrEnumSymbolCore and CreateRenameTypeResult: dead since rename_symbol_by_token routed types through RenameAnyTypeSymbol. - InternalsVisibleTo for the upcoming dnSpy.Extension.MCP.Headless, and headless/ is excluded from the extension's compile glob. E2E: net10 240 pass / 0 fail. * Add a headless host that serves the tools over stdio, without dnSpy dnSpy.Extension.MCP.Headless.exe serves the same 32 tools and 6 resources over the MCP stdio transport with no dnSpy window, so an MCP client (Claude Desktop, Claude Code, Chatbox, Cursor, codex, ...) can start it on demand instead of the user keeping dnSpy open with the server enabled. Follow-up to the headless request on #24. - headless/: Program finds the dnSpy installation it is deployed in, installs an assembly resolver (dnSpy's bin, bin\Extensions\...), then wires HeadlessMcpHost -> McpTools -> McpDispatcher -> StdioTransport. HeadlessMcpHost implements IMcpHost the way dnSpy's document service works with memory-mapped I/O off: files are read into memory (targets stay unlocked), wrapped as DsDotNetDocument, cached in the resolver, PDBs loaded; the C# decompiler comes from dnSpy.Decompiler.ILSpy.Core exactly as dnSpy.Console.exe gets it. stdout is kept for the protocol and Console.Out goes to stderr with the log. Positional arguments preload files/folders through open_files; --dnspy, --quiet, --version. - deploy-headless.ps1 lays it out like dnSpy.Console.exe in each bundle: the exe next to dnSpy.Console.exe, the net10 DLL next to dnSpy.Console.dll (AppHostPatcher -d bin for build.ps1's layout), and the runtime configuration copied from the bundle (net48 dnSpy.exe.config, net10 dnSpy.Console.runtimeconfig.json), so it is framework-dependent or self-contained exactly as the bundle is. The apphost must match the bundle's architecture (an x64 apphost can't load the win-x86 bundle's hostfxr): release.yml builds one per RID and the script refuses a mismatch. - release.yml deploys it into all three zips; build.yml builds it. - run-tests.ps1 -Headless runs the whole suite over stdio against the deployed host (skipping only the HTTP steps [23] and [36]), plus a clean-exit check when stdin closes. - README (EN + zh-CN): a Headless mode section with client configs. It replaces the Claude Desktop example, which used a nonexistent "command": "http". CLAUDE.md documents the host and its rules. Verified: GUI E2E net10 240/240 and net48 240/240; headless E2E net10 239/239 and net48 239/239; the official @modelcontextprotocol/sdk stdio client against the net48, net10 and self-contained win-x64 / win-x86 bundles, built locally with dnSpy's build.ps1 (COREHOST_TRACE confirms each self-contained bundle runs on its own bundled hostfxr). * docs: headless needs no arguments; the AI loads targets with open_files * docs: streamline the README around the two ways to run it The README grew around the in-dnSpy server, with headless bolted on as one section and client setup spread over four places. Rewrite both languages (489 -> 325 lines each) around the two ways to run it: - A headless vs. inside-dnSpy comparison up front, and a quick start that offers both. - One "Connecting a client" section: Claude Code (user / project scope, .mcp.json and its one-time approval, how to check), Claude Desktop step by step (Edit Config, full restart, where the host's log lands), and other clients, including codex's `mcp add`. Checked against Claude Code 2.1.280 (`claude mcp add` + `claude mcp list` report the net10 and net48 hosts as Connected) and codex-cli 0.142.5 (both config forms parse). - One line per tool instead of a paragraph; the curl walkthroughs, architecture notes and technical details (all in CLAUDE.md) cut down to a transport table and a pointer. - Development says changes must pass the end-to-end suite in both modes, and CLAUDE.md records that as a convention. * Merge main (#25): step [36] tests Streamable HTTP sessions, so it is skipped under -Headless like [23]. E2E on the merged code: GUI net10 245/245 and net48 245/245; headless net10 239/239 and net48 239/239. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…restart
Fixes #24. Streamable HTTP sessions live only in dnSpy's memory, so after dnSpy is closed and reopened, a client that kept its Mcp-Session-Id got 404 "Unknown Mcp-Session-Id" on every call. The spec says the client must then re-initialize, but the official TypeScript SDK (which Chatbox is built on) just throws "Error POSTing to endpoint: Unknown Mcp-Session-Id" and keeps the stale id, so the server stayed unusable until the user reconnected by hand. Chatbox's connection test passed because it opens a fresh session each time.
A session carries no state here (each POST is answered inline; the session object is just the id), so POST and the standalone GET stream now adopt an id this process never issued instead of refusing it. Only an id the client explicitly ended with DELETE is still refused with 404, which keeps the spec's behaviour for real terminations.
Reproduced with @modelcontextprotocol/sdk 1.30.0 against a real dnSpy restart (connect, call a tool, restart dnSpy, call again with the same client): before, the second call failed with exactly the reported error and the GET stream's reconnect got 404; after, both succeed. run-tests.ps1 step [36] covers adoption over POST and GET plus the DELETE case. E2E: net10 245 pass / 0 fail, net48 245 pass / 0 fail.