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 <noreply@anthropic.com>
This commit is contained in:
parent
f884d698b2
commit
ec492658c2
11 changed files with 205 additions and 62 deletions
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue