feat(mcp): add screen, clipboard, dialog tools (14)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-03-13 16:08:26 +00:00
parent 39d222d598
commit 985d3c737c
3 changed files with 330 additions and 0 deletions

View file

@ -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)
}

142
pkg/mcp/tools_dialog.go Normal file
View file

@ -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)
}

104
pkg/mcp/tools_screen.go Normal file
View file

@ -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)
}