Skip to content

hive mcp: own MCP servers and their credentials - #3

Merged
unforced merged 2 commits into
Unforced-Dev:mainfrom
unforcedagi:hive-mcp-oauth
Jul 29, 2026
Merged

hive mcp: own MCP servers and their credentials#3
unforced merged 2 commits into
Unforced-Dev:mainfrom
unforcedagi:hive-mcp-oauth

Conversation

@unforcedagi

Copy link
Copy Markdown
Contributor

Why hive has to own this

Buzz cannot configure an HTTP MCP server. I read the source rather than assuming:

  • buzz-acp's McpServer is {name, command, args, env} — stdio only, no url, no headers
  • the backend-provider deploy payload (deploy_payload_json) carries no MCP field at all

That's a boundary in Buzz's surface, not a gap to route around. If agents are to reach an HTTP MCP server, hive owns the configuration and the credential.

Until now the only route was two flat fields on the desktop provider config, which could express exactly one server and hardcoded its name to "mcp". hive-spec has always had mcp: Vec<Mcp> — the format was never the limit.

What this adds

$ hive mcp add parachute --url https://vault.example/vault/unforced/mcp --agent uni
$ hive mcp login parachute --agent uni
$ hive mcp list --agent uni
$ hive mcp refresh parachute --agent uni

Specs are edited, not regenerated. The README calls generated specs "safe to edit and to commit" — but any generator that rewrites the document deletes comments, ordering, and blocks it doesn't know about. toml_edit keeps them, so hive mcp add and a human editor can share one file. There's a test for exactly that.

The login flow

Walks what the MCP spec defines: unauthenticated probe → WWW-Authenticate: resource_metadata → protected-resource metadata (RFC 9728) → authorization-server metadata (RFC 8414) → dynamic client registration (RFC 7591) → PKCE S256 → authorization code → token, resource-bound per RFC 8707.

Why interactive rather than secret put with a minted token. A minted token carries the minter's authority and expires without warning. Verified against a live Parachute vault, whose access tokens last 900 seconds — an agent on a static token fails its first tool call fifteen minutes after setup, and the error reads as a broken MCP server.

The refresh token is stored alongside, and it only helps because the broker serves credentials per connection rather than injecting them at container start. A design that baked credentials into the environment couldn't renew without recreating the container. That's an existing architectural choice paying off.

refresh reads through the broker's grant-checked path under a named hive-cli grant rather than reaching around it, so a manual renewal shows in the audit log exactly like an agent's own fetch.

No new runtime dependencies

HTTP goes through curl, for the same reasons hive_core::docker drives Docker through its CLI — no large dependency tracking a moving API, and every request is a command you can re-run by hand. base64url is forty lines rather than a crate. Only toml_edit and sha2 were added, both already in the workspace.

Verified against a live vault

  • discovery, dynamic registration, PKCE, code exchange — all real, not mocked
  • the stored token opens an actual MCP session (serverInfo: parachute-vault/unforced)
  • refresh produced a genuinely different token
  • 120 tests pass (108 existing + 12 new)

Known limitation

The loopback redirect needs a browser that can reach the host's 127.0.0.1, so a headless box needs someone at its screen. The RFC 8628 device grant would fix it — you'd get a code and type it anywhere — but Parachute advertises only authorization_code and refresh_token today. Noted rather than worked around.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM

unforcedagi and others added 2 commits July 29, 2026 01:02
Buzz cannot configure an HTTP MCP server. `buzz-acp`'s McpServer is
{name, command, args, env} — stdio only, no url, no headers — and the
backend-provider deploy payload carries no MCP field at all. That is a
boundary in the surface, not a gap to route around, so hive owns MCP
configuration outright.

Until now the only way to attach one was two flat fields on the desktop
provider config, which could express exactly ONE server and hardcoded its
name to "mcp". hive-spec has always had `mcp: Vec<Mcp>`; the format was
never the limit.

  hive mcp add parachute --url https://vault/mcp --agent uni
  hive mcp login parachute --agent uni
  hive mcp list --agent uni

Specs are edited with toml_edit rather than regenerated. The README calls
generated specs "safe to edit and to commit", and any generator that
rewrites the document silently deletes comments, ordering, and blocks it
does not know about.

`login` walks the flow the MCP specification defines: unauthenticated
probe -> WWW-Authenticate resource_metadata -> protected-resource metadata
(RFC 9728) -> authorization-server metadata (RFC 8414) -> dynamic client
registration (RFC 7591) -> PKCE S256 -> authorization code -> token, bound
to the resource with RFC 8707.

Why an interactive flow rather than `secret put` with a hand-minted token:
a minted token carries the minter's authority and expires without warning.
Verified against a live Parachute vault, whose access tokens last 900
SECONDS — an agent on a static token would fail its first tool call
fifteen minutes after setup, and the error would read as a broken server.
The refresh token is stored alongside, and matters only because the broker
serves credentials PER CONNECTION rather than injecting them once at
container start; a design that baked credentials into the environment
could not renew them without recreating the container.

`refresh` reads through the broker's grant-checked path under a named
`hive-cli` grant rather than reaching around it, so a manual renewal
appears in the audit log exactly like an agent's own fetch.

No new runtime dependency: HTTP goes through curl for the same reasons
hive_core::docker drives Docker through its CLI, and base64url is forty
lines rather than a crate.

Known limitation: the loopback redirect needs a browser that can reach the
host's 127.0.0.1, so a headless box needs someone at its screen. RFC 8628
device grant would fix that; Parachute advertises only authorization_code
and refresh_token today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
The loopback redirect only resolves on the machine running the command,
and an agent host is exactly the machine nobody is sitting at. Authorizing
from a laptop left the browser on a dead address while the box waited
forever — hit on the first real login.

Now the listener and stdin race, whichever arrives first. Paste the whole
URL or just its query string; both are percent-decoded. This is the same
escape hatch Claude Code offers for MCP auth.

It costs nothing in security: the code is single-use, PKCE-bound and
state-checked on both paths, so accepting it over the terminal is no
weaker than accepting it over loopback. A callback belonging to a
different login attempt is refused rather than exchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
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.

2 participants