Skip to main content
Glama

MCPControl

keyboard.ts6.33 kB
import pkg from 'keysender'; const { Hardware } = pkg; // Define keyboard button type directly type KeyboardButtonType = string; 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, VALID_KEYS, KeyCombinationSchema, KeyHoldOperationSchema, } from '../../tools/validation.zod.js'; import { createLogger } from '../../logger.js'; /** * Keysender implementation of the KeyboardAutomation interface */ export class KeysenderKeyboardAutomation implements KeyboardAutomation { private keyboard = new Hardware().keyboard; private logger = createLogger('keysender:keyboard'); 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})`); } // Start the asynchronous operation and handle errors properly this.keyboard.printText(input.text).catch((err) => { this.logger.error('Error typing text', err); // We can't update the response after it's returned, but at least log the error }); 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 the key using Zod schema KeySchema.parse(key); const keyboardKey = this._findMatchingString(key, VALID_KEYS); // Start the asynchronous operation and handle errors properly this.keyboard.sendKey(keyboardKey).catch((err) => { this.logger.error(`Error pressing key ${key}`, err); // We can't update the response after it's returned, but at least log the error }); return { success: true, message: `Pressed key: ${key}`, }; } catch (error) { return { success: false, message: `Failed to press key: ${error instanceof Error ? error.message : String(error)}`, }; } } _findMatchingString(A: KeyboardButtonType, ButtonList: KeyboardButtonType[]): KeyboardButtonType { const lowerA = A.toLowerCase(); return ButtonList.filter((item) => lowerA == item.toLowerCase())[0]; } async pressKeyCombination(combination: KeyCombination): Promise<WindowsControlResponse> { try { // Validate the key combination using Zod schema KeyCombinationSchema.parse(combination); // Store original keys for the message const keysForMessage = [...combination.keys]; // Validate each key and collect press const validatedKeys: KeyboardButtonType[] = []; for (const key of combination.keys) { KeySchema.parse(key); const keyboardKey = this._findMatchingString(key, VALID_KEYS); validatedKeys.push(keyboardKey); } await this.keyboard.toggleKey(validatedKeys, true, 50).catch((err) => { this.logger.error('Error pressing keys', err); throw err; // Re-throw to be caught by the outer try/catch }); await this.keyboard.toggleKey(validatedKeys, false, 50).catch((err) => { this.logger.error('Error releasing keys', err); throw err; // Re-throw to be caught by the outer try/catch }); return { success: true, message: `Pressed key combination: ${keysForMessage.join('+')}`, }; } catch (error) { // Ensure all keys are released in case of error try { const cleanupPromises: Promise<void>[] = []; for (const key of combination.keys) { try { KeySchema.parse(key); const keyboardKey = this._findMatchingString(key, VALID_KEYS); cleanupPromises.push( this.keyboard.toggleKey(keyboardKey, false).catch((err) => { this.logger.error(`Error releasing key ${key} during cleanup`, err); // Ignore errors during cleanup }), ); } catch (validationError) { this.logger.error(`Error validating key ${key} during cleanup`, validationError); // Continue with other keys } } await Promise.all(cleanupPromises); } catch { // Ignore errors during cleanup } return { success: false, message: `Failed to press key combination: ${error instanceof Error ? error.message : String(error)}`, }; } } async holdKey(operation: KeyHoldOperation): Promise<WindowsControlResponse> { try { // Validate key hold operation using Zod schema KeyHoldOperationSchema.parse(operation); // Toggle the key state (down/up) await this.keyboard.toggleKey(operation.key, operation.state === 'down'); // If it's a key press (down) with duration, wait for the specified duration then release if (operation.state === 'down' && operation.duration) { await new Promise((resolve) => setTimeout(resolve, operation.duration)); await this.keyboard.toggleKey(operation.key, false); } return { success: true, message: `Key ${operation.key} ${operation.state === 'down' ? 'held' : 'released'} successfully${ operation.state === 'down' && operation.duration ? ` for ${operation.duration}ms` : '' }`, }; } catch (error) { // Ensure key is released in case of error during hold if (operation.state === 'down') { try { await this.keyboard.toggleKey(operation.key, false); } catch (releaseError) { this.logger.error(`Error releasing key ${operation.key} during cleanup`, releaseError); // Ignore errors during cleanup } } return { success: false, message: `Failed to ${operation.state} key ${operation.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