feat: republish downstream MCP servers through Exeora - #54
Draft
usehoplite[bot] wants to merge 3 commits into
Draft
usehoplite[bot] wants to merge 3 commits into
usehoplite[bot] wants to merge 3 commits into
Conversation
Exeora becomes an MCP client of the servers a project or a user configures (.exeora/mcp.json at the project root, mcp.json beside the CLI's config): connect launches each stdio server, handshakes it and announces its tools to the relay, which keeps them per project and offers them on the same MCP endpoints under mcp__<server>__<tool> — same OAuth token, same policy, same approval flow, same audit log. The wire surface is additive (mcp.tools and mcp.call frames, the mcp-v1 feature flag), so an old gateway offers none of the downstream tools and an old CLI ignores the new frames, with no protocol version bump. Also repairs a NUL byte that had been sitting in approval.ts since before this branch: git treated the file as binary, so no diff of it could be read. Co-authored-by: Leynier Gutiérrez González <leynier41@gmail.com>
The trusted publication path runs git lfs ls-files before pushing, and a sandbox without the git-lfs binary cannot publish at all. The install is best-effort so a mirror without the package never takes setup down with it. Co-authored-by: Leynier Gutiérrez González <leynier41@gmail.com>
Run rustfmt over the new mcp files and replace the manual max/min bounds in connection.rs with clamp(), as clippy's manual_clamp lint requires. Co-authored-by: Leynier Gutiérrez González <leynier41@gmail.com>
usehoplite
Bot
force-pushed
the
hoplite/naxos-24cf7039
branch
from
September 3, 2026 06:08
94c4244 to
f7ed1a0
Compare
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.
What this changes
Exeora can now be a client of other MCP servers and republish their tools through its own MCP endpoint. A machine that runs
exeora connectlaunches the servers configured in<project root>/.exeora/mcp.json(project level) andmcp.jsonbeside the CLI's own config (user level, samemcpServersshape the ecosystem already uses, stdio only), handshakes each one, and announces the tools it finds to the relay. The gateway stores one announcement per project in the relay Durable Object and registers the tools on the existing MCP endpoints under a prefixed name —mcp__<server>__<tool>— so one client connection reaches the project's files, its commands and its other MCP servers at once.Everything downstream tools enjoy is everything the canonical tools already had: the same OAuth token and scopes, the same per-project policy (
read_onlyhonours the server'sreadOnlyHint, with a missing claim read conservatively as "changes something"), the same confirmation flow (input_requiredwith a signed, argument-hash-bound state naming the republished tool), and the same audit log under the prefixed name. The account endpoint (/mcp) offers downstream tools only when the connection reaches exactly one project, because with several, one prefixed name could mean a different server on a different machine.Surface, by package
packages/protocol—mcp.ts(descriptors,McpServerTools,mcpToolName/parseMcpToolName, themcp__prefix), four shared limits (16 servers/project, 64 tools/server, 16 KB per input schema, 100 KB per announcement), newmcp.tools(executor→relay) andmcp.call(relay→executor) frames, andmcpPolicyAllows/needsMcpApproval.ApprovalRequestMessage.toolwidened from the tool enum to a string so a state can name a downstream tool.crates/exeora-cli— newsrc/mcp/module: config load and merge (project wins on a name clash, broken entries warn instead of poisoning the file), a hand-rolled JSON-RPC 2.0 stdio client (initialize handshake offering2025-06-18and adopting whatever the server answers, paginatedtools/list,tools/call, timeouts, cancellation), and a registry that owns the server processes for the life ofconnectand assembles the announcement under the byte budget. Contract types regenerated from the canonical schemas.apps/gateway— announcement storage in the relay DO (relay-do-mcp.ts), registration of downstream tools (mcp-servers.ts), dispatch through the same resolve → policy → approval → relay → audit road as canonical calls (dispatch-mcp.ts), MCP confirmation (askToConfirmMcp), and wiring on both endpoints. Several modules were extracted to stay inside the 500-line budget.apps/web/landing— a newdocs/mcppage (configuration, how tools appear, policy, limits, security) plus navigation and readme entries..hoplite/settings.json— the sandbox setup installs the pinned Rust toolchain via mise and, best-effort, git-lfs: the trusted publication path needs the git-lfs binary, and a sandbox without it cannot push at all.Compatibility
The change is additive and negotiated by presence, with no
PROTOCOL_VERSIONbump: an old gateway never learnsmcp-v1and offers zero downstream tools; an old CLI ignoresmcp.tools/mcp.callframes it does not know. A server that fails to start announces an error and offers none of its tools rather than tools that fail; a tool whose schema does not fit the byte budget is reported, never silently truncated.Also repairs a NUL byte that had been sitting in
approval.tson the base branch: git treated the file as binary, so no diff of it could be read or reviewed. The byte is now the:separator it was always meant to be.Verification
cargo test --workspace— 95 passed, 0 failed (includes the 7 new Rust tests for the mcp client, config merge, and warning behaviour).bun run test(vitest, node + workerd) — 540 passed, 0 failed across 49 files, including the newrelay-do-mcp,mcp-servers, and protocolmcpsuites.bun run typecheck,biome check .(repo-pinned 2.5.10), andbun run check:file-length(227 files, max 500) all clean.bun run --cwd apps/web buildsucceeds with the new docs page.apps/gateway/.dev.varsthat CI's "Prepare test secrets" step writes; this sandbox now has the same file.oauth/cimd.workers.test.tsfails without that.dev.varsfile (no identity provider configured → 500 on the authorize page), on the clean base branch as well; with the CI-equivalent file in place it passes.