# 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: -------------------------------------------------------------------------------- ``` node_modules dist/ pnpm-lock.yaml .idea ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown [](https://github.com/JetBrains#jetbrains-on-github) # ⚠️ Deprecated **This repository is no longer maintained.** The core functionality has been integrated into all IntelliJ-based IDEs since version 2025.2. The built-in functionality works with SSE and JVM-based proxy (for STDIO) so this NPM package is no longer required. **Migration:** Please refer to the [official documentation](https://www.jetbrains.com/help/idea/mcp-server.html) for details on using the built-in functionality. **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). # JetBrains MCP Proxy Server The server proxies requests from client to JetBrains IDE. ## Install MCP Server plugin https://plugins.jetbrains.com/plugin/26071-mcp-server ## VS Code Installation For one-click installation, click one of the install buttons below: [](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) ### Manual Installation 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)`. ```json { "mcp": { "servers": { "jetbrains": { "command": "npx", "args": ["-y", "@jetbrains/mcp-proxy"] } } } } ``` Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace: ```json { "servers": { "jetbrains": { "command": "npx", "args": ["-y", "@jetbrains/mcp-proxy"] } } } ``` ## Usage with Claude Desktop To use this with Claude Desktop, add the following to your `claude_desktop_config.json`. The full path on MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`, on Windows: `%APPDATA%/Claude/claude_desktop_config.json`. ```json { "mcpServers": { "jetbrains": { "command": "npx", "args": ["-y", "@jetbrains/mcp-proxy"] } } } ``` 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. ## Configuration If you're running multiple IDEs with MCP server and want to connect to the specific one, add to the MCP server configuration: ```json "env": { "IDE_PORT": "<port of IDE's built-in webserver>" } ``` By default, we connect to IDE on 127.0.0.1 but you can specify a different address/host: ```json "env": { "HOST": "<host/address of IDE's built-in webserver>" } ``` To enable logging add: ```json "env": { "LOG_ENABLED": "true" } ``` ## Troubleshooting ### Node.js Version Requirements **Problem:** Error message: `Cannot find module 'node:path'` **Solution:** MCP Proxy doesn't work on Node 16. Upgrade your Node.js installation to version 18 or later. Make sure that `command` in config points to the correct Node.js version. Try to use the full path to the latest version of NodeJS. ### ### MacOS: Plugin Unable to Detect Node.js Installed via nvm **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. **Solution:** Create a symbolic link in `/usr/local/bin` pointing to your nvm npx executable: ```bash which npx &>/dev/null && sudo ln -sf "$(which npx)" /usr/local/bin/npx ``` This one-liner checks if npx exists in your path and creates the necessary symbolic link with proper permissions. ### Using MCP with External Clients or Docker Containers (LibreChat, Cline, etc.) **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. **Solution:** There are two key issues to address: 1. Enable External Connections: In your JetBrains IDE, enable "Can accept external connections" in the _Settings | Build, Execution, Deployment | Debugger_. 2. Configure with LAN IP and Port: Use your machine's LAN IP address instead of `host.docker.internal` Explicitly set the IDE_PORT and HOST in your configuration Example configuration for LibreChat or similar external clients: ```yaml mcpServers: intellij: type: stdio command: sh args: - "-c" - "IDE_PORT=YOUR_IDEA_PORT HOST=YOUR_IDEA_LAN_IP npx -y @jetbrains/mcp-proxy" ``` Replace: `YOUR_IDEA_PORT` with your IDE's debug port (found in IDE settings) `YOUR_IDEA_LAN_IP` with your computer's local network IP (e.g., 192.168.0.12) ## How to build 1. Tested on macOS 2. `brew install node pnpm` 3. Run `pnpm build` to build the project ``` -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- ```markdown ## Code of Conduct 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 { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "outDir": "./dist", "rootDir": "." }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "@jetbrains/mcp-proxy", "version": "1.8.0", "description": "A MCP proxy to redirect requests to JetBrains IDEs", "main": "dist/src/index.js", "type": "module", "repository": { "type": "git", "url": "https://github.com/JetBrains/mcp-jetbrains.git" }, "bin": { "mcp-jetbrains-proxy": "dist/src/index.js" }, "files": [ "dist" ], "scripts": { "build": "tsc && shx chmod +x dist/src/*.js", "prepare": "npm run build", "watch": "tsc --watch" }, "dependencies": { "@modelcontextprotocol/sdk": "1.9.0", "node-fetch": "^3.3.2" }, "devDependencies": { "@types/node": "^22.14.0", "shx": "^0.4.0", "typescript": "^5.8.3" } } ``` -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- ```yaml name: Publish to NPM on: release: types: [published] jobs: build: runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - name: Install pnpm uses: pnpm/action-setup@v3 with: version: latest - name: Install dependencies run: pnpm install --frozen-lockfile - name: Publish to NPM run: | export NODE_AUTH_TOKEN=$(echo "${{ secrets.NPM_TOKEN }}" | tr -d '\n') pnpm publish --no-git-checks --access public --provenance continue-on-error: true - name: Upload npm logs uses: actions/upload-artifact@v4 with: name: npm-debug-logs path: /home/runner/.npm/_logs/*.log ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import {Server} from "@modelcontextprotocol/sdk/server/index.js"; import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js"; import {CallToolRequestSchema, CallToolResult, ListToolsRequestSchema,} from "@modelcontextprotocol/sdk/types.js"; // Logging is enabled only if LOG_ENABLED environment variable is set to 'true' const LOG_ENABLED = process.env.LOG_ENABLED === 'true'; const HOST = process.env.HOST ?? "127.0.0.1" export function log(...args: any[]) { if (LOG_ENABLED) { console.error(...args); } } interface IDEResponseOk { status: string; error: null; } interface IDEResponseErr { status: null; error: string; } type IDEResponse = IDEResponseOk | IDEResponseErr; /** * Globally store the cached IDE endpoint. * We'll update this once at the beginning and every 10 seconds. */ let cachedEndpoint: string | null = null; /** * If you need to remember the last known response from /mcp/list_tools, store it here. * That way, you won't re-check it every single time a new request comes in. */ let previousResponse: string | null = null; /** * Helper to send the "tools changed" notification. */ function sendToolsChanged() { try { log("Sending tools changed notification."); server.notification({method: "notifications/tools/list_changed"}); } catch (error) { log("Error sending tools changed notification:", error); } } /** * Test if /mcp/list_tools is responding on a given endpoint * * @returns true if working, false otherwise */ async function testListTools(endpoint: string): Promise<boolean> { log(`Sending test request to ${endpoint}/mcp/list_tools`); try { const res = await fetch(`${endpoint}/mcp/list_tools`); if (!res.ok) { log(`Test request to ${endpoint}/mcp/list_tools failed with status ${res.status}`); return false; } const currentResponse = await res.text(); log(`Received response from ${endpoint}/mcp/list_tools: ${currentResponse.substring(0, 100)}...`); // If the response changed from last time, notify if (previousResponse !== null && previousResponse !== currentResponse) { log("Response has changed since the last check."); sendToolsChanged(); } previousResponse = currentResponse; return true; } catch (error) { log(`Error during testListTools for endpoint ${endpoint}:`, error); return false; } } /** * Finds and returns a working IDE endpoint using IPv4 by: * 1. Checking process.env.IDE_PORT, or * 2. Scanning ports 63342-63352 * * Throws if none found. */ async function findWorkingIDEEndpoint(): Promise<string> { log("Attempting to find a working IDE endpoint..."); // 1. If user specified a port, just use that if (process.env.IDE_PORT) { log(`IDE_PORT is set to ${process.env.IDE_PORT}. Testing this port.`); const testEndpoint = `http://${HOST}:${process.env.IDE_PORT}/api`; if (await testListTools(testEndpoint)) { log(`IDE_PORT ${process.env.IDE_PORT} is working.`); return testEndpoint; } else { log(`Specified IDE_PORT=${process.env.IDE_PORT} but it is not responding correctly.`); throw new Error(`Specified IDE_PORT=${process.env.IDE_PORT} but it is not responding correctly.`); } } // 2. Reuse existing endpoint if it's still working if (cachedEndpoint != null && await testListTools(cachedEndpoint)) { log('Using cached endpoint, it\'s still working') return cachedEndpoint } // 3. Otherwise, scan a range of ports for (let port = 63342; port <= 63352; port++) { const candidateEndpoint = `http://${HOST}:${port}/api`; log(`Testing port ${port}...`); const isWorking = await testListTools(candidateEndpoint); if (isWorking) { log(`Found working IDE endpoint at ${candidateEndpoint}`); return candidateEndpoint; } else { log(`Port ${port} is not responding correctly.`); } } // If we reach here, no port was found previousResponse = ""; log("No working IDE endpoint found in range 63342-63352"); throw new Error("No working IDE endpoint found in range 63342-63352"); } /** * Updates the cached endpoint by finding a working IDE endpoint. * This runs once at startup and then once every 10 seconds in runServer(). */ async function updateIDEEndpoint() { try { cachedEndpoint = await findWorkingIDEEndpoint(); log(`Updated cachedEndpoint to: ${cachedEndpoint}`); } catch (error) { // If we fail to find a working endpoint, keep the old one if it existed. // It's up to you how to handle this scenario (e.g., set cachedEndpoint = null). log("Failed to update IDE endpoint:", error); } } /** * Main MCP server */ const server = new Server( { name: "jetbrains/proxy", version: "0.1.0", }, { capabilities: { tools: { listChanged: true, }, resources: {}, }, 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. " + "All requests should be formatted as JSON objects according to the Model Context Protocol specification." }, ); /** * Handles listing tools by using the *cached* endpoint (no new search each time). */ server.setRequestHandler(ListToolsRequestSchema, async () => { log("Handling ListToolsRequestSchema request."); if (!cachedEndpoint) { // If no cached endpoint, we can't proceed throw new Error("No working IDE endpoint available."); } try { log(`Using cached endpoint ${cachedEndpoint} to list tools.`); const toolsResponse = await fetch(`${cachedEndpoint}/mcp/list_tools`); if (!toolsResponse.ok) { log(`Failed to fetch tools with status ${toolsResponse.status}`); throw new Error("Unable to list tools"); } const tools = await toolsResponse.json(); log(`Successfully fetched tools: ${JSON.stringify(tools)}`); return {tools}; } catch (error) { log("Error handling ListToolsRequestSchema request:", error); throw error; } }); /** * Handle calls to a specific tool by using the *cached* endpoint. */ async function handleToolCall(name: string, args: any): Promise<CallToolResult> { log(`Handling tool call: name=${name}, args=${JSON.stringify(args)}`); if (!cachedEndpoint) { // If no cached endpoint, we can't proceed throw new Error("No working IDE endpoint available."); } try { log(`ENDPOINT: ${cachedEndpoint} | Tool name: ${name} | args: ${JSON.stringify(args)}`); const response = await fetch(`${cachedEndpoint}/mcp/${name}`, { method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify(args), }); if (!response.ok) { log(`Response failed with status ${response.status} for tool ${name}`); throw new Error(`Response failed: ${response.status}`); } // Parse the IDE's JSON response const {status, error}: IDEResponse = await response.json(); log("Parsed response:", {status, error}); const isError = !!error; const text = status ?? error; log("Final response text:", text); log("Is error:", isError); return { content: [{type: "text", text: text}], isError, }; } catch (error: any) { log("Error in handleToolCall:", error); return { content: [{ type: "text", text: error instanceof Error ? error.message : "Unknown error", }], isError: true, }; } } // 1) Do an initial endpoint check (once at startup) await updateIDEEndpoint(); /** * Request handler for "CallToolRequestSchema" */ server.setRequestHandler(CallToolRequestSchema, async (request) => { log("Handling CallToolRequestSchema request:", request); try { const result = await handleToolCall(request.params.name, request.params.arguments ?? {}); log("Tool call handled successfully:", result); return result; } catch (error) { log("Error handling CallToolRequestSchema request:", error); throw error; } }); /** * Starts the server, connects via stdio, and schedules endpoint checks. */ async function runServer() { log("Initializing server..."); const transport = new StdioServerTransport(); try { await server.connect(transport); log("Server connected to transport."); } catch (error) { log("Error connecting server to transport:", error); throw error; } // 2) Then check again every 10 seconds (in case IDE restarts or ports change) setInterval(updateIDEEndpoint, 10_000); log("Scheduled endpoint check every 10 seconds."); log("JetBrains Proxy MCP Server running on stdio"); } // Start the server runServer().catch(error => { log("Server failed to start:", error); }); ```