MCP diagnostics are process-global, so one project's discovery erases another's
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start with the records named in packages/opencode/src/mcp/discover.ts and packages/opencode/src/config/variable.ts, then trace their callers in cli/cmd/mcp.ts, session/prompt.ts, and config/config.ts. Done means diagnostics are scoped by projectDir, one project's discovery no longer clears or overwrites another's, and the stale-entry fix from #1121 still holds.
Written by the indexing model from the issue text.
Description
Summary
The four MCP diagnostic records are module-level singletons keyed by server name alone, with no notion of which project they belong to. In a process serving more than one project — altimate serve, which is how the extension and every hosted user reach the agent — a second project's discovery erases the first's diagnostics, and two projects that reuse a server name overwrite each other.
| record | file |
|---|---|
_unresolvedEnv |
packages/opencode/src/mcp/discover.ts:53 |
_drift |
packages/opencode/src/mcp/discover.ts:83 |
_discoveredSource |
packages/opencode/src/mcp/discover.ts:156 |
_blankedEnv |
packages/opencode/src/config/variable.ts:37 |
Reproduction
Two temp projects, each with a .vscode/mcp.json whose server has one unresolved {env:VAR}. Run against d00931b5e6 (current main):
after A: unresolvedEnvVars('alpha') = ["ALTIMATE_REPRO_VAR_A"]
after B: unresolvedEnvVars('alpha') = [] ← erased
after B: unresolvedEnvVars('beta') = ["ALTIMATE_REPRO_VAR_B"]
And with both projects using the same server name:
shared name: unresolvedEnvVars('datamate') = ["ALTIMATE_REPRO_VAR_B"] ← A's answer gone
datamate is not a hypothetical collision — it is the name the extension sync writes into every project.
Why it happens
discoverExternalMcp(projectDir) clears all three of its records at the top of the run and repopulates them afterwards. That was deliberate — it is what stops a variable that has since been fixed from being reported forever (#1121) — but the clear is global, so it takes the other project's entries with it. _blankedEnv has the same shape: blankedEnvVars() returns every config source ever parsed in the process, not the active project's.
The clear also sits before the first await while the writes happen after several, so concurrent discovery can interleave one project's clear with another's writes.
Impact
mcp list, mcp status and /mcps are the surfaces people use when a server will not connect. Under altimate serve — the path users actually run, not a niche debugging mode — they can report:
- nothing, for a project whose diagnostics another project cleared
- another project's variable names, under a shared server name
- drift attributed to a file belonging to a different project
A wrong answer here is worse than none, because the whole point of #1121/#701/#790/#878 was to stop people guessing.
Suggested fix
Key each record by projectDir and take the project as a parameter:
export function unresolvedEnvVars(server: string, projectDir: string): string[]
export function configDrift(projectDir: string): { server: string; source: string; fields: string[] }[]
export function discoveredSource(server: string, projectDir: string): string | undefined
export function blankedEnvVars(projectDir: string): { source: string; names: string[] }[]
A discovery run then clears only its own project's entries, which keeps the staleness fix from #1121 while making the clear harmless to everyone else. This is the InstanceState convention the rest of the codebase already follows for per-directory state.
Call sites to update: cli/cmd/mcp.ts (reportConfigDiagnostics), session/prompt.ts (/mcps), and config/config.ts (drift recording).
Provenance
Flagged independently by cubic, kilo, and the harness bot across #1159 and #1160, and deliberately deferred from both as too broad for those PRs.
- Dominant language
- TypeScript
- Stars
- 815
- Forks
- 135
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 59
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from AltimateAI/altimate-code
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
AltimateAI/altimate-code#1378 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
AltimateAI/altimate-code#1359 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
AltimateAI/altimate-code#1323 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
AltimateAI/altimate-code#1288 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
AltimateAI/altimate-code#1285 ·
Maintainers usually reply within 1 day
All issues in AltimateAI/altimate-code
Similar issues
-
priority: P2
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
prime-radiant-inc/evener#3291 ·
Maintainers usually reply within 1 day
-
accessibility bug revealjs
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
quarto-dev/quarto-cli#14961 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 90/100
supabase/agent-skills#614 ·
-
Content
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
RunestoneInteractive/rs#1559 · 1 comment ·
Maintainers usually reply within 2 days