# Directory Structure ``` ├── .github │ └── workflows │ └── publish.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── package.json ├── pnpm-lock.yaml ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules 2 | dist/ 3 | pnpm-lock.yaml 4 | .idea ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | [](https://github.com/JetBrains#jetbrains-on-github) 2 | 3 | # ⚠️ Deprecated 4 | 5 | **This repository is no longer maintained.** The core functionality has been integrated into all IntelliJ-based IDEs since version 2025.2. 6 | The built-in functionality works with SSE and JVM-based proxy (for STDIO) so this NPM package is no longer required. 7 | 8 | **Migration:** Please refer to the [official documentation](https://www.jetbrains.com/help/idea/mcp-server.html) for details on using the built-in functionality. 9 | 10 | **Issues & Support:** For bugs or feature requests related to the built-in MCP functionality, please use the [JetBrains YouTrack](https://youtrack.jetbrains.com/issues?q=project:%20IJPL%20Subsystem:%20%7BMCP%20(Model%20Context%20Protocol)%7D%20). 11 | 12 | # JetBrains MCP Proxy Server 13 | 14 | The server proxies requests from client to JetBrains IDE. 15 | 16 | ## Install MCP Server plugin 17 | 18 | https://plugins.jetbrains.com/plugin/26071-mcp-server 19 | 20 | ## VS Code Installation 21 | 22 | For one-click installation, click one of the install buttons below: 23 | 24 | [](https://insiders.vscode.dev/redirect/mcp/install?name=jetbrains&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40jetbrains%2Fmcp-proxy%22%5D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=jetbrains&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40jetbrains%2Fmcp-proxy%22%5D%7D&quality=insiders) 25 | 26 | ### Manual Installation 27 | 28 | Add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`. 29 | 30 | ```json 31 | { 32 | "mcp": { 33 | "servers": { 34 | "jetbrains": { 35 | "command": "npx", 36 | "args": ["-y", "@jetbrains/mcp-proxy"] 37 | } 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace: 44 | 45 | ```json 46 | { 47 | "servers": { 48 | "jetbrains": { 49 | "command": "npx", 50 | "args": ["-y", "@jetbrains/mcp-proxy"] 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | ## Usage with Claude Desktop 57 | 58 | To use this with Claude Desktop, add the following to your `claude_desktop_config.json`. 59 | The full path on MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`, on Windows: `%APPDATA%/Claude/claude_desktop_config.json`. 60 | 61 | ```json 62 | { 63 | "mcpServers": { 64 | "jetbrains": { 65 | "command": "npx", 66 | "args": ["-y", "@jetbrains/mcp-proxy"] 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | After installing the MCP Server Plugin, and adding the JSON to the config file, restart Claude Desktop, and make sure the Jetbrains product is open before restarting Claude Desktop. 73 | 74 | ## Configuration 75 | 76 | If you're running multiple IDEs with MCP server and want to connect to the specific one, add to the MCP server configuration: 77 | ```json 78 | "env": { 79 | "IDE_PORT": "<port of IDE's built-in webserver>" 80 | } 81 | ``` 82 | 83 | By default, we connect to IDE on 127.0.0.1 but you can specify a different address/host: 84 | ```json 85 | "env": { 86 | "HOST": "<host/address of IDE's built-in webserver>" 87 | } 88 | ``` 89 | 90 | To enable logging add: 91 | ```json 92 | "env": { 93 | "LOG_ENABLED": "true" 94 | } 95 | ``` 96 | 97 | ## Troubleshooting 98 | 99 | ### Node.js Version Requirements 100 | **Problem:** Error message: `Cannot find module 'node:path'` 101 | 102 | **Solution:** 103 | MCP Proxy doesn't work on Node 16. 104 | Upgrade your Node.js installation to version 18 or later. Make sure that `command` in config points to the correct Node.js version. 105 | Try to use the full path to the latest version of NodeJS. 106 | 107 | ### 108 | 109 | ### MacOS: Plugin Unable to Detect Node.js Installed via nvm 110 | **Problem:** On MacOS, if you have Node.js installed through nvm (Node Version Manager), the MCP Server Plugin might be unable to detect your Node.js installation. 111 | 112 | **Solution:** Create a symbolic link in `/usr/local/bin` pointing to your nvm npx executable: 113 | ```bash 114 | which npx &>/dev/null && sudo ln -sf "$(which npx)" /usr/local/bin/npx 115 | ``` 116 | This one-liner checks if npx exists in your path and creates the necessary symbolic link with proper permissions. 117 | 118 | ### Using MCP with External Clients or Docker Containers (LibreChat, Cline, etc.) 119 | 120 | **Problem:** When attempting to connect to the JetBrains MCP proxy from external clients, Docker containers, or third-party applications (like LibreChat), requests to endpoints such as http://host.docker.internal:6365/api/mcp/list_tools may return 404 errors or fail to connect. 121 | **Solution:** There are two key issues to address: 122 | 1. Enable External Connections: 123 | 124 | In your JetBrains IDE, enable "Can accept external connections" in the _Settings | Build, Execution, Deployment | Debugger_. 125 | 126 | 2. Configure with LAN IP and Port: 127 | 128 | Use your machine's LAN IP address instead of `host.docker.internal` 129 | Explicitly set the IDE_PORT and HOST in your configuration 130 | Example configuration for LibreChat or similar external clients: 131 | ```yaml 132 | mcpServers: 133 | intellij: 134 | type: stdio 135 | command: sh 136 | args: 137 | - "-c" 138 | - "IDE_PORT=YOUR_IDEA_PORT HOST=YOUR_IDEA_LAN_IP npx -y @jetbrains/mcp-proxy" 139 | ``` 140 | Replace: 141 | 142 | `YOUR_IDEA_PORT` with your IDE's debug port (found in IDE settings) 143 | `YOUR_IDEA_LAN_IP` with your computer's local network IP (e.g., 192.168.0.12) 144 | 145 | 146 | ## How to build 147 | 1. Tested on macOS 148 | 2. `brew install node pnpm` 149 | 3. Run `pnpm build` to build the project 150 | 151 | ``` -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Code of Conduct 2 | 3 | This project and the corresponding community is governed by the [JetBrains Open Source and Community Code of Conduct](https://confluence.jetbrains.com/display/ALL/JetBrains+Open+Source+and+Community+Code+of+Conduct). Please make sure you read it. ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "outDir": "./dist", 12 | "rootDir": "." 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules"] 16 | } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@jetbrains/mcp-proxy", 3 | "version": "1.8.0", 4 | "description": "A MCP proxy to redirect requests to JetBrains IDEs", 5 | "main": "dist/src/index.js", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/JetBrains/mcp-jetbrains.git" 10 | }, 11 | "bin": { 12 | "mcp-jetbrains-proxy": "dist/src/index.js" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "build": "tsc && shx chmod +x dist/src/*.js", 19 | "prepare": "npm run build", 20 | "watch": "tsc --watch" 21 | }, 22 | "dependencies": { 23 | "@modelcontextprotocol/sdk": "1.9.0", 24 | "node-fetch": "^3.3.2" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^22.14.0", 28 | "shx": "^0.4.0", 29 | "typescript": "^5.8.3" 30 | } 31 | } ``` -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- ```yaml 1 | name: Publish to NPM 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: '20.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v3 22 | with: 23 | version: latest 24 | 25 | - name: Install dependencies 26 | run: pnpm install --frozen-lockfile 27 | 28 | - name: Publish to NPM 29 | run: | 30 | export NODE_AUTH_TOKEN=$(echo "${{ secrets.NPM_TOKEN }}" | tr -d '\n') 31 | pnpm publish --no-git-checks --access public --provenance 32 | continue-on-error: true 33 | 34 | - name: Upload npm logs 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: npm-debug-logs 38 | path: /home/runner/.npm/_logs/*.log 39 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import {Server} from "@modelcontextprotocol/sdk/server/index.js"; 3 | import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import {CallToolRequestSchema, CallToolResult, ListToolsRequestSchema,} from "@modelcontextprotocol/sdk/types.js"; 5 | 6 | // Logging is enabled only if LOG_ENABLED environment variable is set to 'true' 7 | const LOG_ENABLED = process.env.LOG_ENABLED === 'true'; 8 | 9 | const HOST = process.env.HOST ?? "127.0.0.1" 10 | 11 | export function log(...args: any[]) { 12 | if (LOG_ENABLED) { 13 | console.error(...args); 14 | } 15 | } 16 | 17 | interface IDEResponseOk { 18 | status: string; 19 | error: null; 20 | } 21 | 22 | interface IDEResponseErr { 23 | status: null; 24 | error: string; 25 | } 26 | 27 | type IDEResponse = IDEResponseOk | IDEResponseErr; 28 | 29 | /** 30 | * Globally store the cached IDE endpoint. 31 | * We'll update this once at the beginning and every 10 seconds. 32 | */ 33 | let cachedEndpoint: string | null = null; 34 | 35 | /** 36 | * If you need to remember the last known response from /mcp/list_tools, store it here. 37 | * That way, you won't re-check it every single time a new request comes in. 38 | */ 39 | let previousResponse: string | null = null; 40 | 41 | /** 42 | * Helper to send the "tools changed" notification. 43 | */ 44 | function sendToolsChanged() { 45 | try { 46 | log("Sending tools changed notification."); 47 | server.notification({method: "notifications/tools/list_changed"}); 48 | } catch (error) { 49 | log("Error sending tools changed notification:", error); 50 | } 51 | } 52 | 53 | /** 54 | * Test if /mcp/list_tools is responding on a given endpoint 55 | * 56 | * @returns true if working, false otherwise 57 | */ 58 | async function testListTools(endpoint: string): Promise<boolean> { 59 | log(`Sending test request to ${endpoint}/mcp/list_tools`); 60 | try { 61 | const res = await fetch(`${endpoint}/mcp/list_tools`); 62 | if (!res.ok) { 63 | log(`Test request to ${endpoint}/mcp/list_tools failed with status ${res.status}`); 64 | return false; 65 | } 66 | 67 | const currentResponse = await res.text(); 68 | log(`Received response from ${endpoint}/mcp/list_tools: ${currentResponse.substring(0, 100)}...`); 69 | 70 | // If the response changed from last time, notify 71 | if (previousResponse !== null && previousResponse !== currentResponse) { 72 | log("Response has changed since the last check."); 73 | sendToolsChanged(); 74 | } 75 | previousResponse = currentResponse; 76 | 77 | return true; 78 | } catch (error) { 79 | log(`Error during testListTools for endpoint ${endpoint}:`, error); 80 | return false; 81 | } 82 | } 83 | 84 | /** 85 | * Finds and returns a working IDE endpoint using IPv4 by: 86 | * 1. Checking process.env.IDE_PORT, or 87 | * 2. Scanning ports 63342-63352 88 | * 89 | * Throws if none found. 90 | */ 91 | async function findWorkingIDEEndpoint(): Promise<string> { 92 | log("Attempting to find a working IDE endpoint..."); 93 | 94 | // 1. If user specified a port, just use that 95 | if (process.env.IDE_PORT) { 96 | log(`IDE_PORT is set to ${process.env.IDE_PORT}. Testing this port.`); 97 | const testEndpoint = `http://${HOST}:${process.env.IDE_PORT}/api`; 98 | if (await testListTools(testEndpoint)) { 99 | log(`IDE_PORT ${process.env.IDE_PORT} is working.`); 100 | return testEndpoint; 101 | } else { 102 | log(`Specified IDE_PORT=${process.env.IDE_PORT} but it is not responding correctly.`); 103 | throw new Error(`Specified IDE_PORT=${process.env.IDE_PORT} but it is not responding correctly.`); 104 | } 105 | } 106 | 107 | // 2. Reuse existing endpoint if it's still working 108 | if (cachedEndpoint != null && await testListTools(cachedEndpoint)) { 109 | log('Using cached endpoint, it\'s still working') 110 | return cachedEndpoint 111 | } 112 | 113 | // 3. Otherwise, scan a range of ports 114 | for (let port = 63342; port <= 63352; port++) { 115 | const candidateEndpoint = `http://${HOST}:${port}/api`; 116 | log(`Testing port ${port}...`); 117 | const isWorking = await testListTools(candidateEndpoint); 118 | if (isWorking) { 119 | log(`Found working IDE endpoint at ${candidateEndpoint}`); 120 | return candidateEndpoint; 121 | } else { 122 | log(`Port ${port} is not responding correctly.`); 123 | } 124 | } 125 | 126 | // If we reach here, no port was found 127 | previousResponse = ""; 128 | log("No working IDE endpoint found in range 63342-63352"); 129 | throw new Error("No working IDE endpoint found in range 63342-63352"); 130 | } 131 | 132 | /** 133 | * Updates the cached endpoint by finding a working IDE endpoint. 134 | * This runs once at startup and then once every 10 seconds in runServer(). 135 | */ 136 | async function updateIDEEndpoint() { 137 | try { 138 | cachedEndpoint = await findWorkingIDEEndpoint(); 139 | log(`Updated cachedEndpoint to: ${cachedEndpoint}`); 140 | } catch (error) { 141 | // If we fail to find a working endpoint, keep the old one if it existed. 142 | // It's up to you how to handle this scenario (e.g., set cachedEndpoint = null). 143 | log("Failed to update IDE endpoint:", error); 144 | } 145 | } 146 | 147 | /** 148 | * Main MCP server 149 | */ 150 | const server = new Server( 151 | { 152 | name: "jetbrains/proxy", 153 | version: "0.1.0", 154 | }, 155 | { 156 | capabilities: { 157 | tools: { 158 | listChanged: true, 159 | }, 160 | resources: {}, 161 | }, 162 | instructions: "You can interact with an JetBrains IntelliJ IDE and its features through this MCP (Model Context Protocol) server. The server provides access to various IDE tools and functionalities. " + 163 | "All requests should be formatted as JSON objects according to the Model Context Protocol specification." 164 | }, 165 | 166 | ); 167 | 168 | /** 169 | * Handles listing tools by using the *cached* endpoint (no new search each time). 170 | */ 171 | server.setRequestHandler(ListToolsRequestSchema, async () => { 172 | log("Handling ListToolsRequestSchema request."); 173 | if (!cachedEndpoint) { 174 | // If no cached endpoint, we can't proceed 175 | throw new Error("No working IDE endpoint available."); 176 | } 177 | try { 178 | log(`Using cached endpoint ${cachedEndpoint} to list tools.`); 179 | const toolsResponse = await fetch(`${cachedEndpoint}/mcp/list_tools`); 180 | if (!toolsResponse.ok) { 181 | log(`Failed to fetch tools with status ${toolsResponse.status}`); 182 | throw new Error("Unable to list tools"); 183 | } 184 | const tools = await toolsResponse.json(); 185 | log(`Successfully fetched tools: ${JSON.stringify(tools)}`); 186 | return {tools}; 187 | } catch (error) { 188 | log("Error handling ListToolsRequestSchema request:", error); 189 | throw error; 190 | } 191 | }); 192 | 193 | /** 194 | * Handle calls to a specific tool by using the *cached* endpoint. 195 | */ 196 | async function handleToolCall(name: string, args: any): Promise<CallToolResult> { 197 | log(`Handling tool call: name=${name}, args=${JSON.stringify(args)}`); 198 | if (!cachedEndpoint) { 199 | // If no cached endpoint, we can't proceed 200 | throw new Error("No working IDE endpoint available."); 201 | } 202 | 203 | try { 204 | log(`ENDPOINT: ${cachedEndpoint} | Tool name: ${name} | args: ${JSON.stringify(args)}`); 205 | const response = await fetch(`${cachedEndpoint}/mcp/${name}`, { 206 | method: 'POST', 207 | headers: { 208 | "Content-Type": "application/json", 209 | }, 210 | body: JSON.stringify(args), 211 | }); 212 | 213 | if (!response.ok) { 214 | log(`Response failed with status ${response.status} for tool ${name}`); 215 | throw new Error(`Response failed: ${response.status}`); 216 | } 217 | 218 | // Parse the IDE's JSON response 219 | const {status, error}: IDEResponse = await response.json(); 220 | log("Parsed response:", {status, error}); 221 | 222 | const isError = !!error; 223 | const text = status ?? error; 224 | log("Final response text:", text); 225 | log("Is error:", isError); 226 | 227 | return { 228 | content: [{type: "text", text: text}], 229 | isError, 230 | }; 231 | } catch (error: any) { 232 | log("Error in handleToolCall:", error); 233 | return { 234 | content: [{ 235 | type: "text", 236 | text: error instanceof Error ? error.message : "Unknown error", 237 | }], 238 | isError: true, 239 | }; 240 | } 241 | } 242 | 243 | // 1) Do an initial endpoint check (once at startup) 244 | await updateIDEEndpoint(); 245 | 246 | /** 247 | * Request handler for "CallToolRequestSchema" 248 | */ 249 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 250 | log("Handling CallToolRequestSchema request:", request); 251 | try { 252 | const result = await handleToolCall(request.params.name, request.params.arguments ?? {}); 253 | log("Tool call handled successfully:", result); 254 | return result; 255 | } catch (error) { 256 | log("Error handling CallToolRequestSchema request:", error); 257 | throw error; 258 | } 259 | }); 260 | 261 | /** 262 | * Starts the server, connects via stdio, and schedules endpoint checks. 263 | */ 264 | async function runServer() { 265 | log("Initializing server..."); 266 | 267 | const transport = new StdioServerTransport(); 268 | try { 269 | await server.connect(transport); 270 | log("Server connected to transport."); 271 | } catch (error) { 272 | log("Error connecting server to transport:", error); 273 | throw error; 274 | } 275 | 276 | // 2) Then check again every 10 seconds (in case IDE restarts or ports change) 277 | setInterval(updateIDEEndpoint, 10_000); 278 | log("Scheduled endpoint check every 10 seconds."); 279 | 280 | log("JetBrains Proxy MCP Server running on stdio"); 281 | } 282 | 283 | // Start the server 284 | runServer().catch(error => { 285 | log("Server failed to start:", error); 286 | }); ```