Skip to main content
Glama
by Radek44
index.ts10.6 kB
#!/usr/bin/env node /** * MCP Server for Tauri Application Automation * * This server provides tools for automating Tauri desktop applications * through the Model Context Protocol (MCP). */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { TauriDriver } from './tauri-driver.js'; import { launchApp, closeApp, getAppState } from './tools/launch.js'; import { captureScreenshot } from './tools/screenshot.js'; import { clickElement, typeText, waitForElement, getElementText } from './tools/interact.js'; import { executeTauriCommand } from './tools/state.js'; import type { TauriAutomationConfig } from './types.js'; // Parse config from environment variables const config: TauriAutomationConfig = { appPath: process.env.TAURI_APP_PATH, screenshotDir: process.env.TAURI_SCREENSHOT_DIR, webdriverPort: process.env.TAURI_WEBDRIVER_PORT ? parseInt(process.env.TAURI_WEBDRIVER_PORT) : undefined, defaultTimeout: process.env.TAURI_DEFAULT_TIMEOUT ? parseInt(process.env.TAURI_DEFAULT_TIMEOUT) : undefined, tauriDriverPath: process.env.TAURI_DRIVER_PATH, }; // Initialize driver const driver = new TauriDriver(config); // Create MCP server const server = new Server( { name: 'mcp-tauri-automation', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Register tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'launch_app', description: 'Launch a Tauri application via tauri-driver. The tauri-driver must be running on the configured port (default: 4444).', inputSchema: { type: 'object', properties: { appPath: { type: 'string', description: 'Path to the Tauri application binary', }, args: { type: 'array', items: { type: 'string' }, description: 'Optional command-line arguments to pass to the application', }, env: { type: 'object', additionalProperties: { type: 'string' }, description: 'Optional environment variables to set for the application', }, }, required: ['appPath'], }, }, { name: 'close_app', description: 'Close the currently running Tauri application gracefully', inputSchema: { type: 'object', properties: {}, }, }, { name: 'capture_screenshot', description: 'Capture a screenshot of the application window. Returns base64-encoded PNG image data by default.', inputSchema: { type: 'object', properties: { filename: { type: 'string', description: 'Optional filename (without extension) to save the screenshot. If not provided, a timestamp will be used.', }, returnBase64: { type: 'boolean', description: 'Whether to return base64 image data (true) or save to file and return path (false). Default: true', default: true, }, }, }, }, { name: 'click_element', description: 'Click a UI element identified by a CSS selector', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector to identify the element to click (e.g., "#button-id", ".button-class", "button[name=submit]")', }, }, required: ['selector'], }, }, { name: 'type_text', description: 'Type text into an input field or editable element', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector to identify the input element', }, text: { type: 'string', description: 'Text to type into the element', }, clear: { type: 'boolean', description: 'Whether to clear existing text before typing. Default: false', default: false, }, }, required: ['selector', 'text'], }, }, { name: 'wait_for_element', description: 'Wait for an element to appear in the DOM. Useful for handling async UI states.', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector to wait for', }, timeout: { type: 'number', description: 'Timeout in milliseconds. Default: 5000', default: 5000, }, }, required: ['selector'], }, }, { name: 'get_element_text', description: 'Get the text content of an element', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector to identify the element', }, }, required: ['selector'], }, }, { name: 'execute_tauri_command', description: 'Execute a Tauri IPC command. The command must be exposed in the Tauri app\'s src-tauri/src/main.rs file.', inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'Name of the Tauri command to execute', }, args: { type: 'object', description: 'Arguments to pass to the command', additionalProperties: true, }, }, required: ['command'], }, }, { name: 'get_app_state', description: 'Get the current state of the application, including whether it\'s running, session info, and page details', inputSchema: { type: 'object', properties: {}, }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'launch_app': { const result = await launchApp(driver, args as any); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'close_app': { const result = await closeApp(driver); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'capture_screenshot': { const result = await captureScreenshot(driver, args as any); if (result.success && result.data?.base64) { // Return both text description and image return { content: [ { type: 'text', text: result.data.message, }, { type: 'image', data: result.data.base64, mimeType: 'image/png', }, ], }; } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'click_element': { const result = await clickElement(driver, args as any); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'type_text': { const result = await typeText(driver, args as any); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'wait_for_element': { const result = await waitForElement(driver, args as any); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_element_text': { const result = await getElementText(driver, args as any); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'execute_tauri_command': { const result = await executeTauriCommand(driver, args as any); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_app_state': { const result = await getAppState(driver); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error), }, null, 2), }, ], isError: true, }; } }); // Cleanup handler async function cleanup() { try { const state = driver.getAppState(); if (state.isRunning) { console.error('Cleaning up: Closing application...'); await driver.closeApp(); } } catch (error) { console.error('Error during cleanup:', error); } } process.on('SIGINT', async () => { await cleanup(); process.exit(0); }); process.on('SIGTERM', async () => { await cleanup(); process.exit(0); }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP Tauri Automation server running on stdio'); } main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Radek44/mcp-tauri-automation'

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