# 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:
--------------------------------------------------------------------------------
```
# Auto detect text files and perform LF normalization
* text=auto
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# MCP Windows Desktop Automation
A Model Context Protocol (MCP) server for Windows desktop automation using AutoIt.
## Overview
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.
The server exposes:
- **Tools**: All AutoIt functions as MCP tools
- **Resources**: File access and screenshot capabilities
- **Prompts**: Templates for common automation tasks
## Features
- Full wrapping of all AutoIt functions as MCP tools
- Support for both stdio and WebSocket transports
- File access resources for reading files and directories
- Screenshot resources for capturing the screen or specific windows
- Prompt templates for common automation tasks
- Strict TypeScript typing throughout
## Installation
```bash
# Clone the repository
git clone https://github.com/yourusername/mcp-windows-desktop-automation.git
cd mcp-windows-desktop-automation
# Install dependencies
npm install
# Build the project
npm run build
```
## Usage
### Starting the Server
```bash
# Start with stdio transport (default)
npm start
# Start with WebSocket transport
npm start -- --transport=websocket --port=3000
# Enable verbose logging
npm start -- --verbose
```
### Command Line Options
- `--transport=stdio|websocket`: Specify the transport protocol (default: stdio)
- `--port=<number>`: Specify the port for WebSocket transport (default: 3000)
- `--verbose`: Enable verbose logging
## Tools
The server provides tools for:
- **Mouse operations**: Move, click, drag, etc.
- **Keyboard operations**: Send keystrokes, clipboard operations, etc.
- **Window management**: Find, activate, close, resize windows, etc.
- **Control manipulation**: Interact with UI controls, buttons, text fields, etc.
- **Process management**: Start, stop, and monitor processes
- **System operations**: Shutdown, sleep, etc.
## Resources
The server provides resources for:
- **File access**: Read files and list directories
- **Screenshots**: Capture the screen or specific windows
## Prompts
The server provides prompt templates for:
- **Window interaction**: Find and interact with windows
- **Form filling**: Automate form filling tasks
- **Automation tasks**: Create scripts for repetitive tasks
- **Monitoring**: Wait for specific conditions
## Development
```bash
# Run in development mode
npm run dev
# Lint the code
npm run lint
# Run tests
npm run test
```
## License
MIT
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"outDir": "./dist",
"sourceMap": true,
"declaration": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tools module for MCP Windows Desktop Automation
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerMouseTools } from './mouse';
import { registerKeyboardTools } from './keyboard';
import { registerWindowTools } from './window';
import { registerProcessTools } from './process';
import { registerControlTools } from './control';
/**
* Register all AutoIt tools with the MCP server
*/
export function registerAllTools(server: McpServer): void {
registerMouseTools(server);
registerKeyboardTools(server);
registerWindowTools(server);
registerProcessTools(server);
registerControlTools(server);
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-windows-desktop-automation",
"version": "0.1.0",
"description": "MCP server for Windows desktop automation",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"lint": "eslint src/**/*.ts",
"test": "jest"
},
"keywords": [
"mcp",
"windows",
"automation",
"autoit"
],
"author": "",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"node-autoit-koffi": "^1.0.5",
"ws": "^8.18.1"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/node": "^18.15.11",
"@types/ws": "^8.18.0",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"eslint": "^8.38.0",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
}
}
```
--------------------------------------------------------------------------------
/src/utils/logger/logger.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Logger utility for MCP Windows Desktop Automation
*/
enum LogLevel {
VERBOSE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4
}
class Logger {
private level: LogLevel = LogLevel.INFO;
/**
* Set the minimum log level
* @param level The minimum level to log
*/
setLevel(level: LogLevel): void {
this.level = level;
}
/**
* Log verbose information
* @param message The message to log
* @param data Additional data to log (will be JSON stringified)
*/
verbose(message: string, data?: any): void {
if (this.level <= LogLevel.VERBOSE) {
console.log(`[VERBOSE] ${message}`, data ? JSON.stringify(data) : '');
}
}
/**
* Log debug information
* @param message The message to log
* @param data Additional data to log
*/
debug(message: string, data?: any): void {
if (this.level <= LogLevel.DEBUG) {
console.log(`[DEBUG] ${message}`, data || '');
}
}
/**
* Log general information
* @param message The message to log
* @param data Additional data to log
*/
info(message: string, data?: any): void {
if (this.level <= LogLevel.INFO) {
console.log(`[INFO] ${message}`, data || '');
}
}
/**
* Log warnings
* @param message The message to log
* @param data Additional data to log
*/
warn(message: string, data?: any): void {
if (this.level <= LogLevel.WARN) {
console.warn(`[WARN] ${message}`, data || '');
}
}
/**
* Log errors
* @param message The message to log
* @param data Additional data to log
*/
error(message: string, data?: any): void {
if (this.level <= LogLevel.ERROR) {
console.error(`[ERROR] ${message}`, data || '');
}
}
}
export const log = new Logger();
```
--------------------------------------------------------------------------------
/src/utils/types.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Shared type definitions for MCP Windows Desktop Automation
*/
import { z } from 'zod';
import { CallToolResult, TextContent } from '@modelcontextprotocol/sdk/types.js';
/**
* Point coordinates
*/
export interface Point {
x: number;
y: number;
}
/**
* Rectangle coordinates
*/
export interface Rect {
left: number;
top: number;
right: number;
bottom: number;
}
/**
* Standard tool response creator
*/
export function createToolResponse(message: string): CallToolResult {
return {
content: [
{
type: 'text',
text: message
} as TextContent
]
};
}
/**
* Standard error response creator
*/
export function createErrorResponse(error: Error | string): CallToolResult {
const errorMessage = typeof error === 'string' ? error : error.message;
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`
} as TextContent
],
isError: true
};
}
/**
* Common Zod schemas for tool parameters
*/
export const schemas = {
// Window identification
windowTitle: z.string().describe('Window title'),
windowText: z.string().optional().describe('Window text'),
// Mouse parameters
mouseButton: z.enum(['left', 'right', 'middle']).optional().default('left').describe('Mouse button'),
mouseSpeed: z.number().min(1).max(100).optional().default(10).describe('Mouse movement speed (1-100)'),
mouseX: z.number().describe('X coordinate'),
mouseY: z.number().describe('Y coordinate'),
mouseClicks: z.number().min(1).optional().default(1).describe('Number of clicks'),
// Control parameters
controlName: z.string().describe('Control identifier'),
controlText: z.string().describe('Text to set/send to control'),
// Process parameters
processName: z.string().describe('Process name or executable path'),
processTimeout: z.number().optional().describe('Timeout in milliseconds'),
// Common parameters
handle: z.number().describe('Window or control handle'),
bufferSize: z.number().optional().describe('Buffer size for string operations')
};
```
--------------------------------------------------------------------------------
/src/server/server.ts:
--------------------------------------------------------------------------------
```typescript
/**
* MCP Server configuration for Windows Desktop Automation
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { WebSocket, WebSocketServer, RawData } from 'ws';
import { createServer as createHttpServer, IncomingMessage, Server as HttpServer } from 'http';
import { log } from '../utils/logger/logger';
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
/**
* Custom WebSocket transport for MCP
*/
class WebSocketServerTransport implements Transport {
private ws: WebSocket | null = null;
sessionId?: string;
constructor(ws: WebSocket) {
this.ws = ws;
this.sessionId = Math.random().toString(36).substring(2, 15);
ws.on('message', (data: RawData) => {
try {
const message = JSON.parse(data.toString()) as JSONRPCMessage;
this.onmessage?.(message);
} catch (error) {
this.onerror?.(new Error(`Failed to parse message: ${error}`));
}
});
ws.on('close', () => {
this.ws = null;
this.onclose?.();
});
ws.on('error', (error: Error) => {
this.onerror?.(error);
});
}
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
async start(): Promise<void> {
log.info('WebSocket transport started');
}
async send(message: JSONRPCMessage): Promise<void> {
if (!this.ws) {
throw new Error('WebSocket not connected');
}
return new Promise((resolve, reject) => {
this.ws!.send(JSON.stringify(message), (error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async close(): Promise<void> {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
}
/**
* Server configuration options
*/
export interface ServerConfig {
name: string;
version: string;
transport: 'stdio' | 'websocket';
port?: number;
}
/**
* Create and configure an MCP server
*/
export async function setupServer(config: ServerConfig): Promise<{
server: McpServer;
httpServer?: HttpServer;
}> {
// Create the MCP server
const server = new McpServer({
name: config.name,
version: config.version
}, {
capabilities: {
tools: {},
resources: {
subscribe: true,
listChanged: true
},
prompts: {
listChanged: true
}
}
});
// Configure the transport
if (config.transport === 'stdio') {
log.info('Using stdio transport');
const transport = new StdioServerTransport();
await server.connect(transport);
return { server };
} else if (config.transport === 'websocket') {
log.info(`Using WebSocket transport on port ${config.port}`);
// Create HTTP server
const httpServer = createHttpServer();
const wss = new WebSocketServer({ server: httpServer });
// Handle WebSocket connections
wss.on('connection', async (ws: WebSocket) => {
log.info('New WebSocket connection');
const transport = new WebSocketServerTransport(ws);
await server.connect(transport);
});
// Start HTTP server
httpServer.listen(config.port || 3000);
return { server, httpServer };
} else {
throw new Error(`Unsupported transport: ${config.transport}`);
}
}
```
--------------------------------------------------------------------------------
/src/tools/keyboard.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Keyboard-related tools for MCP Windows Desktop Automation
*/
import * as autoIt from 'node-autoit-koffi';
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { createToolResponse, createErrorResponse } from '../utils/types';
import { log } from '../utils/logger/logger';
/**
* Register keyboard-related tools with the MCP server
*/
export function registerKeyboardTools(server: McpServer): void {
// send - Send keystrokes to the active window
server.tool(
'send',
{
text: z.string().describe('Text or keys to send'),
mode: z.number().optional().default(0).describe('Send mode flag')
},
async ({ text, mode }) => {
try {
log.verbose('send called', { text, mode });
await autoIt.init();
await autoIt.send(text, mode);
return createToolResponse(`Sent keystrokes: "${text}" with mode ${mode}`);
} catch (error) {
log.error('send failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// clipGet - Get the text from the clipboard
server.tool(
'clipGet',
{
bufSize: z.number().optional().describe('Buffer size for clipboard content')
},
async ({ bufSize }) => {
try {
log.verbose('clipGet called', { bufSize });
await autoIt.init();
const clipboardContent = await autoIt.clipGet(bufSize);
log.verbose('clipGet result', JSON.stringify({ clipboardContent }));
return createToolResponse(`Clipboard content: "${clipboardContent}"`);
} catch (error) {
log.error('clipGet failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// clipPut - Put text into the clipboard
server.tool(
'clipPut',
{
text: z.string().describe('Text to put in the clipboard')
},
async ({ text }) => {
try {
log.verbose('clipPut called', { text });
await autoIt.init();
await autoIt.clipPut(text);
return createToolResponse(`Text set to clipboard: "${text}"`);
} catch (error) {
log.error('clipPut failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// autoItSetOption - Set AutoIt options
server.tool(
'autoItSetOption',
{
option: z.string().describe('Option name'),
value: z.number().describe('Option value')
},
async ({ option, value }) => {
try {
log.verbose('autoItSetOption called', { option, value });
await autoIt.init();
const result = await autoIt.autoItSetOption(option, value);
return createToolResponse(`AutoIt option "${option}" set to ${value} with result: ${result}`);
} catch (error) {
log.error('autoItSetOption failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// opt - Alias for autoItSetOption
server.tool(
'opt',
{
option: z.string().describe('Option name'),
value: z.number().describe('Option value')
},
async ({ option, value }) => {
try {
log.verbose('opt called', { option, value });
await autoIt.init();
const result = await autoIt.opt(option, value);
return createToolResponse(`AutoIt option "${option}" set to ${value} with result: ${result}`);
} catch (error) {
log.error('opt failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// toolTip - Display a tooltip
server.tool(
'toolTip',
{
text: z.string().describe('Tooltip text'),
x: z.number().optional().describe('X coordinate'),
y: z.number().optional().describe('Y coordinate')
},
async ({ text, x, y }) => {
try {
log.verbose('toolTip called', { text, x, y });
await autoIt.init();
await autoIt.toolTip(text, x, y);
const position = x !== undefined && y !== undefined ? ` at position (${x}, ${y})` : '';
return createToolResponse(`Tooltip displayed: "${text}"${position}`);
} catch (error) {
log.error('toolTip failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/mouse.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Mouse-related tools for MCP Windows Desktop Automation
*/
import * as autoIt from 'node-autoit-koffi';
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { createToolResponse, createErrorResponse, schemas } from '../utils/types';
import { log } from '../utils/logger/logger';
/**
* Register mouse-related tools with the MCP server
*/
export function registerMouseTools(server: McpServer): void {
// mouseMove - Move the mouse cursor to the specified coordinates
server.tool(
'mouseMove',
{
x: schemas.mouseX,
y: schemas.mouseY,
speed: schemas.mouseSpeed
},
async ({ x, y, speed }) => {
try {
log.verbose('mouseMove called', { x, y, speed });
await autoIt.init();
const result = await autoIt.mouseMove(x, y, speed);
return createToolResponse(`Mouse moved to (${x}, ${y}) with result: ${result}`);
} catch (error) {
log.error('mouseMove failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseClick - Click the mouse at the current or specified position
server.tool(
'mouseClick',
{
button: schemas.mouseButton,
x: schemas.mouseX.optional(),
y: schemas.mouseY.optional(),
clicks: schemas.mouseClicks,
speed: schemas.mouseSpeed
},
async ({ button, x, y, clicks, speed }) => {
try {
log.verbose('mouseClick called', { button, x, y, clicks, speed });
await autoIt.init();
const result = await autoIt.mouseClick(button, x, y, clicks, speed);
return createToolResponse(`Mouse clicked ${button} button ${clicks} time(s) with result: ${result}`);
} catch (error) {
log.error('mouseClick failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseClickDrag - Click and drag the mouse from one position to another
server.tool(
'mouseClickDrag',
{
button: schemas.mouseButton,
x1: schemas.mouseX,
y1: schemas.mouseY,
x2: schemas.mouseX,
y2: schemas.mouseY,
speed: schemas.mouseSpeed
},
async ({ button, x1, y1, x2, y2, speed }) => {
try {
log.verbose('mouseClickDrag called', { button, x1, y1, x2, y2, speed });
await autoIt.init();
const result = await autoIt.mouseClickDrag(button, x1, y1, x2, y2, speed);
return createToolResponse(`Mouse dragged from (${x1}, ${y1}) to (${x2}, ${y2}) with result: ${result}`);
} catch (error) {
log.error('mouseClickDrag failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseDown - Press and hold the specified mouse button
server.tool(
'mouseDown',
{
button: schemas.mouseButton
},
async ({ button }) => {
try {
log.verbose('mouseDown called', { button });
await autoIt.init();
await autoIt.mouseDown(button);
return createToolResponse(`Mouse ${button} button pressed down`);
} catch (error) {
log.error('mouseDown failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseUp - Release the specified mouse button
server.tool(
'mouseUp',
{
button: schemas.mouseButton
},
async ({ button }) => {
try {
log.verbose('mouseUp called', { button });
await autoIt.init();
await autoIt.mouseUp(button);
return createToolResponse(`Mouse ${button} button released`);
} catch (error) {
log.error('mouseUp failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseGetPos - Get the current mouse cursor position
server.tool(
'mouseGetPos',
{},
async () => {
try {
log.verbose('mouseGetPos called');
await autoIt.init();
const position = await autoIt.mouseGetPos();
log.verbose('mouseGetPos result', JSON.stringify(position));
return createToolResponse(`Mouse position: (${position.x}, ${position.y})`);
} catch (error) {
log.error('mouseGetPos failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseGetCursor - Get the current mouse cursor type
server.tool(
'mouseGetCursor',
{},
async () => {
try {
log.verbose('mouseGetCursor called');
await autoIt.init();
const cursor = await autoIt.mouseGetCursor();
log.verbose('mouseGetCursor result', JSON.stringify(cursor));
return createToolResponse(`Mouse cursor type: ${cursor}`);
} catch (error) {
log.error('mouseGetCursor failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// mouseWheel - Scroll the mouse wheel
server.tool(
'mouseWheel',
{
direction: z.enum(['up', 'down']).describe('Scroll direction'),
clicks: z.number().min(1).describe('Number of clicks to scroll')
},
async ({ direction, clicks }) => {
try {
log.verbose('mouseWheel called', { direction, clicks });
await autoIt.init();
await autoIt.mouseWheel(direction, clicks);
return createToolResponse(`Mouse wheel scrolled ${direction} ${clicks} click(s)`);
} catch (error) {
log.error('mouseWheel failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
}
```
--------------------------------------------------------------------------------
/src/resources/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Resources module for MCP Windows Desktop Automation
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as autoIt from 'node-autoit-koffi';
import { log } from '../utils/logger/logger';
/**
* Register all resources with the MCP server
*/
export function registerAllResources(server: McpServer): void {
// Register file resources
registerFileResources(server);
// Register screenshot resources
registerScreenshotResources(server);
}
/**
* Register file resources
*/
function registerFileResources(server: McpServer): void {
server.resource(
'file',
new ResourceTemplate('file://{path*}', {
list: async () => {
return { resources: [] }; // Empty list by default
}
}),
async (uri, params) => {
try {
// Ensure filePath is a string
const filePath = Array.isArray(params.path) ? params.path.join('/') : params.path;
log.verbose('Reading file resource', JSON.stringify({ uri: uri.href, filePath }));
// Check if file exists
const stats = await fs.stat(filePath);
if (stats.isDirectory()) {
// List directory contents
const files = await fs.readdir(filePath);
const resources = await Promise.all(
files.map(async (file) => {
const fullPath = path.join(filePath, file);
const fileStats = await fs.stat(fullPath);
return {
uri: `file://${fullPath.replace(/\\/g, '/')}`,
name: file,
description: fileStats.isDirectory() ? 'Directory' : 'File'
};
})
);
return {
contents: [{
uri: uri.href,
text: `Directory: ${filePath}\n${files.join('\n')}`,
mimeType: 'text/plain'
}]
};
} else {
// Read file content
const content = await fs.readFile(filePath);
const extension = path.extname(filePath).toLowerCase();
// Determine if it's a text or binary file
const isTextFile = [
'.txt', '.md', '.js', '.ts', '.html', '.css', '.json', '.xml',
'.csv', '.log', '.ini', '.cfg', '.conf', '.py', '.c', '.cpp',
'.h', '.java', '.sh', '.bat', '.ps1'
].includes(extension);
if (isTextFile) {
return {
contents: [{
uri: uri.href,
text: content.toString('utf-8'),
mimeType: getMimeType(extension)
}]
};
} else {
return {
contents: [{
uri: uri.href,
blob: content.toString('base64'),
mimeType: getMimeType(extension)
}]
};
}
}
} catch (error) {
log.error('Error reading file resource', error);
throw new Error(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`);
}
}
);
}
/**
* Register screenshot resources
*/
function registerScreenshotResources(server: McpServer): void {
server.resource(
'screenshot',
new ResourceTemplate('screenshot://{window?}', {
list: async () => {
return { resources: [] }; // Empty list by default
}
}),
async (uri, params) => {
try {
await autoIt.init();
// Ensure window is a string if provided
const windowName = params.window ? String(params.window) : undefined;
log.verbose('Taking screenshot', JSON.stringify({ uri: uri.href, window: windowName }));
// If window parameter is provided, activate that window first
if (windowName) {
const windowExists = await autoIt.winExists(windowName);
if (windowExists) {
await autoIt.winActivate(windowName);
// Wait a moment for the window to activate
await new Promise(resolve => setTimeout(resolve, 500));
} else {
throw new Error(`Window "${windowName}" not found`);
}
}
// TODO: Implement actual screenshot capture
// This is a placeholder - in a real implementation, you would use
// a library like 'screenshot-desktop' or other Windows API bindings
// to capture the screen or specific window
// For now, we'll return a placeholder message
return {
contents: [{
uri: uri.href,
text: `Screenshot of ${windowName || 'full screen'} would be captured here`,
mimeType: 'text/plain'
}]
};
// In a real implementation, you would return something like:
/*
return {
contents: [{
uri: uri.href,
blob: screenshotBase64Data,
mimeType: 'image/png'
}]
};
*/
} catch (error) {
log.error('Error taking screenshot', error);
throw new Error(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`);
}
}
);
}
/**
* Get MIME type based on file extension
*/
function getMimeType(extension: string): string {
const mimeTypes: Record<string, string> = {
'.txt': 'text/plain',
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.ts': 'application/typescript',
'.json': 'application/json',
'.xml': 'application/xml',
'.md': 'text/markdown',
'.csv': 'text/csv',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.zip': 'application/zip',
'.rar': 'application/x-rar-compressed',
'.7z': 'application/x-7z-compressed',
'.tar': 'application/x-tar',
'.gz': 'application/gzip'
};
return mimeTypes[extension] || 'application/octet-stream';
}
```
--------------------------------------------------------------------------------
/src/prompts/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Prompts module for MCP Windows Desktop Automation
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { log } from '../utils/logger/logger';
/**
* Register all prompts with the MCP server
*/
export function registerAllPrompts(server: McpServer): void {
// Register window interaction prompts
registerWindowPrompts(server);
// Register form filling prompts
registerFormPrompts(server);
// Register automation task prompts
registerAutomationPrompts(server);
}
/**
* Register window interaction prompts
*/
function registerWindowPrompts(server: McpServer): void {
// Prompt for finding and interacting with a window
server.prompt(
'findWindow',
{
windowTitle: z.string().describe('Title or partial title of the window to find'),
action: z.enum(['activate', 'close', 'minimize', 'maximize']).describe('Action to perform on the window')
},
({ windowTitle, action }) => {
log.verbose('findWindow prompt called', { windowTitle, action });
let actionDescription: string;
switch (action) {
case 'activate':
actionDescription = 'activate (bring to front)';
break;
case 'close':
actionDescription = 'close';
break;
case 'minimize':
actionDescription = 'minimize';
break;
case 'maximize':
actionDescription = 'maximize';
break;
}
return {
description: `Find a window with title "${windowTitle}" and ${actionDescription} it`,
messages: [
{
role: 'user',
content: {
type: 'text',
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?`
}
}
]
};
}
);
// Prompt for getting information about a window
server.prompt(
'windowInfo',
{
windowTitle: z.string().describe('Title or partial title of the window')
},
({ windowTitle }) => {
log.verbose('windowInfo prompt called', { windowTitle });
return {
description: `Get information about a window with title "${windowTitle}"`,
messages: [
{
role: 'user',
content: {
type: 'text',
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?`
}
}
]
};
}
);
}
/**
* Register form filling prompts
*/
function registerFormPrompts(server: McpServer): void {
// Prompt for filling out a form
server.prompt(
'fillForm',
{
windowTitle: z.string().describe('Title of the window containing the form'),
formFields: z.string().describe('Description of form fields and values to fill in')
},
({ windowTitle, formFields }) => {
log.verbose('fillForm prompt called', { windowTitle, formFields });
return {
description: `Fill out a form in window "${windowTitle}"`,
messages: [
{
role: 'user',
content: {
type: 'text',
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?`
}
}
]
};
}
);
// Prompt for submitting a form
server.prompt(
'submitForm',
{
windowTitle: z.string().describe('Title of the window containing the form'),
submitButtonText: z.string().describe('Text on the submit button')
},
({ windowTitle, submitButtonText }) => {
log.verbose('submitForm prompt called', { windowTitle, submitButtonText });
return {
description: `Submit a form in window "${windowTitle}"`,
messages: [
{
role: 'user',
content: {
type: 'text',
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?`
}
}
]
};
}
);
}
/**
* Register automation task prompts
*/
function registerAutomationPrompts(server: McpServer): void {
// Prompt for automating a repetitive task
server.prompt(
'automateTask',
{
taskDescription: z.string().describe('Description of the repetitive task to automate'),
repetitions: z.string().describe('Number of times to repeat the task')
},
({ taskDescription, repetitions }) => {
log.verbose('automateTask prompt called', { taskDescription, repetitions });
return {
description: `Automate a repetitive task: ${taskDescription}`,
messages: [
{
role: 'user',
content: {
type: 'text',
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?`
}
}
]
};
}
);
// Prompt for monitoring a window or process
server.prompt(
'monitorWindow',
{
windowTitle: z.string().describe('Title of the window to monitor'),
condition: z.string().describe('Condition to monitor for (e.g., "appears", "disappears", "contains text X")')
},
({ windowTitle, condition }) => {
log.verbose('monitorWindow prompt called', { windowTitle, condition });
return {
description: `Monitor window "${windowTitle}" for condition: ${condition}`,
messages: [
{
role: 'user',
content: {
type: 'text',
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?`
}
}
]
};
}
);
// Prompt for taking a screenshot
server.prompt(
'takeScreenshot',
{
target: z.enum(['fullscreen', 'window', 'region']).describe('What to capture in the screenshot'),
windowTitle: z.string().optional().describe('Title of the window to capture (if target is "window")')
},
({ target, windowTitle }) => {
log.verbose('takeScreenshot prompt called', { target, windowTitle });
let promptText: string;
if (target === 'fullscreen') {
promptText = 'I need to take a screenshot of the entire screen.';
} else if (target === 'window' && windowTitle) {
promptText = `I need to take a screenshot of a window with the title "${windowTitle}".`;
} else if (target === 'region') {
promptText = 'I need to take a screenshot of a specific region of the screen.';
} else {
promptText = 'I need to take a screenshot.';
}
return {
description: `Take a screenshot of ${target === 'window' ? `window "${windowTitle}"` : target}`,
messages: [
{
role: 'user',
content: {
type: 'text',
text: `${promptText} Can you help me do this using AutoIt functions?`
}
}
]
};
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/process.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Process-related tools for MCP Windows Desktop Automation
*/
import * as autoIt from 'node-autoit-koffi';
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { createToolResponse, createErrorResponse, schemas } from '../utils/types';
import { log } from '../utils/logger/logger';
/**
* Register process-related tools with the MCP server
*/
export function registerProcessTools(server: McpServer): void {
// run - Run a program
server.tool(
'run',
{
program: z.string().describe('Program path or command'),
workingDir: z.string().optional().describe('Working directory'),
showFlag: z.number().optional().describe('Window show flag')
},
async ({ program, workingDir, showFlag }) => {
try {
log.verbose('run called', { program, workingDir, showFlag });
await autoIt.init();
const result = await autoIt.run(program, workingDir, showFlag);
return createToolResponse(`Program "${program}" started with process ID: ${result}`);
} catch (error) {
log.error('run failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// runWait - Run a program and wait for it to complete
server.tool(
'runWait',
{
program: z.string().describe('Program path or command'),
workingDir: z.string().optional().describe('Working directory'),
showFlag: z.number().optional().describe('Window show flag')
},
async ({ program, workingDir, showFlag }) => {
try {
log.verbose('runWait called', { program, workingDir, showFlag });
await autoIt.init();
const result = await autoIt.runWait(program, workingDir, showFlag);
return createToolResponse(`Program "${program}" completed with exit code: ${result}`);
} catch (error) {
log.error('runWait failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// runAs - Run a program as a different user
server.tool(
'runAs',
{
user: z.string().describe('Username'),
domain: z.string().describe('Domain'),
password: z.string().describe('Password'),
logonFlag: z.number().describe('Logon flag'),
program: z.string().describe('Program path or command'),
workingDir: z.string().optional().describe('Working directory'),
showFlag: z.number().optional().describe('Window show flag')
},
async ({ user, domain, password, logonFlag, program, workingDir, showFlag }) => {
try {
log.verbose('runAs called', { user, domain, logonFlag, program, workingDir, showFlag });
await autoIt.init();
const result = await autoIt.runAs(user, domain, password, logonFlag, program, workingDir, showFlag);
return createToolResponse(`Program "${program}" started as user "${user}" with process ID: ${result}`);
} catch (error) {
log.error('runAs failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// runAsWait - Run a program as a different user and wait for it to complete
server.tool(
'runAsWait',
{
user: z.string().describe('Username'),
domain: z.string().describe('Domain'),
password: z.string().describe('Password'),
logonFlag: z.number().describe('Logon flag'),
program: z.string().describe('Program path or command'),
workingDir: z.string().optional().describe('Working directory'),
showFlag: z.number().optional().describe('Window show flag')
},
async ({ user, domain, password, logonFlag, program, workingDir, showFlag }) => {
try {
log.verbose('runAsWait called', { user, domain, logonFlag, program, workingDir, showFlag });
await autoIt.init();
const result = await autoIt.runAsWait(user, domain, password, logonFlag, program, workingDir, showFlag);
return createToolResponse(`Program "${program}" completed as user "${user}" with exit code: ${result}`);
} catch (error) {
log.error('runAsWait failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// processExists - Check if a process exists
server.tool(
'processExists',
{
process: schemas.processName
},
async ({ process }) => {
try {
log.verbose('processExists called', { process });
await autoIt.init();
const result = await autoIt.processExists(process);
const exists = result !== 0;
return createToolResponse(
exists
? `Process "${process}" exists with PID: ${result}`
: `Process "${process}" does not exist`
);
} catch (error) {
log.error('processExists failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// processClose - Close a process
server.tool(
'processClose',
{
process: schemas.processName
},
async ({ process }) => {
try {
log.verbose('processClose called', { process });
await autoIt.init();
const result = await autoIt.processClose(process);
const success = result === 1;
return createToolResponse(
success
? `Process "${process}" closed successfully`
: `Failed to close process "${process}"`
);
} catch (error) {
log.error('processClose failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// processSetPriority - Set process priority
server.tool(
'processSetPriority',
{
process: schemas.processName,
priority: z.number().describe('Priority level (0-4)')
},
async ({ process, priority }) => {
try {
log.verbose('processSetPriority called', { process, priority });
await autoIt.init();
const result = await autoIt.processSetPriority(process, priority);
const success = result === 1;
return createToolResponse(
success
? `Priority for process "${process}" set to ${priority}`
: `Failed to set priority for process "${process}"`
);
} catch (error) {
log.error('processSetPriority failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// processWait - Wait for a process to exist
server.tool(
'processWait',
{
process: schemas.processName,
timeout: schemas.processTimeout
},
async ({ process, timeout }) => {
try {
log.verbose('processWait called', { process, timeout });
await autoIt.init();
const result = await autoIt.processWait(process, timeout);
const success = result !== 0;
return createToolResponse(
success
? `Process "${process}" exists with PID: ${result}`
: `Timed out waiting for process "${process}"`
);
} catch (error) {
log.error('processWait failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// processWaitClose - Wait for a process to close
server.tool(
'processWaitClose',
{
process: schemas.processName,
timeout: schemas.processTimeout
},
async ({ process, timeout }) => {
try {
log.verbose('processWaitClose called', { process, timeout });
await autoIt.init();
const result = await autoIt.processWaitClose(process, timeout);
const success = result === 1;
return createToolResponse(
success
? `Process "${process}" closed within the timeout`
: `Timed out waiting for process "${process}" to close`
);
} catch (error) {
log.error('processWaitClose failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// shutdown - Shut down the system
server.tool(
'shutdown',
{
flags: z.number().describe('Shutdown flags')
},
async ({ flags }) => {
try {
log.verbose('shutdown called', { flags });
await autoIt.init();
const result = await autoIt.shutdown(flags);
const success = result === 1;
return createToolResponse(
success
? `System shutdown initiated with flags: ${flags}`
: `Failed to initiate system shutdown`
);
} catch (error) {
log.error('shutdown failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/window.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Window-related tools for MCP Windows Desktop Automation
*/
import * as autoIt from 'node-autoit-koffi';
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { createToolResponse, createErrorResponse, schemas } from '../utils/types';
import { log } from '../utils/logger/logger';
/**
* Register window-related tools with the MCP server
*/
export function registerWindowTools(server: McpServer): void {
// winActivate - Activate a window
server.tool(
'winActivate',
{
title: schemas.windowTitle,
text: schemas.windowText
},
async ({ title, text }) => {
try {
log.verbose('winActivate called', { title, text });
await autoIt.init();
const result = await autoIt.winActivate(title, text);
return createToolResponse(`Window "${title}" activated with result: ${result}`);
} catch (error) {
log.error('winActivate failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winActivateByHandle - Activate a window by handle
server.tool(
'winActivateByHandle',
{
handle: schemas.handle
},
async ({ handle }) => {
try {
log.verbose('winActivateByHandle called', { handle });
await autoIt.init();
const result = await autoIt.winActivateByHandle(handle);
return createToolResponse(`Window with handle ${handle} activated with result: ${result}`);
} catch (error) {
log.error('winActivateByHandle failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winActive - Check if a window is active
server.tool(
'winActive',
{
title: schemas.windowTitle,
text: z.string().describe('Window text')
},
async ({ title, text }) => {
try {
log.verbose('winActive called', { title, text });
await autoIt.init();
const result = await autoIt.winActive(title, text);
const isActive = result === 1;
return createToolResponse(`Window "${title}" is ${isActive ? 'active' : 'not active'}`);
} catch (error) {
log.error('winActive failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winClose - Close a window
server.tool(
'winClose',
{
title: schemas.windowTitle,
text: schemas.windowText
},
async ({ title, text }) => {
try {
log.verbose('winClose called', { title, text });
await autoIt.init();
const result = await autoIt.winClose(title, text);
return createToolResponse(`Window "${title}" closed with result: ${result}`);
} catch (error) {
log.error('winClose failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winExists - Check if a window exists
server.tool(
'winExists',
{
title: schemas.windowTitle,
text: schemas.windowText
},
async ({ title, text }) => {
try {
log.verbose('winExists called', { title, text });
await autoIt.init();
const result = await autoIt.winExists(title, text);
const exists = result === 1;
return createToolResponse(`Window "${title}" ${exists ? 'exists' : 'does not exist'}`);
} catch (error) {
log.error('winExists failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winGetHandle - Get a window handle
server.tool(
'winGetHandle',
{
title: schemas.windowTitle,
text: schemas.windowText
},
async ({ title, text }) => {
try {
log.verbose('winGetHandle called', { title, text });
await autoIt.init();
const handle = await autoIt.winGetHandle(title, text);
log.verbose('winGetHandle result', JSON.stringify({ handle }));
return createToolResponse(`Window "${title}" handle: ${handle}`);
} catch (error) {
log.error('winGetHandle failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winGetPos - Get window position and size
server.tool(
'winGetPos',
{
title: schemas.windowTitle,
text: schemas.windowText
},
async ({ title, text }) => {
try {
log.verbose('winGetPos called', { title, text });
await autoIt.init();
const rect = await autoIt.winGetPos(title, text);
log.verbose('winGetPos result', JSON.stringify(rect));
const width = rect.right - rect.left;
const height = rect.bottom - rect.top;
return createToolResponse(
`Window "${title}" position: Left=${rect.left}, Top=${rect.top}, Width=${width}, Height=${height}`
);
} catch (error) {
log.error('winGetPos failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winGetText - Get window text
server.tool(
'winGetText',
{
title: schemas.windowTitle,
text: schemas.windowText,
bufSize: schemas.bufferSize
},
async ({ title, text, bufSize }) => {
try {
log.verbose('winGetText called', { title, text, bufSize });
await autoIt.init();
const windowText = await autoIt.winGetText(title, text, bufSize);
log.verbose('winGetText result', JSON.stringify({ windowText }));
return createToolResponse(`Window "${title}" text: "${windowText}"`);
} catch (error) {
log.error('winGetText failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winGetTitle - Get window title
server.tool(
'winGetTitle',
{
title: schemas.windowTitle,
text: schemas.windowText,
bufSize: schemas.bufferSize
},
async ({ title, text, bufSize }) => {
try {
log.verbose('winGetTitle called', { title, text, bufSize });
await autoIt.init();
const windowTitle = await autoIt.winGetTitle(title, text, bufSize);
log.verbose('winGetTitle result', JSON.stringify({ windowTitle }));
return createToolResponse(`Window "${title}" title: "${windowTitle}"`);
} catch (error) {
log.error('winGetTitle failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winMove - Move and resize a window
server.tool(
'winMove',
{
title: schemas.windowTitle,
text: schemas.windowText,
x: z.number().describe('X coordinate'),
y: z.number().describe('Y coordinate'),
width: z.number().optional().describe('Window width'),
height: z.number().optional().describe('Window height')
},
async ({ title, text, x, y, width, height }) => {
try {
log.verbose('winMove called', { title, text, x, y, width, height });
await autoIt.init();
const result = await autoIt.winMove(title, text, x, y, width, height);
const sizeInfo = width && height ? ` and resized to ${width}x${height}` : '';
return createToolResponse(`Window "${title}" moved to (${x}, ${y})${sizeInfo} with result: ${result}`);
} catch (error) {
log.error('winMove failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winSetState - Set window state (minimized, maximized, etc.)
server.tool(
'winSetState',
{
title: schemas.windowTitle,
text: schemas.windowText,
flags: z.number().describe('State flags')
},
async ({ title, text, flags }) => {
try {
log.verbose('winSetState called', { title, text, flags });
await autoIt.init();
const result = await autoIt.winSetState(title, text, flags);
return createToolResponse(`Window "${title}" state set to ${flags} with result: ${result}`);
} catch (error) {
log.error('winSetState failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winWait - Wait for a window to exist
server.tool(
'winWait',
{
title: schemas.windowTitle,
text: schemas.windowText,
timeout: z.number().optional().describe('Timeout in seconds')
},
async ({ title, text, timeout }) => {
try {
log.verbose('winWait called', { title, text, timeout });
await autoIt.init();
const result = await autoIt.winWait(title, text, timeout);
const success = result === 1;
return createToolResponse(
success
? `Window "${title}" appeared within the timeout`
: `Window "${title}" did not appear within the timeout`
);
} catch (error) {
log.error('winWait failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winWaitActive - Wait for a window to be active
server.tool(
'winWaitActive',
{
title: schemas.windowTitle,
text: schemas.windowText,
timeout: z.number().optional().describe('Timeout in seconds')
},
async ({ title, text, timeout }) => {
try {
log.verbose('winWaitActive called', { title, text, timeout });
await autoIt.init();
const result = await autoIt.winWaitActive(title, text, timeout);
const success = result === 1;
return createToolResponse(
success
? `Window "${title}" became active within the timeout`
: `Window "${title}" did not become active within the timeout`
);
} catch (error) {
log.error('winWaitActive failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// winWaitClose - Wait for a window to close
server.tool(
'winWaitClose',
{
title: schemas.windowTitle,
text: schemas.windowText,
timeout: z.number().optional().describe('Timeout in seconds')
},
async ({ title, text, timeout }) => {
try {
log.verbose('winWaitClose called', { title, text, timeout });
await autoIt.init();
const result = await autoIt.winWaitClose(title, text, timeout);
const success = result === 1;
return createToolResponse(
success
? `Window "${title}" closed within the timeout`
: `Window "${title}" did not close within the timeout`
);
} catch (error) {
log.error('winWaitClose failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/control.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Control-related tools for MCP Windows Desktop Automation
*/
import * as autoIt from 'node-autoit-koffi';
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { createToolResponse, createErrorResponse, schemas } from '../utils/types';
import { log } from '../utils/logger/logger';
/**
* Register control-related tools with the MCP server
*/
export function registerControlTools(server: McpServer): void {
// controlClick - Click on a control
server.tool(
'controlClick',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName,
button: schemas.mouseButton,
clicks: schemas.mouseClicks,
x: z.number().optional().describe('X coordinate within control'),
y: z.number().optional().describe('Y coordinate within control')
},
async ({ title, text, control, button, clicks, x, y }) => {
try {
log.verbose('controlClick called', { title, text, control, button, clicks, x, y });
await autoIt.init();
const result = await autoIt.controlClick(title, text, control, button, clicks, x, y);
const success = result === 1;
return createToolResponse(
success
? `Clicked on control "${control}" in window "${title}"`
: `Failed to click on control "${control}" in window "${title}"`
);
} catch (error) {
log.error('controlClick failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlClickByHandle - Click on a control by handle
server.tool(
'controlClickByHandle',
{
windowHandle: schemas.handle,
controlHandle: schemas.handle,
button: schemas.mouseButton,
clicks: schemas.mouseClicks,
x: z.number().optional().describe('X coordinate within control'),
y: z.number().optional().describe('Y coordinate within control')
},
async ({ windowHandle, controlHandle, button, clicks, x, y }) => {
try {
log.verbose('controlClickByHandle called', { windowHandle, controlHandle, button, clicks, x, y });
await autoIt.init();
const result = await autoIt.controlClickByHandle(windowHandle, controlHandle, button, clicks, x, y);
const success = result === 1;
return createToolResponse(
success
? `Clicked on control handle ${controlHandle} in window handle ${windowHandle}`
: `Failed to click on control handle ${controlHandle} in window handle ${windowHandle}`
);
} catch (error) {
log.error('controlClickByHandle failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlCommand - Send a command to a control
server.tool(
'controlCommand',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName,
command: z.string().describe('Command to send'),
extra: z.string().optional().describe('Extra parameter for the command'),
bufSize: schemas.bufferSize
},
async ({ title, text, control, command, extra, bufSize }) => {
try {
log.verbose('controlCommand called', { title, text, control, command, extra, bufSize });
await autoIt.init();
const result = await autoIt.controlCommand(title, text, control, command, extra, bufSize);
log.verbose('controlCommand result', JSON.stringify({ result }));
return createToolResponse(`Command "${command}" sent to control "${control}" with result: ${result}`);
} catch (error) {
log.error('controlCommand failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlGetText - Get text from a control
server.tool(
'controlGetText',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName,
bufSize: schemas.bufferSize
},
async ({ title, text, control, bufSize }) => {
try {
log.verbose('controlGetText called', { title, text, control, bufSize });
await autoIt.init();
const controlText = await autoIt.controlGetText(title, text, control, bufSize);
log.verbose('controlGetText result', JSON.stringify({ controlText }));
return createToolResponse(`Text from control "${control}": "${controlText}"`);
} catch (error) {
log.error('controlGetText failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlSetText - Set text in a control
server.tool(
'controlSetText',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName,
controlText: schemas.controlText
},
async ({ title, text, control, controlText }) => {
try {
log.verbose('controlSetText called', { title, text, control, controlText });
await autoIt.init();
const result = await autoIt.controlSetText(title, text, control, controlText);
const success = result === 1;
return createToolResponse(
success
? `Text set in control "${control}" to "${controlText}"`
: `Failed to set text in control "${control}"`
);
} catch (error) {
log.error('controlSetText failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlSend - Send keystrokes to a control
server.tool(
'controlSend',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName,
sendText: schemas.controlText,
mode: z.number().optional().describe('Send mode flag')
},
async ({ title, text, control, sendText, mode }) => {
try {
log.verbose('controlSend called', { title, text, control, sendText, mode });
await autoIt.init();
const result = await autoIt.controlSend(title, text, control, sendText, mode);
const success = result === 1;
return createToolResponse(
success
? `Keystrokes "${sendText}" sent to control "${control}"`
: `Failed to send keystrokes to control "${control}"`
);
} catch (error) {
log.error('controlSend failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlFocus - Set focus to a control
server.tool(
'controlFocus',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName
},
async ({ title, text, control }) => {
try {
log.verbose('controlFocus called', { title, text, control });
await autoIt.init();
const result = await autoIt.controlFocus(title, text, control);
const success = result === 1;
return createToolResponse(
success
? `Focus set to control "${control}"`
: `Failed to set focus to control "${control}"`
);
} catch (error) {
log.error('controlFocus failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlGetHandle - Get a control handle
server.tool(
'controlGetHandle',
{
windowHandle: schemas.handle,
control: schemas.controlName
},
async ({ windowHandle, control }) => {
try {
log.verbose('controlGetHandle called', { windowHandle, control });
await autoIt.init();
const handle = await autoIt.controlGetHandle(windowHandle, control);
log.verbose('controlGetHandle result', JSON.stringify({ handle }));
return createToolResponse(`Control "${control}" handle: ${handle}`);
} catch (error) {
log.error('controlGetHandle failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlGetPos - Get control position and size
server.tool(
'controlGetPos',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName
},
async ({ title, text, control }) => {
try {
log.verbose('controlGetPos called', { title, text, control });
await autoIt.init();
const rect = await autoIt.controlGetPos(title, text, control);
log.verbose('controlGetPos result', JSON.stringify(rect));
const width = rect.right - rect.left;
const height = rect.bottom - rect.top;
return createToolResponse(
`Control "${control}" position: Left=${rect.left}, Top=${rect.top}, Width=${width}, Height=${height}`
);
} catch (error) {
log.error('controlGetPos failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlMove - Move and resize a control
server.tool(
'controlMove',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName,
x: z.number().describe('X coordinate'),
y: z.number().describe('Y coordinate'),
width: z.number().optional().describe('Control width'),
height: z.number().optional().describe('Control height')
},
async ({ title, text, control, x, y, width, height }) => {
try {
log.verbose('controlMove called', { title, text, control, x, y, width, height });
await autoIt.init();
const result = await autoIt.controlMove(title, text, control, x, y, width, height);
const success = result === 1;
const sizeInfo = width && height ? ` and resized to ${width}x${height}` : '';
return createToolResponse(
success
? `Control "${control}" moved to (${x}, ${y})${sizeInfo}`
: `Failed to move control "${control}"`
);
} catch (error) {
log.error('controlMove failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlShow - Show a control
server.tool(
'controlShow',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName
},
async ({ title, text, control }) => {
try {
log.verbose('controlShow called', { title, text, control });
await autoIt.init();
const result = await autoIt.controlShow(title, text, control);
const success = result === 1;
return createToolResponse(
success
? `Control "${control}" shown`
: `Failed to show control "${control}"`
);
} catch (error) {
log.error('controlShow failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
// controlHide - Hide a control
server.tool(
'controlHide',
{
title: schemas.windowTitle,
text: schemas.windowText,
control: schemas.controlName
},
async ({ title, text, control }) => {
try {
log.verbose('controlHide called', { title, text, control });
await autoIt.init();
const result = await autoIt.controlHide(title, text, control);
const success = result === 1;
return createToolResponse(
success
? `Control "${control}" hidden`
: `Failed to hide control "${control}"`
);
} catch (error) {
log.error('controlHide failed', error);
return createErrorResponse(error instanceof Error ? error : String(error));
}
}
);
}
```