Skip to main content
Glama

Cursor MCP Server

by johnneerdael
WindowsApiService.js11.6 kB
import * as activeWin from 'active-win'; import robot from 'robotjs'; // Maximum retry attempts for window operations const MAX_RETRIES = 3; const RETRY_DELAY = 500; // ms const OPERATION_TIMEOUT = 10000; // 10 seconds max for any operation export const KeyboardEventTypes = { KeyDown: 'keyDown', KeyUp: 'keyUp' }; export class WindowsApiService { // Track windows created by our tool managedWindows = new Set(); // Register a window as managed by our tool registerManagedWindow(processId) { this.managedWindows.add(processId); } // Check if a window is managed by our tool isManagedWindow(processId) { return this.managedWindows.has(processId); } async withTimeout(operation, timeoutMs = OPERATION_TIMEOUT) { const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error('Operation timed out')), timeoutMs); }); return Promise.race([operation(), timeout]); } async verifyWindow(window, options = {}) { return this.withTimeout(async () => { try { // Check if window still exists const activeWindow = await activeWin.activeWindow(); const exists = activeWindow && activeWindow.owner.processId === window.owner.processId; if (!exists) return false; // During initial creation, we only check if the window exists if (options.initialCreation) { return true; } // For established windows, verify it's managed by us if (!this.isManagedWindow(window.owner.processId)) { return false; } // Verify it's a Cursor window by checking title const title = activeWindow.title; if (!title.includes('Cursor')) { return false; } return true; } catch (error) { console.error('Error verifying window:', error); return false; } }); } async withRetry(operation, window, options = {}) { const { requireFocus = false } = options; let lastError; for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { // Verify window is valid if (!await this.isWindowResponding(window)) { throw new Error('Window is not responding'); } // Focus window if required if (requireFocus) { await this.focusWindow(window); } // Execute the operation return await operation(); } catch (error) { lastError = error; console.error(`Operation failed (attempt ${attempt}/${MAX_RETRIES}):`, error); if (attempt < MAX_RETRIES) { await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); } } } throw lastError || new Error('Operation failed after retries'); } async withWindowContext(window, operation) { try { // Perform the operation const result = await operation(); return result; } catch (error) { throw error; } } async findWindowByProcessId(processId, options = {}) { try { const activeWindow = await activeWin.activeWindow(); const window = activeWindow && activeWindow.owner.processId === processId ? activeWindow : null; if (window) { // For initial creation, register the window as managed if (options.initialCreation) { this.registerManagedWindow(processId); } if (await this.verifyWindow(window, options)) { return window; } } return null; } catch (error) { console.error('Error finding window:', error); return null; } } async findWindowByUuid(uuid) { try { const activeWindow = await activeWin.activeWindow(); if (activeWindow && activeWindow.title.includes('Cursor')) { return activeWindow; } return null; } catch (error) { console.error('Error finding window by UUID:', error); return null; } } async focusWindow(window) { return this.withRetry(async () => { // Verify focus was obtained await new Promise(resolve => setTimeout(resolve, 100)); const title = window.title; if (!title) { throw new Error('Failed to obtain window focus'); } return true; }, window); } async sendKeyToWindow(window, keyCode, modifiers) { await this.withRetry(async () => { // Ensure window is focused before sending keys await this.ensureWindowFocus(window); // Verify window is still valid and responding if (!await this.isWindowResponding(window)) { throw new Error('Window is not responding'); } // Send key down event await this.simulateKeyboardEvent(window, { type: KeyboardEventTypes.KeyDown, keyCode, modifiers }); // Small delay between down and up await new Promise(resolve => setTimeout(resolve, 50)); // Send key up event await this.simulateKeyboardEvent(window, { type: KeyboardEventTypes.KeyUp, keyCode, modifiers }); }, window, { requireFocus: true }); } async ensureWindowFocus(window) { // Small delay to allow window manager to process await new Promise(resolve => setTimeout(resolve, 100)); // Verify window is actually focused by checking title const title = window.title; if (!title) { // If not focused, try alternative method using screen coordinates const screen = robot.getScreenSize(); const centerX = Math.floor(screen.width / 2); const centerY = Math.floor(screen.height / 2); // Move mouse to center of screen and click robot.moveMouse(centerX, centerY); robot.mouseClick(); // Additional delay to ensure focus await new Promise(resolve => setTimeout(resolve, 100)); // Final verification const finalTitle = window.title; if (!finalTitle) { throw new Error('Failed to focus window after multiple attempts'); } } } async simulateKeyboardEvent(window, event) { const { type, keyCode, modifiers = {} } = event; const { alt, ctrl, shift } = modifiers; try { // Ensure we're focused on the right window await this.ensureWindowFocus(window); const key = String.fromCharCode(keyCode).toLowerCase(); if (!key) { throw new Error(`Unsupported key code: ${keyCode}`); } // Toggle the key robot.keyToggle(key, type === KeyboardEventTypes.KeyDown ? 'down' : 'up'); // Small delay after event to ensure processing await new Promise(resolve => setTimeout(resolve, 30)); } catch (error) { console.error('Error simulating keyboard event:', error); throw error; } } async openCommandPalette(window) { await this.withRetry(async () => { try { // Ensure we're focused on the right window await this.ensureWindowFocus(window); // Press Ctrl+Shift+P using only robotjs robot.keyToggle('control', 'down'); await new Promise(resolve => setTimeout(resolve, 50)); robot.keyToggle('shift', 'down'); await new Promise(resolve => setTimeout(resolve, 50)); robot.keyToggle('p', 'down'); await new Promise(resolve => setTimeout(resolve, 50)); // Release in reverse order with delays robot.keyToggle('p', 'up'); await new Promise(resolve => setTimeout(resolve, 50)); robot.keyToggle('shift', 'up'); await new Promise(resolve => setTimeout(resolve, 50)); robot.keyToggle('control', 'up'); await new Promise(resolve => setTimeout(resolve, 50)); // Wait for command palette to appear await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { console.error('Error opening command palette:', error); throw error; } }, window); } async getVirtualKeyForChar(char) { const upperChar = char.toUpperCase(); if (upperChar === ' ') return 32; // Space key code if (upperChar === ':') return 186; // Colon key code if (upperChar >= 'A' && upperChar <= 'Z') { return upperChar.charCodeAt(0); } throw new Error(`No virtual key code for character: ${char}`); } async getWindowTitle(window) { try { return window.title; } catch (error) { console.error('Error getting window title:', error); return ''; } } async isWindowResponding(window) { try { const title = window.title; return title.length > 0; } catch (error) { console.error('Error checking window state:', error); return false; } } async openClineTab(window) { await this.withRetry(async () => { // 1. Open command palette await this.openCommandPalette(window); await new Promise(resolve => setTimeout(resolve, 500)); // 2. Type "Cline: Open in New Tab" const text = "Cline: Open in New Tab"; for (const char of text) { const keyCode = await this.getVirtualKeyForChar(char); await this.simulateKeyboardEvent(window, { type: KeyboardEventTypes.KeyDown, keyCode }); await new Promise(resolve => setTimeout(resolve, 30)); await this.simulateKeyboardEvent(window, { type: KeyboardEventTypes.KeyUp, keyCode }); await new Promise(resolve => setTimeout(resolve, 30)); } // 3. Press Enter await this.simulateKeyboardEvent(window, { type: KeyboardEventTypes.KeyDown, keyCode: 13 // Enter key code }); await new Promise(resolve => setTimeout(resolve, 30)); await this.simulateKeyboardEvent(window, { type: KeyboardEventTypes.KeyUp, keyCode: 13 // Enter key code }); // Wait for command to execute await new Promise(resolve => setTimeout(resolve, 500)); }, window); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnneerdael/multiplatform-cursor-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server