diff --git a/pkg/mcp/tools_clipboard.go b/pkg/mcp/tools_clipboard.go new file mode 100644 index 0000000..6390088 --- /dev/null +++ b/pkg/mcp/tools_clipboard.go @@ -0,0 +1,84 @@ +// pkg/mcp/tools_clipboard.go +package mcp + +import ( + "context" + + "forge.lthn.ai/core/gui/pkg/clipboard" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// --- clipboard_read --- + +type ClipboardReadInput struct{} +type ClipboardReadOutput struct { + Content string `json:"content"` +} + +func (s *Subsystem) clipboardRead(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardReadInput) (*mcp.CallToolResult, ClipboardReadOutput, error) { + result, _, err := s.core.QUERY(clipboard.QueryText{}) + if err != nil { + return nil, ClipboardReadOutput{}, err + } + content, _ := result.(clipboard.ClipboardContent) + return nil, ClipboardReadOutput{Content: content.Text}, nil +} + +// --- clipboard_write --- + +type ClipboardWriteInput struct { + Text string `json:"text"` +} +type ClipboardWriteOutput struct { + Success bool `json:"success"` +} + +func (s *Subsystem) clipboardWrite(_ context.Context, _ *mcp.CallToolRequest, input ClipboardWriteInput) (*mcp.CallToolResult, ClipboardWriteOutput, error) { + result, _, err := s.core.PERFORM(clipboard.TaskSetText{Text: input.Text}) + if err != nil { + return nil, ClipboardWriteOutput{}, err + } + success, _ := result.(bool) + return nil, ClipboardWriteOutput{Success: success}, nil +} + +// --- clipboard_has --- + +type ClipboardHasInput struct{} +type ClipboardHasOutput struct { + HasContent bool `json:"hasContent"` +} + +func (s *Subsystem) clipboardHas(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardHasInput) (*mcp.CallToolResult, ClipboardHasOutput, error) { + result, _, err := s.core.QUERY(clipboard.QueryText{}) + if err != nil { + return nil, ClipboardHasOutput{}, err + } + content, _ := result.(clipboard.ClipboardContent) + return nil, ClipboardHasOutput{HasContent: content.HasContent}, nil +} + +// --- clipboard_clear --- + +type ClipboardClearInput struct{} +type ClipboardClearOutput struct { + Success bool `json:"success"` +} + +func (s *Subsystem) clipboardClear(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardClearInput) (*mcp.CallToolResult, ClipboardClearOutput, error) { + result, _, err := s.core.PERFORM(clipboard.TaskClear{}) + if err != nil { + return nil, ClipboardClearOutput{}, err + } + success, _ := result.(bool) + return nil, ClipboardClearOutput{Success: success}, nil +} + +// --- Registration --- + +func (s *Subsystem) registerClipboardTools(server *mcp.Server) { + mcp.AddTool(server, &mcp.Tool{Name: "clipboard_read", Description: "Read the current clipboard content"}, s.clipboardRead) + mcp.AddTool(server, &mcp.Tool{Name: "clipboard_write", Description: "Write text to the clipboard"}, s.clipboardWrite) + mcp.AddTool(server, &mcp.Tool{Name: "clipboard_has", Description: "Check if the clipboard has content"}, s.clipboardHas) + mcp.AddTool(server, &mcp.Tool{Name: "clipboard_clear", Description: "Clear the clipboard"}, s.clipboardClear) +} diff --git a/pkg/mcp/tools_dialog.go b/pkg/mcp/tools_dialog.go new file mode 100644 index 0000000..82dba72 --- /dev/null +++ b/pkg/mcp/tools_dialog.go @@ -0,0 +1,142 @@ +// pkg/mcp/tools_dialog.go +package mcp + +import ( + "context" + + "forge.lthn.ai/core/gui/pkg/dialog" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// --- dialog_open_file --- + +type DialogOpenFileInput struct { + Title string `json:"title,omitempty"` + Directory string `json:"directory,omitempty"` + Filters []dialog.FileFilter `json:"filters,omitempty"` + AllowMultiple bool `json:"allowMultiple,omitempty"` +} +type DialogOpenFileOutput struct { + Paths []string `json:"paths"` +} + +func (s *Subsystem) dialogOpenFile(_ context.Context, _ *mcp.CallToolRequest, input DialogOpenFileInput) (*mcp.CallToolResult, DialogOpenFileOutput, error) { + result, _, err := s.core.PERFORM(dialog.TaskOpenFile{Opts: dialog.OpenFileOptions{ + Title: input.Title, + Directory: input.Directory, + Filters: input.Filters, + AllowMultiple: input.AllowMultiple, + }}) + if err != nil { + return nil, DialogOpenFileOutput{}, err + } + paths, _ := result.([]string) + return nil, DialogOpenFileOutput{Paths: paths}, nil +} + +// --- dialog_save_file --- + +type DialogSaveFileInput struct { + Title string `json:"title,omitempty"` + Directory string `json:"directory,omitempty"` + Filename string `json:"filename,omitempty"` + Filters []dialog.FileFilter `json:"filters,omitempty"` +} +type DialogSaveFileOutput struct { + Path string `json:"path"` +} + +func (s *Subsystem) dialogSaveFile(_ context.Context, _ *mcp.CallToolRequest, input DialogSaveFileInput) (*mcp.CallToolResult, DialogSaveFileOutput, error) { + result, _, err := s.core.PERFORM(dialog.TaskSaveFile{Opts: dialog.SaveFileOptions{ + Title: input.Title, + Directory: input.Directory, + Filename: input.Filename, + Filters: input.Filters, + }}) + if err != nil { + return nil, DialogSaveFileOutput{}, err + } + path, _ := result.(string) + return nil, DialogSaveFileOutput{Path: path}, nil +} + +// --- dialog_open_directory --- + +type DialogOpenDirectoryInput struct { + Title string `json:"title,omitempty"` + Directory string `json:"directory,omitempty"` +} +type DialogOpenDirectoryOutput struct { + Path string `json:"path"` +} + +func (s *Subsystem) dialogOpenDirectory(_ context.Context, _ *mcp.CallToolRequest, input DialogOpenDirectoryInput) (*mcp.CallToolResult, DialogOpenDirectoryOutput, error) { + result, _, err := s.core.PERFORM(dialog.TaskOpenDirectory{Opts: dialog.OpenDirectoryOptions{ + Title: input.Title, + Directory: input.Directory, + }}) + if err != nil { + return nil, DialogOpenDirectoryOutput{}, err + } + path, _ := result.(string) + return nil, DialogOpenDirectoryOutput{Path: path}, nil +} + +// --- dialog_confirm --- + +type DialogConfirmInput struct { + Title string `json:"title"` + Message string `json:"message"` + Buttons []string `json:"buttons,omitempty"` +} +type DialogConfirmOutput struct { + Button string `json:"button"` +} + +func (s *Subsystem) dialogConfirm(_ context.Context, _ *mcp.CallToolRequest, input DialogConfirmInput) (*mcp.CallToolResult, DialogConfirmOutput, error) { + result, _, err := s.core.PERFORM(dialog.TaskMessageDialog{Opts: dialog.MessageDialogOptions{ + Type: dialog.DialogQuestion, + Title: input.Title, + Message: input.Message, + Buttons: input.Buttons, + }}) + if err != nil { + return nil, DialogConfirmOutput{}, err + } + button, _ := result.(string) + return nil, DialogConfirmOutput{Button: button}, nil +} + +// --- dialog_prompt --- + +type DialogPromptInput struct { + Title string `json:"title"` + Message string `json:"message"` +} +type DialogPromptOutput struct { + Button string `json:"button"` +} + +func (s *Subsystem) dialogPrompt(_ context.Context, _ *mcp.CallToolRequest, input DialogPromptInput) (*mcp.CallToolResult, DialogPromptOutput, error) { + result, _, err := s.core.PERFORM(dialog.TaskMessageDialog{Opts: dialog.MessageDialogOptions{ + Type: dialog.DialogInfo, + Title: input.Title, + Message: input.Message, + Buttons: []string{"OK", "Cancel"}, + }}) + if err != nil { + return nil, DialogPromptOutput{}, err + } + button, _ := result.(string) + return nil, DialogPromptOutput{Button: button}, nil +} + +// --- Registration --- + +func (s *Subsystem) registerDialogTools(server *mcp.Server) { + mcp.AddTool(server, &mcp.Tool{Name: "dialog_open_file", Description: "Show an open file dialog"}, s.dialogOpenFile) + mcp.AddTool(server, &mcp.Tool{Name: "dialog_save_file", Description: "Show a save file dialog"}, s.dialogSaveFile) + mcp.AddTool(server, &mcp.Tool{Name: "dialog_open_directory", Description: "Show a directory picker dialog"}, s.dialogOpenDirectory) + mcp.AddTool(server, &mcp.Tool{Name: "dialog_confirm", Description: "Show a confirmation dialog"}, s.dialogConfirm) + mcp.AddTool(server, &mcp.Tool{Name: "dialog_prompt", Description: "Show a prompt dialog"}, s.dialogPrompt) +} diff --git a/pkg/mcp/tools_screen.go b/pkg/mcp/tools_screen.go new file mode 100644 index 0000000..1bc9538 --- /dev/null +++ b/pkg/mcp/tools_screen.go @@ -0,0 +1,104 @@ +// pkg/mcp/tools_screen.go +package mcp + +import ( + "context" + + "forge.lthn.ai/core/gui/pkg/screen" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// --- screen_list --- + +type ScreenListInput struct{} +type ScreenListOutput struct { + Screens []screen.Screen `json:"screens"` +} + +func (s *Subsystem) screenList(_ context.Context, _ *mcp.CallToolRequest, _ ScreenListInput) (*mcp.CallToolResult, ScreenListOutput, error) { + result, _, err := s.core.QUERY(screen.QueryAll{}) + if err != nil { + return nil, ScreenListOutput{}, err + } + screens, _ := result.([]screen.Screen) + return nil, ScreenListOutput{Screens: screens}, nil +} + +// --- screen_get --- + +type ScreenGetInput struct { + ID string `json:"id"` +} +type ScreenGetOutput struct { + Screen *screen.Screen `json:"screen"` +} + +func (s *Subsystem) screenGet(_ context.Context, _ *mcp.CallToolRequest, input ScreenGetInput) (*mcp.CallToolResult, ScreenGetOutput, error) { + result, _, err := s.core.QUERY(screen.QueryByID{ID: input.ID}) + if err != nil { + return nil, ScreenGetOutput{}, err + } + scr, _ := result.(*screen.Screen) + return nil, ScreenGetOutput{Screen: scr}, nil +} + +// --- screen_primary --- + +type ScreenPrimaryInput struct{} +type ScreenPrimaryOutput struct { + Screen *screen.Screen `json:"screen"` +} + +func (s *Subsystem) screenPrimary(_ context.Context, _ *mcp.CallToolRequest, _ ScreenPrimaryInput) (*mcp.CallToolResult, ScreenPrimaryOutput, error) { + result, _, err := s.core.QUERY(screen.QueryPrimary{}) + if err != nil { + return nil, ScreenPrimaryOutput{}, err + } + scr, _ := result.(*screen.Screen) + return nil, ScreenPrimaryOutput{Screen: scr}, nil +} + +// --- screen_at_point --- + +type ScreenAtPointInput struct { + X int `json:"x"` + Y int `json:"y"` +} +type ScreenAtPointOutput struct { + Screen *screen.Screen `json:"screen"` +} + +func (s *Subsystem) screenAtPoint(_ context.Context, _ *mcp.CallToolRequest, input ScreenAtPointInput) (*mcp.CallToolResult, ScreenAtPointOutput, error) { + result, _, err := s.core.QUERY(screen.QueryAtPoint{X: input.X, Y: input.Y}) + if err != nil { + return nil, ScreenAtPointOutput{}, err + } + scr, _ := result.(*screen.Screen) + return nil, ScreenAtPointOutput{Screen: scr}, nil +} + +// --- screen_work_areas --- + +type ScreenWorkAreasInput struct{} +type ScreenWorkAreasOutput struct { + WorkAreas []screen.Rect `json:"workAreas"` +} + +func (s *Subsystem) screenWorkAreas(_ context.Context, _ *mcp.CallToolRequest, _ ScreenWorkAreasInput) (*mcp.CallToolResult, ScreenWorkAreasOutput, error) { + result, _, err := s.core.QUERY(screen.QueryWorkAreas{}) + if err != nil { + return nil, ScreenWorkAreasOutput{}, err + } + areas, _ := result.([]screen.Rect) + return nil, ScreenWorkAreasOutput{WorkAreas: areas}, nil +} + +// --- Registration --- + +func (s *Subsystem) registerScreenTools(server *mcp.Server) { + mcp.AddTool(server, &mcp.Tool{Name: "screen_list", Description: "List all connected displays/screens"}, s.screenList) + mcp.AddTool(server, &mcp.Tool{Name: "screen_get", Description: "Get information about a specific screen"}, s.screenGet) + mcp.AddTool(server, &mcp.Tool{Name: "screen_primary", Description: "Get the primary screen"}, s.screenPrimary) + mcp.AddTool(server, &mcp.Tool{Name: "screen_at_point", Description: "Get the screen at a specific point"}, s.screenAtPoint) + mcp.AddTool(server, &mcp.Tool{Name: "screen_work_areas", Description: "Get work areas for all screens"}, s.screenWorkAreas) +}