Skip to content

feat: add MCP server install for coding agents (workos mcp)#184

Merged
nicknisi merged 8 commits into
mainfrom
nicknisi/mcp
Jul 6, 2026
Merged

feat: add MCP server install for coding agents (workos mcp)#184
nicknisi merged 8 commits into
mainfrom
nicknisi/mcp

Conversation

@nicknisi

@nicknisi nicknisi commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Gives the GA management MCP server (https://mcp.workos.com/mcp) a CLI + onboarding surface. Developers previously had to discover the URL on their own and hand-edit a different config format per agent; this adds a workos mcp command group plus three onboarding surfaces, all writing secret-free HTTP config (OAuth happens in the client on first use).

Three commits, each independently reviewed and validated:

  • cb1e45e — core lib + command group: src/lib/mcp-clients.ts client writers (Claude Code + Codex shell out to their own mcp add CLIs; Cursor via JSONC merge on ~/.cursor/mcp.json), workos mcp install | remove | status with --agent filtering, JSON output modes, per-agent outcome matrix (a missing binary or version gap is a failed row, never a crash).
  • 33d38b6 — onboarding surfaces: consent prompt at the end of workos install, one-time stderr banner (yields to the telemetry notice / unclaimed warning via a one-notice-per-run gate), and a workos doctor MCP section (report-only; warns only on a misconfigured Cursor URL, never on absent MCP). Declining once silences every automatic surface forever via a prefs marker; workos mcp install stays available manually.
  • 75b273d — adoption telemetry: install-flow accept/decline and banner impressions emit queued mcp offer command events (mcp.entry_point, mcp.accepted, mcp.agents_installed, mcp.agents_failed). Queued events ride the CLI's unconditional final flush + store-forward, so they ship even though the installer session has already ended. The telemetry gateway's command schema is passthrough and flattens custom attributes onto spans — no backend change required.

Safety properties

  • Non-TTY / CI / agent contexts (CLAUDECODE, CURSOR_AGENT, WORKOS_MODE=agent, …) never see a prompt or banner and never get config written by an automatic surface
  • No subcommand requires CLI auth (config is secret-free by design)
  • Cursor merge preserves comments/formatting via jsonc-parser and never overwrites a file it can't parse
  • Cancel (ctrl-C) at the prompt is not recorded as a decline

Validation

  • pnpm build / pnpm typecheck / pnpm test (2175 tests) all green
  • Client writers verified against real Claude Code, Codex, and Cursor installs
  • Banner + event smoke-tested end-to-end in an isolated HOME with a real pty: banner renders exactly once, debug flush shows command(mcp offer, 0ms, success=true), telemetry notice wins the first-run slot

Rollback

Revert the PR; workos mcp remove cleans user machines. Prefs markers are inert without the code.

@devin-ai-integration devin-ai-integration Bot 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds MCP server onboarding and management for coding agents. The main changes are:

  • New workos mcp install, remove, and status commands for Claude Code, Codex, and Cursor.
  • Cursor config editing through JSONC merge while Claude Code and Codex use their own MCP CLIs.
  • Install-flow prompt, one-time startup banner, and doctor report section for MCP setup status.
  • Preference markers and telemetry attributes for MCP offer impressions, accepts, declines, and per-agent outcomes.

Confidence Score: 5/5

The MCP onboarding and management changes appear merge-ready with no blocking issues identified.

The implementation scope is well covered by command, onboarding, doctor, preference, and telemetry tests, and no code defects were identified in the reviewed changes.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proofs for the posted P1 findings.
  • T-Rex validated MCP CLI command changes across before/after states, including installation/removal flows and error handling.
  • T-Rex validated MCP onboarding changes, including banner behavior, preference updates, and telemetry across non-TTY and CI scenarios.
  • T-Rex documented doctor MCP testing across three scenarios, including wrong URL telemetry and related outputs.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. General comment

    P1 workos mcp status rejects the claimed --agent filter

    • Bug
      • The PR contract says the new workos mcp command group supports --agent filtering. On head, install and remove accept --agent, but status --agent cursor --json exits with invalid_usage / Unknown argument: agent. This prevents users and automation from querying status for a single agent with the same filtering contract as install/remove.
    • Cause
      • In src/bin.ts, the mcp status registration uses (y) => y and never defines the agent option, and src/commands/mcp.ts defines runMcpStatus() without taking or applying McpCommandOptions. The relevant implementation is src/bin.ts:532-539 and src/commands/mcp.ts:114-124.
    • Fix
      • Add the same agent option definition to the mcp status subcommand, pass argv.agent into runMcpStatus, and update runMcpStatus(options?: McpCommandOptions) to call resolveAgentFilter and filter createMcpClients() results by the selected agent keys before emitting JSON/table output.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Doctor misses misconfigured Cursor MCP URL because Cursor is not detected from isolated HOME

    • Bug
      • The PR contract says workos doctor should include an MCP section/status and warn when Cursor has a WorkOS MCP entry pointing at the wrong URL. In the executed head run with isolated HOME containing ~/.cursor/mcp.json and workos.url set to https://wrong.example.com/mcp, the human output has no MCP Server section and the JSON output has no mcp field or MCP_MISCONFIGURED issue. The focused artifact shows issues: [] for MCP and summary remains at the two unrelated warnings only.
    • Cause
      • src/doctor/checks/mcp.ts depends on detectMcpClients(), whose Cursor availability in src/lib/mcp-clients.ts is based on homedir(). In this runtime, setting HOME for the CLI did not affect Node's os.homedir() value, so the check looked outside the isolated Cursor fixture and returned no clients; runDoctor then normalizes null to undefined and output.ts renders no MCP section.
    • Fix
      • Make Cursor config discovery honor the runtime HOME/XDG_CONFIG_HOME used by the CLI/test environment instead of relying solely on os.homedir(), or otherwise inject the home directory into MCP detection. Add an executable doctor test that runs with isolated HOME containing ~/.cursor/mcp.json and asserts the MCP section/JSON field and MCP_MISCONFIGURED issue appear for wrong URLs while absent MCP remains non-warning.

    T-Rex Ran code and verified through T-Rex

Reviews (4): Last reviewed commit: "chore: fix linting" | Re-trigger Greptile

Comment thread src/lib/mcp-notice.ts Outdated
nicknisi added a commit that referenced this pull request Jul 6, 2026
…stall

The 30s Promise.race in maybeOfferMcpInstall returned at the deadline
while offerMcpInstall kept running: a never-answered clack.confirm still
held stdin (ref'd, raw mode), so the process could not exit and
`workos install` stayed wedged despite the wrapper.

Thread an AbortController from the deadline into clack.confirm — on
abort, clack resolves the prompt as a cancel and releases stdin — and
skip install work when an answer lands after the deadline. A pty run
with stdin held open now exits at ~30s instead of hanging.

Addresses Greptile P1 on #184.
nicknisi added 6 commits July 6, 2026 12:29
… agents

Adds `workos mcp install|remove|status` plus a client-writer library
(src/lib/mcp-clients.ts) that configures the secret-free WorkOS MCP server
(https://mcp.workos.com/mcp, HTTP transport) in Claude Code, Codex, and Cursor.

- Claude Code / Codex via their own CLIs; Cursor via a jsonc-parser merge of
  ~/.cursor/mcp.json that preserves comments, trailing commas, and other servers
- Every per-agent operation degrades to a reported outcome, never throws
- Both human and JSON output modes; no auth guard (config is secret-free)
- Codex eagerly starts OAuth on add and exits non-zero despite persisting the
  server, so add() verifies actual config state before reporting failure

Review: 1 cycle, PASS (0 critical/high).
Wire the Phase 1 MCP client library into three onboarding surfaces plus
adoption telemetry:

- Install-flow consent prompt after the skills step (maybeOfferMcpInstall):
  human/TTY-only, not-JSON, decline-respecting, and best-effort so it can
  never fail or wedge `workos install`.
- One-time startup banner (maybeShowMcpNotice) nudging `workos mcp install`,
  behind a shared one-notice-per-run gate (startup-notice-gate.ts) so the
  telemetry notice and unclaimed warning take precedence.
- `workos doctor` MCP section (checkMcp) reporting per-agent status; a
  warning is derived only for a Cursor entry with an unexpected URL, never
  for absent MCP.

Shared gate state (declined/banner-shown markers, eligibility predicate)
lives in src/lib/mcp-notice.ts and persists in the plain prefs store.

Review (1 pass): fixed a JSON-mode prompt-suppression gap so `install
--json` on a TTY stays machine-clean. Known limitation: install-flow
capture('mcp install') tags don't reach the backend because the installer
session ends (runWithCore finally) before the post-install offer runs;
tracked for a follow-up session-independent wire event.
The install-flow prompt and banner previously used analytics.capture(),
which only folds props into installer session tags — dead by the time
the offer runs, because run-with-core's finally has already shut down
analytics and flushed session.end. Queue real 'mcp offer' command
events instead, carrying entry_point, accepted, and per-agent outcomes
as mcp.* attributes; queued events ride bin.ts's unconditional final
flush, with store-forward covering anything the flush misses. The
telemetry gateway's command schema is passthrough and flattens custom
attributes onto command spans, so no backend change is needed.

Also emits a banner impression event, making banner→install conversion
directly measurable instead of approximate.
…stall

The 30s Promise.race in maybeOfferMcpInstall returned at the deadline
while offerMcpInstall kept running: a never-answered clack.confirm still
held stdin (ref'd, raw mode), so the process could not exit and
`workos install` stayed wedged despite the wrapper.

Thread an AbortController from the deadline into clack.confirm — on
abort, clack resolves the prompt as a cancel and releases stdin — and
skip install work when an answer lands after the deadline. A pty run
with stdin held open now exits at ~30s instead of hanging.

Addresses Greptile P1 on #184.
Commit 9464704 was regenerated without a lockfile by pnpm 8 (a stale
mise shim shadowing the repo's pnpm@10.28.2): it downgraded the
lockfile to v6 format and bumped in-range transitive deps, including
@anthropic-ai/claude-agent-sdk 0.2.62 -> 0.2.141, whose SDKResultError
type change broke the build. This restores the v9 lockfile as of
4136f62; `pnpm i --frozen-lockfile`, build, and the full test suite
pass again.
@nicknisi nicknisi merged commit f040f72 into main Jul 6, 2026
6 checks passed
@nicknisi nicknisi deleted the nicknisi/mcp branch July 6, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant