fix(detector): stop counting plugin catalog templates as MCP servers - #206
Open
swarit-stepsecurity wants to merge 3 commits into
Open
Conversation
The MCP walk matches on basename anywhere under $HOME, and an agent plugin marketplace is a clone of a catalog repo where every entry ships a template .mcp.json. On this machine that turned 7 real configs into 53: 16 from ~/.claude/plugins/marketplaces and 30 from ~/.codex/.tmp/plugins, none installed, none loaded by any agent. In enterprise mode 36 of them carried an mcpServers block, so the backend recorded stripe, slack, gmail, notion and friends as servers on the device. Every plugin package is marked by a .claude-plugin/.codex-plugin manifest at its root, so a walked hit inside one can be classified rather than guessed at: keep the package's own .mcp.json when the package is installed (both agents install under plugins/cache), drop catalog entries and drop MCP-shaped files vendored elsewhere in a payload — Claude only ever loads .mcp.json from a plugin, so a plugin repo's own opencode.json is not a live config either. Configs outside a plugin package are untouched. Fixes step-security#201 Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
There was a problem hiding this comment.
Pull request overview
This PR fixes inflated MCP server discovery by distinguishing real MCP configs from agent plugin marketplace/catalog templates during the $HOME walk, preventing non-installed plugin templates from being counted/reported as MCP servers.
Changes:
- Added plugin-package classification for walked MCP config hits, dropping marketplace templates and vendored MCP-shaped files inside plugin payloads.
- Introduced manifest-based plugin root detection for Claude/Codex plugins and treated installed plugin
.mcp.jsonas separate sources (claude_plugin/codex_plugin). - Added tests covering catalog-vs-installed plugin behavior and bounded manifest lookup.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/detector/mcp_plugins.go | Adds plugin manifest/root detection and classification logic to drop marketplace templates and keep installed plugin configs. |
| internal/detector/mcp_plugins_test.go | Adds unit tests validating plugin classification behavior for Claude/Codex and walk-root bounding. |
| internal/detector/mcp_discovery.go | Routes walked MCP hits through the new classifier before appending discovered config specs. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+56
to
+60
| for i := 0; i < maxPluginRootLookup; i++ { | ||
| for _, m := range mcpPluginManifests { | ||
| if info, err := os.Stat(filepath.Join(dir, m.dir, "plugin.json")); err == nil && !info.IsDir() { | ||
| return dir, m, true | ||
| } |
Comment on lines
+79
to
+83
| if runtime.GOOS == "windows" { | ||
| p = strings.ToLower(p) | ||
| } | ||
| sep := string(filepath.Separator) | ||
| return strings.Contains(p, sep+"plugins"+sep+"cache"+sep) |
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.
Fixes #201.
The MCP walk matches on basename anywhere under
$HOME, and an agent plugin marketplace is a clone of a catalog repo where every entry ships a template.mcp.json. On my machine that turned 7 real configs into 53: 16 from~/.claude/plugins/marketplaces, 30 from~/.codex/.tmp/plugins(not in the issue), none installed and none loaded by any agent. In enterprise mode 36 of them carry anmcpServersblock, so the backend recordedstripe,slack,gmail,notion,datadogand friends as servers on the device — the fleet inventory andmcp_servers_countwere inflated, not just the pretty summary.Every plugin package is marked by a
.claude-plugin/.codex-pluginmanifest at its root, so a hit inside one can be classified instead of guessed at:.mcp.json, package installed (both agents install underplugins/cache) → kept asclaude_plugin/codex_plugin.mcp.jsonfrom a plugin, so a plugin repo's ownopencode.json(theponytail/*hits in the issue) is not a live config either.discovered_mcpPreferred manifest detection over a list of catalog paths so this holds for agents whose plugin dirs we haven't seen yet.
Scanned before/after on the same machine: 53 → 7, exactly the seven real configs. Configs outside a plugin package are untouched. An installed-but-disabled plugin is still reported; gating on
enabledPluginsis a separate call if we want it.