# Directory Structure ``` ├── .gitattributes ├── .gitignore ├── LICENSE ├── package.json ├── README.md ├── src │ ├── index.ts │ ├── prompts │ │ └── index.ts │ ├── resources │ │ └── index.ts │ ├── server │ │ └── server.ts │ ├── tools │ │ ├── control.ts │ │ ├── index.ts │ │ ├── keyboard.ts │ │ ├── mouse.ts │ │ ├── process.ts │ │ └── window.ts │ └── utils │ ├── logger │ │ └── logger.ts │ └── types.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- ``` 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Windows Desktop Automation 2 | 3 | A Model Context Protocol (MCP) server for Windows desktop automation using AutoIt. 4 | 5 | ## Overview 6 | 7 | This project provides a TypeScript MCP server that wraps the [node-autoit-koffi](https://www.npmjs.com/package/node-autoit-koffi) package, allowing LLM applications to automate Windows desktop tasks through the MCP protocol. 8 | 9 | The server exposes: 10 | - **Tools**: All AutoIt functions as MCP tools 11 | - **Resources**: File access and screenshot capabilities 12 | - **Prompts**: Templates for common automation tasks 13 | 14 | ## Features 15 | 16 | - Full wrapping of all AutoIt functions as MCP tools 17 | - Support for both stdio and WebSocket transports 18 | - File access resources for reading files and directories 19 | - Screenshot resources for capturing the screen or specific windows 20 | - Prompt templates for common automation tasks 21 | - Strict TypeScript typing throughout 22 | 23 | ## Installation 24 | 25 | ```bash 26 | # Clone the repository 27 | git clone https://github.com/yourusername/mcp-windows-desktop-automation.git 28 | cd mcp-windows-desktop-automation 29 | 30 | # Install dependencies 31 | npm install 32 | 33 | # Build the project 34 | npm run build 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### Starting the Server 40 | 41 | ```bash 42 | # Start with stdio transport (default) 43 | npm start 44 | 45 | # Start with WebSocket transport 46 | npm start -- --transport=websocket --port=3000 47 | 48 | # Enable verbose logging 49 | npm start -- --verbose 50 | ``` 51 | 52 | ### Command Line Options 53 | 54 | - `--transport=stdio|websocket`: Specify the transport protocol (default: stdio) 55 | - `--port=<number>`: Specify the port for WebSocket transport (default: 3000) 56 | - `--verbose`: Enable verbose logging 57 | 58 | ## Tools 59 | 60 | The server provides tools for: 61 | 62 | - **Mouse operations**: Move, click, drag, etc. 63 | - **Keyboard operations**: Send keystrokes, clipboard operations, etc. 64 | - **Window management**: Find, activate, close, resize windows, etc. 65 | - **Control manipulation**: Interact with UI controls, buttons, text fields, etc. 66 | - **Process management**: Start, stop, and monitor processes 67 | - **System operations**: Shutdown, sleep, etc. 68 | 69 | ## Resources 70 | 71 | The server provides resources for: 72 | 73 | - **File access**: Read files and list directories 74 | - **Screenshots**: Capture the screen or specific windows 75 | 76 | ## Prompts 77 | 78 | The server provides prompt templates for: 79 | 80 | - **Window interaction**: Find and interact with windows 81 | - **Form filling**: Automate form filling tasks 82 | - **Automation tasks**: Create scripts for repetitive tasks 83 | - **Monitoring**: Wait for specific conditions 84 | 85 | ## Development 86 | 87 | ```bash 88 | # Run in development mode 89 | npm run dev 90 | 91 | # Lint the code 92 | npm run lint 93 | 94 | # Run tests 95 | npm run test 96 | ``` 97 | 98 | ## License 99 | 100 | MIT 101 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "strictPropertyInitialization": true, 11 | "noImplicitAny": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "outDir": "./dist", 15 | "sourceMap": true, 16 | "declaration": true, 17 | "resolveJsonModule": true 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["node_modules", "dist"] 21 | } 22 | ``` -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tools module for MCP Windows Desktop Automation 3 | */ 4 | 5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 6 | import { registerMouseTools } from './mouse'; 7 | import { registerKeyboardTools } from './keyboard'; 8 | import { registerWindowTools } from './window'; 9 | import { registerProcessTools } from './process'; 10 | import { registerControlTools } from './control'; 11 | 12 | /** 13 | * Register all AutoIt tools with the MCP server 14 | */ 15 | export function registerAllTools(server: McpServer): void { 16 | registerMouseTools(server); 17 | registerKeyboardTools(server); 18 | registerWindowTools(server); 19 | registerProcessTools(server); 20 | registerControlTools(server); 21 | } 22 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mcp-windows-desktop-automation", 3 | "version": "0.1.0", 4 | "description": "MCP server for Windows desktop automation", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "node dist/index.js", 9 | "dev": "ts-node src/index.ts", 10 | "lint": "eslint src/**/*.ts", 11 | "test": "jest" 12 | }, 13 | "keywords": [ 14 | "mcp", 15 | "windows", 16 | "automation", 17 | "autoit" 18 | ], 19 | "author": "", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@modelcontextprotocol/sdk": "^1.7.0", 23 | "node-autoit-koffi": "^1.0.5", 24 | "ws": "^8.18.1" 25 | }, 26 | "devDependencies": { 27 | "@types/express": "^4.17.17", 28 | "@types/node": "^18.15.11", 29 | "@types/ws": "^8.18.0", 30 | "@typescript-eslint/eslint-plugin": "^5.57.1", 31 | "@typescript-eslint/parser": "^5.57.1", 32 | "eslint": "^8.38.0", 33 | "jest": "^29.5.0", 34 | "ts-jest": "^29.1.0", 35 | "ts-node": "^10.9.1", 36 | "typescript": "^5.0.4" 37 | } 38 | } 39 | ``` -------------------------------------------------------------------------------- /src/utils/logger/logger.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Logger utility for MCP Windows Desktop Automation 3 | */ 4 | 5 | enum LogLevel { 6 | VERBOSE = 0, 7 | DEBUG = 1, 8 | INFO = 2, 9 | WARN = 3, 10 | ERROR = 4 11 | } 12 | 13 | class Logger { 14 | private level: LogLevel = LogLevel.INFO; 15 | 16 | /** 17 | * Set the minimum log level 18 | * @param level The minimum level to log 19 | */ 20 | setLevel(level: LogLevel): void { 21 | this.level = level; 22 | } 23 | 24 | /** 25 | * Log verbose information 26 | * @param message The message to log 27 | * @param data Additional data to log (will be JSON stringified) 28 | */ 29 | verbose(message: string, data?: any): void { 30 | if (this.level <= LogLevel.VERBOSE) { 31 | console.log(`[VERBOSE] ${message}`, data ? JSON.stringify(data) : ''); 32 | } 33 | } 34 | 35 | /** 36 | * Log debug information 37 | * @param message The message to log 38 | * @param data Additional data to log 39 | */ 40 | debug(message: string, data?: any): void { 41 | if (this.level <= LogLevel.DEBUG) { 42 | console.log(`[DEBUG] ${message}`, data || ''); 43 | } 44 | } 45 | 46 | /** 47 | * Log general information 48 | * @param message The message to log 49 | * @param data Additional data to log 50 | */ 51 | info(message: string, data?: any): void { 52 | if (this.level <= LogLevel.INFO) { 53 | console.log(`[INFO] ${message}`, data || ''); 54 | } 55 | } 56 | 57 | /** 58 | * Log warnings 59 | * @param message The message to log 60 | * @param data Additional data to log 61 | */ 62 | warn(message: string, data?: any): void { 63 | if (this.level <= LogLevel.WARN) { 64 | console.warn(`[WARN] ${message}`, data || ''); 65 | } 66 | } 67 | 68 | /** 69 | * Log errors 70 | * @param message The message to log 71 | * @param data Additional data to log 72 | */ 73 | error(message: string, data?: any): void { 74 | if (this.level <= LogLevel.ERROR) { 75 | console.error(`[ERROR] ${message}`, data || ''); 76 | } 77 | } 78 | } 79 | 80 | export const log = new Logger(); 81 | ``` -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared type definitions for MCP Windows Desktop Automation 3 | */ 4 | 5 | import { z } from 'zod'; 6 | import { CallToolResult, TextContent } from '@modelcontextprotocol/sdk/types.js'; 7 | 8 | /** 9 | * Point coordinates 10 | */ 11 | export interface Point { 12 | x: number; 13 | y: number; 14 | } 15 | 16 | /** 17 | * Rectangle coordinates 18 | */ 19 | export interface Rect { 20 | left: number; 21 | top: number; 22 | right: number; 23 | bottom: number; 24 | } 25 | 26 | /** 27 | * Standard tool response creator 28 | */ 29 | export function createToolResponse(message: string): CallToolResult { 30 | return { 31 | content: [ 32 | { 33 | type: 'text', 34 | text: message 35 | } as TextContent 36 | ] 37 | }; 38 | } 39 | 40 | /** 41 | * Standard error response creator 42 | */ 43 | export function createErrorResponse(error: Error | string): CallToolResult { 44 | const errorMessage = typeof error === 'string' ? error : error.message; 45 | return { 46 | content: [ 47 | { 48 | type: 'text', 49 | text: `Error: ${errorMessage}` 50 | } as TextContent 51 | ], 52 | isError: true 53 | }; 54 | } 55 | 56 | /** 57 | * Common Zod schemas for tool parameters 58 | */ 59 | export const schemas = { 60 | // Window identification 61 | windowTitle: z.string().describe('Window title'), 62 | windowText: z.string().optional().describe('Window text'), 63 | 64 | // Mouse parameters 65 | mouseButton: z.enum(['left', 'right', 'middle']).optional().default('left').describe('Mouse button'), 66 | mouseSpeed: z.number().min(1).max(100).optional().default(10).describe('Mouse movement speed (1-100)'), 67 | mouseX: z.number().describe('X coordinate'), 68 | mouseY: z.number().describe('Y coordinate'), 69 | mouseClicks: z.number().min(1).optional().default(1).describe('Number of clicks'), 70 | 71 | // Control parameters 72 | controlName: z.string().describe('Control identifier'), 73 | controlText: z.string().describe('Text to set/send to control'), 74 | 75 | // Process parameters 76 | processName: z.string().describe('Process name or executable path'), 77 | processTimeout: z.number().optional().describe('Timeout in milliseconds'), 78 | 79 | // Common parameters 80 | handle: z.number().describe('Window or control handle'), 81 | bufferSize: z.number().optional().describe('Buffer size for string operations') 82 | }; 83 | ``` -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * MCP Server configuration for Windows Desktop Automation 3 | */ 4 | 5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 6 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 7 | import { WebSocket, WebSocketServer, RawData } from 'ws'; 8 | import { createServer as createHttpServer, IncomingMessage, Server as HttpServer } from 'http'; 9 | import { log } from '../utils/logger/logger'; 10 | import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; 11 | import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; 12 | 13 | /** 14 | * Custom WebSocket transport for MCP 15 | */ 16 | class WebSocketServerTransport implements Transport { 17 | private ws: WebSocket | null = null; 18 | sessionId?: string; 19 | 20 | constructor(ws: WebSocket) { 21 | this.ws = ws; 22 | this.sessionId = Math.random().toString(36).substring(2, 15); 23 | 24 | ws.on('message', (data: RawData) => { 25 | try { 26 | const message = JSON.parse(data.toString()) as JSONRPCMessage; 27 | this.onmessage?.(message); 28 | } catch (error) { 29 | this.onerror?.(new Error(`Failed to parse message: ${error}`)); 30 | } 31 | }); 32 | 33 | ws.on('close', () => { 34 | this.ws = null; 35 | this.onclose?.(); 36 | }); 37 | 38 | ws.on('error', (error: Error) => { 39 | this.onerror?.(error); 40 | }); 41 | } 42 | 43 | onclose?: () => void; 44 | onerror?: (error: Error) => void; 45 | onmessage?: (message: JSONRPCMessage) => void; 46 | 47 | async start(): Promise<void> { 48 | log.info('WebSocket transport started'); 49 | } 50 | 51 | async send(message: JSONRPCMessage): Promise<void> { 52 | if (!this.ws) { 53 | throw new Error('WebSocket not connected'); 54 | } 55 | 56 | return new Promise((resolve, reject) => { 57 | this.ws!.send(JSON.stringify(message), (error?: Error) => { 58 | if (error) { 59 | reject(error); 60 | } else { 61 | resolve(); 62 | } 63 | }); 64 | }); 65 | } 66 | 67 | async close(): Promise<void> { 68 | if (this.ws) { 69 | this.ws.close(); 70 | this.ws = null; 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Server configuration options 77 | */ 78 | export interface ServerConfig { 79 | name: string; 80 | version: string; 81 | transport: 'stdio' | 'websocket'; 82 | port?: number; 83 | } 84 | 85 | /** 86 | * Create and configure an MCP server 87 | */ 88 | export async function setupServer(config: ServerConfig): Promise<{ 89 | server: McpServer; 90 | httpServer?: HttpServer; 91 | }> { 92 | // Create the MCP server 93 | const server = new McpServer({ 94 | name: config.name, 95 | version: config.version 96 | }, { 97 | capabilities: { 98 | tools: {}, 99 | resources: { 100 | subscribe: true, 101 | listChanged: true 102 | }, 103 | prompts: { 104 | listChanged: true 105 | } 106 | } 107 | }); 108 | 109 | // Configure the transport 110 | if (config.transport === 'stdio') { 111 | log.info('Using stdio transport'); 112 | const transport = new StdioServerTransport(); 113 | await server.connect(transport); 114 | return { server }; 115 | } else if (config.transport === 'websocket') { 116 | log.info(`Using WebSocket transport on port ${config.port}`); 117 | 118 | // Create HTTP server 119 | const httpServer = createHttpServer(); 120 | const wss = new WebSocketServer({ server: httpServer }); 121 | 122 | // Handle WebSocket connections 123 | wss.on('connection', async (ws: WebSocket) => { 124 | log.info('New WebSocket connection'); 125 | const transport = new WebSocketServerTransport(ws); 126 | await server.connect(transport); 127 | }); 128 | 129 | // Start HTTP server 130 | httpServer.listen(config.port || 3000); 131 | 132 | return { server, httpServer }; 133 | } else { 134 | throw new Error(`Unsupported transport: ${config.transport}`); 135 | } 136 | } 137 | ``` -------------------------------------------------------------------------------- /src/tools/keyboard.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Keyboard-related tools for MCP Windows Desktop Automation 3 | */ 4 | 5 | import * as autoIt from 'node-autoit-koffi'; 6 | import { z } from 'zod'; 7 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 8 | import { createToolResponse, createErrorResponse } from '../utils/types'; 9 | import { log } from '../utils/logger/logger'; 10 | 11 | /** 12 | * Register keyboard-related tools with the MCP server 13 | */ 14 | export function registerKeyboardTools(server: McpServer): void { 15 | // send - Send keystrokes to the active window 16 | server.tool( 17 | 'send', 18 | { 19 | text: z.string().describe('Text or keys to send'), 20 | mode: z.number().optional().default(0).describe('Send mode flag') 21 | }, 22 | async ({ text, mode }) => { 23 | try { 24 | log.verbose('send called', { text, mode }); 25 | await autoIt.init(); 26 | await autoIt.send(text, mode); 27 | return createToolResponse(`Sent keystrokes: "${text}" with mode ${mode}`); 28 | } catch (error) { 29 | log.error('send failed', error); 30 | return createErrorResponse(error instanceof Error ? error : String(error)); 31 | } 32 | } 33 | ); 34 | 35 | // clipGet - Get the text from the clipboard 36 | server.tool( 37 | 'clipGet', 38 | { 39 | bufSize: z.number().optional().describe('Buffer size for clipboard content') 40 | }, 41 | async ({ bufSize }) => { 42 | try { 43 | log.verbose('clipGet called', { bufSize }); 44 | await autoIt.init(); 45 | const clipboardContent = await autoIt.clipGet(bufSize); 46 | log.verbose('clipGet result', JSON.stringify({ clipboardContent })); 47 | return createToolResponse(`Clipboard content: "${clipboardContent}"`); 48 | } catch (error) { 49 | log.error('clipGet failed', error); 50 | return createErrorResponse(error instanceof Error ? error : String(error)); 51 | } 52 | } 53 | ); 54 | 55 | // clipPut - Put text into the clipboard 56 | server.tool( 57 | 'clipPut', 58 | { 59 | text: z.string().describe('Text to put in the clipboard') 60 | }, 61 | async ({ text }) => { 62 | try { 63 | log.verbose('clipPut called', { text }); 64 | await autoIt.init(); 65 | await autoIt.clipPut(text); 66 | return createToolResponse(`Text set to clipboard: "${text}"`); 67 | } catch (error) { 68 | log.error('clipPut failed', error); 69 | return createErrorResponse(error instanceof Error ? error : String(error)); 70 | } 71 | } 72 | ); 73 | 74 | // autoItSetOption - Set AutoIt options 75 | server.tool( 76 | 'autoItSetOption', 77 | { 78 | option: z.string().describe('Option name'), 79 | value: z.number().describe('Option value') 80 | }, 81 | async ({ option, value }) => { 82 | try { 83 | log.verbose('autoItSetOption called', { option, value }); 84 | await autoIt.init(); 85 | const result = await autoIt.autoItSetOption(option, value); 86 | return createToolResponse(`AutoIt option "${option}" set to ${value} with result: ${result}`); 87 | } catch (error) { 88 | log.error('autoItSetOption failed', error); 89 | return createErrorResponse(error instanceof Error ? error : String(error)); 90 | } 91 | } 92 | ); 93 | 94 | // opt - Alias for autoItSetOption 95 | server.tool( 96 | 'opt', 97 | { 98 | option: z.string().describe('Option name'), 99 | value: z.number().describe('Option value') 100 | }, 101 | async ({ option, value }) => { 102 | try { 103 | log.verbose('opt called', { option, value }); 104 | await autoIt.init(); 105 | const result = await autoIt.opt(option, value); 106 | return createToolResponse(`AutoIt option "${option}" set to ${value} with result: ${result}`); 107 | } catch (error) { 108 | log.error('opt failed', error); 109 | return createErrorResponse(error instanceof Error ? error : String(error)); 110 | } 111 | } 112 | ); 113 | 114 | // toolTip - Display a tooltip 115 | server.tool( 116 | 'toolTip', 117 | { 118 | text: z.string().describe('Tooltip text'), 119 | x: z.number().optional().describe('X coordinate'), 120 | y: z.number().optional().describe('Y coordinate') 121 | }, 122 | async ({ text, x, y }) => { 123 | try { 124 | log.verbose('toolTip called', { text, x, y }); 125 | await autoIt.init(); 126 | await autoIt.toolTip(text, x, y); 127 | const position = x !== undefined && y !== undefined ? ` at position (${x}, ${y})` : ''; 128 | return createToolResponse(`Tooltip displayed: "${text}"${position}`); 129 | } catch (error) { 130 | log.error('toolTip failed', error); 131 | return createErrorResponse(error instanceof Error ? error : String(error)); 132 | } 133 | } 134 | ); 135 | } 136 | ``` -------------------------------------------------------------------------------- /src/tools/mouse.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Mouse-related tools for MCP Windows Desktop Automation 3 | */ 4 | 5 | import * as autoIt from 'node-autoit-koffi'; 6 | import { z } from 'zod'; 7 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 8 | import { createToolResponse, createErrorResponse, schemas } from '../utils/types'; 9 | import { log } from '../utils/logger/logger'; 10 | 11 | /** 12 | * Register mouse-related tools with the MCP server 13 | */ 14 | export function registerMouseTools(server: McpServer): void { 15 | // mouseMove - Move the mouse cursor to the specified coordinates 16 | server.tool( 17 | 'mouseMove', 18 | { 19 | x: schemas.mouseX, 20 | y: schemas.mouseY, 21 | speed: schemas.mouseSpeed 22 | }, 23 | async ({ x, y, speed }) => { 24 | try { 25 | log.verbose('mouseMove called', { x, y, speed }); 26 | await autoIt.init(); 27 | const result = await autoIt.mouseMove(x, y, speed); 28 | return createToolResponse(`Mouse moved to (${x}, ${y}) with result: ${result}`); 29 | } catch (error) { 30 | log.error('mouseMove failed', error); 31 | return createErrorResponse(error instanceof Error ? error : String(error)); 32 | } 33 | } 34 | ); 35 | 36 | // mouseClick - Click the mouse at the current or specified position 37 | server.tool( 38 | 'mouseClick', 39 | { 40 | button: schemas.mouseButton, 41 | x: schemas.mouseX.optional(), 42 | y: schemas.mouseY.optional(), 43 | clicks: schemas.mouseClicks, 44 | speed: schemas.mouseSpeed 45 | }, 46 | async ({ button, x, y, clicks, speed }) => { 47 | try { 48 | log.verbose('mouseClick called', { button, x, y, clicks, speed }); 49 | await autoIt.init(); 50 | const result = await autoIt.mouseClick(button, x, y, clicks, speed); 51 | return createToolResponse(`Mouse clicked ${button} button ${clicks} time(s) with result: ${result}`); 52 | } catch (error) { 53 | log.error('mouseClick failed', error); 54 | return createErrorResponse(error instanceof Error ? error : String(error)); 55 | } 56 | } 57 | ); 58 | 59 | // mouseClickDrag - Click and drag the mouse from one position to another 60 | server.tool( 61 | 'mouseClickDrag', 62 | { 63 | button: schemas.mouseButton, 64 | x1: schemas.mouseX, 65 | y1: schemas.mouseY, 66 | x2: schemas.mouseX, 67 | y2: schemas.mouseY, 68 | speed: schemas.mouseSpeed 69 | }, 70 | async ({ button, x1, y1, x2, y2, speed }) => { 71 | try { 72 | log.verbose('mouseClickDrag called', { button, x1, y1, x2, y2, speed }); 73 | await autoIt.init(); 74 | const result = await autoIt.mouseClickDrag(button, x1, y1, x2, y2, speed); 75 | return createToolResponse(`Mouse dragged from (${x1}, ${y1}) to (${x2}, ${y2}) with result: ${result}`); 76 | } catch (error) { 77 | log.error('mouseClickDrag failed', error); 78 | return createErrorResponse(error instanceof Error ? error : String(error)); 79 | } 80 | } 81 | ); 82 | 83 | // mouseDown - Press and hold the specified mouse button 84 | server.tool( 85 | 'mouseDown', 86 | { 87 | button: schemas.mouseButton 88 | }, 89 | async ({ button }) => { 90 | try { 91 | log.verbose('mouseDown called', { button }); 92 | await autoIt.init(); 93 | await autoIt.mouseDown(button); 94 | return createToolResponse(`Mouse ${button} button pressed down`); 95 | } catch (error) { 96 | log.error('mouseDown failed', error); 97 | return createErrorResponse(error instanceof Error ? error : String(error)); 98 | } 99 | } 100 | ); 101 | 102 | // mouseUp - Release the specified mouse button 103 | server.tool( 104 | 'mouseUp', 105 | { 106 | button: schemas.mouseButton 107 | }, 108 | async ({ button }) => { 109 | try { 110 | log.verbose('mouseUp called', { button }); 111 | await autoIt.init(); 112 | await autoIt.mouseUp(button); 113 | return createToolResponse(`Mouse ${button} button released`); 114 | } catch (error) { 115 | log.error('mouseUp failed', error); 116 | return createErrorResponse(error instanceof Error ? error : String(error)); 117 | } 118 | } 119 | ); 120 | 121 | // mouseGetPos - Get the current mouse cursor position 122 | server.tool( 123 | 'mouseGetPos', 124 | {}, 125 | async () => { 126 | try { 127 | log.verbose('mouseGetPos called'); 128 | await autoIt.init(); 129 | const position = await autoIt.mouseGetPos(); 130 | log.verbose('mouseGetPos result', JSON.stringify(position)); 131 | return createToolResponse(`Mouse position: (${position.x}, ${position.y})`); 132 | } catch (error) { 133 | log.error('mouseGetPos failed', error); 134 | return createErrorResponse(error instanceof Error ? error : String(error)); 135 | } 136 | } 137 | ); 138 | 139 | // mouseGetCursor - Get the current mouse cursor type 140 | server.tool( 141 | 'mouseGetCursor', 142 | {}, 143 | async () => { 144 | try { 145 | log.verbose('mouseGetCursor called'); 146 | await autoIt.init(); 147 | const cursor = await autoIt.mouseGetCursor(); 148 | log.verbose('mouseGetCursor result', JSON.stringify(cursor)); 149 | return createToolResponse(`Mouse cursor type: ${cursor}`); 150 | } catch (error) { 151 | log.error('mouseGetCursor failed', error); 152 | return createErrorResponse(error instanceof Error ? error : String(error)); 153 | } 154 | } 155 | ); 156 | 157 | // mouseWheel - Scroll the mouse wheel 158 | server.tool( 159 | 'mouseWheel', 160 | { 161 | direction: z.enum(['up', 'down']).describe('Scroll direction'), 162 | clicks: z.number().min(1).describe('Number of clicks to scroll') 163 | }, 164 | async ({ direction, clicks }) => { 165 | try { 166 | log.verbose('mouseWheel called', { direction, clicks }); 167 | await autoIt.init(); 168 | await autoIt.mouseWheel(direction, clicks); 169 | return createToolResponse(`Mouse wheel scrolled ${direction} ${clicks} click(s)`); 170 | } catch (error) { 171 | log.error('mouseWheel failed', error); 172 | return createErrorResponse(error instanceof Error ? error : String(error)); 173 | } 174 | } 175 | ); 176 | } 177 | ``` -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Resources module for MCP Windows Desktop Automation 3 | */ 4 | 5 | import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; 6 | import * as fs from 'fs/promises'; 7 | import * as path from 'path'; 8 | import * as autoIt from 'node-autoit-koffi'; 9 | import { log } from '../utils/logger/logger'; 10 | 11 | /** 12 | * Register all resources with the MCP server 13 | */ 14 | export function registerAllResources(server: McpServer): void { 15 | // Register file resources 16 | registerFileResources(server); 17 | 18 | // Register screenshot resources 19 | registerScreenshotResources(server); 20 | } 21 | 22 | /** 23 | * Register file resources 24 | */ 25 | function registerFileResources(server: McpServer): void { 26 | server.resource( 27 | 'file', 28 | new ResourceTemplate('file://{path*}', { 29 | list: async () => { 30 | return { resources: [] }; // Empty list by default 31 | } 32 | }), 33 | async (uri, params) => { 34 | try { 35 | // Ensure filePath is a string 36 | const filePath = Array.isArray(params.path) ? params.path.join('/') : params.path; 37 | log.verbose('Reading file resource', JSON.stringify({ uri: uri.href, filePath })); 38 | 39 | // Check if file exists 40 | const stats = await fs.stat(filePath); 41 | 42 | if (stats.isDirectory()) { 43 | // List directory contents 44 | const files = await fs.readdir(filePath); 45 | const resources = await Promise.all( 46 | files.map(async (file) => { 47 | const fullPath = path.join(filePath, file); 48 | const fileStats = await fs.stat(fullPath); 49 | return { 50 | uri: `file://${fullPath.replace(/\\/g, '/')}`, 51 | name: file, 52 | description: fileStats.isDirectory() ? 'Directory' : 'File' 53 | }; 54 | }) 55 | ); 56 | 57 | return { 58 | contents: [{ 59 | uri: uri.href, 60 | text: `Directory: ${filePath}\n${files.join('\n')}`, 61 | mimeType: 'text/plain' 62 | }] 63 | }; 64 | } else { 65 | // Read file content 66 | const content = await fs.readFile(filePath); 67 | const extension = path.extname(filePath).toLowerCase(); 68 | 69 | // Determine if it's a text or binary file 70 | const isTextFile = [ 71 | '.txt', '.md', '.js', '.ts', '.html', '.css', '.json', '.xml', 72 | '.csv', '.log', '.ini', '.cfg', '.conf', '.py', '.c', '.cpp', 73 | '.h', '.java', '.sh', '.bat', '.ps1' 74 | ].includes(extension); 75 | 76 | if (isTextFile) { 77 | return { 78 | contents: [{ 79 | uri: uri.href, 80 | text: content.toString('utf-8'), 81 | mimeType: getMimeType(extension) 82 | }] 83 | }; 84 | } else { 85 | return { 86 | contents: [{ 87 | uri: uri.href, 88 | blob: content.toString('base64'), 89 | mimeType: getMimeType(extension) 90 | }] 91 | }; 92 | } 93 | } 94 | } catch (error) { 95 | log.error('Error reading file resource', error); 96 | throw new Error(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`); 97 | } 98 | } 99 | ); 100 | } 101 | 102 | /** 103 | * Register screenshot resources 104 | */ 105 | function registerScreenshotResources(server: McpServer): void { 106 | server.resource( 107 | 'screenshot', 108 | new ResourceTemplate('screenshot://{window?}', { 109 | list: async () => { 110 | return { resources: [] }; // Empty list by default 111 | } 112 | }), 113 | async (uri, params) => { 114 | try { 115 | await autoIt.init(); 116 | // Ensure window is a string if provided 117 | const windowName = params.window ? String(params.window) : undefined; 118 | log.verbose('Taking screenshot', JSON.stringify({ uri: uri.href, window: windowName })); 119 | 120 | // If window parameter is provided, activate that window first 121 | if (windowName) { 122 | const windowExists = await autoIt.winExists(windowName); 123 | if (windowExists) { 124 | await autoIt.winActivate(windowName); 125 | // Wait a moment for the window to activate 126 | await new Promise(resolve => setTimeout(resolve, 500)); 127 | } else { 128 | throw new Error(`Window "${windowName}" not found`); 129 | } 130 | } 131 | 132 | // TODO: Implement actual screenshot capture 133 | // This is a placeholder - in a real implementation, you would use 134 | // a library like 'screenshot-desktop' or other Windows API bindings 135 | // to capture the screen or specific window 136 | 137 | // For now, we'll return a placeholder message 138 | return { 139 | contents: [{ 140 | uri: uri.href, 141 | text: `Screenshot of ${windowName || 'full screen'} would be captured here`, 142 | mimeType: 'text/plain' 143 | }] 144 | }; 145 | 146 | // In a real implementation, you would return something like: 147 | /* 148 | return { 149 | contents: [{ 150 | uri: uri.href, 151 | blob: screenshotBase64Data, 152 | mimeType: 'image/png' 153 | }] 154 | }; 155 | */ 156 | } catch (error) { 157 | log.error('Error taking screenshot', error); 158 | throw new Error(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`); 159 | } 160 | } 161 | ); 162 | } 163 | 164 | /** 165 | * Get MIME type based on file extension 166 | */ 167 | function getMimeType(extension: string): string { 168 | const mimeTypes: Record<string, string> = { 169 | '.txt': 'text/plain', 170 | '.html': 'text/html', 171 | '.css': 'text/css', 172 | '.js': 'application/javascript', 173 | '.ts': 'application/typescript', 174 | '.json': 'application/json', 175 | '.xml': 'application/xml', 176 | '.md': 'text/markdown', 177 | '.csv': 'text/csv', 178 | '.png': 'image/png', 179 | '.jpg': 'image/jpeg', 180 | '.jpeg': 'image/jpeg', 181 | '.gif': 'image/gif', 182 | '.svg': 'image/svg+xml', 183 | '.pdf': 'application/pdf', 184 | '.doc': 'application/msword', 185 | '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 186 | '.xls': 'application/vnd.ms-excel', 187 | '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 188 | '.ppt': 'application/vnd.ms-powerpoint', 189 | '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 190 | '.zip': 'application/zip', 191 | '.rar': 'application/x-rar-compressed', 192 | '.7z': 'application/x-7z-compressed', 193 | '.tar': 'application/x-tar', 194 | '.gz': 'application/gzip' 195 | }; 196 | 197 | return mimeTypes[extension] || 'application/octet-stream'; 198 | } 199 | ``` -------------------------------------------------------------------------------- /src/prompts/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Prompts module for MCP Windows Desktop Automation 3 | */ 4 | 5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 6 | import { z } from 'zod'; 7 | import { log } from '../utils/logger/logger'; 8 | 9 | /** 10 | * Register all prompts with the MCP server 11 | */ 12 | export function registerAllPrompts(server: McpServer): void { 13 | // Register window interaction prompts 14 | registerWindowPrompts(server); 15 | 16 | // Register form filling prompts 17 | registerFormPrompts(server); 18 | 19 | // Register automation task prompts 20 | registerAutomationPrompts(server); 21 | } 22 | 23 | /** 24 | * Register window interaction prompts 25 | */ 26 | function registerWindowPrompts(server: McpServer): void { 27 | // Prompt for finding and interacting with a window 28 | server.prompt( 29 | 'findWindow', 30 | { 31 | windowTitle: z.string().describe('Title or partial title of the window to find'), 32 | action: z.enum(['activate', 'close', 'minimize', 'maximize']).describe('Action to perform on the window') 33 | }, 34 | ({ windowTitle, action }) => { 35 | log.verbose('findWindow prompt called', { windowTitle, action }); 36 | 37 | let actionDescription: string; 38 | switch (action) { 39 | case 'activate': 40 | actionDescription = 'activate (bring to front)'; 41 | break; 42 | case 'close': 43 | actionDescription = 'close'; 44 | break; 45 | case 'minimize': 46 | actionDescription = 'minimize'; 47 | break; 48 | case 'maximize': 49 | actionDescription = 'maximize'; 50 | break; 51 | } 52 | 53 | return { 54 | description: `Find a window with title "${windowTitle}" and ${actionDescription} it`, 55 | messages: [ 56 | { 57 | role: 'user', 58 | content: { 59 | type: 'text', 60 | text: `I need to find a window with the title "${windowTitle}" and ${actionDescription} it. Can you help me with the steps to do this using AutoIt functions?` 61 | } 62 | } 63 | ] 64 | }; 65 | } 66 | ); 67 | 68 | // Prompt for getting information about a window 69 | server.prompt( 70 | 'windowInfo', 71 | { 72 | windowTitle: z.string().describe('Title or partial title of the window') 73 | }, 74 | ({ windowTitle }) => { 75 | log.verbose('windowInfo prompt called', { windowTitle }); 76 | 77 | return { 78 | description: `Get information about a window with title "${windowTitle}"`, 79 | messages: [ 80 | { 81 | role: 'user', 82 | content: { 83 | type: 'text', 84 | text: `I need to get information about a window with the title "${windowTitle}". Can you help me retrieve details like its position, size, state, and text content using AutoIt functions?` 85 | } 86 | } 87 | ] 88 | }; 89 | } 90 | ); 91 | } 92 | 93 | /** 94 | * Register form filling prompts 95 | */ 96 | function registerFormPrompts(server: McpServer): void { 97 | // Prompt for filling out a form 98 | server.prompt( 99 | 'fillForm', 100 | { 101 | windowTitle: z.string().describe('Title of the window containing the form'), 102 | formFields: z.string().describe('Description of form fields and values to fill in') 103 | }, 104 | ({ windowTitle, formFields }) => { 105 | log.verbose('fillForm prompt called', { windowTitle, formFields }); 106 | 107 | return { 108 | description: `Fill out a form in window "${windowTitle}"`, 109 | messages: [ 110 | { 111 | role: 'user', 112 | content: { 113 | type: 'text', 114 | text: `I need to fill out a form in a window with the title "${windowTitle}". The form has the following fields that need to be filled:\n\n${formFields}\n\nCan you help me automate filling out this form using AutoIt functions?` 115 | } 116 | } 117 | ] 118 | }; 119 | } 120 | ); 121 | 122 | // Prompt for submitting a form 123 | server.prompt( 124 | 'submitForm', 125 | { 126 | windowTitle: z.string().describe('Title of the window containing the form'), 127 | submitButtonText: z.string().describe('Text on the submit button') 128 | }, 129 | ({ windowTitle, submitButtonText }) => { 130 | log.verbose('submitForm prompt called', { windowTitle, submitButtonText }); 131 | 132 | return { 133 | description: `Submit a form in window "${windowTitle}"`, 134 | messages: [ 135 | { 136 | role: 'user', 137 | content: { 138 | type: 'text', 139 | text: `I need to submit a form in a window with the title "${windowTitle}" by clicking the "${submitButtonText}" button. Can you help me automate this using AutoIt functions?` 140 | } 141 | } 142 | ] 143 | }; 144 | } 145 | ); 146 | } 147 | 148 | /** 149 | * Register automation task prompts 150 | */ 151 | function registerAutomationPrompts(server: McpServer): void { 152 | // Prompt for automating a repetitive task 153 | server.prompt( 154 | 'automateTask', 155 | { 156 | taskDescription: z.string().describe('Description of the repetitive task to automate'), 157 | repetitions: z.string().describe('Number of times to repeat the task') 158 | }, 159 | ({ taskDescription, repetitions }) => { 160 | log.verbose('automateTask prompt called', { taskDescription, repetitions }); 161 | 162 | return { 163 | description: `Automate a repetitive task: ${taskDescription}`, 164 | messages: [ 165 | { 166 | role: 'user', 167 | content: { 168 | type: 'text', 169 | text: `I need to automate the following repetitive task ${repetitions} times:\n\n${taskDescription}\n\nCan you help me create an automation script using AutoIt functions to accomplish this?` 170 | } 171 | } 172 | ] 173 | }; 174 | } 175 | ); 176 | 177 | // Prompt for monitoring a window or process 178 | server.prompt( 179 | 'monitorWindow', 180 | { 181 | windowTitle: z.string().describe('Title of the window to monitor'), 182 | condition: z.string().describe('Condition to monitor for (e.g., "appears", "disappears", "contains text X")') 183 | }, 184 | ({ windowTitle, condition }) => { 185 | log.verbose('monitorWindow prompt called', { windowTitle, condition }); 186 | 187 | return { 188 | description: `Monitor window "${windowTitle}" for condition: ${condition}`, 189 | messages: [ 190 | { 191 | role: 'user', 192 | content: { 193 | type: 'text', 194 | text: `I need to monitor a window with the title "${windowTitle}" and wait until the following condition is met: ${condition}. Can you help me create an automation script using AutoIt functions to accomplish this?` 195 | } 196 | } 197 | ] 198 | }; 199 | } 200 | ); 201 | 202 | // Prompt for taking a screenshot 203 | server.prompt( 204 | 'takeScreenshot', 205 | { 206 | target: z.enum(['fullscreen', 'window', 'region']).describe('What to capture in the screenshot'), 207 | windowTitle: z.string().optional().describe('Title of the window to capture (if target is "window")') 208 | }, 209 | ({ target, windowTitle }) => { 210 | log.verbose('takeScreenshot prompt called', { target, windowTitle }); 211 | 212 | let promptText: string; 213 | if (target === 'fullscreen') { 214 | promptText = 'I need to take a screenshot of the entire screen.'; 215 | } else if (target === 'window' && windowTitle) { 216 | promptText = `I need to take a screenshot of a window with the title "${windowTitle}".`; 217 | } else if (target === 'region') { 218 | promptText = 'I need to take a screenshot of a specific region of the screen.'; 219 | } else { 220 | promptText = 'I need to take a screenshot.'; 221 | } 222 | 223 | return { 224 | description: `Take a screenshot of ${target === 'window' ? `window "${windowTitle}"` : target}`, 225 | messages: [ 226 | { 227 | role: 'user', 228 | content: { 229 | type: 'text', 230 | text: `${promptText} Can you help me do this using AutoIt functions?` 231 | } 232 | } 233 | ] 234 | }; 235 | } 236 | ); 237 | } 238 | ``` -------------------------------------------------------------------------------- /src/tools/process.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Process-related tools for MCP Windows Desktop Automation 3 | */ 4 | 5 | import * as autoIt from 'node-autoit-koffi'; 6 | import { z } from 'zod'; 7 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 8 | import { createToolResponse, createErrorResponse, schemas } from '../utils/types'; 9 | import { log } from '../utils/logger/logger'; 10 | 11 | /** 12 | * Register process-related tools with the MCP server 13 | */ 14 | export function registerProcessTools(server: McpServer): void { 15 | // run - Run a program 16 | server.tool( 17 | 'run', 18 | { 19 | program: z.string().describe('Program path or command'), 20 | workingDir: z.string().optional().describe('Working directory'), 21 | showFlag: z.number().optional().describe('Window show flag') 22 | }, 23 | async ({ program, workingDir, showFlag }) => { 24 | try { 25 | log.verbose('run called', { program, workingDir, showFlag }); 26 | await autoIt.init(); 27 | const result = await autoIt.run(program, workingDir, showFlag); 28 | return createToolResponse(`Program "${program}" started with process ID: ${result}`); 29 | } catch (error) { 30 | log.error('run failed', error); 31 | return createErrorResponse(error instanceof Error ? error : String(error)); 32 | } 33 | } 34 | ); 35 | 36 | // runWait - Run a program and wait for it to complete 37 | server.tool( 38 | 'runWait', 39 | { 40 | program: z.string().describe('Program path or command'), 41 | workingDir: z.string().optional().describe('Working directory'), 42 | showFlag: z.number().optional().describe('Window show flag') 43 | }, 44 | async ({ program, workingDir, showFlag }) => { 45 | try { 46 | log.verbose('runWait called', { program, workingDir, showFlag }); 47 | await autoIt.init(); 48 | const result = await autoIt.runWait(program, workingDir, showFlag); 49 | return createToolResponse(`Program "${program}" completed with exit code: ${result}`); 50 | } catch (error) { 51 | log.error('runWait failed', error); 52 | return createErrorResponse(error instanceof Error ? error : String(error)); 53 | } 54 | } 55 | ); 56 | 57 | // runAs - Run a program as a different user 58 | server.tool( 59 | 'runAs', 60 | { 61 | user: z.string().describe('Username'), 62 | domain: z.string().describe('Domain'), 63 | password: z.string().describe('Password'), 64 | logonFlag: z.number().describe('Logon flag'), 65 | program: z.string().describe('Program path or command'), 66 | workingDir: z.string().optional().describe('Working directory'), 67 | showFlag: z.number().optional().describe('Window show flag') 68 | }, 69 | async ({ user, domain, password, logonFlag, program, workingDir, showFlag }) => { 70 | try { 71 | log.verbose('runAs called', { user, domain, logonFlag, program, workingDir, showFlag }); 72 | await autoIt.init(); 73 | const result = await autoIt.runAs(user, domain, password, logonFlag, program, workingDir, showFlag); 74 | return createToolResponse(`Program "${program}" started as user "${user}" with process ID: ${result}`); 75 | } catch (error) { 76 | log.error('runAs failed', error); 77 | return createErrorResponse(error instanceof Error ? error : String(error)); 78 | } 79 | } 80 | ); 81 | 82 | // runAsWait - Run a program as a different user and wait for it to complete 83 | server.tool( 84 | 'runAsWait', 85 | { 86 | user: z.string().describe('Username'), 87 | domain: z.string().describe('Domain'), 88 | password: z.string().describe('Password'), 89 | logonFlag: z.number().describe('Logon flag'), 90 | program: z.string().describe('Program path or command'), 91 | workingDir: z.string().optional().describe('Working directory'), 92 | showFlag: z.number().optional().describe('Window show flag') 93 | }, 94 | async ({ user, domain, password, logonFlag, program, workingDir, showFlag }) => { 95 | try { 96 | log.verbose('runAsWait called', { user, domain, logonFlag, program, workingDir, showFlag }); 97 | await autoIt.init(); 98 | const result = await autoIt.runAsWait(user, domain, password, logonFlag, program, workingDir, showFlag); 99 | return createToolResponse(`Program "${program}" completed as user "${user}" with exit code: ${result}`); 100 | } catch (error) { 101 | log.error('runAsWait failed', error); 102 | return createErrorResponse(error instanceof Error ? error : String(error)); 103 | } 104 | } 105 | ); 106 | 107 | // processExists - Check if a process exists 108 | server.tool( 109 | 'processExists', 110 | { 111 | process: schemas.processName 112 | }, 113 | async ({ process }) => { 114 | try { 115 | log.verbose('processExists called', { process }); 116 | await autoIt.init(); 117 | const result = await autoIt.processExists(process); 118 | const exists = result !== 0; 119 | return createToolResponse( 120 | exists 121 | ? `Process "${process}" exists with PID: ${result}` 122 | : `Process "${process}" does not exist` 123 | ); 124 | } catch (error) { 125 | log.error('processExists failed', error); 126 | return createErrorResponse(error instanceof Error ? error : String(error)); 127 | } 128 | } 129 | ); 130 | 131 | // processClose - Close a process 132 | server.tool( 133 | 'processClose', 134 | { 135 | process: schemas.processName 136 | }, 137 | async ({ process }) => { 138 | try { 139 | log.verbose('processClose called', { process }); 140 | await autoIt.init(); 141 | const result = await autoIt.processClose(process); 142 | const success = result === 1; 143 | return createToolResponse( 144 | success 145 | ? `Process "${process}" closed successfully` 146 | : `Failed to close process "${process}"` 147 | ); 148 | } catch (error) { 149 | log.error('processClose failed', error); 150 | return createErrorResponse(error instanceof Error ? error : String(error)); 151 | } 152 | } 153 | ); 154 | 155 | // processSetPriority - Set process priority 156 | server.tool( 157 | 'processSetPriority', 158 | { 159 | process: schemas.processName, 160 | priority: z.number().describe('Priority level (0-4)') 161 | }, 162 | async ({ process, priority }) => { 163 | try { 164 | log.verbose('processSetPriority called', { process, priority }); 165 | await autoIt.init(); 166 | const result = await autoIt.processSetPriority(process, priority); 167 | const success = result === 1; 168 | return createToolResponse( 169 | success 170 | ? `Priority for process "${process}" set to ${priority}` 171 | : `Failed to set priority for process "${process}"` 172 | ); 173 | } catch (error) { 174 | log.error('processSetPriority failed', error); 175 | return createErrorResponse(error instanceof Error ? error : String(error)); 176 | } 177 | } 178 | ); 179 | 180 | // processWait - Wait for a process to exist 181 | server.tool( 182 | 'processWait', 183 | { 184 | process: schemas.processName, 185 | timeout: schemas.processTimeout 186 | }, 187 | async ({ process, timeout }) => { 188 | try { 189 | log.verbose('processWait called', { process, timeout }); 190 | await autoIt.init(); 191 | const result = await autoIt.processWait(process, timeout); 192 | const success = result !== 0; 193 | return createToolResponse( 194 | success 195 | ? `Process "${process}" exists with PID: ${result}` 196 | : `Timed out waiting for process "${process}"` 197 | ); 198 | } catch (error) { 199 | log.error('processWait failed', error); 200 | return createErrorResponse(error instanceof Error ? error : String(error)); 201 | } 202 | } 203 | ); 204 | 205 | // processWaitClose - Wait for a process to close 206 | server.tool( 207 | 'processWaitClose', 208 | { 209 | process: schemas.processName, 210 | timeout: schemas.processTimeout 211 | }, 212 | async ({ process, timeout }) => { 213 | try { 214 | log.verbose('processWaitClose called', { process, timeout }); 215 | await autoIt.init(); 216 | const result = await autoIt.processWaitClose(process, timeout); 217 | const success = result === 1; 218 | return createToolResponse( 219 | success 220 | ? `Process "${process}" closed within the timeout` 221 | : `Timed out waiting for process "${process}" to close` 222 | ); 223 | } catch (error) { 224 | log.error('processWaitClose failed', error); 225 | return createErrorResponse(error instanceof Error ? error : String(error)); 226 | } 227 | } 228 | ); 229 | 230 | // shutdown - Shut down the system 231 | server.tool( 232 | 'shutdown', 233 | { 234 | flags: z.number().describe('Shutdown flags') 235 | }, 236 | async ({ flags }) => { 237 | try { 238 | log.verbose('shutdown called', { flags }); 239 | await autoIt.init(); 240 | const result = await autoIt.shutdown(flags); 241 | const success = result === 1; 242 | return createToolResponse( 243 | success 244 | ? `System shutdown initiated with flags: ${flags}` 245 | : `Failed to initiate system shutdown` 246 | ); 247 | } catch (error) { 248 | log.error('shutdown failed', error); 249 | return createErrorResponse(error instanceof Error ? error : String(error)); 250 | } 251 | } 252 | ); 253 | } 254 | ``` -------------------------------------------------------------------------------- /src/tools/window.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Window-related tools for MCP Windows Desktop Automation 3 | */ 4 | 5 | import * as autoIt from 'node-autoit-koffi'; 6 | import { z } from 'zod'; 7 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 8 | import { createToolResponse, createErrorResponse, schemas } from '../utils/types'; 9 | import { log } from '../utils/logger/logger'; 10 | 11 | /** 12 | * Register window-related tools with the MCP server 13 | */ 14 | export function registerWindowTools(server: McpServer): void { 15 | // winActivate - Activate a window 16 | server.tool( 17 | 'winActivate', 18 | { 19 | title: schemas.windowTitle, 20 | text: schemas.windowText 21 | }, 22 | async ({ title, text }) => { 23 | try { 24 | log.verbose('winActivate called', { title, text }); 25 | await autoIt.init(); 26 | const result = await autoIt.winActivate(title, text); 27 | return createToolResponse(`Window "${title}" activated with result: ${result}`); 28 | } catch (error) { 29 | log.error('winActivate failed', error); 30 | return createErrorResponse(error instanceof Error ? error : String(error)); 31 | } 32 | } 33 | ); 34 | 35 | // winActivateByHandle - Activate a window by handle 36 | server.tool( 37 | 'winActivateByHandle', 38 | { 39 | handle: schemas.handle 40 | }, 41 | async ({ handle }) => { 42 | try { 43 | log.verbose('winActivateByHandle called', { handle }); 44 | await autoIt.init(); 45 | const result = await autoIt.winActivateByHandle(handle); 46 | return createToolResponse(`Window with handle ${handle} activated with result: ${result}`); 47 | } catch (error) { 48 | log.error('winActivateByHandle failed', error); 49 | return createErrorResponse(error instanceof Error ? error : String(error)); 50 | } 51 | } 52 | ); 53 | 54 | // winActive - Check if a window is active 55 | server.tool( 56 | 'winActive', 57 | { 58 | title: schemas.windowTitle, 59 | text: z.string().describe('Window text') 60 | }, 61 | async ({ title, text }) => { 62 | try { 63 | log.verbose('winActive called', { title, text }); 64 | await autoIt.init(); 65 | const result = await autoIt.winActive(title, text); 66 | const isActive = result === 1; 67 | return createToolResponse(`Window "${title}" is ${isActive ? 'active' : 'not active'}`); 68 | } catch (error) { 69 | log.error('winActive failed', error); 70 | return createErrorResponse(error instanceof Error ? error : String(error)); 71 | } 72 | } 73 | ); 74 | 75 | // winClose - Close a window 76 | server.tool( 77 | 'winClose', 78 | { 79 | title: schemas.windowTitle, 80 | text: schemas.windowText 81 | }, 82 | async ({ title, text }) => { 83 | try { 84 | log.verbose('winClose called', { title, text }); 85 | await autoIt.init(); 86 | const result = await autoIt.winClose(title, text); 87 | return createToolResponse(`Window "${title}" closed with result: ${result}`); 88 | } catch (error) { 89 | log.error('winClose failed', error); 90 | return createErrorResponse(error instanceof Error ? error : String(error)); 91 | } 92 | } 93 | ); 94 | 95 | // winExists - Check if a window exists 96 | server.tool( 97 | 'winExists', 98 | { 99 | title: schemas.windowTitle, 100 | text: schemas.windowText 101 | }, 102 | async ({ title, text }) => { 103 | try { 104 | log.verbose('winExists called', { title, text }); 105 | await autoIt.init(); 106 | const result = await autoIt.winExists(title, text); 107 | const exists = result === 1; 108 | return createToolResponse(`Window "${title}" ${exists ? 'exists' : 'does not exist'}`); 109 | } catch (error) { 110 | log.error('winExists failed', error); 111 | return createErrorResponse(error instanceof Error ? error : String(error)); 112 | } 113 | } 114 | ); 115 | 116 | // winGetHandle - Get a window handle 117 | server.tool( 118 | 'winGetHandle', 119 | { 120 | title: schemas.windowTitle, 121 | text: schemas.windowText 122 | }, 123 | async ({ title, text }) => { 124 | try { 125 | log.verbose('winGetHandle called', { title, text }); 126 | await autoIt.init(); 127 | const handle = await autoIt.winGetHandle(title, text); 128 | log.verbose('winGetHandle result', JSON.stringify({ handle })); 129 | return createToolResponse(`Window "${title}" handle: ${handle}`); 130 | } catch (error) { 131 | log.error('winGetHandle failed', error); 132 | return createErrorResponse(error instanceof Error ? error : String(error)); 133 | } 134 | } 135 | ); 136 | 137 | // winGetPos - Get window position and size 138 | server.tool( 139 | 'winGetPos', 140 | { 141 | title: schemas.windowTitle, 142 | text: schemas.windowText 143 | }, 144 | async ({ title, text }) => { 145 | try { 146 | log.verbose('winGetPos called', { title, text }); 147 | await autoIt.init(); 148 | const rect = await autoIt.winGetPos(title, text); 149 | log.verbose('winGetPos result', JSON.stringify(rect)); 150 | const width = rect.right - rect.left; 151 | const height = rect.bottom - rect.top; 152 | return createToolResponse( 153 | `Window "${title}" position: Left=${rect.left}, Top=${rect.top}, Width=${width}, Height=${height}` 154 | ); 155 | } catch (error) { 156 | log.error('winGetPos failed', error); 157 | return createErrorResponse(error instanceof Error ? error : String(error)); 158 | } 159 | } 160 | ); 161 | 162 | // winGetText - Get window text 163 | server.tool( 164 | 'winGetText', 165 | { 166 | title: schemas.windowTitle, 167 | text: schemas.windowText, 168 | bufSize: schemas.bufferSize 169 | }, 170 | async ({ title, text, bufSize }) => { 171 | try { 172 | log.verbose('winGetText called', { title, text, bufSize }); 173 | await autoIt.init(); 174 | const windowText = await autoIt.winGetText(title, text, bufSize); 175 | log.verbose('winGetText result', JSON.stringify({ windowText })); 176 | return createToolResponse(`Window "${title}" text: "${windowText}"`); 177 | } catch (error) { 178 | log.error('winGetText failed', error); 179 | return createErrorResponse(error instanceof Error ? error : String(error)); 180 | } 181 | } 182 | ); 183 | 184 | // winGetTitle - Get window title 185 | server.tool( 186 | 'winGetTitle', 187 | { 188 | title: schemas.windowTitle, 189 | text: schemas.windowText, 190 | bufSize: schemas.bufferSize 191 | }, 192 | async ({ title, text, bufSize }) => { 193 | try { 194 | log.verbose('winGetTitle called', { title, text, bufSize }); 195 | await autoIt.init(); 196 | const windowTitle = await autoIt.winGetTitle(title, text, bufSize); 197 | log.verbose('winGetTitle result', JSON.stringify({ windowTitle })); 198 | return createToolResponse(`Window "${title}" title: "${windowTitle}"`); 199 | } catch (error) { 200 | log.error('winGetTitle failed', error); 201 | return createErrorResponse(error instanceof Error ? error : String(error)); 202 | } 203 | } 204 | ); 205 | 206 | // winMove - Move and resize a window 207 | server.tool( 208 | 'winMove', 209 | { 210 | title: schemas.windowTitle, 211 | text: schemas.windowText, 212 | x: z.number().describe('X coordinate'), 213 | y: z.number().describe('Y coordinate'), 214 | width: z.number().optional().describe('Window width'), 215 | height: z.number().optional().describe('Window height') 216 | }, 217 | async ({ title, text, x, y, width, height }) => { 218 | try { 219 | log.verbose('winMove called', { title, text, x, y, width, height }); 220 | await autoIt.init(); 221 | const result = await autoIt.winMove(title, text, x, y, width, height); 222 | const sizeInfo = width && height ? ` and resized to ${width}x${height}` : ''; 223 | return createToolResponse(`Window "${title}" moved to (${x}, ${y})${sizeInfo} with result: ${result}`); 224 | } catch (error) { 225 | log.error('winMove failed', error); 226 | return createErrorResponse(error instanceof Error ? error : String(error)); 227 | } 228 | } 229 | ); 230 | 231 | // winSetState - Set window state (minimized, maximized, etc.) 232 | server.tool( 233 | 'winSetState', 234 | { 235 | title: schemas.windowTitle, 236 | text: schemas.windowText, 237 | flags: z.number().describe('State flags') 238 | }, 239 | async ({ title, text, flags }) => { 240 | try { 241 | log.verbose('winSetState called', { title, text, flags }); 242 | await autoIt.init(); 243 | const result = await autoIt.winSetState(title, text, flags); 244 | return createToolResponse(`Window "${title}" state set to ${flags} with result: ${result}`); 245 | } catch (error) { 246 | log.error('winSetState failed', error); 247 | return createErrorResponse(error instanceof Error ? error : String(error)); 248 | } 249 | } 250 | ); 251 | 252 | // winWait - Wait for a window to exist 253 | server.tool( 254 | 'winWait', 255 | { 256 | title: schemas.windowTitle, 257 | text: schemas.windowText, 258 | timeout: z.number().optional().describe('Timeout in seconds') 259 | }, 260 | async ({ title, text, timeout }) => { 261 | try { 262 | log.verbose('winWait called', { title, text, timeout }); 263 | await autoIt.init(); 264 | const result = await autoIt.winWait(title, text, timeout); 265 | const success = result === 1; 266 | return createToolResponse( 267 | success 268 | ? `Window "${title}" appeared within the timeout` 269 | : `Window "${title}" did not appear within the timeout` 270 | ); 271 | } catch (error) { 272 | log.error('winWait failed', error); 273 | return createErrorResponse(error instanceof Error ? error : String(error)); 274 | } 275 | } 276 | ); 277 | 278 | // winWaitActive - Wait for a window to be active 279 | server.tool( 280 | 'winWaitActive', 281 | { 282 | title: schemas.windowTitle, 283 | text: schemas.windowText, 284 | timeout: z.number().optional().describe('Timeout in seconds') 285 | }, 286 | async ({ title, text, timeout }) => { 287 | try { 288 | log.verbose('winWaitActive called', { title, text, timeout }); 289 | await autoIt.init(); 290 | const result = await autoIt.winWaitActive(title, text, timeout); 291 | const success = result === 1; 292 | return createToolResponse( 293 | success 294 | ? `Window "${title}" became active within the timeout` 295 | : `Window "${title}" did not become active within the timeout` 296 | ); 297 | } catch (error) { 298 | log.error('winWaitActive failed', error); 299 | return createErrorResponse(error instanceof Error ? error : String(error)); 300 | } 301 | } 302 | ); 303 | 304 | // winWaitClose - Wait for a window to close 305 | server.tool( 306 | 'winWaitClose', 307 | { 308 | title: schemas.windowTitle, 309 | text: schemas.windowText, 310 | timeout: z.number().optional().describe('Timeout in seconds') 311 | }, 312 | async ({ title, text, timeout }) => { 313 | try { 314 | log.verbose('winWaitClose called', { title, text, timeout }); 315 | await autoIt.init(); 316 | const result = await autoIt.winWaitClose(title, text, timeout); 317 | const success = result === 1; 318 | return createToolResponse( 319 | success 320 | ? `Window "${title}" closed within the timeout` 321 | : `Window "${title}" did not close within the timeout` 322 | ); 323 | } catch (error) { 324 | log.error('winWaitClose failed', error); 325 | return createErrorResponse(error instanceof Error ? error : String(error)); 326 | } 327 | } 328 | ); 329 | } 330 | ``` -------------------------------------------------------------------------------- /src/tools/control.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Control-related tools for MCP Windows Desktop Automation 3 | */ 4 | 5 | import * as autoIt from 'node-autoit-koffi'; 6 | import { z } from 'zod'; 7 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 8 | import { createToolResponse, createErrorResponse, schemas } from '../utils/types'; 9 | import { log } from '../utils/logger/logger'; 10 | 11 | /** 12 | * Register control-related tools with the MCP server 13 | */ 14 | export function registerControlTools(server: McpServer): void { 15 | // controlClick - Click on a control 16 | server.tool( 17 | 'controlClick', 18 | { 19 | title: schemas.windowTitle, 20 | text: schemas.windowText, 21 | control: schemas.controlName, 22 | button: schemas.mouseButton, 23 | clicks: schemas.mouseClicks, 24 | x: z.number().optional().describe('X coordinate within control'), 25 | y: z.number().optional().describe('Y coordinate within control') 26 | }, 27 | async ({ title, text, control, button, clicks, x, y }) => { 28 | try { 29 | log.verbose('controlClick called', { title, text, control, button, clicks, x, y }); 30 | await autoIt.init(); 31 | const result = await autoIt.controlClick(title, text, control, button, clicks, x, y); 32 | const success = result === 1; 33 | return createToolResponse( 34 | success 35 | ? `Clicked on control "${control}" in window "${title}"` 36 | : `Failed to click on control "${control}" in window "${title}"` 37 | ); 38 | } catch (error) { 39 | log.error('controlClick failed', error); 40 | return createErrorResponse(error instanceof Error ? error : String(error)); 41 | } 42 | } 43 | ); 44 | 45 | // controlClickByHandle - Click on a control by handle 46 | server.tool( 47 | 'controlClickByHandle', 48 | { 49 | windowHandle: schemas.handle, 50 | controlHandle: schemas.handle, 51 | button: schemas.mouseButton, 52 | clicks: schemas.mouseClicks, 53 | x: z.number().optional().describe('X coordinate within control'), 54 | y: z.number().optional().describe('Y coordinate within control') 55 | }, 56 | async ({ windowHandle, controlHandle, button, clicks, x, y }) => { 57 | try { 58 | log.verbose('controlClickByHandle called', { windowHandle, controlHandle, button, clicks, x, y }); 59 | await autoIt.init(); 60 | const result = await autoIt.controlClickByHandle(windowHandle, controlHandle, button, clicks, x, y); 61 | const success = result === 1; 62 | return createToolResponse( 63 | success 64 | ? `Clicked on control handle ${controlHandle} in window handle ${windowHandle}` 65 | : `Failed to click on control handle ${controlHandle} in window handle ${windowHandle}` 66 | ); 67 | } catch (error) { 68 | log.error('controlClickByHandle failed', error); 69 | return createErrorResponse(error instanceof Error ? error : String(error)); 70 | } 71 | } 72 | ); 73 | 74 | // controlCommand - Send a command to a control 75 | server.tool( 76 | 'controlCommand', 77 | { 78 | title: schemas.windowTitle, 79 | text: schemas.windowText, 80 | control: schemas.controlName, 81 | command: z.string().describe('Command to send'), 82 | extra: z.string().optional().describe('Extra parameter for the command'), 83 | bufSize: schemas.bufferSize 84 | }, 85 | async ({ title, text, control, command, extra, bufSize }) => { 86 | try { 87 | log.verbose('controlCommand called', { title, text, control, command, extra, bufSize }); 88 | await autoIt.init(); 89 | const result = await autoIt.controlCommand(title, text, control, command, extra, bufSize); 90 | log.verbose('controlCommand result', JSON.stringify({ result })); 91 | return createToolResponse(`Command "${command}" sent to control "${control}" with result: ${result}`); 92 | } catch (error) { 93 | log.error('controlCommand failed', error); 94 | return createErrorResponse(error instanceof Error ? error : String(error)); 95 | } 96 | } 97 | ); 98 | 99 | // controlGetText - Get text from a control 100 | server.tool( 101 | 'controlGetText', 102 | { 103 | title: schemas.windowTitle, 104 | text: schemas.windowText, 105 | control: schemas.controlName, 106 | bufSize: schemas.bufferSize 107 | }, 108 | async ({ title, text, control, bufSize }) => { 109 | try { 110 | log.verbose('controlGetText called', { title, text, control, bufSize }); 111 | await autoIt.init(); 112 | const controlText = await autoIt.controlGetText(title, text, control, bufSize); 113 | log.verbose('controlGetText result', JSON.stringify({ controlText })); 114 | return createToolResponse(`Text from control "${control}": "${controlText}"`); 115 | } catch (error) { 116 | log.error('controlGetText failed', error); 117 | return createErrorResponse(error instanceof Error ? error : String(error)); 118 | } 119 | } 120 | ); 121 | 122 | // controlSetText - Set text in a control 123 | server.tool( 124 | 'controlSetText', 125 | { 126 | title: schemas.windowTitle, 127 | text: schemas.windowText, 128 | control: schemas.controlName, 129 | controlText: schemas.controlText 130 | }, 131 | async ({ title, text, control, controlText }) => { 132 | try { 133 | log.verbose('controlSetText called', { title, text, control, controlText }); 134 | await autoIt.init(); 135 | const result = await autoIt.controlSetText(title, text, control, controlText); 136 | const success = result === 1; 137 | return createToolResponse( 138 | success 139 | ? `Text set in control "${control}" to "${controlText}"` 140 | : `Failed to set text in control "${control}"` 141 | ); 142 | } catch (error) { 143 | log.error('controlSetText failed', error); 144 | return createErrorResponse(error instanceof Error ? error : String(error)); 145 | } 146 | } 147 | ); 148 | 149 | // controlSend - Send keystrokes to a control 150 | server.tool( 151 | 'controlSend', 152 | { 153 | title: schemas.windowTitle, 154 | text: schemas.windowText, 155 | control: schemas.controlName, 156 | sendText: schemas.controlText, 157 | mode: z.number().optional().describe('Send mode flag') 158 | }, 159 | async ({ title, text, control, sendText, mode }) => { 160 | try { 161 | log.verbose('controlSend called', { title, text, control, sendText, mode }); 162 | await autoIt.init(); 163 | const result = await autoIt.controlSend(title, text, control, sendText, mode); 164 | const success = result === 1; 165 | return createToolResponse( 166 | success 167 | ? `Keystrokes "${sendText}" sent to control "${control}"` 168 | : `Failed to send keystrokes to control "${control}"` 169 | ); 170 | } catch (error) { 171 | log.error('controlSend failed', error); 172 | return createErrorResponse(error instanceof Error ? error : String(error)); 173 | } 174 | } 175 | ); 176 | 177 | // controlFocus - Set focus to a control 178 | server.tool( 179 | 'controlFocus', 180 | { 181 | title: schemas.windowTitle, 182 | text: schemas.windowText, 183 | control: schemas.controlName 184 | }, 185 | async ({ title, text, control }) => { 186 | try { 187 | log.verbose('controlFocus called', { title, text, control }); 188 | await autoIt.init(); 189 | const result = await autoIt.controlFocus(title, text, control); 190 | const success = result === 1; 191 | return createToolResponse( 192 | success 193 | ? `Focus set to control "${control}"` 194 | : `Failed to set focus to control "${control}"` 195 | ); 196 | } catch (error) { 197 | log.error('controlFocus failed', error); 198 | return createErrorResponse(error instanceof Error ? error : String(error)); 199 | } 200 | } 201 | ); 202 | 203 | // controlGetHandle - Get a control handle 204 | server.tool( 205 | 'controlGetHandle', 206 | { 207 | windowHandle: schemas.handle, 208 | control: schemas.controlName 209 | }, 210 | async ({ windowHandle, control }) => { 211 | try { 212 | log.verbose('controlGetHandle called', { windowHandle, control }); 213 | await autoIt.init(); 214 | const handle = await autoIt.controlGetHandle(windowHandle, control); 215 | log.verbose('controlGetHandle result', JSON.stringify({ handle })); 216 | return createToolResponse(`Control "${control}" handle: ${handle}`); 217 | } catch (error) { 218 | log.error('controlGetHandle failed', error); 219 | return createErrorResponse(error instanceof Error ? error : String(error)); 220 | } 221 | } 222 | ); 223 | 224 | // controlGetPos - Get control position and size 225 | server.tool( 226 | 'controlGetPos', 227 | { 228 | title: schemas.windowTitle, 229 | text: schemas.windowText, 230 | control: schemas.controlName 231 | }, 232 | async ({ title, text, control }) => { 233 | try { 234 | log.verbose('controlGetPos called', { title, text, control }); 235 | await autoIt.init(); 236 | const rect = await autoIt.controlGetPos(title, text, control); 237 | log.verbose('controlGetPos result', JSON.stringify(rect)); 238 | const width = rect.right - rect.left; 239 | const height = rect.bottom - rect.top; 240 | return createToolResponse( 241 | `Control "${control}" position: Left=${rect.left}, Top=${rect.top}, Width=${width}, Height=${height}` 242 | ); 243 | } catch (error) { 244 | log.error('controlGetPos failed', error); 245 | return createErrorResponse(error instanceof Error ? error : String(error)); 246 | } 247 | } 248 | ); 249 | 250 | // controlMove - Move and resize a control 251 | server.tool( 252 | 'controlMove', 253 | { 254 | title: schemas.windowTitle, 255 | text: schemas.windowText, 256 | control: schemas.controlName, 257 | x: z.number().describe('X coordinate'), 258 | y: z.number().describe('Y coordinate'), 259 | width: z.number().optional().describe('Control width'), 260 | height: z.number().optional().describe('Control height') 261 | }, 262 | async ({ title, text, control, x, y, width, height }) => { 263 | try { 264 | log.verbose('controlMove called', { title, text, control, x, y, width, height }); 265 | await autoIt.init(); 266 | const result = await autoIt.controlMove(title, text, control, x, y, width, height); 267 | const success = result === 1; 268 | const sizeInfo = width && height ? ` and resized to ${width}x${height}` : ''; 269 | return createToolResponse( 270 | success 271 | ? `Control "${control}" moved to (${x}, ${y})${sizeInfo}` 272 | : `Failed to move control "${control}"` 273 | ); 274 | } catch (error) { 275 | log.error('controlMove failed', error); 276 | return createErrorResponse(error instanceof Error ? error : String(error)); 277 | } 278 | } 279 | ); 280 | 281 | // controlShow - Show a control 282 | server.tool( 283 | 'controlShow', 284 | { 285 | title: schemas.windowTitle, 286 | text: schemas.windowText, 287 | control: schemas.controlName 288 | }, 289 | async ({ title, text, control }) => { 290 | try { 291 | log.verbose('controlShow called', { title, text, control }); 292 | await autoIt.init(); 293 | const result = await autoIt.controlShow(title, text, control); 294 | const success = result === 1; 295 | return createToolResponse( 296 | success 297 | ? `Control "${control}" shown` 298 | : `Failed to show control "${control}"` 299 | ); 300 | } catch (error) { 301 | log.error('controlShow failed', error); 302 | return createErrorResponse(error instanceof Error ? error : String(error)); 303 | } 304 | } 305 | ); 306 | 307 | // controlHide - Hide a control 308 | server.tool( 309 | 'controlHide', 310 | { 311 | title: schemas.windowTitle, 312 | text: schemas.windowText, 313 | control: schemas.controlName 314 | }, 315 | async ({ title, text, control }) => { 316 | try { 317 | log.verbose('controlHide called', { title, text, control }); 318 | await autoIt.init(); 319 | const result = await autoIt.controlHide(title, text, control); 320 | const success = result === 1; 321 | return createToolResponse( 322 | success 323 | ? `Control "${control}" hidden` 324 | : `Failed to hide control "${control}"` 325 | ); 326 | } catch (error) { 327 | log.error('controlHide failed', error); 328 | return createErrorResponse(error instanceof Error ? error : String(error)); 329 | } 330 | } 331 | ); 332 | } 333 | ```