From ec492658c29ea32b7ade6c53d28ec51dd10b39c6 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 13 Mar 2026 16:28:15 +0000 Subject: [PATCH] fix(mcp): add safe type assertions and complete stub handlers 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 --- pkg/mcp/mcp_test.go | 3 ++- pkg/mcp/tools_clipboard.go | 21 ++++++++++++---- pkg/mcp/tools_contextmenu.go | 40 ++++++++++++++++++++++++------- pkg/mcp/tools_dialog.go | 26 ++++++++++++++++---- pkg/mcp/tools_environment.go | 11 +++++++-- pkg/mcp/tools_layout.go | 42 +++++++++++++++++++++++++++----- pkg/mcp/tools_notification.go | 11 +++++++-- pkg/mcp/tools_screen.go | 26 ++++++++++++++++---- pkg/mcp/tools_tray.go | 6 ++++- pkg/mcp/tools_webview.go | 36 ++++++++++++++++++++++------ pkg/mcp/tools_window.go | 45 +++++++++++++++++++---------------- 11 files changed, 205 insertions(+), 62 deletions(-) diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index 86bb4d3..d3a3453 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -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) } diff --git a/pkg/mcp/tools_clipboard.go b/pkg/mcp/tools_clipboard.go index 6390088..827586a 100644 --- a/pkg/mcp/tools_clipboard.go +++ b/pkg/mcp/tools_clipboard.go @@ -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 } diff --git a/pkg/mcp/tools_contextmenu.go b/pkg/mcp/tools_contextmenu.go index 4db9685..74e9f8b 100644 --- a/pkg/mcp/tools_contextmenu.go +++ b/pkg/mcp/tools_contextmenu.go @@ -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 } diff --git a/pkg/mcp/tools_dialog.go b/pkg/mcp/tools_dialog.go index 82dba72..06bdf66 100644 --- a/pkg/mcp/tools_dialog.go +++ b/pkg/mcp/tools_dialog.go @@ -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 } diff --git a/pkg/mcp/tools_environment.go b/pkg/mcp/tools_environment.go index 1526764..87eb0df 100644 --- a/pkg/mcp/tools_environment.go +++ b/pkg/mcp/tools_environment.go @@ -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 } diff --git a/pkg/mcp/tools_layout.go b/pkg/mcp/tools_layout.go index e6237f9..24ec8fa 100644 --- a/pkg/mcp/tools_layout.go +++ b/pkg/mcp/tools_layout.go @@ -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 } diff --git a/pkg/mcp/tools_notification.go b/pkg/mcp/tools_notification.go index 873b39d..259e59f 100644 --- a/pkg/mcp/tools_notification.go +++ b/pkg/mcp/tools_notification.go @@ -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 } diff --git a/pkg/mcp/tools_screen.go b/pkg/mcp/tools_screen.go index 1bc9538..8a276b9 100644 --- a/pkg/mcp/tools_screen.go +++ b/pkg/mcp/tools_screen.go @@ -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 } diff --git a/pkg/mcp/tools_tray.go b/pkg/mcp/tools_tray.go index 1b4b548..0cbad22 100644 --- a/pkg/mcp/tools_tray.go +++ b/pkg/mcp/tools_tray.go @@ -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 } diff --git a/pkg/mcp/tools_webview.go b/pkg/mcp/tools_webview.go index 9df6dc7..b598a4b 100644 --- a/pkg/mcp/tools_webview.go +++ b/pkg/mcp/tools_webview.go @@ -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 } diff --git a/pkg/mcp/tools_window.go b/pkg/mcp/tools_window.go index f8a3aab..e5ac73f 100644 --- a/pkg/mcp/tools_window.go +++ b/pkg/mcp/tools_window.go @@ -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 }