Skip to content

feat(relay): forward local MCP tools and coding agents to Hub - #122

Merged
jlrickert merged 10 commits into
mainfrom
feat/relay-protocol-v2
Oct 5, 2026
Merged

jlrickert merged 10 commits into
mainfrom
feat/relay-protocol-v2

Conversation

@jlrickert

Copy link
Copy Markdown
Owner

Summary

Lets a tap relay offer MCP tools, including the machine's coding agents, to Hub, and lets tap mcp serve the caller's relayed tools. Also includes a split-inference mode for tap launch claude and per-part hashes for node_edit.

Relay

  • Protocol 2: hubs and relays negotiate the protocol in register/registered, and every frame carries a fixed envelope version so older hubs still accept newer relays. Adds tools and call frames, their limits and tool error codes.
  • MCP tool forwarding: a user-only relay.mcp block names stdio or streamable-HTTP MCP servers. The relay runs them, lists their tools to Hub, and runs calls. Commands, URLs, headers and environment never cross the wire.
  • Sharing is Hub's decision: the shareable flag, ToolServer.Shareable and Call.Shared are removed. Owners enable and share servers on Hub.
  • Protocol 3: optional appTitle/appUrl on infer frames, validated and sent to OpenRouter providers as X-Title/HTTP-Referer.
  • Built-in runner servers: a new relay.runners block (all off by default) offers claude, codex, opencode, pi and claude-tools when their CLIs are on PATH.

tap runner serve: a stdio MCP server with one tool per installed coding agent CLI. Runs a task in an allowed root with a timeout, and guards against recursive delegation (TAP_RUNNER_ROOTS, TAP_RUNNER_TIMEOUT, TAP_RUNNER_DEPTH).

tap mcp: proxies Hub's /mcp/relay so relayed tools (mcp__<owner>__<server>__<tool>) appear beside the KEG tools. When TAP_AGENT is set, Hub applies that agent's gate. Disable with TAP_RELAY_TOOLS=off.

tap launch claude: split mode is now the default. Claude models use the user's own Claude login, Hub catalog models join the /model picker, and the Claude credential never reaches Hub. --hub restores Hub-only inference. Hub agent subagents become --agents scoped to their tools.

node_edit preconditions: node_read returns content_hash and meta_hash, and node_edit takes expected_content_hash/expected_meta_hash, so editing content and metadata at the same time no longer conflicts. Reads send each document once, in structured content.

Incompatible changes (v0.x)

  • relay.mcp.<name>.shareable is gone; the key is ignored.
  • node_edit no longer accepts expected_hash.
  • tap launch claude defaults to split mode; pass --hub for the old behaviour.

Hub

Hub support is in jlrickert/tapper-hub feat/relay-mcp-tools. It will pin the release that contains this.

Test plan

  • go build ./..., go vet ./..., go test ./...
  • go test -race ./pkg/keg ./pkg/tapper ./pkg/parity
  • task lint:docs, task lint:fs; task render-integrations leaves no diff
  • Each commit builds and vets on its own

Stamp every frame with a fixed EnvelopeVersion so hubs that predate
protocol 2 still accept a newer relay's register, and negotiate the
protocol in register/registered instead. Protocol 2 defines the tools
and call frames, their limits, and the tool error codes. The relay
offers [1,2], refuses a hub that picks a version it did not offer, and
always advertises a concurrency limit of at least 1.
Add a user-only relay.mcp block naming stdio or streamable HTTP MCP
servers. The relay starts each server, lists its tools, and sends them
to protocol 2 hubs in a tools frame, re-listing on change and
restarting a server that exits. Hub calls a tool by the server and tool
names the relay listed; the command, URL, headers and environment never
cross the wire. Stdio servers get a filtered environment and their own
process group, each server has its own concurrency limit and call
timeout, and a server serves only its owner unless marked shareable.
A relay may now offer MCP servers without any providers.
Relay protocol 3 adds optional appTitle and appUrl fields to infer
frames, which Hub sets to name where a request came from: its chat,
`tap launch <harness>`, or its API. Both are validated (printable
ASCII, an absolute http(s) URL, length limits) because they become
HTTP headers. The relay now offers protocols 1 through 3 and passes
the app to OpenRouter providers only, as X-Title and HTTP-Referer, so
usage is attributed in OpenRouter's logs.
Remove the owner-side shareable flag from relay.mcp config, the tools
frame (ToolServer.Shareable) and the call frame (Call.Shared), and stop
refusing shared calls on the relay. Who may call a relayed MCP server
is now decided only on Hub, where its owner enables it and shares it
with namespaces or people.

Incompatible change: the relay.mcp.<name>.shareable option no longer
exists. A config that still sets it loads, and the key is ignored.
tap mcp now connects in the background to the selected Hub's
/mcp/relay endpoint and serves the caller's relayed MCP tools
(mcp__<owner>__<server>__<tool>) beside the KEG tools, refreshing every
60s and whenever Hub announces a change. With TAP_AGENT set it asks as
that agent, so Hub applies the agent's tool gate. Relayed tools carry
no KEG authority, so they bypass the flight gate, the agent allowlist
and the public-response rewrite. TAP_RELAY_TOOLS=off turns them off, as
does running inside a delegated runner.
tap runner serve is a stdio MCP server with one tool per coding agent
CLI installed on this machine: claude_code_run, codex_run,
opencode_run and pi_run. Each runs a task non-interactively in a
working directory under the allowed roots (TAP_RUNNER_ROOTS), within
a timeout (TAP_RUNNER_TIMEOUT), and returns the agent's reply and
session id. TAP_RUNNER_DEPTH stops a delegated agent from delegating
again. Every runner keeps its own permission settings.
A new relay.runners block turns on built-in MCP servers, all off by
default: claude, codex, opencode and pi each run
`tap runner serve --runner NAME`, and claude-tools runs
`claude mcp serve`. Each is offered only when its CLI is on PATH, and
a relay.mcp entry of the same name replaces it, or removes it with
enabled: false. A relay can now start with built-in servers alone, and
the config schema accepts a disabled relay.mcp entry with no endpoint
and no longer lists shareable.
tap launch claude now defaults to split mode: Claude Code keeps the
user's own Claude login for Claude models, and Hub catalog models join
its /model picker. The forwarder routes each request by model under a
/t/<key>/ path prefix, and the user's Claude credential never reaches
Hub. --hub restores Hub-only inference and --subscription leaves
models alone. A Hub agent's subagents become Claude Code --agents
limited to their own tools plus --subagent-builtins, and the forwarder
sends X-Tap-Harness so Hub can label requests by harness.

Incompatible change: `tap launch claude` previously sent every model
through Hub by default; pass --hub for that behaviour.
node_read now returns content_hash and meta_hash beside the combined
hash, and node_edit takes expected_content_hash and expected_meta_hash,
so editing one part never conflicts with a concurrent edit to the
other. Edit results and conflict errors carry the new or current part
hashes, and the remote keg sends and decodes them; node_delete and
node_move keep the combined hash. node_read and the full
keg_settings_read also send each document once, in structured content,
with a short text summary.

Incompatible change: node_edit no longer accepts expected_hash; pass
expected_content_hash and/or expected_meta_hash from node_read.
Describe where relayed tools appear (Hub's chat, /mcp/relay and
tap mcp), how owners enable and share servers on Hub, with a checklist
for a shared server that does not show up, protocol 3 app attribution,
and the built-in runner servers and their tool arguments.
@jlrickert
jlrickert merged commit 4454579 into main Oct 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant