/* Copyright (C) 2026 Moko Consulting * SPDX-License-Identifier: GPL-3.0-or-later * * Tool: windows_dialog (#21) */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { runPowerShell } from '../shell.js'; export function registerDialogTools(server: McpServer): void { server.tool( 'windows_dialog', 'Show system dialogs: message box, input prompt, file/folder picker.', { type: z.enum(['message', 'input', 'file_open', 'file_save', 'folder']).describe('Dialog type'), title: z.string().default('mcp_windows').describe('Dialog title'), message: z.string().optional().describe('Message text (for message/input)'), buttons: z.enum(['ok', 'okcancel', 'yesno', 'yesnocancel']).default('ok').describe('Buttons (for message)'), filter: z.string().optional().describe('File filter (for file dialogs, e.g. "Text files|*.txt|All files|*.*")'), default_path: z.string().optional().describe('Default path/filename'), }, async ({ type, title, message, buttons, filter, default_path }) => { let ps: string; switch (type) { case 'message': { const btnMap: Record = { ok: 0, okcancel: 1, yesno: 4, yesnocancel: 3 }; ps = ` Add-Type -AssemblyName System.Windows.Forms $result = [System.Windows.Forms.MessageBox]::Show('${(message || '').replace(/'/g, "''")}', '${title.replace(/'/g, "''")}', ${btnMap[buttons]}) $result.ToString()`; break; } case 'input': ps = ` Add-Type -AssemblyName Microsoft.VisualBasic $result = [Microsoft.VisualBasic.Interaction]::InputBox('${(message || 'Enter value:').replace(/'/g, "''")}', '${title.replace(/'/g, "''")}', '${(default_path || '').replace(/'/g, "''")}') if ($result) { $result } else { '__CANCELLED__' }`; break; case 'file_open': ps = ` Add-Type -AssemblyName System.Windows.Forms $dlg = New-Object System.Windows.Forms.OpenFileDialog $dlg.Title = '${title.replace(/'/g, "''")}' ${filter ? `$dlg.Filter = '${filter.replace(/'/g, "''")}'` : ''} ${default_path ? `$dlg.InitialDirectory = '${default_path.replace(/'/g, "''")}'` : ''} $dlg.Multiselect = $false if ($dlg.ShowDialog() -eq 'OK') { $dlg.FileName } else { '__CANCELLED__' }`; break; case 'file_save': ps = ` Add-Type -AssemblyName System.Windows.Forms $dlg = New-Object System.Windows.Forms.SaveFileDialog $dlg.Title = '${title.replace(/'/g, "''")}' ${filter ? `$dlg.Filter = '${filter.replace(/'/g, "''")}'` : ''} ${default_path ? `$dlg.FileName = '${default_path.replace(/'/g, "''")}'` : ''} if ($dlg.ShowDialog() -eq 'OK') { $dlg.FileName } else { '__CANCELLED__' }`; break; case 'folder': ps = ` Add-Type -AssemblyName System.Windows.Forms $dlg = New-Object System.Windows.Forms.FolderBrowserDialog $dlg.Description = '${(message || title).replace(/'/g, "''")}' ${default_path ? `$dlg.SelectedPath = '${default_path.replace(/'/g, "''")}'` : ''} if ($dlg.ShowDialog() -eq 'OK') { $dlg.SelectedPath } else { '__CANCELLED__' }`; break; } const result = await runPowerShell(ps, { timeout: 120000 }); // Long timeout — user interaction if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } const output = result.stdout.trim(); if (output === '__CANCELLED__') { return { content: [{ type: 'text', text: 'Dialog cancelled by user.' }] }; } return { content: [{ type: 'text', text: output }] }; }, ); }