# 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 |
```