| 82 | ].join('\n'); |
| 83 | |
| 84 | class CursorTarget implements AgentTarget { |
| 85 | readonly id = 'cursor' as const; |
| 86 | readonly displayName = 'Cursor'; |
| 87 | readonly docsUrl = 'https://docs.cursor.com/context/model-context-protocol'; |
| 88 | |
| 89 | supportsLocation(_loc: Location): boolean { |
| 90 | // Both supported, but `local` writes more files (mcp.json + rules); |
| 91 | // `global` writes only mcp.json. The orchestrator surfaces the |
| 92 | // difference via describePaths. |
| 93 | return true; |
| 94 | } |
| 95 | |
| 96 | detect(loc: Location): DetectionResult { |
| 97 | const mcpPath = mcpJsonPath(loc); |
| 98 | const config = readJsonFile(mcpPath); |
| 99 | const alreadyConfigured = !!config.mcpServers?.codegraph; |
| 100 | // "Installed" heuristic: does ~/.cursor exist (global) or has the |
| 101 | // user opted into a project-local cursor config dir? |
| 102 | const installed = loc === 'global' |
| 103 | ? fs.existsSync(path.join(os.homedir(), '.cursor')) |
| 104 | : fs.existsSync(path.join(process.cwd(), '.cursor')); |
| 105 | return { installed, alreadyConfigured, configPath: mcpPath }; |
| 106 | } |
| 107 | |
| 108 | install(loc: Location, _opts: InstallOptions): WriteResult { |
| 109 | const files: WriteResult['files'] = []; |
| 110 | |
| 111 | files.push(writeMcpEntry(loc)); |
| 112 | |
| 113 | // We no longer write `.cursor/rules/codegraph.mdc` — the codegraph |
| 114 | // usage guidance ships in the MCP server's `initialize` response, |
| 115 | // the single source of truth (issue #529). Strip a rules file a |
| 116 | // previous install created so an upgrade self-heals. |
| 117 | if (loc === 'local') { |
| 118 | const rulesCleanup = removeRulesEntry(); |
| 119 | if (rulesCleanup.action === 'removed') files.push(rulesCleanup); |
| 120 | } |
| 121 | |
| 122 | return { |
| 123 | files, |
| 124 | notes: ['Restart Cursor for MCP changes to take effect.'], |
| 125 | }; |
| 126 | } |
| 127 | |
| 128 | uninstall(loc: Location): WriteResult { |
| 129 | const files: WriteResult['files'] = []; |
| 130 | |
| 131 | const mcpPath = mcpJsonPath(loc); |
| 132 | const config = readJsonFile(mcpPath); |
| 133 | if (config.mcpServers?.codegraph) { |
| 134 | delete config.mcpServers.codegraph; |
| 135 | if (Object.keys(config.mcpServers).length === 0) { |
| 136 | delete config.mcpServers; |
| 137 | } |
| 138 | writeJsonFile(mcpPath, config); |
| 139 | files.push({ path: mcpPath, action: 'removed' }); |
| 140 | } else { |
| 141 | files.push({ path: mcpPath, action: 'not-found' }); |
nothing calls this directly
no outgoing calls
no test coverage detected