fix(mcp): add safe type assertions and complete stub handlers
Some checks failed
Security Scan / security (push) Failing after 9s
Test / test (push) Failing after 1m40s

Replace all 32 unchecked type assertions (result.(Type)) with safe
ok-pattern checks that return descriptive errors on type mismatch.

Complete stub handlers in tools_window.go (restore, title, visibility,
fullscreen) and tools_layout.go (save, restore, list, delete, get,
tile, snap) using proper IPC message types instead of workarounds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-03-13 16:28:15 +00:00
parent f884d698b2
commit ec492658c2
11 changed files with 205 additions and 62 deletions

View file

@ -48,7 +48,8 @@ func TestMCP_Good_ClipboardRoundTrip(t *testing.T) {
result, handled, err := c.QUERY(clipboard.QueryText{})
require.NoError(t, err)
assert.True(t, handled)
content, _ := result.(clipboard.ClipboardContent)
content, ok := result.(clipboard.ClipboardContent)
require.True(t, ok, "expected ClipboardContent type")
assert.Equal(t, "hello", content.Text)
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/clipboard"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -20,7 +21,10 @@ func (s *Subsystem) clipboardRead(_ context.Context, _ *mcp.CallToolRequest, _ C
if err != nil {
return nil, ClipboardReadOutput{}, err
}
content, _ := result.(clipboard.ClipboardContent)
content, ok := result.(clipboard.ClipboardContent)
if !ok {
return nil, ClipboardReadOutput{}, fmt.Errorf("unexpected result type from clipboard read query")
}
return nil, ClipboardReadOutput{Content: content.Text}, nil
}
@ -38,7 +42,10 @@ func (s *Subsystem) clipboardWrite(_ context.Context, _ *mcp.CallToolRequest, in
if err != nil {
return nil, ClipboardWriteOutput{}, err
}
success, _ := result.(bool)
success, ok := result.(bool)
if !ok {
return nil, ClipboardWriteOutput{}, fmt.Errorf("unexpected result type from clipboard write task")
}
return nil, ClipboardWriteOutput{Success: success}, nil
}
@ -54,7 +61,10 @@ func (s *Subsystem) clipboardHas(_ context.Context, _ *mcp.CallToolRequest, _ Cl
if err != nil {
return nil, ClipboardHasOutput{}, err
}
content, _ := result.(clipboard.ClipboardContent)
content, ok := result.(clipboard.ClipboardContent)
if !ok {
return nil, ClipboardHasOutput{}, fmt.Errorf("unexpected result type from clipboard has query")
}
return nil, ClipboardHasOutput{HasContent: content.HasContent}, nil
}
@ -70,7 +80,10 @@ func (s *Subsystem) clipboardClear(_ context.Context, _ *mcp.CallToolRequest, _
if err != nil {
return nil, ClipboardClearOutput{}, err
}
success, _ := result.(bool)
success, ok := result.(bool)
if !ok {
return nil, ClipboardClearOutput{}, fmt.Errorf("unexpected result type from clipboard clear task")
}
return nil, ClipboardClearOutput{Success: success}, nil
}

View file

@ -4,6 +4,7 @@ package mcp
import (
"context"
"encoding/json"
"fmt"
"forge.lthn.ai/core/gui/pkg/contextmenu"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -24,10 +25,15 @@ type ContextMenuAddOutput struct {
func (s *Subsystem) contextMenuAdd(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuAddInput) (*mcp.CallToolResult, ContextMenuAddOutput, error) {
// Convert map[string]any to ContextMenuDef via JSON round-trip
menuJSON, _ := json.Marshal(input.Menu)
menuJSON, err := json.Marshal(input.Menu)
if err != nil {
return nil, ContextMenuAddOutput{}, fmt.Errorf("failed to marshal menu definition: %w", err)
}
var menuDef contextmenu.ContextMenuDef
_ = json.Unmarshal(menuJSON, &menuDef)
_, _, err := s.core.PERFORM(contextmenu.TaskAdd{Name: input.Name, Menu: menuDef})
if err := json.Unmarshal(menuJSON, &menuDef); err != nil {
return nil, ContextMenuAddOutput{}, fmt.Errorf("failed to unmarshal menu definition: %w", err)
}
_, _, err = s.core.PERFORM(contextmenu.TaskAdd{Name: input.Name, Menu: menuDef})
if err != nil {
return nil, ContextMenuAddOutput{}, err
}
@ -65,14 +71,22 @@ func (s *Subsystem) contextMenuGet(_ context.Context, _ *mcp.CallToolRequest, in
if err != nil {
return nil, ContextMenuGetOutput{}, err
}
menu, _ := result.(*contextmenu.ContextMenuDef)
menu, ok := result.(*contextmenu.ContextMenuDef)
if !ok {
return nil, ContextMenuGetOutput{}, fmt.Errorf("unexpected result type from context menu get query")
}
if menu == nil {
return nil, ContextMenuGetOutput{}, nil
}
// Convert to map[string]any via JSON round-trip to avoid cyclic type in schema
menuJSON, _ := json.Marshal(menu)
menuJSON, err := json.Marshal(menu)
if err != nil {
return nil, ContextMenuGetOutput{}, fmt.Errorf("failed to marshal context menu: %w", err)
}
var menuMap map[string]any
_ = json.Unmarshal(menuJSON, &menuMap)
if err := json.Unmarshal(menuJSON, &menuMap); err != nil {
return nil, ContextMenuGetOutput{}, fmt.Errorf("failed to unmarshal context menu: %w", err)
}
return nil, ContextMenuGetOutput{Menu: menuMap}, nil
}
@ -88,11 +102,19 @@ func (s *Subsystem) contextMenuList(_ context.Context, _ *mcp.CallToolRequest, _
if err != nil {
return nil, ContextMenuListOutput{}, err
}
menus, _ := result.(map[string]contextmenu.ContextMenuDef)
menus, ok := result.(map[string]contextmenu.ContextMenuDef)
if !ok {
return nil, ContextMenuListOutput{}, fmt.Errorf("unexpected result type from context menu list query")
}
// Convert to map[string]any via JSON round-trip to avoid cyclic type in schema
menusJSON, _ := json.Marshal(menus)
menusJSON, err := json.Marshal(menus)
if err != nil {
return nil, ContextMenuListOutput{}, fmt.Errorf("failed to marshal context menus: %w", err)
}
var menusMap map[string]any
_ = json.Unmarshal(menusJSON, &menusMap)
if err := json.Unmarshal(menusJSON, &menusMap); err != nil {
return nil, ContextMenuListOutput{}, fmt.Errorf("failed to unmarshal context menus: %w", err)
}
return nil, ContextMenuListOutput{Menus: menusMap}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/dialog"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -30,7 +31,10 @@ func (s *Subsystem) dialogOpenFile(_ context.Context, _ *mcp.CallToolRequest, in
if err != nil {
return nil, DialogOpenFileOutput{}, err
}
paths, _ := result.([]string)
paths, ok := result.([]string)
if !ok {
return nil, DialogOpenFileOutput{}, fmt.Errorf("unexpected result type from open file dialog")
}
return nil, DialogOpenFileOutput{Paths: paths}, nil
}
@ -56,7 +60,10 @@ func (s *Subsystem) dialogSaveFile(_ context.Context, _ *mcp.CallToolRequest, in
if err != nil {
return nil, DialogSaveFileOutput{}, err
}
path, _ := result.(string)
path, ok := result.(string)
if !ok {
return nil, DialogSaveFileOutput{}, fmt.Errorf("unexpected result type from save file dialog")
}
return nil, DialogSaveFileOutput{Path: path}, nil
}
@ -78,7 +85,10 @@ func (s *Subsystem) dialogOpenDirectory(_ context.Context, _ *mcp.CallToolReques
if err != nil {
return nil, DialogOpenDirectoryOutput{}, err
}
path, _ := result.(string)
path, ok := result.(string)
if !ok {
return nil, DialogOpenDirectoryOutput{}, fmt.Errorf("unexpected result type from open directory dialog")
}
return nil, DialogOpenDirectoryOutput{Path: path}, nil
}
@ -103,7 +113,10 @@ func (s *Subsystem) dialogConfirm(_ context.Context, _ *mcp.CallToolRequest, inp
if err != nil {
return nil, DialogConfirmOutput{}, err
}
button, _ := result.(string)
button, ok := result.(string)
if !ok {
return nil, DialogConfirmOutput{}, fmt.Errorf("unexpected result type from confirm dialog")
}
return nil, DialogConfirmOutput{Button: button}, nil
}
@ -127,7 +140,10 @@ func (s *Subsystem) dialogPrompt(_ context.Context, _ *mcp.CallToolRequest, inpu
if err != nil {
return nil, DialogPromptOutput{}, err
}
button, _ := result.(string)
button, ok := result.(string)
if !ok {
return nil, DialogPromptOutput{}, fmt.Errorf("unexpected result type from prompt dialog")
}
return nil, DialogPromptOutput{Button: button}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/environment"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -20,7 +21,10 @@ func (s *Subsystem) themeGet(_ context.Context, _ *mcp.CallToolRequest, _ ThemeG
if err != nil {
return nil, ThemeGetOutput{}, err
}
theme, _ := result.(environment.ThemeInfo)
theme, ok := result.(environment.ThemeInfo)
if !ok {
return nil, ThemeGetOutput{}, fmt.Errorf("unexpected result type from theme query")
}
return nil, ThemeGetOutput{Theme: theme}, nil
}
@ -36,7 +40,10 @@ func (s *Subsystem) themeSystem(_ context.Context, _ *mcp.CallToolRequest, _ The
if err != nil {
return nil, ThemeSystemOutput{}, err
}
info, _ := result.(environment.EnvironmentInfo)
info, ok := result.(environment.EnvironmentInfo)
if !ok {
return nil, ThemeSystemOutput{}, fmt.Errorf("unexpected result type from environment info query")
}
return nil, ThemeSystemOutput{Info: info}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/window"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -18,13 +19,10 @@ type LayoutSaveOutput struct {
}
func (s *Subsystem) layoutSave(_ context.Context, _ *mcp.CallToolRequest, input LayoutSaveInput) (*mcp.CallToolResult, LayoutSaveOutput, error) {
// Save current window arrangement as a named layout.
// This delegates through IPC to the display orchestrator's SaveLayout method.
result, _, err := s.core.QUERY(window.QueryWindowList{})
_, _, err := s.core.PERFORM(window.TaskSaveLayout{Name: input.Name})
if err != nil {
return nil, LayoutSaveOutput{}, err
}
_ = result // Layout saving is coordinated by the display orchestrator
return nil, LayoutSaveOutput{Success: true}, nil
}
@ -38,6 +36,10 @@ type LayoutRestoreOutput struct {
}
func (s *Subsystem) layoutRestore(_ context.Context, _ *mcp.CallToolRequest, input LayoutRestoreInput) (*mcp.CallToolResult, LayoutRestoreOutput, error) {
_, _, err := s.core.PERFORM(window.TaskRestoreLayout{Name: input.Name})
if err != nil {
return nil, LayoutRestoreOutput{}, err
}
return nil, LayoutRestoreOutput{Success: true}, nil
}
@ -49,7 +51,15 @@ type LayoutListOutput struct {
}
func (s *Subsystem) layoutList(_ context.Context, _ *mcp.CallToolRequest, _ LayoutListInput) (*mcp.CallToolResult, LayoutListOutput, error) {
return nil, LayoutListOutput{}, nil
result, _, err := s.core.QUERY(window.QueryLayoutList{})
if err != nil {
return nil, LayoutListOutput{}, err
}
layouts, ok := result.([]window.LayoutInfo)
if !ok {
return nil, LayoutListOutput{}, fmt.Errorf("unexpected result type from layout list query")
}
return nil, LayoutListOutput{Layouts: layouts}, nil
}
// --- layout_delete ---
@ -62,6 +72,10 @@ type LayoutDeleteOutput struct {
}
func (s *Subsystem) layoutDelete(_ context.Context, _ *mcp.CallToolRequest, input LayoutDeleteInput) (*mcp.CallToolResult, LayoutDeleteOutput, error) {
_, _, err := s.core.PERFORM(window.TaskDeleteLayout{Name: input.Name})
if err != nil {
return nil, LayoutDeleteOutput{}, err
}
return nil, LayoutDeleteOutput{Success: true}, nil
}
@ -75,7 +89,15 @@ type LayoutGetOutput struct {
}
func (s *Subsystem) layoutGet(_ context.Context, _ *mcp.CallToolRequest, input LayoutGetInput) (*mcp.CallToolResult, LayoutGetOutput, error) {
return nil, LayoutGetOutput{}, nil
result, _, err := s.core.QUERY(window.QueryLayoutGet{Name: input.Name})
if err != nil {
return nil, LayoutGetOutput{}, err
}
layout, ok := result.(*window.Layout)
if !ok {
return nil, LayoutGetOutput{}, fmt.Errorf("unexpected result type from layout get query")
}
return nil, LayoutGetOutput{Layout: layout}, nil
}
// --- layout_tile ---
@ -89,6 +111,10 @@ type LayoutTileOutput struct {
}
func (s *Subsystem) layoutTile(_ context.Context, _ *mcp.CallToolRequest, input LayoutTileInput) (*mcp.CallToolResult, LayoutTileOutput, error) {
_, _, err := s.core.PERFORM(window.TaskTileWindows{Mode: input.Mode, Windows: input.Windows})
if err != nil {
return nil, LayoutTileOutput{}, err
}
return nil, LayoutTileOutput{Success: true}, nil
}
@ -103,6 +129,10 @@ type LayoutSnapOutput struct {
}
func (s *Subsystem) layoutSnap(_ context.Context, _ *mcp.CallToolRequest, input LayoutSnapInput) (*mcp.CallToolResult, LayoutSnapOutput, error) {
_, _, err := s.core.PERFORM(window.TaskSnapWindow{Name: input.Name, Position: input.Position})
if err != nil {
return nil, LayoutSnapOutput{}, err
}
return nil, LayoutSnapOutput{Success: true}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/notification"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -43,7 +44,10 @@ func (s *Subsystem) notificationPermissionRequest(_ context.Context, _ *mcp.Call
if err != nil {
return nil, NotificationPermissionRequestOutput{}, err
}
granted, _ := result.(bool)
granted, ok := result.(bool)
if !ok {
return nil, NotificationPermissionRequestOutput{}, fmt.Errorf("unexpected result type from notification permission request")
}
return nil, NotificationPermissionRequestOutput{Granted: granted}, nil
}
@ -59,7 +63,10 @@ func (s *Subsystem) notificationPermissionCheck(_ context.Context, _ *mcp.CallTo
if err != nil {
return nil, NotificationPermissionCheckOutput{}, err
}
status, _ := result.(notification.PermissionStatus)
status, ok := result.(notification.PermissionStatus)
if !ok {
return nil, NotificationPermissionCheckOutput{}, fmt.Errorf("unexpected result type from notification permission check")
}
return nil, NotificationPermissionCheckOutput{Granted: status.Granted}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/screen"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -20,7 +21,10 @@ func (s *Subsystem) screenList(_ context.Context, _ *mcp.CallToolRequest, _ Scre
if err != nil {
return nil, ScreenListOutput{}, err
}
screens, _ := result.([]screen.Screen)
screens, ok := result.([]screen.Screen)
if !ok {
return nil, ScreenListOutput{}, fmt.Errorf("unexpected result type from screen list query")
}
return nil, ScreenListOutput{Screens: screens}, nil
}
@ -38,7 +42,10 @@ func (s *Subsystem) screenGet(_ context.Context, _ *mcp.CallToolRequest, input S
if err != nil {
return nil, ScreenGetOutput{}, err
}
scr, _ := result.(*screen.Screen)
scr, ok := result.(*screen.Screen)
if !ok {
return nil, ScreenGetOutput{}, fmt.Errorf("unexpected result type from screen get query")
}
return nil, ScreenGetOutput{Screen: scr}, nil
}
@ -54,7 +61,10 @@ func (s *Subsystem) screenPrimary(_ context.Context, _ *mcp.CallToolRequest, _ S
if err != nil {
return nil, ScreenPrimaryOutput{}, err
}
scr, _ := result.(*screen.Screen)
scr, ok := result.(*screen.Screen)
if !ok {
return nil, ScreenPrimaryOutput{}, fmt.Errorf("unexpected result type from screen primary query")
}
return nil, ScreenPrimaryOutput{Screen: scr}, nil
}
@ -73,7 +83,10 @@ func (s *Subsystem) screenAtPoint(_ context.Context, _ *mcp.CallToolRequest, inp
if err != nil {
return nil, ScreenAtPointOutput{}, err
}
scr, _ := result.(*screen.Screen)
scr, ok := result.(*screen.Screen)
if !ok {
return nil, ScreenAtPointOutput{}, fmt.Errorf("unexpected result type from screen at point query")
}
return nil, ScreenAtPointOutput{Screen: scr}, nil
}
@ -89,7 +102,10 @@ func (s *Subsystem) screenWorkAreas(_ context.Context, _ *mcp.CallToolRequest, _
if err != nil {
return nil, ScreenWorkAreasOutput{}, err
}
areas, _ := result.([]screen.Rect)
areas, ok := result.([]screen.Rect)
if !ok {
return nil, ScreenWorkAreasOutput{}, fmt.Errorf("unexpected result type from screen work areas query")
}
return nil, ScreenWorkAreasOutput{WorkAreas: areas}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/systray"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -67,7 +68,10 @@ func (s *Subsystem) trayInfo(_ context.Context, _ *mcp.CallToolRequest, _ TrayIn
if err != nil {
return nil, TrayInfoOutput{}, err
}
config, _ := result.(map[string]any)
config, ok := result.(map[string]any)
if !ok {
return nil, TrayInfoOutput{}, fmt.Errorf("unexpected result type from tray config query")
}
return nil, TrayInfoOutput{Config: config}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/webview"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -102,7 +103,10 @@ func (s *Subsystem) webviewScreenshot(_ context.Context, _ *mcp.CallToolRequest,
if err != nil {
return nil, WebviewScreenshotOutput{}, err
}
sr, _ := result.(webview.ScreenshotResult)
sr, ok := result.(webview.ScreenshotResult)
if !ok {
return nil, WebviewScreenshotOutput{}, fmt.Errorf("unexpected result type from webview screenshot")
}
return nil, WebviewScreenshotOutput{Base64: sr.Base64, MimeType: sr.MimeType}, nil
}
@ -242,7 +246,10 @@ func (s *Subsystem) webviewConsole(_ context.Context, _ *mcp.CallToolRequest, in
if err != nil {
return nil, WebviewConsoleOutput{}, err
}
msgs, _ := result.([]webview.ConsoleMessage)
msgs, ok := result.([]webview.ConsoleMessage)
if !ok {
return nil, WebviewConsoleOutput{}, fmt.Errorf("unexpected result type from webview console query")
}
return nil, WebviewConsoleOutput{Messages: msgs}, nil
}
@ -280,7 +287,10 @@ func (s *Subsystem) webviewQuery(_ context.Context, _ *mcp.CallToolRequest, inpu
if err != nil {
return nil, WebviewQueryOutput{}, err
}
el, _ := result.(*webview.ElementInfo)
el, ok := result.(*webview.ElementInfo)
if !ok {
return nil, WebviewQueryOutput{}, fmt.Errorf("unexpected result type from webview query")
}
return nil, WebviewQueryOutput{Element: el}, nil
}
@ -300,7 +310,10 @@ func (s *Subsystem) webviewQueryAll(_ context.Context, _ *mcp.CallToolRequest, i
if err != nil {
return nil, WebviewQueryAllOutput{}, err
}
els, _ := result.([]*webview.ElementInfo)
els, ok := result.([]*webview.ElementInfo)
if !ok {
return nil, WebviewQueryAllOutput{}, fmt.Errorf("unexpected result type from webview query all")
}
return nil, WebviewQueryAllOutput{Elements: els}, nil
}
@ -320,7 +333,10 @@ func (s *Subsystem) webviewDOMTree(_ context.Context, _ *mcp.CallToolRequest, in
if err != nil {
return nil, WebviewDOMTreeOutput{}, err
}
html, _ := result.(string)
html, ok := result.(string)
if !ok {
return nil, WebviewDOMTreeOutput{}, fmt.Errorf("unexpected result type from webview DOM tree query")
}
return nil, WebviewDOMTreeOutput{HTML: html}, nil
}
@ -339,7 +355,10 @@ func (s *Subsystem) webviewURL(_ context.Context, _ *mcp.CallToolRequest, input
if err != nil {
return nil, WebviewURLOutput{}, err
}
url, _ := result.(string)
url, ok := result.(string)
if !ok {
return nil, WebviewURLOutput{}, fmt.Errorf("unexpected result type from webview URL query")
}
return nil, WebviewURLOutput{URL: url}, nil
}
@ -358,7 +377,10 @@ func (s *Subsystem) webviewTitle(_ context.Context, _ *mcp.CallToolRequest, inpu
if err != nil {
return nil, WebviewTitleOutput{}, err
}
title, _ := result.(string)
title, ok := result.(string)
if !ok {
return nil, WebviewTitleOutput{}, fmt.Errorf("unexpected result type from webview title query")
}
return nil, WebviewTitleOutput{Title: title}, nil
}

View file

@ -3,6 +3,7 @@ package mcp
import (
"context"
"fmt"
"forge.lthn.ai/core/gui/pkg/window"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -20,7 +21,10 @@ func (s *Subsystem) windowList(_ context.Context, _ *mcp.CallToolRequest, _ Wind
if err != nil {
return nil, WindowListOutput{}, err
}
windows, _ := result.([]window.WindowInfo)
windows, ok := result.([]window.WindowInfo)
if !ok {
return nil, WindowListOutput{}, fmt.Errorf("unexpected result type from window list query")
}
return nil, WindowListOutput{Windows: windows}, nil
}
@ -38,7 +42,10 @@ func (s *Subsystem) windowGet(_ context.Context, _ *mcp.CallToolRequest, input W
if err != nil {
return nil, WindowGetOutput{}, err
}
info, _ := result.(*window.WindowInfo)
info, ok := result.(*window.WindowInfo)
if !ok {
return nil, WindowGetOutput{}, fmt.Errorf("unexpected result type from window get query")
}
return nil, WindowGetOutput{Window: info}, nil
}
@ -54,7 +61,10 @@ func (s *Subsystem) windowFocused(_ context.Context, _ *mcp.CallToolRequest, _ W
if err != nil {
return nil, WindowFocusedOutput{}, err
}
windows, _ := result.([]window.WindowInfo)
windows, ok := result.([]window.WindowInfo)
if !ok {
return nil, WindowFocusedOutput{}, fmt.Errorf("unexpected result type from window list query")
}
for _, w := range windows {
if w.Focused {
return nil, WindowFocusedOutput{Window: w.Name}, nil
@ -98,7 +108,10 @@ func (s *Subsystem) windowCreate(_ context.Context, _ *mcp.CallToolRequest, inpu
if err != nil {
return nil, WindowCreateOutput{}, err
}
info, _ := result.(window.WindowInfo)
info, ok := result.(window.WindowInfo)
if !ok {
return nil, WindowCreateOutput{}, fmt.Errorf("unexpected result type from window create task")
}
return nil, WindowCreateOutput{Window: info}, nil
}
@ -226,8 +239,7 @@ type WindowRestoreOutput struct {
}
func (s *Subsystem) windowRestore(_ context.Context, _ *mcp.CallToolRequest, input WindowRestoreInput) (*mcp.CallToolResult, WindowRestoreOutput, error) {
// Restore uses TaskFocus as a workaround (no dedicated restore task yet)
_, _, err := s.core.PERFORM(window.TaskFocus{Name: input.Name})
_, _, err := s.core.PERFORM(window.TaskRestore{Name: input.Name})
if err != nil {
return nil, WindowRestoreOutput{}, err
}
@ -262,8 +274,7 @@ type WindowTitleOutput struct {
}
func (s *Subsystem) windowTitle(_ context.Context, _ *mcp.CallToolRequest, input WindowTitleInput) (*mcp.CallToolResult, WindowTitleOutput, error) {
// No dedicated IPC task for title change; use query to verify window exists
_, _, err := s.core.QUERY(window.QueryWindowByName{Name: input.Name})
_, _, err := s.core.PERFORM(window.TaskSetTitle{Name: input.Name, Title: input.Title})
if err != nil {
return nil, WindowTitleOutput{}, err
}
@ -281,12 +292,9 @@ type WindowVisibilityOutput struct {
}
func (s *Subsystem) windowVisibility(_ context.Context, _ *mcp.CallToolRequest, input WindowVisibilityInput) (*mcp.CallToolResult, WindowVisibilityOutput, error) {
// Visibility uses focus/close as approximation (no dedicated visibility task yet)
if input.Visible {
_, _, err := s.core.PERFORM(window.TaskFocus{Name: input.Name})
if err != nil {
return nil, WindowVisibilityOutput{}, err
}
_, _, err := s.core.PERFORM(window.TaskSetVisibility{Name: input.Name, Visible: input.Visible})
if err != nil {
return nil, WindowVisibilityOutput{}, err
}
return nil, WindowVisibilityOutput{Success: true}, nil
}
@ -302,12 +310,9 @@ type WindowFullscreenOutput struct {
}
func (s *Subsystem) windowFullscreen(_ context.Context, _ *mcp.CallToolRequest, input WindowFullscreenInput) (*mcp.CallToolResult, WindowFullscreenOutput, error) {
// No dedicated fullscreen task in IPC yet; use maximise as approximation
if input.Fullscreen {
_, _, err := s.core.PERFORM(window.TaskMaximise{Name: input.Name})
if err != nil {
return nil, WindowFullscreenOutput{}, err
}
_, _, err := s.core.PERFORM(window.TaskFullscreen{Name: input.Name, Fullscreen: input.Fullscreen})
if err != nil {
return nil, WindowFullscreenOutput{}, err
}
return nil, WindowFullscreenOutput{Success: true}, nil
}