#
tokens: 4387/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# 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
[![official JetBrains project](http://jb.gg/badges/incubator-flat-square.svg)](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:

[![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)

### 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);
});
```