#
tokens: 5801/50000 7/7 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [![official JetBrains project](http://jb.gg/badges/incubator-flat-square.svg)](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 | [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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 | });
```