test: add go mcp verification script
This commit is contained in:
219
scripts/verify-go-mcp.ps1
Normal file
219
scripts/verify-go-mcp.ps1
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
|
||||||
|
$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
|
||||||
|
$expectedTools = @(
|
||||||
|
"win_helper_version",
|
||||||
|
"win_helper_self_check",
|
||||||
|
"win_helper_scan_windows",
|
||||||
|
"win_helper_dump_uia"
|
||||||
|
)
|
||||||
|
|
||||||
|
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("isphere-go-mcp-verify-" + [guid]::NewGuid().ToString("N"))
|
||||||
|
$goMcpExe = Join-Path $tempRoot "isphere-mcp.exe"
|
||||||
|
$harnessDir = Join-Path $repo ("runs\go-mcp-harness-" + [guid]::NewGuid().ToString("N"))
|
||||||
|
$harnessMain = Join-Path $harnessDir "main.go"
|
||||||
|
|
||||||
|
function Assert-True([bool]$Condition, [string]$Message) {
|
||||||
|
if (-not $Condition) {
|
||||||
|
throw $Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-Step([string]$Name, [scriptblock]$Body) {
|
||||||
|
Write-Host "## $Name"
|
||||||
|
& $Body
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
|
||||||
|
New-Item -ItemType Directory -Force -Path $harnessDir | Out-Null
|
||||||
|
|
||||||
|
Push-Location $repo
|
||||||
|
try {
|
||||||
|
Invoke-Step "build C# helper" {
|
||||||
|
& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repo "scripts\build-win-helper.ps1") | Out-Host
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "build-win-helper.ps1 failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-Step "build Go MCP binary" {
|
||||||
|
& go build -o $goMcpExe ./cmd/isphere-mcp
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "go build ./cmd/isphere-mcp failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
Assert-True (Test-Path -LiteralPath $goMcpExe) "Go MCP binary was not created: $goMcpExe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-Step "write temporary SDK harness" {
|
||||||
|
@"
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
|
"isphere-ai-bridge/internal/mcpserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var expectedTools = []string{
|
||||||
|
"win_helper_version",
|
||||||
|
"win_helper_self_check",
|
||||||
|
"win_helper_scan_windows",
|
||||||
|
"win_helper_dump_uia",
|
||||||
|
}
|
||||||
|
|
||||||
|
var forbiddenTerms = []string{"send", "search", "file", "login", "conversation", "upload", "download", "receive", "autologin"}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
||||||
|
server := mcpserver.NewServer()
|
||||||
|
serverErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
serverErr <- server.Run(ctx, serverTransport)
|
||||||
|
}()
|
||||||
|
|
||||||
|
client := mcp.NewClient(&mcp.Implementation{Name: "verify-go-mcp", Version: "0.1.0"}, nil)
|
||||||
|
session, err := client.Connect(ctx, clientTransport, nil)
|
||||||
|
if err != nil {
|
||||||
|
fail("initialize/connect", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = session.Close()
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case <-serverErr:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
fail("server shutdown", fmt.Errorf("server did not stop"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
listResult, err := session.ListTools(ctx, &mcp.ListToolsParams{})
|
||||||
|
if err != nil {
|
||||||
|
fail("tools/list", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toolNames := make([]string, 0, len(listResult.Tools))
|
||||||
|
for _, tool := range listResult.Tools {
|
||||||
|
toolNames = append(toolNames, tool.Name)
|
||||||
|
lower := strings.ToLower(tool.Name)
|
||||||
|
for _, term := range forbiddenTerms {
|
||||||
|
if strings.Contains(lower, term) {
|
||||||
|
fail("tools/list", fmt.Errorf("forbidden action term %q found in tool %q", term, tool.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(toolNames)
|
||||||
|
want := append([]string(nil), expectedTools...)
|
||||||
|
sort.Strings(want)
|
||||||
|
if len(toolNames) != 4 || !reflect.DeepEqual(toolNames, want) {
|
||||||
|
fail("tools/list", fmt.Errorf("tool names = %#v, want %#v", toolNames, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "win_helper_version", Arguments: map[string]any{}})
|
||||||
|
if err != nil {
|
||||||
|
fail("tools/call win_helper_version", err)
|
||||||
|
}
|
||||||
|
if callResult.IsError {
|
||||||
|
fail("tools/call win_helper_version", fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
structured, ok := callResult.StructuredContent.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
payload, _ := json.Marshal(callResult.StructuredContent)
|
||||||
|
if err := json.Unmarshal(payload, &structured); err != nil {
|
||||||
|
fail("tools/call win_helper_version", fmt.Errorf("structuredContent was not object: %T", callResult.StructuredContent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if structured["ok"] != true {
|
||||||
|
fail("tools/call win_helper_version", fmt.Errorf("ok = %#v, want true", structured["ok"]))
|
||||||
|
}
|
||||||
|
data, ok := structured["data"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
fail("tools/call win_helper_version", fmt.Errorf("data missing or not object: %#v", structured["data"]))
|
||||||
|
}
|
||||||
|
helperName, _ := data["helper_name"].(string)
|
||||||
|
if helperName != "ISphereWinHelper" {
|
||||||
|
fail("tools/call win_helper_version", fmt.Errorf("helper_name = %q, want ISphereWinHelper", helperName))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]any{
|
||||||
|
"ok": true,
|
||||||
|
"transport": "sdk_in_memory",
|
||||||
|
"initialized": true,
|
||||||
|
"tool_count": len(toolNames),
|
||||||
|
"tools": toolNames,
|
||||||
|
"helper_name": helperName,
|
||||||
|
"real_isphere_login_required": false,
|
||||||
|
"python_runtime_required": false,
|
||||||
|
"action_tools_present": false,
|
||||||
|
}
|
||||||
|
encoded, _ := json.Marshal(result)
|
||||||
|
fmt.Println(string(encoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fail(step string, err error) {
|
||||||
|
payload, _ := json.Marshal(map[string]any{"ok": false, "step": step, "error": err.Error()})
|
||||||
|
fmt.Fprintln(os.Stderr, string(payload))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
"@ | Set-Content -LiteralPath $harnessMain -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
$harnessJson = $null
|
||||||
|
Invoke-Step "run MCP SDK harness initialize/tools/list/tools/call" {
|
||||||
|
Push-Location $harnessDir
|
||||||
|
try {
|
||||||
|
$output = & go run .
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "go run SDK harness failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
$output | ForEach-Object { Write-Host $_ }
|
||||||
|
$lastLine = @($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) | Select-Object -Last 1
|
||||||
|
$script:harnessJson = $lastLine | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-True ($script:harnessJson.ok -eq $true) "SDK harness did not return ok=true"
|
||||||
|
Assert-True ($script:harnessJson.helper_name -eq "ISphereWinHelper") "SDK harness helper_name mismatch: $($script:harnessJson.helper_name)"
|
||||||
|
Assert-True ($script:harnessJson.tool_count -eq 4) "SDK harness tool_count mismatch: $($script:harnessJson.tool_count)"
|
||||||
|
|
||||||
|
[ordered]@{
|
||||||
|
ok = $true
|
||||||
|
verification = "go_mcp_sdk_harness"
|
||||||
|
go_mcp_binary_built = (Test-Path -LiteralPath $goMcpExe)
|
||||||
|
helper_name = $script:harnessJson.helper_name
|
||||||
|
tool_count = $script:harnessJson.tool_count
|
||||||
|
tools = $script:harnessJson.tools
|
||||||
|
python_runtime_required = $false
|
||||||
|
real_isphere_login_required = $false
|
||||||
|
action_tools_present = $false
|
||||||
|
} | ConvertTo-Json -Depth 8 -Compress
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Test-Path -LiteralPath $tempRoot) {
|
||||||
|
Remove-Item -LiteralPath $tempRoot -Recurse -Force
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $harnessDir) {
|
||||||
|
Remove-Item -LiteralPath $harnessDir -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user