123 lines
4.0 KiB
Go
123 lines
4.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
|
|
"isphere-ai-bridge/internal/helperclient"
|
|
)
|
|
|
|
const (
|
|
ToolNameVersion = "win_helper_version"
|
|
ToolNameSelfCheck = "win_helper_self_check"
|
|
ToolNameScanWindows = "win_helper_scan_windows"
|
|
ToolNameDumpUIA = "win_helper_dump_uia"
|
|
)
|
|
|
|
type HelperCaller interface {
|
|
Call(ctx context.Context, op string, args map[string]any) (*helperclient.HelperResponse, error)
|
|
}
|
|
|
|
type VersionArgs struct{}
|
|
|
|
type SelfCheckArgs struct{}
|
|
|
|
type ScanWindowsArgs struct {
|
|
IncludeAllVisible bool `json:"include_all_visible,omitempty" jsonschema:"include all visible desktop windows, not only likely iSphere candidates"`
|
|
}
|
|
|
|
type DumpUIAArgs struct {
|
|
Hwnd string `json:"hwnd,omitempty" jsonschema:"window handle to dump, for example 0x001A0B2C"`
|
|
MaxDepth int `json:"max_depth,omitempty" jsonschema:"maximum UI Automation tree depth"`
|
|
IncludeText bool `json:"include_text,omitempty" jsonschema:"include text values when available"`
|
|
MaxChildren int `json:"max_children,omitempty" jsonschema:"maximum children per node"`
|
|
}
|
|
|
|
func RegisterWinHelperTools(server *mcp.Server, caller HelperCaller) {
|
|
if caller == nil {
|
|
caller = helperclient.Client{}
|
|
}
|
|
|
|
mcp.AddTool[VersionArgs, map[string]any](server, &mcp.Tool{
|
|
Name: ToolNameVersion,
|
|
Description: "Return ISphereWinHelper version and protocol metadata.",
|
|
}, func(ctx context.Context, _ *mcp.CallToolRequest, _ VersionArgs) (*mcp.CallToolResult, map[string]any, error) {
|
|
return callHelper(ctx, caller, "version", map[string]any{})
|
|
})
|
|
|
|
mcp.AddTool[SelfCheckArgs, map[string]any](server, &mcp.Tool{
|
|
Name: ToolNameSelfCheck,
|
|
Description: "Run ISphereWinHelper read-only desktop and UI Automation availability checks.",
|
|
}, func(ctx context.Context, _ *mcp.CallToolRequest, _ SelfCheckArgs) (*mcp.CallToolResult, map[string]any, error) {
|
|
return callHelper(ctx, caller, "self_check", map[string]any{})
|
|
})
|
|
|
|
mcp.AddTool[ScanWindowsArgs, map[string]any](server, &mcp.Tool{
|
|
Name: ToolNameScanWindows,
|
|
Description: "List visible desktop windows or likely iSphere window candidates in read-only mode.",
|
|
}, func(ctx context.Context, _ *mcp.CallToolRequest, input ScanWindowsArgs) (*mcp.CallToolResult, map[string]any, error) {
|
|
return callHelper(ctx, caller, "scan_windows", map[string]any{
|
|
"include_all_visible": input.IncludeAllVisible,
|
|
})
|
|
})
|
|
|
|
mcp.AddTool[DumpUIAArgs, map[string]any](server, &mcp.Tool{
|
|
Name: ToolNameDumpUIA,
|
|
Description: "Dump a read-only UI Automation tree for a specified window handle.",
|
|
}, func(ctx context.Context, _ *mcp.CallToolRequest, input DumpUIAArgs) (*mcp.CallToolResult, map[string]any, error) {
|
|
return callHelper(ctx, caller, "dump_uia", map[string]any{
|
|
"hwnd": input.Hwnd,
|
|
"max_depth": float64(input.MaxDepth),
|
|
"include_text": input.IncludeText,
|
|
"max_children": float64(input.MaxChildren),
|
|
})
|
|
})
|
|
}
|
|
|
|
func callHelper(ctx context.Context, caller HelperCaller, op string, args map[string]any) (*mcp.CallToolResult, map[string]any, error) {
|
|
response, err := caller.Call(ctx, op, args)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return nil, helperResponseToMap(response), nil
|
|
}
|
|
|
|
func helperResponseToMap(response *helperclient.HelperResponse) map[string]any {
|
|
if response == nil {
|
|
return map[string]any{"ok": false}
|
|
}
|
|
|
|
out := map[string]any{
|
|
"protocol": response.Protocol,
|
|
"request_id": response.RequestID,
|
|
"op": response.Op,
|
|
"ok": response.OK,
|
|
"data": decodeRawData(response.Data),
|
|
"warnings": response.Warnings,
|
|
}
|
|
if response.Error != nil {
|
|
out["error"] = map[string]any{
|
|
"code": response.Error.Code,
|
|
"message": response.Error.Message,
|
|
}
|
|
} else {
|
|
out["error"] = nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
func decodeRawData(data map[string]json.RawMessage) map[string]any {
|
|
decoded := map[string]any{}
|
|
for key, raw := range data {
|
|
var value any
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
decoded[key] = string(raw)
|
|
continue
|
|
}
|
|
decoded[key] = value
|
|
}
|
|
return decoded
|
|
}
|