Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 134
feat: auto-discover MCP servers from external AI tool configs#311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -7,6 +7,7 @@ __pycache__ | ||
| .idea | ||
| .vscode | ||
| .codex | ||
| .claude | ||
| *~ | ||
| playground | ||
| tmp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| --- | ||
| description: "Discover MCP servers from external AI tool configs and add them permanently" | ||
| --- | ||
| Discover MCP servers configured in other AI tools (VS Code, Cursor, GitHub Copilot, Claude Code, Gemini CLI) and add them to the altimate-code config. | ||
| ## Instructions | ||
| 1. First, call the `mcp_discover` tool with `action: "list"` to see what's available. | ||
| 2. Show the user the results — which servers are new and which are already configured. | ||
| 3. If there are new servers, ask the user which ones they want to add and what scope (project or global). | ||
| 4. Call `mcp_discover` with `action: "add"`, the chosen `scope`, and the selected `servers` array. | ||
| 5. Report what was added and where. | ||
| If $ARGUMENTS contains `--scope global`, use `scope: "global"`. Otherwise default to `scope: "project"`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -15,4 +15,7 @@ | ||
| "github-triage": false, | ||
| "github-pr-search": false, | ||
| }, | ||
| "experimental": { | ||
| "auto_mcp_discovery": false, | ||
| }, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -110,3 +110,41 @@ When [LSP servers](lsp.md) are configured, the `lsp` tool provides: | ||
| - Go-to-definition | ||
| - Hover information | ||
| - Completions | ||
| ### MCP Discover Tool | ||
| The `mcp_discover` tool finds MCP servers configured in other AI coding tools and can add them to your altimate-code config permanently. | ||
| **Supported sources:** | ||
| | Tool | Config Path | Key | | ||
| |------|------------|-----| | ||
| | VS Code / Copilot | `.vscode/mcp.json` | `servers` | | ||
| | Cursor | `.cursor/mcp.json` | `mcpServers` | | ||
| | GitHub Copilot | `.github/copilot/mcp.json` | `mcpServers` | | ||
| | Claude Code | `.mcp.json`, `~/.claude.json` | `mcpServers` | | ||
| | Gemini CLI | `.gemini/settings.json` | `mcpServers` | | ||
| **Actions:** | ||
| - `mcp_discover(action: "list")` — Show discovered servers and which are already in your config | ||
| - `mcp_discover(action: "add", scope: "project")` — Write new servers to `.altimate-code/altimate-code.json` | ||
| - `mcp_discover(action: "add", scope: "global")` — Write to the global config dir (`~/.config/opencode/`) | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| **Auto-discovery:** At startup, altimate-code discovers external MCP servers and shows a toast notification. Servers from your home directory (`~/.claude.json`, `~/.gemini/settings.json`) are auto-enabled since they're user-owned. Servers from project-level files (`.vscode/mcp.json`, `.mcp.json`, `.cursor/mcp.json`) are discovered but **disabled by default** for security — run `/discover-and-add-mcps` to review and enable them. | ||
| !!! tip | ||
| Home-directory MCP servers (from `~/.claude.json`, `~/.gemini/settings.json`) are loaded automatically. Project-scoped servers require explicit approval via `/discover-and-add-mcps` or `mcp_discover(action: "add")`. | ||
| !!! warning "Security: untrusted repositories" | ||
| Project-level MCP configs (`.vscode/mcp.json`, `.mcp.json`, `.cursor/mcp.json`) are discovered but not auto-connected. This prevents malicious repositories from executing arbitrary commands. You must explicitly approve project-scoped servers before they run. | ||
| To disable auto-discovery, set in your config: | ||
| ```json | ||
| { | ||
| "experimental": { | ||
| "auto_mcp_discovery": false | ||
| } | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import z from "zod" | ||
| import { Tool } from "../../tool/tool" | ||
| import { discoverExternalMcp } from "../../mcp/discover" | ||
| import { resolveConfigPath, addMcpToConfig, findAllConfigPaths, listMcpInConfig } from "../../mcp/config" | ||
| import { Instance } from "../../project/instance" | ||
| import { Global } from "../../global" | ||
| /** | ||
| * Check which MCP server names are permanently configured on disk | ||
| * (as opposed to ephemeral auto-discovered servers in memory). | ||
| */ | ||
| async function getPersistedMcpNames(): Promise<Set<string>> { | ||
| const configPaths = await findAllConfigPaths(Instance.worktree, Global.Path.config) | ||
| const names = new Set<string>() | ||
| for (const p of configPaths) { | ||
| for (const name of await listMcpInConfig(p)) { | ||
| names.add(name) | ||
| } | ||
| } | ||
| return names | ||
| } | ||
| /** Redact server details for safe display — show type and name only, not commands/URLs */ | ||
| function safeDetail(server: { type: string } & Record<string, any>): string { | ||
| if (server.type === "remote") return "(remote)" | ||
| if (server.type === "local" && Array.isArray(server.command) && server.command.length > 0) { | ||
| // Show only the executable name, not args (which may contain credentials) | ||
| return `(local: ${server.command[0]})` | ||
| } | ||
| return `(${server.type})` | ||
| } | ||
| export const McpDiscoverTool = Tool.define("mcp_discover", { | ||
| description: | ||
| "Discover MCP servers from external AI tool configs (VS Code, Cursor, Claude Code, Copilot, Gemini) and optionally add them to altimate-code config permanently.", | ||
| parameters: z.object({ | ||
| action: z | ||
| .enum(["list", "add"]) | ||
| .describe('"list" to show discovered servers, "add" to write them to config'), | ||
| scope: z | ||
| .enum(["project", "global"]) | ||
| .optional() | ||
| .default("project") | ||
| .describe('Where to write when action is "add". "project" = .altimate-code/altimate-code.json, "global" = ~/.config/opencode/'), | ||
| servers: z | ||
| .array(z.string()) | ||
| .optional() | ||
| .describe('Server names to add. If omitted with action "add", adds all new servers.'), | ||
| }), | ||
| async execute(args, ctx) { | ||
| const { servers: discovered } = await discoverExternalMcp(Instance.worktree) | ||
| const discoveredNames = Object.keys(discovered) | ||
| if (discoveredNames.length === 0) { | ||
| return { | ||
| title: "MCP Discover: none found", | ||
| metadata: { discovered: 0, new: 0, existing: 0, added: 0 }, | ||
| output: | ||
| "No MCP servers found in external configs.\nChecked: .vscode/mcp.json, .cursor/mcp.json, .github/copilot/mcp.json, .mcp.json (project + home), .gemini/settings.json (project + home), ~/.claude.json", | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| // Check what's actually persisted on disk, NOT the merged in-memory config | ||
| const persistedNames = await getPersistedMcpNames() | ||
| const newServers = discoveredNames.filter((n) => !persistedNames.has(n)) | ||
| const alreadyAdded = discoveredNames.filter((n) => persistedNames.has(n)) | ||
| // Build discovery report — redact details for security (no raw commands/URLs) | ||
| const lines: string[] = [] | ||
| if (newServers.length > 0) { | ||
| lines.push(`New servers (not yet in config):`) | ||
| for (const name of newServers) { | ||
| lines.push(` - ${name} ${safeDetail(discovered[name])}`) | ||
| } | ||
| } | ||
| if (alreadyAdded.length > 0) { | ||
| lines.push(`\nAlready in config: ${alreadyAdded.join(", ")}`) | ||
| } | ||
| if (args.action === "list") { | ||
| return { | ||
| title: `MCP Discover: ${newServers.length} new, ${alreadyAdded.length} existing`, | ||
| metadata: { discovered: discoveredNames.length, new: newServers.length, existing: alreadyAdded.length, added: 0 }, | ||
| output: lines.join("\n"), | ||
| } | ||
| } | ||
| // action === "add" | ||
| const toAdd = args.servers | ||
| ? args.servers.filter((n) => newServers.includes(n)) | ||
| : newServers | ||
| if (toAdd.length === 0) { | ||
| return { | ||
| title: "MCP Discover: nothing to add", | ||
| metadata: { discovered: discoveredNames.length, new: newServers.length, existing: alreadyAdded.length, added: 0 }, | ||
| output: lines.join("\n") + "\n\nNo matching servers to add.", | ||
| } | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const useGlobal = args.scope === "global" | ||
| const configPath = await resolveConfigPath( | ||
| useGlobal ? Global.Path.config : Instance.worktree, | ||
| useGlobal, | ||
| ) | ||
| for (const name of toAdd) { | ||
| await addMcpToConfig(name, discovered[name], configPath) | ||
| } | ||
| lines.push(`\nAdded ${toAdd.length} server(s) to ${configPath}: ${toAdd.join(", ")}`) | ||
| lines.push("These servers are already active in the current session via auto-discovery.") | ||
| return { | ||
| title: `MCP Discover: added ${toAdd.length} server(s)`, | ||
| metadata: { discovered: discoveredNames.length, new: newServers.length, existing: alreadyAdded.length, added: toAdd.length }, | ||
| output: lines.join("\n"), | ||
| } | ||
| }, | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -264,6 +264,24 @@ export namespace Config { | ||
| result.plugin = deduplicatePlugins(result.plugin ?? []) | ||
| // altimate_change start — auto-discover MCP servers from external AI tool configs | ||
| if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG && result.experimental?.auto_mcp_discovery !== false) { | ||
| const { discoverExternalMcp, setDiscoveryResult } = await import("../mcp/discover") | ||
| const { servers: externalMcp, sources } = await discoverExternalMcp(Instance.worktree) | ||
| if (Object.keys(externalMcp).length > 0) { | ||
| result.mcp ??= {} | ||
| const added: string[] = [] | ||
| for (const [name, server] of Object.entries(externalMcp)) { | ||
| if (!(name in result.mcp)) { | ||
| result.mcp[name] = server | ||
| added.push(name) | ||
| } | ||
| } | ||
| setDiscoveryResult(added, sources) | ||
Comment on lines
+267
to
+280
| ||
| } | ||
| } | ||
| // altimate_change end | ||
| return { | ||
| config: result, | ||
| directories, | ||
| @@ -1273,6 +1291,12 @@ export namespace Config { | ||
| .optional() | ||
| .describe("Use environment fingerprint to select relevant skills once per session (default: false). Set to true to enable LLM-based skill filtering."), | ||
| // altimate_change end | ||
| // altimate_change start - auto MCP discovery toggle | ||
| auto_mcp_discovery: z | ||
| .boolean() | ||
| .optional() | ||
| .describe("Auto-discover MCP servers from VS Code, Claude Code, Copilot, and Gemini configs at startup (default: true). Set to false to disable."), | ||
| // altimate_change end | ||
| }) | ||
| .optional(), | ||
| }) | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.