@@ -7,6 +7,26 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
77import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' ;
88import * as vscode from 'vscode' ;
99import { 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+ }
1030import { applyDCollection } from '../diagnostic' ;
1131import { TLADocumentSymbolProvider } from '../symbols/tlaSymbols' ;
1232import { 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 ;
0 commit comments