Serve the MCP endpoint over HTTP from a self-hosted relay - #75
Merged
Merged
Conversation
The relay has been able to run a Streamable-HTTP MCP server at /wanctl-mcp since the MCP work landed; nothing in selfhost could turn it on. Pass the seed through, along with the two origins an MCP session needs to act as a controller: wanctl_login resolves the portal origin for the enrollment URL and every data tool resolves the relay URL to reach the broker. With the seed alone the endpoint answers initialize and tools/list and then fails the first login with "no portal configured". The relay reaches itself over loopback, matching WANCTL_WEBFETCH_RELAY_URL, so MCP traffic does not hairpin out through the public edge. Document both transports for users in a new portal guide: stdio for a host that can spawn wanctl, the hosted endpoint for one that cannot. It also states the limit that a hosted session hits, which is that it pins device identities in memory and stays fail-closed on first contact unless the operator opts in. Drop Feishu from the MCP tool text. The portal's identity provider is deployment configuration, and the tool was telling every user to look for a button that a GitHub-backed portal does not have. Fix the docs site build, red on main since the WebFetch docs landed: architecture.zh.md had lost the device-identity link its source carries, and three documents linked to webfetch.md, which the site does not publish. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 16, 2026
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.
Why
The relay has carried a Streamable-HTTP MCP server at
/wanctl-mcpsince the MCP work landed —main.gomounts it wheneverWANCTL_MCP_SEEDis set. Nothing inselfhost/could turn it on, so no deployment ever did. AI hosts that cannot spawn a localwanctlprocess (a browser chat, a cloud agent runner) have no way to reach the tools that a stdio host gets for free.No Go change was needed for the endpoint itself. Only the wiring and the documentation were missing.
What changed
Compose. The relay now receives
WANCTL_MCP_SEED,WANCTL_MCP_ALLOW_UNSAFE_TRUST_SERVER,WANCTL_PORTALandWANCTL_RELAY. The two seeds default to empty, which is off.The two origins are not optional extras. An MCP session is a controller:
wanctl_loginresolves the portal origin to build the enrollment URL, and every data tool resolves the relay URL to reach the broker. Measured with a localwanctl mcp --httpcarrying the seed and nothing else:WANCTL_RELAYpoints at the container's own loopback, matchingWANCTL_WEBFETCH_RELAY_URL, so MCP traffic does not hairpin out through the public edge and back.Docs. A new portal guide,
ai__mcp, covers both transports for users: stdio for a host that can spawnwanctl, the hosted endpoint for one that cannot, the registration command for each, the two-step portal login, the first-contact pairing approval, what a rebind credential is, and the security boundary.docs/self-hosting.mdgained an "Optional: enable the hosted MCP endpoint" section, anddocs/plans/2026-09-17-hosted-mcp.mdrecords the decision, the measurements and the rollback.Feishu wording. The MCP tool descriptions and the login prompt told every user to sign in via Feishu. The portal's identity provider is deployment configuration, and a GitHub-backed portal has no such button. The text is now provider-neutral.
go test ./internal/mcp/...passes.The limit this ships with
A hosted session keeps device trust in an in-memory store, and
wanctl_trust_serveris fail-closed unless the operator setsWANCTL_MCP_ALLOW_UNSAFE_TRUST_SERVER=1. Out of the box a hosted session can therefore log in and list devices but cannot run anything — the firstwanctl_execstops atDEVICE IDENTITY CONFIRMATION REQUIRED. Whether a relay you own yourself is the attacker that guard is protecting against is an operator decision, so the opt-in ships unset and both documents say plainly what that costs.Drive-by: the docs site build was red on main
tools/docsite/build.pyhas failed onmainsince the WebFetch docs landed, which blocks publishing anything at all:architecture.zh.mdhad lost the device-identity paragraph its source carries, and three documents linked towebfetch.md, which the site does not publish. The webfetch links now point at the repository copy,device-identity.mdjoined the site, and the build is green:docs/webfetch.zh.mdis a condensed rewrite rather than a structural translation ofdocs/webfetch.md(58 lines against 151, different headings, a missing table and code block), so publishing that page needs a real translation pass and is deliberately left out of this PR.Verified against a live deployment
Enabled on the z10 relay and exercised end to end:
initializereturns 200 with anMcp-Session-Id,tools/listreturns 17 tools,wanctl_loginwith no arguments returns the portal enroll URL, Claude Code registered with--transport httpconnects and calls the tool, and a real one-time code exchanged throughwanctl_loginbound the session to its namespace, after whichwanctl_peerslisted every online device. The relay logsMCP server enabled at /wanctl-mcp; the postgres container was untouched by the--no-depsrecreate.🤖 Generated with Claude Code