Files
isphere-ai-bridge/internal/tools/winhelper.go
2026-07-05 19:19:07 +08:00

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
}