Skip to content

Commit 5b9d2ec

Browse files
committed
Enhance MCPServer integration with Cursor by programmatically registering and unregistering the server. Removed outdated comments and improved logging for server registration status.
[Feature] Signed-off-by: Markus Alexander Kuppe <[email protected]>
1 parent 73fffc2 commit 5b9d2ec

File tree

2 files changed

+45
-35
lines changed

2 files changed

+45
-35
lines changed

src/lm/MCPServer.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
77
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
88
import * as vscode from 'vscode';
99
import { exists } from '../common';
10+
11+
// Type declarations for Cursor's MCP Extension API
12+
// These types are only available in Cursor, not in VSCode
13+
declare module 'vscode' {
14+
// eslint-disable-next-line @typescript-eslint/no-namespace
15+
export namespace cursor {
16+
// eslint-disable-next-line @typescript-eslint/no-namespace
17+
export namespace mcp {
18+
export interface RemoteServerConfig {
19+
name: string;
20+
server: {
21+
url: string;
22+
};
23+
}
24+
25+
export function registerServer(config: RemoteServerConfig): void;
26+
export function unregisterServer(serverName: string): void;
27+
}
28+
}
29+
}
1030
import { applyDCollection } from '../diagnostic';
1131
import { TLADocumentSymbolProvider } from '../symbols/tlaSymbols';
1232
import { parseSpec, transpilePlusCal } from '../commands/parseModule';
@@ -282,39 +302,23 @@ export class MCPServer implements vscode.Disposable {
282302
this.mcpServer = app.listen(port, () => {
283303
// Get the actual port that was assigned (important when port is 0)
284304
const actualPort = (this.mcpServer?.address() as { port: number })?.port || port;
305+
285306
console.log(`TLA+ MCP server listening at http://localhost:${actualPort}/mcp`);
286-
// Only show the information message if running in Cursor, not VSCode.
307+
308+
// Programmatically register the MCP server in Cursor using the extension API
287309
const isCursor = vscode.env.appName?.toLowerCase().includes('cursor');
288-
if (isCursor) {
289-
// Show an information message in Cursor including a button to add the MCP server to Cursor.
290-
const addToCursor = 'Add to Cursor';
291-
vscode.window.showInformationMessage(
292-
`TLA+ MCP server listening at http://localhost:${actualPort}/mcp`,
293-
addToCursor
294-
).then(opt => {
295-
if (opt === addToCursor) {
296-
// We will display the information message in Cursor regardless of whether the user has
297-
// already added the MCP server.
298-
// Fortunately, adding the MCP server is idempotent, so it can be safely repeated
299-
// without causing issues.
300-
vscode.workspace.getConfiguration().update(
301-
'tlaplus.mcp.port',
302-
actualPort,
303-
vscode.ConfigurationTarget.Global
304-
);
305-
// base64 encode the URL
306-
const CURSOR_CONFIG = JSON.stringify({
310+
if (isCursor && (vscode as any).cursor?.mcp?.registerServer) {
311+
try {
312+
(vscode as any).cursor.mcp.registerServer({
313+
name: 'TLA+ MCP Server',
314+
server: {
307315
url: `http://localhost:${actualPort}/mcp`
308-
});
309-
// https://docs.cursor.com/deeplinks
310-
// eslint-disable-next-line max-len
311-
const CURSOR_DEEPLINK = `cursor://anysphere.cursor-deeplink/mcp/install?name=TLA+MCP+Server&config=${Buffer.from(CURSOR_CONFIG).toString('base64')}`;
312-
console.log(`Cursor deeplink: ${CURSOR_DEEPLINK}`);
313-
// Use the external URI handler to open the deeplink in Cursor because
314-
// vscode.commands.executeCommand('vscode.open', CURSOR_DEEPLINK) doesn't work.
315-
vscode.env.openExternal(vscode.Uri.parse(CURSOR_DEEPLINK));
316-
}
317-
});
316+
}
317+
});
318+
console.log(`TLA+ MCP server registered programmatically in Cursor at http://localhost:${actualPort}/mcp`);
319+
} catch (error) {
320+
console.warn('Failed to register TLA+ MCP server programmatically:', error);
321+
}
318322
}
319323
}).on('error', (err) => {
320324
vscode.window.showErrorMessage(
@@ -323,14 +327,24 @@ export class MCPServer implements vscode.Disposable {
323327
);
324328
console.error('Error starting TLA+ MCP server:', err);
325329
});
326-
327330
} catch (err) {
328331
// eslint-disable-next-line max-len
329332
vscode.window.showErrorMessage(`Failed to start TLA+ MCP server: ${err instanceof Error ? err.message : String(err)}`);
330333
}
331334
}
332335

333336
public dispose(): void {
337+
// Unregister from Cursor
338+
const isCursor = vscode.env.appName?.toLowerCase().includes('cursor');
339+
if (isCursor && (vscode as any).cursor?.mcp?.unregisterServer) {
340+
try {
341+
(vscode as any).cursor.mcp.unregisterServer('TLA+ MCP Server');
342+
console.log('TLA+ MCP server unregistered from Cursor');
343+
} catch (error) {
344+
console.warn('Failed to unregister TLA+ MCP server from Cursor:', error);
345+
}
346+
}
347+
334348
if (this.mcpServer) {
335349
this.mcpServer.close();
336350
this.mcpServer = undefined;

src/main.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
271271
if (typeof mcpPort === 'number' && (mcpPort >= 1024 && mcpPort <= 65535 || mcpPort === 0)) {
272272
const tlaMcpServer = new MCPServer(mcpPort);
273273
context.subscriptions.push(tlaMcpServer);
274-
275-
// TODO : At this point, we would like to programmatically register the MCP server in Cursor
276-
// without creating a permanent file. A permanent file, i.e., static configuration makes sense for
277-
// an external MCP server, but not for the transient MCP server that is started by the extension.
278274
}
279275
syncTlcStatisticsSetting()
280276
.catch((err) => console.error(err))

0 commit comments

Comments
 (0)