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

@jeremyjeremy 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).

CopilotAI balanced review requested due to automatic review settings August 28, 2026 10:44
@jeremy
jeremy requested a review from a team as a code ownerAugust 28, 2026 10:44
@jeremy
jeremyforce-pushed the feature/mcp-subcommand branch from aa11d9b to 2757539CompareAugust 28, 2026 10:48

CopilotAI 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
FileDescription
.surfaceRecords the MCP command surface.
README.mdDocuments MCP usage.
go.modAdds MCP dependencies.
go.sumRecords dependency checksums.
scripts/sync-mcp-model.shSynchronizes SDK model snapshots.
internal/auth/auth.goAdds install IDs to OAuth requests.
internal/auth/auth_test.goTests OAuth install IDs.
internal/auth/install_id.goPersists per-install identifiers.
internal/auth/install_id_test.goTests identifier persistence.
internal/cmd/auth.goDisplays the install ID.
internal/cmd/help.goCategorizes the MCP command.
internal/cmd/help_test.goUpdates help expectations.
internal/cmd/mcp.goImplements hey mcp.
internal/cmd/mcp_test.goTests command integration.
internal/cmd/root.goRegisters the command.
internal/mcpserver/catalog.goLoads the embedded catalog.
internal/mcpserver/catalog_test.goTests catalog derivation.
internal/mcpserver/dispatch.goDispatches actions to the SDK.
internal/mcpserver/dispatch_test.goTests request construction.
internal/mcpserver/domains.goDefines exposed domains.
internal/mcpserver/server.goConfigures the MCP gateway.
internal/mcpserver/server_test.goTests gateway behavior.
internal/mcpserver/model/PROVENANCE.jsonRecords snapshot provenance.
internal/mcpserver/model/behavior-model.jsonVendors operation traits.
internal/mcpserver/model/openapi.jsonVendors API schemas.

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

Comment threadinternal/mcpserver/domains.go Outdated
Comment threadinternal/mcpserver/dispatch.go
Comment threadinternal/mcpserver/dispatch.go
Comment threadinternal/mcpserver/catalog_test.go
Comment threadinternal/auth/auth.go Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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 threadinternal/mcpserver/domains.go Outdated
Comment threadinternal/mcpserver/dispatch.go
Comment threadinternal/mcpserver/domains.go
Comment threadinternal/mcpserver/dispatch.go
Comment threadinternal/mcpserver/dispatch.go
Comment threadinternal/auth/install_id.go Outdated
@jeremy
jeremyforce-pushed the feature/mcp-subcommand branch from 2757539 to adba328CompareAugust 28, 2026 10:54
@jeremy
jeremyforce-pushed the feature/mcp-subcommand branch 2 times, most recently from da2eea2 to ec33a59CompareAugust 28, 2026 11:01

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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 threadinternal/mcpserver/dispatch.go Outdated
Comment threadinternal/mcpserver/dispatch.go Outdated
Comment threadinternal/mcpserver/dispatch.go
@jeremy
jeremyforce-pushed the feature/mcp-subcommand branch from ec33a59 to 24a146fCompareAugust 28, 2026 11:11

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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 threadinternal/mcpserver/dispatch.go Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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 threadinternal/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
jeremyforce-pushed the feature/mcp-subcommand branch from e38a672 to c0cfa54CompareAugust 28, 2026 16:51
@jeremy
jeremy merged commit a4c59d4 into mainAug 28, 2026
33 checks passed
@jeremy
jeremy deleted the feature/mcp-subcommand branch August 28, 2026 17:02
Sign up for freeto 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

@jeremy