Skip to content

Commit 6be3bd9

Browse files
committed
Expose MCP connection info and token
Add support for exposing MCP connection info (ws port, token, binary path, running state) so external IDEs can connect. Implements a new Show MCP Connection Info command (with quick-pick and clipboard copy) and registers a getConnectionInfo IPC call. Passes session token into the MCP installer and includes --token in generated mcp_servers entries. Update stdio MCP tool to accept a --token CLI arg and enforce authentication in the WebSocket bridge (CLI token > env token). Also update contributions/settings to document the feature.
1 parent ca37338 commit 6be3bd9

11 files changed

Lines changed: 168 additions & 24 deletions

File tree

src/vs/workbench/contrib/roopik/browser/commands/mcpCommands.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { registerAction2, Action2 } from '../../../../../platform/actions/common
1818
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
1919
import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js';
2020
import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js';
21+
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
2122
import { IMcpServerService, AgentId, AgentStatus } from '../../common/mcp/index.js';
2223

2324
/**
@@ -236,4 +237,75 @@ export function registerMcpCommands(): void {
236237
}
237238
}
238239
});
240+
241+
// Show MCP Connection Info (for external IDEs)
242+
registerAction2(class extends Action2 {
243+
constructor() {
244+
super({
245+
id: 'roopik.mcp.showConnectionInfo',
246+
title: localize2('roopik.mcp.showConnectionInfo', 'Show MCP Connection Info'),
247+
category: localize2('roopik.category', 'Roopik'),
248+
f1: true
249+
});
250+
}
251+
252+
async run(accessor: ServicesAccessor): Promise<void> {
253+
const notificationService = accessor.get(INotificationService);
254+
const mcpServerService = accessor.get(IMcpServerService);
255+
const quickInputService = accessor.get(IQuickInputService);
256+
const clipboardService = accessor.get(IClipboardService);
257+
258+
try {
259+
const connectionInfo = await mcpServerService.getConnectionInfo();
260+
261+
if (!connectionInfo.isRunning) {
262+
notificationService.warn('MCP Server is not running. Enable it in Settings > Roopik > MCP.');
263+
return;
264+
}
265+
266+
// Build the MCP config JSON that users can copy
267+
const mcpConfig = JSON.stringify({
268+
roopik: {
269+
command: connectionInfo.binaryPath,
270+
args: ['--ws-port', connectionInfo.wsPort.toString(), '--token', connectionInfo.token]
271+
}
272+
}, null, 2);
273+
274+
const items: IQuickPickItem[] = [
275+
{
276+
label: '$(key) Copy Token',
277+
description: connectionInfo.token,
278+
detail: 'Copy authentication token to clipboard'
279+
},
280+
{
281+
label: '$(terminal) Copy MCP Config',
282+
description: 'JSON config for mcp_servers',
283+
detail: 'Copy complete MCP server config for external IDEs'
284+
},
285+
{
286+
label: '$(info) Connection Details',
287+
description: `Port: ${connectionInfo.wsPort}`,
288+
detail: `Binary: ${connectionInfo.binaryPath}`
289+
}
290+
];
291+
292+
const selected = await quickInputService.pick(items, {
293+
title: 'Roopik MCP Connection Info',
294+
placeHolder: 'Select what to copy (token changes on IDE restart)'
295+
});
296+
297+
if (selected) {
298+
if (selected.label.includes('Copy Token')) {
299+
await clipboardService.writeText(connectionInfo.token);
300+
notificationService.info('Token copied to clipboard');
301+
} else if (selected.label.includes('Copy MCP Config')) {
302+
await clipboardService.writeText(mcpConfig);
303+
notificationService.info('MCP config copied to clipboard');
304+
}
305+
}
306+
} catch (error) {
307+
notificationService.error(`Failed to get connection info: ${error}`);
308+
}
309+
}
310+
});
239311
}

src/vs/workbench/contrib/roopik/browser/mcpServerServiceClient.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { IMainProcessService } from '../../../../platform/ipc/common/mainProcess
2020
import {
2121
IMcpServerService,
2222
McpServerStatus,
23+
McpConnectionInfo,
2324
AgentId,
2425
AgentStatus,
2526
McpIntegrationStatus,
@@ -98,4 +99,12 @@ export class McpServerServiceClient implements IMcpServerService {
9899
async syncAgentRegistrations(): Promise<void> {
99100
return this.channel.call('syncAgentRegistrations');
100101
}
102+
103+
// ========================================
104+
// Connection Info (for external IDEs)
105+
// ========================================
106+
107+
async getConnectionInfo(): Promise<McpConnectionInfo> {
108+
return this.channel.call('getConnectionInfo');
109+
}
101110
}

src/vs/workbench/contrib/roopik/browser/roopik.contribution.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
133133
type: 'boolean',
134134
default: true,
135135
order: 10,
136-
markdownDescription: '**Enable STDIO MCP** [Recommended] - Allow AI agents (Claude, Codex, etc.) to control Roopik IDE via the standard STDIO-based MCP protocol.'
136+
markdownDescription: '**Enable STDIO MCP** [Recommended] - Allow AI agents (Claude, Codex, etc.) to control Roopik IDE via the standard STDIO-based MCP protocol.\n\nFor external IDEs (Cursor, Windsurf, VS Code): Run command [Show MCP Connection Info](command:roopik.mcp.showConnectionInfo) to get token and setup instructions.'
137137
},
138138
'roopik.mcp.stdioMCPPort': {
139139
type: 'number',
@@ -202,6 +202,17 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
202202
description: 'Register with Windsurf IDE. Allows Windsurf AI to control Roopik IDE.'
203203
},
204204

205+
// ========================================
206+
// MCP Connection Info (for external IDEs)
207+
// ========================================
208+
'roopik.mcp.connectionInfo': {
209+
type: 'object',
210+
default: {},
211+
order: 27,
212+
markdownDescription: '**Connect External IDEs** - Use Roopik MCP tools from Cursor, Windsurf, or VS Code.\n\n[Show Connection Info](command:roopik.mcp.showConnectionInfo) - Get token and command to configure external IDEs.\n\n*Note: Token changes when Roopik restarts. Re-register agents after restart.*',
213+
ignoreSync: true
214+
},
215+
205216
// ========================================
206217
// Browser Settings
207218
// ========================================

src/vs/workbench/contrib/roopik/common/mcp/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export {
1111
IMcpServerService,
1212
McpServerStatus,
13+
McpConnectionInfo,
1314
AgentId,
1415
AgentStatus,
1516
McpIntegrationStatus

src/vs/workbench/contrib/roopik/common/mcp/mcpServerService.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ export interface McpIntegrationStatus {
7575
lastChecked: number;
7676
}
7777

78+
/**
79+
* Connection info for external IDEs
80+
*/
81+
export interface McpConnectionInfo {
82+
/** WebSocket port */
83+
wsPort: number;
84+
/** Authentication token (for external IDEs) */
85+
token: string;
86+
/** Path to MCP binary */
87+
binaryPath: string;
88+
/** Whether the server is running */
89+
isRunning: boolean;
90+
}
91+
7892
// ============================================================================
7993
// Service Interface
8094
// ============================================================================
@@ -134,4 +148,7 @@ export interface IMcpServerService {
134148

135149
/** Sync all agent registrations with current settings */
136150
syncAgentRegistrations(): Promise<void>;
151+
152+
/** Get connection info for external IDEs */
153+
getConnectionInfo(): Promise<McpConnectionInfo>;
137154
}

src/vs/workbench/contrib/roopik/electron-main/channel/mcpServerChannel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export class McpServerChannel implements IServerChannel {
7373
case 'syncAgentRegistrations':
7474
return this.service.syncAgentRegistrations();
7575

76+
// Connection info (for external IDEs)
77+
case 'getConnectionInfo':
78+
return this.service.getConnectionInfo();
79+
7680
default:
7781
throw new Error(`[McpServerChannel] Unknown command: ${command}`);
7882
}

src/vs/workbench/contrib/roopik/electron-main/mcp/installer/mcpInstaller.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ const CODEX_EXTENSION_ID = 'openai.chatgpt';
9090
export class McpInstaller extends Disposable {
9191
private _wsPort: number = 9876;
9292
private _binaryPath: string;
93+
private _authToken: string = '';
9394
private _platformAdapter: McpPlatformAdapter | null = null;
9495

9596
// Events
@@ -125,6 +126,14 @@ export class McpInstaller extends Disposable {
125126
this._binaryPath = path;
126127
}
127128

129+
/**
130+
* Set the authentication token for external agent configs
131+
* External IDEs (Cursor, Windsurf) need this passed via --token arg
132+
*/
133+
setAuthToken(token: string): void {
134+
this._authToken = token;
135+
}
136+
128137
// ==========================================================================
129138
// Agent Detection
130139
// ==========================================================================
@@ -462,10 +471,11 @@ export class McpInstaller extends Disposable {
462471
// Read existing config
463472
const existingConfig = readJsonConfig<{ mcpServers?: Record<string, unknown> }>(configPath);
464473

465-
// Generate server entry
474+
// Generate server entry with token for external IDEs
466475
const serverEntry = generateMcpServerEntry({
467476
binaryPath: this._binaryPath,
468-
wsPort: this._wsPort
477+
wsPort: this._wsPort,
478+
token: this._authToken
469479
});
470480

471481
// Add Roopik to config

src/vs/workbench/contrib/roopik/electron-main/mcp/installer/mcpInstallerUtils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,16 +583,18 @@ export function backupConfig(filePath: string): string | null {
583583

584584
/**
585585
* Generate the MCP server entry for registration
586+
* @param token - Required for external IDEs (Cursor, Windsurf), they don't inherit env var
586587
*/
587588
export function generateMcpServerEntry(options: {
588589
binaryPath: string;
589590
wsPort: number;
591+
token: string;
590592
}): McpServerEntry {
591593
return {
592594
name: ROOPIK_MCP_NAME,
593595
transport: 'stdio',
594596
command: options.binaryPath,
595-
args: ['--ws-port', options.wsPort.toString()],
597+
args: ['--ws-port', options.wsPort.toString(), '--token', options.token],
596598
env: {}
597599
};
598600
}

src/vs/workbench/contrib/roopik/electron-main/mcp/mcpServerService.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { Disposable } from '../../../../../base/common/lifecycle.js';
2626
import { ILoggerService } from '../../../../../platform/log/common/log.js';
2727
import { getRoopikLogger } from '../../common/roopikLogger.js';
2828
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
29-
import { IMcpServerService, McpServerStatus, AgentId, AgentStatus, McpIntegrationStatus } from '../../common/mcp/mcpServerService.js';
29+
import { IMcpServerService, McpServerStatus, McpConnectionInfo, AgentId, AgentStatus, McpIntegrationStatus } from '../../common/mcp/mcpServerService.js';
3030
import type { DevServerService } from '../projectMode/devServer/devServerService.js';
3131
import type { BrowserViewService } from '../projectMode/browserViewService.js';
3232
import type { ComponentService } from '../component/componentService.js';
@@ -37,6 +37,7 @@ import type { IRoopikStorageService } from '../../common/storage/storageService.
3737
import { ToolExecutor } from './executor/index.js';
3838
import { McpWebSocketServer } from './websocket/index.js';
3939
import { McpInstaller, type McpPlatformAdapter, type McpIntegrationSettings } from './installer/index.js';
40+
import { getMcpBinaryPath } from './installer/mcpInstallerUtils.js';
4041

4142
// ============================================================================
4243
// MCP Server Service Implementation
@@ -186,6 +187,8 @@ export class McpServerService extends Disposable implements IMcpServerService {
186187
private async initializeInstaller(): Promise<void> {
187188
this.installer = new McpInstaller();
188189
this.installer.setWsPort(this.wsPort);
190+
// Pass token for external IDEs (Cursor, Windsurf) - they need it via --token arg
191+
this.installer.setAuthToken(this.sessionToken);
189192

190193
// Create platform adapter for VS Code extension access
191194
const platformAdapter: McpPlatformAdapter = {
@@ -310,12 +313,12 @@ export class McpServerService extends Disposable implements IMcpServerService {
310313
// Initialize Tool Executor if needed
311314
if (!this.toolExecutor) {
312315
this.toolExecutor = new ToolExecutor(
313-
this.browserViewService,
314-
this.storageService,
315-
this.canvasService,
316-
this.componentService,
317-
this.devServerService
318-
);
316+
this.browserViewService,
317+
this.storageService,
318+
this.canvasService,
319+
this.componentService,
320+
this.devServerService
321+
);
319322
}
320323
await this.startWebSocketServer();
321324
await this.initializeInstaller();
@@ -412,6 +415,15 @@ export class McpServerService extends Disposable implements IMcpServerService {
412415
this._onAgentStatusChanged.fire(await this.getAgentStatus());
413416
}
414417

418+
async getConnectionInfo(): Promise<McpConnectionInfo> {
419+
return {
420+
wsPort: this.wsPort,
421+
token: this.sessionToken,
422+
binaryPath: getMcpBinaryPath(),
423+
isRunning: this.wsServer !== null
424+
};
425+
}
426+
415427
private agentIdToSettingKey(agentId: AgentId): string | null {
416428
const map: Record<AgentId, string> = {
417429
'claude-code': 'roopik.mcp.agents.claudeCode',

tools/roopik/mcp-stdio-server/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { TOOL_DEFINITIONS, PROMPT_DEFINITIONS, MCP_INSTRUCTIONS } from './tools.
3838
interface CliArgs {
3939
wsPort?: number;
4040
wsUrl?: string;
41+
token?: string;
4142
}
4243

4344
function parseArgs(): CliArgs {
@@ -51,6 +52,9 @@ function parseArgs(): CliArgs {
5152
} else if (args[i] === '--ws-url' && args[i + 1]) {
5253
result.wsUrl = args[i + 1];
5354
i++;
55+
} else if (args[i] === '--token' && args[i + 1]) {
56+
result.token = args[i + 1];
57+
i++;
5458
}
5559
}
5660

@@ -88,7 +92,7 @@ async function main(): Promise<void> {
8892
console.error(`[Roopik MCP] Auto-discovered Roopik at ${serverUrl}`);
8993
}
9094

91-
bridge = new WebSocketBridge({ serverUrl });
95+
bridge = new WebSocketBridge({ serverUrl, token: cliArgs.token });
9296
await bridge.connect();
9397
console.error('[Roopik MCP] Connected to Roopik IDE');
9498

0 commit comments

Comments
 (0)