Skip to content

fix(mcp): inject keyring credentials into MCP server child process#17

Merged
facundofarias merged 2 commits into
mainfrom
fix/mcp-inject-credentials
May 29, 2026
Merged

fix(mcp): inject keyring credentials into MCP server child process#17
facundofarias merged 2 commits into
mainfrom
fix/mcp-inject-credentials

Conversation

@facundofarias
Copy link
Copy Markdown
Contributor

@facundofarias facundofarias commented May 29, 2026

Summary

Telemetry showed 3 of 4 dhq mcp invocations failing with MCP server exited: exit status 1 (all darwin, all non-agent shells on 0.16.3 / 0.14.1). Root cause: the deployhq-mcp-server npm package reads credentials from DEPLOYHQ_ACCOUNT, DEPLOYHQ_EMAIL, DEPLOYHQ_API_KEY env vars and calls process.exit(1) if any is missing — but dhq mcp was spawning the binary with c.Env = os.Environ(), so users who logged in via dhq auth login (creds → keyring) silently never had those vars and the server crashed at startup.

Changes

  • Extracted credential resolution into Context.Credentials() returning the resolved (account, email, apiKey) triplet using the same precedence as Client(): flags → env → file → keyring.
  • Client() refactored to use it — no behavior change for the SDK client path.
  • dhq mcp now calls Credentials() before spawning the server and merges the three DEPLOYHQ_* env vars into the child's environment if they aren't already set.
  • mergeEnv preserves any explicit env override the user set (so power users can still point the server at a different account for one session) and skips empty values.

Test plan

  • go build ./..., go vet ./..., go test ./..., golangci-lint run
  • TestMergeEnv covers: adds missing keys, preserves user-set keys, skips empty override values.
  • Smoke: dhq auth login, then dhq mcp in a fresh shell with no DEPLOYHQ_* exports — server stays running.
  • Smoke: DEPLOYHQ_ACCOUNT=other dhq mcp — user override wins.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • MCP server now automatically receives resolved DeployHQ credentials when launched for smoother integration.
  • Bug Fixes
    • CLI credential resolution improved with clearer guidance when authentication or account info is missing.
  • Tests
    • Added unit tests covering environment-merge behavior to ensure credential injection is correct.

Review Change Stack

The deployhq-mcp-server reads credentials from DEPLOYHQ_ACCOUNT,
DEPLOYHQ_EMAIL, and DEPLOYHQ_API_KEY env vars and calls
process.exit(1) if any is missing. But `dhq mcp` was just spawning
the binary with c.Env = os.Environ() — the user's keyring-stored
credentials never reached the child process.

Telemetry showed 3 of 4 mcp invocations (75%) failing with
"MCP server exited: exit status 1", all from users with no agent
env wrapper (no codex/claude-code/warp shell) on darwin. These are
people who ran `dhq auth login` (creds → keyring), then `dhq mcp`,
and got a silent crash because the child couldn't see the keyring.

Changes:
- Extract credential resolution into a Context.Credentials helper
  that returns the resolved (account, email, apiKey) triplet using
  the same precedence as Client(): flags → env → file → keyring.
- Refactor Client() to use it (no behavior change for that path).
- In `dhq mcp`, call Credentials() before spawning the server and
  merge the three DEPLOYHQ_* vars into the child env if they aren't
  already set by the user.
- mergeEnv preserves any explicit env override the user set
  (e.g. pointing the server at a different account for one session)
  and skips empty values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8d6f562-e659-47f8-bc9f-a6312f1599d2

📥 Commits

Reviewing files that changed from the base of the PR and between 2c32144 and 4a6e370.

📒 Files selected for processing (2)
  • internal/commands/mcp.go
  • internal/commands/mcp_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/commands/mcp.go
  • internal/commands/mcp_test.go

Walkthrough

This PR extracts credential resolution into a reusable Credentials() method on the CLI context, refactors the Client() method to use it, and then applies that method to inject credentials into the MCP server process via environment variables.

Changes

Credential Resolution and MCP Environment Injection

Layer / File(s) Summary
Credential Resolution Abstraction
internal/cli/context.go
New Credentials() method resolves account, email, and apiKey from config and auth store, returning "Not logged in" errors when auth fails. Client() is refactored to delegate credential resolution to Credentials() instead of duplicating the logic inline.
MCP Server Credential Injection
internal/commands/mcp.go, internal/commands/mcp_test.go
MCP server launch resolves credentials via Context.Credentials() and merges them into the child process environment as DEPLOYHQ_ACCOUNT, DEPLOYHQ_EMAIL, and DEPLOYHQ_API_KEY using a new mergeEnv helper. The helper preserves existing non-empty env entries, treats existing empty env values as missing so overrides can replace them, and skips injecting empty override values. Table-driven tests validate merging, existing-key preservation, and empty-value filtering.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: injecting keyring credentials into the MCP server child process environment.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-inject-credentials

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

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

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: 2c32144df0

ℹ️ 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/commands/mcp.go Outdated
Comment on lines +115 to +116
if _, ok := present[k]; ok {
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat empty credential env vars as missing

When a user has an empty exported credential like DEPLOYHQ_API_KEY= and valid keyring credentials, Credentials() resolves the real key, but mergeEnv marks the empty variable as present and skips injecting the resolved value. The MCP server still receives an empty required env var and exits at startup, so this leaves a common “env var exists but is blank” variant of the keyring-login failure unfixed.

Useful? React with 👍 / 👎.

Codex review on PR #17 caught a residual failure mode: when a user has
exported a credential as empty (e.g. DEPLOYHQ_API_KEY= with no value)
alongside valid keyring credentials, the previous mergeEnv saw the
key as present and skipped injecting the resolved value, so the MCP
server still received a blank required env var and exited at startup.

mergeEnv now drops empty exported entries for keys we're about to
override, and the override is appended in their place. Non-empty user
exports are still preserved (power-user override path), and unrelated
empty exports (e.g. OTHER_THING=) are left alone.

Two new test cases cover the empty-export drop and the unrelated-key
preservation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@facundofarias facundofarias merged commit beff8d1 into main May 29, 2026
3 checks passed
@facundofarias facundofarias deleted the fix/mcp-inject-credentials branch May 29, 2026 06:47
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