Skip to content

Serve HEY over MCP with hey mcp - #357

Merged
jeremy merged 1 commit into
mainfrom
feature/mcp-subcommand
Aug 28, 2026
Merged

Serve HEY over MCP with hey mcp#357
jeremy merged 1 commit into
mainfrom
feature/mcp-subcommand

Conversation

@jeremy

@jeremy jeremy commented Aug 28, 2026

Copy link
Copy Markdown
Member

What

hey mcp runs an MCP (Model Context Protocol) server on stdin/stdout, serving HEY as tools backed by the signed-in account. Seven domain gateway tools — hey_boxes, hey_search, hey_threads, hey_contacts, hey_todos, hey_calendar, hey_identity — derived from hey-sdk's model exports via the shared toolkit at github.com/basecamp/mcp, dispatching real API calls through the CLI's authenticated, account-scoped SDK client. One install, no separate binary:

claude mcp add hey -- hey mcp
hey mcp --read-only                # serve only read-only actions
hey mcp --domains boxes,search     # narrow the served surface (fails closed on unknown keys)

Design

Why not import hey-mcp-server? This repo is public; basecamp/hey-mcp-server is private, so a module dependency is a broken build for everyone outside the org, and vendoring it would publish it wholesale. Everything the server actually needs is already public — the basecamp/mcp toolkit (catalog, gateway, mcptest) and hey-sdk's model exports — so the CLI assembles the same shape from those parts, per the toolkit's own two-instance-by-duplication convention. hey-mcp-server remains the home of the hosted/HTTP story; when its catalog curation finds a shared public home (hey-sdk being the natural candidate), both consumers converge on it.

Real dispatch, not the stub. hey-mcp-server's handler is still the spike boundary (dispatch names the operation it would call). Here the CLI's *hey.Client — auth, token refresh, retry, account scoping, body limits — is already in hand, so the dispatcher maps catalog operations onto the SDK's generic verbs: path params substituted and escaped, query params encoded, remaining params gathered into the request body with property names checked against the body schema (a typo guard — types and required properties stay the server's to enforce, with its errors surfaced in-band). Calling convention (path/query/body all flat in params, describe serves the schema, paginated listings surface their geared_pagination cursor as next_page to pass back as page) lives in internal/mcpserver/dispatch.go; it should migrate to the toolkit once a second instance proves it.

Catalog snapshot. internal/mcpserver/model/ vendors hey-sdk's behavior-model.json + openapi.json at go/v0.28.0 — the same version go.mod pins — synced by scripts/sync-mcp-model.sh with provenance recorded. Unmapped SDK tags are pinned by test, so growing the surface is a deliberate one-line change plus a snapshot refresh.

Auth composition with #355/#356. hey mcp never touches the login flow: it reads stored credentials through the same auth.Manager → SDK bridge as every other command (requireAuth() + the package sdk client), and the Manager's proactive refresh keeps a long-running server alive across token expiry. The ephemeral-port listener (#356) and per-install id (#355) change how tokens are acquired, not how they're read, so this composes with both unchanged — based on main; a rebase after they land is a no-op for this diff.

Tests

  • internal/mcpserver: catalog derivation pinned (seven tools, unmapped tags accounted, snapshot ref asserted against go.mod's hey-sdk version); buildRequest table (path substitution/escaping, scalar formatting, query encoding, body gathering, schema-checked strays, missing/malformed params); wire-level tests via the toolkit's mcptest harness (tools/list, describe, dispatch-through-API, read-only refusal, domain narrowing).
  • internal/cmd: command registration, account-scope policy, auth requirement, and an end-to-end test running the actual hey mcp command against an httptest HEY server through the transport seam — MCP initialize + tools/list + a tool call, asserting the CLI's bearer token rides on the dispatched request. --read-only/--domains passthrough proven over the wire.
  • make check green except internal/tui's pre-existing unix-socket-path failures on this macOS sandbox (fail on main too, as Bind the OAuth callback listener to an ephemeral loopback port (RFC 8252 §7.3) #356 also notes). go test -race clean on the new packages. Size budget: 34.3 MiB of 37 (the embedded 425 KB model is noise).

Copilot AI balanced review requested due to automatic review settings August 28, 2026 10:44
@jeremy
jeremy requested a review from a team as a code owner August 28, 2026 10:44
@jeremy
jeremy force-pushed the feature/mcp-subcommand branch from aa11d9b to 2757539 Compare August 28, 2026 10:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds hey mcp, a stdio MCP server exposing authenticated HEY operations.

Changes:

  • Adds five catalog-derived MCP gateway tools and dispatch.
  • Adds model snapshots, tests, documentation, and dependencies.
  • Also includes per-install OAuth identifier changes from #355.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 25 out of 27 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
.surface Records the MCP command surface.
README.md Documents MCP usage.
go.mod Adds MCP dependencies.
go.sum Records dependency checksums.
scripts/sync-mcp-model.sh Synchronizes SDK model snapshots.
internal/auth/auth.go Adds install IDs to OAuth requests.
internal/auth/auth_test.go Tests OAuth install IDs.
internal/auth/install_id.go Persists per-install identifiers.
internal/auth/install_id_test.go Tests identifier persistence.
internal/cmd/auth.go Displays the install ID.
internal/cmd/help.go Categorizes the MCP command.
internal/cmd/help_test.go Updates help expectations.
internal/cmd/mcp.go Implements hey mcp.
internal/cmd/mcp_test.go Tests command integration.
internal/cmd/root.go Registers the command.
internal/mcpserver/catalog.go Loads the embedded catalog.
internal/mcpserver/catalog_test.go Tests catalog derivation.
internal/mcpserver/dispatch.go Dispatches actions to the SDK.
internal/mcpserver/dispatch_test.go Tests request construction.
internal/mcpserver/domains.go Defines exposed domains.
internal/mcpserver/server.go Configures the MCP gateway.
internal/mcpserver/server_test.go Tests gateway behavior.
internal/mcpserver/model/PROVENANCE.json Records snapshot provenance.
internal/mcpserver/model/behavior-model.json Vendors operation traits.
internal/mcpserver/model/openapi.json Vendors API schemas.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/mcpserver/domains.go Outdated
Comment thread internal/mcpserver/dispatch.go
Comment thread internal/mcpserver/dispatch.go
Comment thread internal/mcpserver/catalog_test.go
Comment thread internal/auth/auth.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa11d9b30d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/mcpserver/domains.go Outdated
Comment thread internal/mcpserver/dispatch.go
Comment thread internal/mcpserver/domains.go
Comment thread internal/mcpserver/dispatch.go
Comment thread internal/mcpserver/dispatch.go
Comment thread internal/auth/install_id.go Outdated
@jeremy
jeremy force-pushed the feature/mcp-subcommand branch from 2757539 to adba328 Compare August 28, 2026 10:54
@github-actions github-actions Bot removed the auth label Aug 28, 2026
@jeremy
jeremy force-pushed the feature/mcp-subcommand branch 2 times, most recently from da2eea2 to ec33a59 Compare August 28, 2026 11:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec33a59c10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/mcpserver/dispatch.go Outdated
Comment thread internal/mcpserver/dispatch.go Outdated
Comment thread internal/mcpserver/dispatch.go
@jeremy
jeremy force-pushed the feature/mcp-subcommand branch from ec33a59 to 24a146f Compare August 28, 2026 11:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24a146f378

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/mcpserver/dispatch.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e38a672cd4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/mcpserver/model/behavior-model.json
hey mcp runs an MCP server on stdin/stdout: seven domain gateway tools
(boxes, search, threads, contacts, todos, calendar, identity) derived
from hey-sdk's model exports via the shared toolkit at
github.com/basecamp/mcp, dispatching real API calls through the CLI's
authenticated, account-scoped SDK client — the same keychain-stored
credentials every other command uses.

The catalog vendors a hey-sdk model snapshot (behavior-model.json +
openapi.json at go/v0.28.0, the version go.mod pins — kept in lockstep
by test), synced by scripts/sync-mcp-model.sh with provenance recorded,
and the dispatcher maps catalog operations onto the SDK's generic
verbs: path and required query params enforced, substituted, and
escaped, remaining params gathered into the request body with property
names checked against the body schema as a typo guard. Listings with
more pages surface their geared_pagination cursor as
{"next_page": cursor, "results": ...} — HEY pages by cursor, and a
numeric page is answered with the first page forever. Failures are
in-band isError results; stdout belongs to the MCP wire, logs go to
stderr.

--read-only serves only read-only actions and refuses write dispatch;
--domains narrows the served surface, failing closed on unknown keys.
@jeremy
jeremy force-pushed the feature/mcp-subcommand branch from e38a672 to c0cfa54 Compare August 28, 2026 16:51
@jeremy
jeremy merged commit a4c59d4 into main Aug 28, 2026
33 checks passed
@jeremy
jeremy deleted the feature/mcp-subcommand branch August 28, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants