feat(mcp): collect OpenCode MCP server configs - #187
Open
raysubham wants to merge 1 commit into
Open
Conversation
OpenCode was invisible to the MCP inventory. It declares servers under a
top-level "mcp" key rather than "mcpServers", accepts its config under two
basenames (opencode.json and opencode.jsonc), and its own documented
examples carry comments and trailing commas — so nothing about it was
readable by the existing paths.
- Read ~/.config/opencode/opencode.{json,jsonc} as known exact paths. The
layout is identical on every platform, and both are resolved against the
home the detector was given rather than the process environment, which
on Windows belongs to the service account.
- Recognize both basenames in the bounded walk. That covers project-level
configs — which OpenCode searches for upward from the working directory
— without any project-root logic or new walk root of its own.
- Label by basename ahead of the substring vendor cases, so a config under
a directory named cursor/ or claude/ is still reported as OpenCode.
- Route both spellings through the JSONC path, since hujson.Standardize
handles the trailing commas that the comment stripper alone does not.
Secrets are unaffected: the field allowlist stays deny-by-default, so
OpenCode's environment and headers blocks are never collected.
A value under "mcp" that is not a map of servers is skipped rather than
emitted empty. "mcp" is an ordinary enough word to appear as a scalar flag
in a config whose real servers live under mcpServers, and emitting a null
for it would make the backend reject the whole document and drop the valid
servers beside it — a regression that would reach every walked .mcp.json,
not only OpenCode's own files.
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
OpenCode servers were invisible to the MCP inventory. Three things about its config kept it out of every existing path:
mcpkey, notmcpServers;opencode.jsonandopencode.jsonc;This reads all of it.
~/.config/opencode/opencode.{json,jsonc}— known exact paths, same layout on every platformmcppreserved as-is — this pipeline filters fields, it never rewrites another vendor's schemaBoth global paths resolve against the home the detector was given, never the process environment, which on Windows belongs to the service account. Both spellings route through
hujson.Standardize, which handles the trailing commas the comment stripper alone does not.Secrets
Unchanged and deny-by-default. The allowlist is still
command/args/serverUrl/url, so OpenCode'senvironmentandheadersblocks are never collected, and a parse failure still emits empty content rather than raw bytes. Byte-exact filter tests and forbidden-key assertions cover both.The one behavioural guard worth reviewing
internal/detector/mcp.go— a value undermcpthat is not a map of servers is skipped, not emitted empty:Without it,
{"mcpServers":{...},"mcp":true}emits{"mcp":null,"mcpServers":{...}}, the backend rejects the whole document (mcp is not an object), and the validmcpServersbeside it is dropped.mcpis an ordinary enough word to show up as a scalar flag, so that regression would reach every walked.mcp.json, not just OpenCode's files.Scoped deliberately:
"mcp": nullis left emitting{"mcp":{}}.nullis the one non-mapjson.Unmarshalaccepts into a map type, so it decodes to an empty set, not a filter failure. An empty object parses backend-side and evicts nothing, and it is exactly what{"mcpServers":null}has always done — tightening onlymcpwould make it the odd key out. Pinned byte-exact in both directions.mcpServers/context_servers/serversare untouched. Same latent shape, but pre-existing and not introduced here.Tests
mcpvalues (true,"enabled",["a"],42): siblings survive, and a lone scalar fails closed with no content.cursor-tools/,claude-tools/and.vscode/rows — each would be claimed by a later substring case if the basename case did not come first..mcp.jsoninside an opencode checkout staysDiscovered, as doesopencode.json.bak.Mutation-checked rather than assumed: reverting the nil guard reproduces the exact bad wire shape, moving the basename case last flips all three ordering rows, and weakening the guard to
len(filtered) > 0flips the null assertion.Docs
README.md,SCAN_COVERAGE.md(global + project rows) anddocs/mcp-audit.md, which now names OpenCode'senvironmentalongsideenvas stripped. The MCP table's first column widened 16 → 18 to fitOpenCode (project); the 11 sibling rows are whitespace-only.Not fixed here:
docs/mcp-audit.md:75still claims non-JSON configs are included as-is, which is false today and predates this change. Separate fix.Not stacked on #186
#186 (credential inventory) also touches OpenCode config parsing. This branch is independently based on
mainand shares no files with it, so either can merge first. Once both land, the literalDetectKnownUserConfigsassertion belongs in a follow-up.Verification
gofmtclean ·go vet0 ·go test ./... -race -count=1→ 42 packages ok, 0 failures, 0 data races ·golangci-lint run ./...→ 0 issues ·make smoke→ 44/44 ·CGO_ENABLED=0builds green forlinux/amd64,darwin/arm64,windows/amd64.No version or changelog bump — that is the release cut's job.
End-to-end coverage is in a companion integration-test PR, held until this ships in a release.