Skip to main content
Glama

MCPControl

keyboard.ts5.5 kB
import { execSync } from 'child_process'; import { writeFileSync, unlinkSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { KeyboardInput, KeyCombination, KeyHoldOperation } from '../../types/common.js'; import { WindowsControlResponse } from '../../types/responses.js'; import { KeyboardAutomation } from '../../interfaces/automation.js'; import { MAX_TEXT_LENGTH, KeySchema, KeyCombinationSchema, KeyHoldOperationSchema, } from '../../tools/validation.zod.js'; import { getAutoHotkeyPath } from './utils.js'; /** * AutoHotkey implementation of the KeyboardAutomation interface */ export class AutoHotkeyKeyboardAutomation implements KeyboardAutomation { /** * Execute an AutoHotkey script */ private executeScript(script: string): void { const scriptPath = join(tmpdir(), `mcp-ahk-${Date.now()}.ahk`); try { // Write the script to a temporary file writeFileSync(scriptPath, script, 'utf8'); // Execute the script with AutoHotkey v2 const autohotkeyPath = getAutoHotkeyPath(); execSync(`"${autohotkeyPath}" "${scriptPath}"`, { stdio: 'pipe' }); } finally { // Clean up the temporary script file try { unlinkSync(scriptPath); } catch { // Ignore cleanup errors } } } /** * Convert key name to AutoHotkey format */ private formatKey(key: string): string { const keyMap: Record<string, string> = { control: 'Ctrl', ctrl: 'Ctrl', shift: 'Shift', alt: 'Alt', meta: 'LWin', windows: 'LWin', enter: 'Enter', return: 'Enter', escape: 'Escape', esc: 'Escape', backspace: 'Backspace', delete: 'Delete', tab: 'Tab', space: 'Space', up: 'Up', down: 'Down', left: 'Left', right: 'Right', home: 'Home', end: 'End', pageup: 'PgUp', pagedown: 'PgDn', f1: 'F1', f2: 'F2', f3: 'F3', f4: 'F4', f5: 'F5', f6: 'F6', f7: 'F7', f8: 'F8', f9: 'F9', f10: 'F10', f11: 'F11', f12: 'F12', }; const lowerKey = key.toLowerCase(); return keyMap[lowerKey] || key; } typeText(input: KeyboardInput): WindowsControlResponse { try { // Validate text if (!input.text) { throw new Error('Text is required'); } if (input.text.length > MAX_TEXT_LENGTH) { throw new Error(`Text too long: ${input.text.length} characters (max ${MAX_TEXT_LENGTH})`); } // Escape special characters for AutoHotkey const escapedText = input.text .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/`/g, '``') .replace(/{/g, '{{') .replace(/}/g, '}}'); const script = ` SendText("${escapedText}") ExitApp `; this.executeScript(script); return { success: true, message: `Typed text successfully`, }; } catch (error) { return { success: false, message: `Failed to type text: ${error instanceof Error ? error.message : String(error)}`, }; } } pressKey(key: string): WindowsControlResponse { try { // Validate key KeySchema.parse(key); const formattedKey = this.formatKey(key); const script = ` Send("{${formattedKey}}") ExitApp `; this.executeScript(script); return { success: true, message: `Pressed key: ${key}`, }; } catch (error) { return { success: false, message: `Failed to press key: ${error instanceof Error ? error.message : String(error)}`, }; } } // eslint-disable-next-line @typescript-eslint/require-await async pressKeyCombination(combination: KeyCombination): Promise<WindowsControlResponse> { try { // Validate combination KeyCombinationSchema.parse(combination); // Build the key combination string const keys = combination.keys.map((key) => this.formatKey(key)); const comboString = keys.join('+'); const script = ` Send("{${comboString}}") ExitApp `; this.executeScript(script); return { success: true, message: `Pressed key combination: ${combination.keys.join('+')}`, }; } catch (error) { return { success: false, message: `Failed to press key combination: ${error instanceof Error ? error.message : String(error)}`, }; } } // eslint-disable-next-line @typescript-eslint/require-await async holdKey(operation: KeyHoldOperation): Promise<WindowsControlResponse> { try { // Validate operation KeyHoldOperationSchema.parse(operation); const formattedKey = this.formatKey(operation.key); const script = operation.state === 'up' ? ` Send("{${formattedKey} up}") ExitApp ` : ` Send("{${formattedKey} down}") ExitApp `; this.executeScript(script); return { success: true, message: operation.state === 'up' ? `Released key: ${operation.key}` : `Holding key: ${operation.key}`, }; } catch (error) { return { success: false, message: `Failed to ${operation.state === 'up' ? 'release' : 'hold'} key: ${error instanceof Error ? error.message : String(error)}`, }; } } }

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/claude-did-this/MCPControl'

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