Skip to content

Commit 445fc62

Browse files
authored
Merge pull request #69 from RoopikLabs/external-agent-support
Expose MCP connection info and token
2 parents ca37338 + 6be3bd9 commit 445fc62

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)