899 lines
39 KiB
PowerShell
899 lines
39 KiB
PowerShell
$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",
|
|
"isphere_receive_messages",
|
|
"isphere_search_contacts",
|
|
"isphere_search_groups",
|
|
"isphere_receive_files",
|
|
"isphere_send_file",
|
|
"isphere_send_message"
|
|
)
|
|
|
|
$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"
|
|
"crypto/cipher"
|
|
"crypto/des"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"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",
|
|
"isphere_receive_messages",
|
|
"isphere_search_contacts",
|
|
"isphere_search_groups",
|
|
"isphere_receive_files",
|
|
"isphere_send_file",
|
|
"isphere_send_message",
|
|
}
|
|
|
|
var forbiddenTerms = []string{"login", "upload", "autologin"}
|
|
|
|
func main() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
|
|
for _, key := range []string{"ISPHERE_PACKET_LOG_FILE", "ISPHERE_PACKET_LOG_DIR", "ISPHERE_MSGLIB_SIDECAR_EXE", "ISPHERE_MSGLIB_SQLITE_DLL", "ISPHERE_MSGLIB_DB", "ISPHERE_MSGLIB_PASSWORD", "ISPHERE_SEND_FILE_ALLOWED_DIR"} {
|
|
if err := os.Unsetenv(key); err != nil {
|
|
fail("server/env", err)
|
|
}
|
|
}
|
|
sendStateDir, err := os.MkdirTemp("", "isphere-send-state-*")
|
|
if err != nil {
|
|
fail("server/send_state", err)
|
|
}
|
|
defer os.RemoveAll(sendStateDir)
|
|
if err := os.Setenv("ISPHERE_SEND_AUDIT_PATH", filepath.Join(sendStateDir, "send-preview.jsonl")); err != nil {
|
|
fail("server/send_state", err)
|
|
}
|
|
if err := os.Setenv("ISPHERE_SEND_IDEMPOTENCY_PATH", filepath.Join(sendStateDir, "send-idempotency.jsonl")); err != nil {
|
|
fail("server/send_state", err)
|
|
}
|
|
sendFileDir, err := os.MkdirTemp("", "isphere-send-file-*")
|
|
if err != nil {
|
|
fail("server/send_file_state", err)
|
|
}
|
|
defer os.RemoveAll(sendFileDir)
|
|
sendFilePath := filepath.Join(sendFileDir, "redacted-preview.txt")
|
|
if err := os.WriteFile(sendFilePath, []byte("hello"), 0o600); err != nil {
|
|
fail("server/send_file_state", err)
|
|
}
|
|
if err := os.Setenv("ISPHERE_SEND_FILE_ALLOWED_DIR", sendFileDir); err != nil {
|
|
fail("server/send_file_state", err)
|
|
}
|
|
|
|
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
|
server, err := mcpserver.NewServerFromEnv()
|
|
if err != nil {
|
|
fail("server/config", err)
|
|
}
|
|
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) != 10 || !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))
|
|
}
|
|
|
|
receiveResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{
|
|
"source_preference": "local_readonly",
|
|
"preview": true,
|
|
"cursor": "",
|
|
"include_attachment_metadata": true,
|
|
"limit": 5,
|
|
}})
|
|
if err != nil {
|
|
fail("tools/call isphere_receive_messages", err)
|
|
}
|
|
if receiveResult.IsError {
|
|
fail("tools/call isphere_receive_messages", fmt.Errorf("tool returned isError=true: %#v", receiveResult.Content))
|
|
}
|
|
var receivePayload map[string]any
|
|
receiveEncoded, _ := json.Marshal(receiveResult.StructuredContent)
|
|
if err := json.Unmarshal(receiveEncoded, &receivePayload); err != nil {
|
|
fail("tools/call isphere_receive_messages", fmt.Errorf("decode structuredContent %s: %w", receiveEncoded, err))
|
|
}
|
|
if receivePayload["ok"] != true {
|
|
fail("tools/call isphere_receive_messages", fmt.Errorf("ok = %#v, want true", receivePayload["ok"]))
|
|
}
|
|
messagesValue, ok := receivePayload["messages"].([]any)
|
|
if !ok {
|
|
fail("tools/call isphere_receive_messages", fmt.Errorf("messages missing or not array: %s", receiveEncoded))
|
|
}
|
|
if len(messagesValue) != 0 {
|
|
fail("tools/call isphere_receive_messages", fmt.Errorf("default empty source returned %d messages, want 0", len(messagesValue)))
|
|
}
|
|
defaultContactCount := verifySearchContacts(ctx, session, "sender", "tools/call isphere_search_contacts default", 0)
|
|
defaultGroupCount := verifySearchGroups(ctx, session, "project", "tools/call isphere_search_groups default", 0)
|
|
defaultFileCount := verifyReceiveFiles(ctx, session, "report", "tools/call isphere_receive_files default", 0, "", "")
|
|
sendPreviewOK, productionSendEnabled := verifySendMessagePreview(ctx, session)
|
|
sendFilePreviewOK, sendFileProductionEnabled := verifySendFilePreview(ctx, session, sendFilePath)
|
|
configuredReceiveCount, configuredContactCount, configuredGroupCount, configuredFileCount := verifyConfiguredReceiveSource(ctx)
|
|
configuredDirReceiveCount, configuredDirContactCount, configuredDirGroupCount, configuredDirFileCount := verifyConfiguredDirectorySource(ctx)
|
|
|
|
result := map[string]any{
|
|
"ok": true,
|
|
"transport": "sdk_in_memory",
|
|
"initialized": true,
|
|
"tool_count": len(toolNames),
|
|
"tools": toolNames,
|
|
"helper_name": helperName,
|
|
"receive_message_count": len(messagesValue),
|
|
"contact_count": defaultContactCount,
|
|
"group_count": defaultGroupCount,
|
|
"file_count": defaultFileCount,
|
|
"configured_receive_message_count": configuredReceiveCount,
|
|
"configured_contact_count": configuredContactCount,
|
|
"configured_group_count": configuredGroupCount,
|
|
"configured_file_count": configuredFileCount,
|
|
"configured_dir_receive_message_count": configuredDirReceiveCount,
|
|
"configured_dir_contact_count": configuredDirContactCount,
|
|
"configured_dir_group_count": configuredDirGroupCount,
|
|
"configured_dir_file_count": configuredDirFileCount,
|
|
"packet_log_file_configured": os.Getenv("ISPHERE_PACKET_LOG_FILE") != "",
|
|
"packet_log_dir_configured": os.Getenv("ISPHERE_PACKET_LOG_DIR") != "",
|
|
"real_isphere_login_required": false,
|
|
"python_runtime_required": false,
|
|
"action_tools_present": false,
|
|
"send_preview_tool_present": sendPreviewOK,
|
|
"production_send_enabled": productionSendEnabled,
|
|
"send_file_preview_tool_present": sendFilePreviewOK,
|
|
"send_file_production_enabled": sendFileProductionEnabled,
|
|
"msglib_configured": false,
|
|
}
|
|
encoded, _ := json.Marshal(result)
|
|
fmt.Println(string(encoded))
|
|
}
|
|
|
|
|
|
func verifyConfiguredReceiveSource(ctx context.Context) (int, int, int, int) {
|
|
plaintext := "--------------------------------------------------------------------------------------------------------------------------------------------\n" +
|
|
"2026/7/7 15:30:07\n" +
|
|
"<message id=\"msg-fixture-1\" from=\"project-room@conference.imopenfire1-lanzhou/imp_pc_4.1.2.6842\" to=\"sender@imopenfire1-lanzhou\" type=\"groupchat\">\n" +
|
|
" <body>redacted-report.docx</body>\n" +
|
|
" <received xmlns=\"urn:xmpp:receipts\" id=\"receipt-fixture-1\" type=\"1\" stamp=\"\" />\n" +
|
|
" <subject>FILE_TRANSFER_CANCEL</subject>\n" +
|
|
" <isphere xmlns=\"isphere.im\" type=\"1001\" sendtime=\"1783423807000\" version=\"1\" />\n" +
|
|
" <readed />\n" +
|
|
"</message>"
|
|
line, err := encryptPacketLogLineForHarness(plaintext)
|
|
if err != nil {
|
|
fail("fixture/encrypt", err)
|
|
}
|
|
file, err := os.CreateTemp("", "isphere-packet-log-*.txt")
|
|
if err != nil {
|
|
fail("fixture/create", err)
|
|
}
|
|
fixturePath := file.Name()
|
|
defer os.Remove(fixturePath)
|
|
if _, err := file.WriteString("\n" + line + "\n"); err != nil {
|
|
_ = file.Close()
|
|
fail("fixture/write", err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
fail("fixture/close", err)
|
|
}
|
|
|
|
oldValue, hadOldValue := os.LookupEnv("ISPHERE_PACKET_LOG_FILE")
|
|
if err := os.Setenv("ISPHERE_PACKET_LOG_FILE", fixturePath); err != nil {
|
|
fail("fixture/env", err)
|
|
}
|
|
defer func() {
|
|
if hadOldValue {
|
|
_ = os.Setenv("ISPHERE_PACKET_LOG_FILE", oldValue)
|
|
} else {
|
|
_ = os.Unsetenv("ISPHERE_PACKET_LOG_FILE")
|
|
}
|
|
}()
|
|
|
|
serverCtx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
|
server, err := mcpserver.NewServerFromEnv()
|
|
if err != nil {
|
|
fail("fixture/server_config", err)
|
|
}
|
|
serverErr := make(chan error, 1)
|
|
go func() {
|
|
serverErr <- server.Run(serverCtx, serverTransport)
|
|
}()
|
|
|
|
client := mcp.NewClient(&mcp.Implementation{Name: "verify-go-mcp-fixture", Version: "0.1.0"}, nil)
|
|
session, err := client.Connect(serverCtx, clientTransport, nil)
|
|
if err != nil {
|
|
cancel()
|
|
fail("fixture/connect", err)
|
|
}
|
|
defer func() {
|
|
_ = session.Close()
|
|
cancel()
|
|
select {
|
|
case <-serverErr:
|
|
case <-time.After(2 * time.Second):
|
|
fail("fixture/server_shutdown", fmt.Errorf("server did not stop"))
|
|
}
|
|
}()
|
|
|
|
callResult, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{
|
|
"conversation_id": "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou",
|
|
"query": "report",
|
|
"since": "2026-07-07T11:30:00Z",
|
|
"source_preference": "auto",
|
|
"preview": true,
|
|
"cursor": "",
|
|
"include_attachment_metadata": true,
|
|
"limit": 5,
|
|
}})
|
|
if err != nil {
|
|
fail("fixture/call isphere_receive_messages", err)
|
|
}
|
|
if callResult.IsError {
|
|
fail("fixture/call isphere_receive_messages", fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var receivePayload map[string]any
|
|
receiveEncoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(receiveEncoded, &receivePayload); err != nil {
|
|
fail("fixture/decode", fmt.Errorf("decode structuredContent %s: %w", receiveEncoded, err))
|
|
}
|
|
messagesValue, ok := receivePayload["messages"].([]any)
|
|
if !ok {
|
|
fail("fixture/decode", fmt.Errorf("messages missing or not array: %s", receiveEncoded))
|
|
}
|
|
if len(messagesValue) != 1 {
|
|
fail("fixture/assert", fmt.Errorf("configured source returned %d messages, want 1", len(messagesValue)))
|
|
}
|
|
message, ok := messagesValue[0].(map[string]any)
|
|
if !ok {
|
|
fail("fixture/assert", fmt.Errorf("message was not object: %#v", messagesValue[0]))
|
|
}
|
|
if message["id"] != "msg-fixture-1" || message["text"] != "redacted-report.docx" || message["subject"] != "FILE_TRANSFER_CANCEL" {
|
|
fail("fixture/assert", fmt.Errorf("unexpected message: %#v", message))
|
|
}
|
|
if message["message_id"] != "msg-fixture-1" || message["content_text"] != "redacted-report.docx" || message["content_type"] != "file" || message["source"] != "local_readonly" || message["raw_ref"] != "packetlog_message" {
|
|
fail("fixture/assert_contract", fmt.Errorf("unexpected contract message fields: %#v", message))
|
|
}
|
|
attachments, ok := message["attachments"].([]any)
|
|
if !ok || len(attachments) != 1 {
|
|
fail("fixture/assert_contract", fmt.Errorf("attachments = %#v, want one", message["attachments"]))
|
|
}
|
|
attachment, ok := attachments[0].(map[string]any)
|
|
if !ok || attachment["file_id"] != "msg-fixture-1:redacted-report.docx" || attachment["file_name"] != "redacted-report.docx" || attachment["download_ref"] != nil {
|
|
fail("fixture/assert_contract", fmt.Errorf("unexpected attachment: %#v", attachments[0]))
|
|
}
|
|
if message["conversation_id"] != "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou" {
|
|
fail("fixture/assert", fmt.Errorf("conversation_id = %#v", message["conversation_id"]))
|
|
}
|
|
contactCount := verifySearchContacts(ctx, session, "sender", "fixture/call isphere_search_contacts", 1)
|
|
groupCount := verifySearchGroups(ctx, session, "project", "fixture/call isphere_search_groups", 1)
|
|
fileCount := verifyReceiveFiles(ctx, session, "report", "fixture/call isphere_receive_files", 1, "msg-fixture-1:redacted-report.docx", "redacted-report.docx")
|
|
return len(messagesValue), contactCount, groupCount, fileCount
|
|
}
|
|
|
|
|
|
func verifyConfiguredDirectorySource(ctx context.Context) (int, int, int, int) {
|
|
plaintext := "--------------------------------------------------------------------------------------------------------------------------------------------\n" +
|
|
"2026/7/7 15:30:07\n" +
|
|
"<message id=\"msg-dir-fixture-1\" from=\"project-room@conference.imopenfire1-lanzhou/imp_pc_4.1.2.6842\" to=\"sender@imopenfire1-lanzhou\" type=\"groupchat\">\n" +
|
|
" <body>redacted-dir-report.docx</body>\n" +
|
|
" <received xmlns=\"urn:xmpp:receipts\" id=\"receipt-dir-fixture-1\" type=\"1\" stamp=\"\" />\n" +
|
|
" <subject>FILE_TRANSFER_CANCEL</subject>\n" +
|
|
" <isphere xmlns=\"isphere.im\" type=\"1001\" sendtime=\"1783423807000\" version=\"1\" />\n" +
|
|
" <readed />\n" +
|
|
"</message>"
|
|
line, err := encryptPacketLogLineForHarness(plaintext)
|
|
if err != nil {
|
|
fail("dir_fixture/encrypt", err)
|
|
}
|
|
dir, err := os.MkdirTemp("", "isphere-packet-log-dir-*")
|
|
if err != nil {
|
|
fail("dir_fixture/create", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
if err := os.WriteFile(filepath.Join(dir, "b-ignored.tmp"), []byte("ignored\n"), 0o600); err != nil {
|
|
fail("dir_fixture/write_ignored", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "a-packet-reader.log"), []byte("\n"+line+"\n"), 0o600); err != nil {
|
|
fail("dir_fixture/write", err)
|
|
}
|
|
|
|
oldFileValue, hadOldFileValue := os.LookupEnv("ISPHERE_PACKET_LOG_FILE")
|
|
oldDirValue, hadOldDirValue := os.LookupEnv("ISPHERE_PACKET_LOG_DIR")
|
|
_ = os.Unsetenv("ISPHERE_PACKET_LOG_FILE")
|
|
if err := os.Setenv("ISPHERE_PACKET_LOG_DIR", dir); err != nil {
|
|
fail("dir_fixture/env", err)
|
|
}
|
|
defer func() {
|
|
if hadOldFileValue {
|
|
_ = os.Setenv("ISPHERE_PACKET_LOG_FILE", oldFileValue)
|
|
} else {
|
|
_ = os.Unsetenv("ISPHERE_PACKET_LOG_FILE")
|
|
}
|
|
if hadOldDirValue {
|
|
_ = os.Setenv("ISPHERE_PACKET_LOG_DIR", oldDirValue)
|
|
} else {
|
|
_ = os.Unsetenv("ISPHERE_PACKET_LOG_DIR")
|
|
}
|
|
}()
|
|
|
|
serverCtx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
|
server, err := mcpserver.NewServerFromEnv()
|
|
if err != nil {
|
|
fail("dir_fixture/server_config", err)
|
|
}
|
|
serverErr := make(chan error, 1)
|
|
go func() {
|
|
serverErr <- server.Run(serverCtx, serverTransport)
|
|
}()
|
|
|
|
client := mcp.NewClient(&mcp.Implementation{Name: "verify-go-mcp-dir-fixture", Version: "0.1.0"}, nil)
|
|
session, err := client.Connect(serverCtx, clientTransport, nil)
|
|
if err != nil {
|
|
cancel()
|
|
fail("dir_fixture/connect", err)
|
|
}
|
|
defer func() {
|
|
_ = session.Close()
|
|
cancel()
|
|
select {
|
|
case <-serverErr:
|
|
case <-time.After(2 * time.Second):
|
|
fail("dir_fixture/server_shutdown", fmt.Errorf("server did not stop"))
|
|
}
|
|
}()
|
|
|
|
callResult, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{
|
|
"conversation_id": "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou",
|
|
"query": "dir-report",
|
|
"since": "2026-07-07T11:30:00Z",
|
|
"source_preference": "auto",
|
|
"preview": true,
|
|
"cursor": "",
|
|
"include_attachment_metadata": true,
|
|
"limit": 5,
|
|
}})
|
|
if err != nil {
|
|
fail("dir_fixture/call isphere_receive_messages", err)
|
|
}
|
|
if callResult.IsError {
|
|
fail("dir_fixture/call isphere_receive_messages", fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var receivePayload map[string]any
|
|
receiveEncoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(receiveEncoded, &receivePayload); err != nil {
|
|
fail("dir_fixture/decode", fmt.Errorf("decode structuredContent %s: %w", receiveEncoded, err))
|
|
}
|
|
messagesValue, ok := receivePayload["messages"].([]any)
|
|
if !ok || len(messagesValue) != 1 {
|
|
fail("dir_fixture/assert", fmt.Errorf("messages = %#v, want one", receivePayload["messages"]))
|
|
}
|
|
message, ok := messagesValue[0].(map[string]any)
|
|
if !ok {
|
|
fail("dir_fixture/assert", fmt.Errorf("message was not object: %#v", messagesValue[0]))
|
|
}
|
|
if message["id"] != "msg-dir-fixture-1" || message["text"] != "redacted-dir-report.docx" || message["subject"] != "FILE_TRANSFER_CANCEL" {
|
|
fail("dir_fixture/assert", fmt.Errorf("unexpected message: %#v", message))
|
|
}
|
|
if message["message_id"] != "msg-dir-fixture-1" || message["content_text"] != "redacted-dir-report.docx" || message["content_type"] != "file" || message["source"] != "local_readonly" || message["raw_ref"] != "packetlog_message" {
|
|
fail("dir_fixture/assert_contract", fmt.Errorf("unexpected contract message fields: %#v", message))
|
|
}
|
|
attachments, ok := message["attachments"].([]any)
|
|
if !ok || len(attachments) != 1 {
|
|
fail("dir_fixture/assert_contract", fmt.Errorf("attachments = %#v, want one", message["attachments"]))
|
|
}
|
|
attachment, ok := attachments[0].(map[string]any)
|
|
if !ok || attachment["file_id"] != "msg-dir-fixture-1:redacted-dir-report.docx" || attachment["file_name"] != "redacted-dir-report.docx" || attachment["download_ref"] != nil {
|
|
fail("dir_fixture/assert_contract", fmt.Errorf("unexpected attachment: %#v", attachments[0]))
|
|
}
|
|
contactCount := verifySearchContacts(ctx, session, "sender", "dir_fixture/call isphere_search_contacts", 1)
|
|
groupCount := verifySearchGroups(ctx, session, "project", "dir_fixture/call isphere_search_groups", 1)
|
|
fileCount := verifyReceiveFiles(ctx, session, "dir-report", "dir_fixture/call isphere_receive_files", 1, "msg-dir-fixture-1:redacted-dir-report.docx", "redacted-dir-report.docx")
|
|
return len(messagesValue), contactCount, groupCount, fileCount
|
|
}
|
|
|
|
func verifySendMessagePreview(ctx context.Context, session *mcp.ClientSession) (bool, bool) {
|
|
content := "redacted preview body"
|
|
contentHash := sha256HexForHarness(content)
|
|
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_send_message", Arguments: map[string]any{
|
|
"target_type": "direct",
|
|
"target_id": "sender@imopenfire1-lanzhou",
|
|
"content_text": content,
|
|
"content_sha256": contentHash,
|
|
"idempotency_key": "verify-preview-idempotency-key",
|
|
"execution_mode": "preview",
|
|
}})
|
|
if err != nil {
|
|
fail("tools/call isphere_send_message preview", err)
|
|
}
|
|
if callResult.IsError {
|
|
fail("tools/call isphere_send_message preview", fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var previewPayload map[string]any
|
|
encoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(encoded, &previewPayload); err != nil {
|
|
fail("tools/call isphere_send_message preview", fmt.Errorf("decode structuredContent %s: %w", encoded, err))
|
|
}
|
|
if previewPayload["ok"] != true || previewPayload["send_status"] != "planned" || previewPayload["connector"] != "implatform-sidecar" || previewPayload["connector_stage"] != "preview" {
|
|
fail("tools/call isphere_send_message preview", fmt.Errorf("unexpected preview status: %s", encoded))
|
|
}
|
|
contentValue, ok := previewPayload["content"].(map[string]any)
|
|
if !ok || contentValue["content_sha256"] != contentHash {
|
|
fail("tools/call isphere_send_message preview", fmt.Errorf("unexpected content metadata: %s", encoded))
|
|
}
|
|
if _, exists := contentValue["content_text"]; exists {
|
|
fail("tools/call isphere_send_message preview", fmt.Errorf("preview leaked raw content: %s", encoded))
|
|
}
|
|
sideEffects, ok := previewPayload["side_effects"].(map[string]any)
|
|
if !ok || sideEffects["sent_message"] != false || sideEffects["clicked_ui"] != false || sideEffects["captured_network"] != false {
|
|
fail("tools/call isphere_send_message preview", fmt.Errorf("unexpected side effects: %s", encoded))
|
|
}
|
|
|
|
productionResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_send_message", Arguments: map[string]any{
|
|
"target_type": "direct",
|
|
"target_id": "sender@imopenfire1-lanzhou",
|
|
"content_text": content,
|
|
"content_sha256": contentHash,
|
|
"idempotency_key": "verify-production-idempotency-key",
|
|
"execution_mode": "production",
|
|
}})
|
|
if err != nil {
|
|
fail("tools/call isphere_send_message production_blocked", err)
|
|
}
|
|
if productionResult.IsError {
|
|
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("tool returned isError=true: %#v", productionResult.Content))
|
|
}
|
|
var productionPayload map[string]any
|
|
productionEncoded, _ := json.Marshal(productionResult.StructuredContent)
|
|
if err := json.Unmarshal(productionEncoded, &productionPayload); err != nil {
|
|
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("decode structuredContent %s: %w", productionEncoded, err))
|
|
}
|
|
if productionPayload["ok"] != false || productionPayload["send_status"] != "blocked" || productionPayload["connector_stage"] != "blocked" {
|
|
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("production was not blocked: %s", productionEncoded))
|
|
}
|
|
productionSideEffects, ok := productionPayload["side_effects"].(map[string]any)
|
|
if !ok || productionSideEffects["sent_message"] != false || productionSideEffects["typed_text"] != false || productionSideEffects["uploaded_file"] != false {
|
|
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("unexpected production side effects: %s", productionEncoded))
|
|
}
|
|
return true, false
|
|
}
|
|
|
|
func verifySendFilePreview(ctx context.Context, session *mcp.ClientSession, filePath string) (bool, bool) {
|
|
fileHash := sha256FileHexForHarness(filePath)
|
|
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_send_file", Arguments: map[string]any{
|
|
"target_type": "direct",
|
|
"target_id": "sender@imopenfire1-lanzhou",
|
|
"file_path": filePath,
|
|
"file_sha256": fileHash,
|
|
"idempotency_key": "verify-file-preview-idempotency-key",
|
|
"execution_mode": "preview",
|
|
"preview": true,
|
|
}})
|
|
if err != nil {
|
|
fail("tools/call isphere_send_file preview", err)
|
|
}
|
|
if callResult.IsError {
|
|
fail("tools/call isphere_send_file preview", fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var previewPayload map[string]any
|
|
encoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(encoded, &previewPayload); err != nil {
|
|
fail("tools/call isphere_send_file preview", fmt.Errorf("decode structuredContent %s: %w", encoded, err))
|
|
}
|
|
if previewPayload["ok"] != true || previewPayload["send_status"] != "planned" || previewPayload["file_sha256"] != fileHash || previewPayload["file_size_bytes"] != float64(5) {
|
|
fail("tools/call isphere_send_file preview", fmt.Errorf("unexpected send-file preview status: %s", encoded))
|
|
}
|
|
if previewPayload["production_send_enabled"] != false || previewPayload["real_send_attempted"] != false {
|
|
fail("tools/call isphere_send_file preview", fmt.Errorf("send-file preview should not enable or attempt send: %s", encoded))
|
|
}
|
|
sideEffects, ok := previewPayload["side_effect_flags"].(map[string]any)
|
|
if !ok || sideEffects["sent_file"] != false || sideEffects["uploaded_file"] != false || sideEffects["clicked_ui"] != false {
|
|
fail("tools/call isphere_send_file preview", fmt.Errorf("unexpected send-file side effects: %s", encoded))
|
|
}
|
|
|
|
productionResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_send_file", Arguments: map[string]any{
|
|
"target_type": "direct",
|
|
"target_id": "sender@imopenfire1-lanzhou",
|
|
"file_path": filePath,
|
|
"file_sha256": fileHash,
|
|
"idempotency_key": "verify-file-production-idempotency-key",
|
|
"execution_mode": "production",
|
|
}})
|
|
if err != nil {
|
|
fail("tools/call isphere_send_file production_blocked", err)
|
|
}
|
|
if productionResult.IsError {
|
|
fail("tools/call isphere_send_file production_blocked", fmt.Errorf("tool returned isError=true: %#v", productionResult.Content))
|
|
}
|
|
var productionPayload map[string]any
|
|
productionEncoded, _ := json.Marshal(productionResult.StructuredContent)
|
|
if err := json.Unmarshal(productionEncoded, &productionPayload); err != nil {
|
|
fail("tools/call isphere_send_file production_blocked", fmt.Errorf("decode structuredContent %s: %w", productionEncoded, err))
|
|
}
|
|
if productionPayload["ok"] != false || productionPayload["send_status"] != "blocked" || productionPayload["production_send_enabled"] != false || productionPayload["real_send_attempted"] != false {
|
|
fail("tools/call isphere_send_file production_blocked", fmt.Errorf("production file send was not blocked: %s", productionEncoded))
|
|
}
|
|
productionSideEffects, ok := productionPayload["side_effect_flags"].(map[string]any)
|
|
if !ok || productionSideEffects["sent_file"] != false || productionSideEffects["uploaded_file"] != false || productionSideEffects["captured_network"] != false {
|
|
fail("tools/call isphere_send_file production_blocked", fmt.Errorf("unexpected production file side effects: %s", productionEncoded))
|
|
}
|
|
return true, false
|
|
}
|
|
|
|
func verifySearchContacts(ctx context.Context, session *mcp.ClientSession, query string, step string, wantCount int) int {
|
|
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_search_contacts", Arguments: map[string]any{
|
|
"query": query,
|
|
"source_preference": "local_readonly",
|
|
"include_inactive": true,
|
|
"cursor": "",
|
|
"limit": 5,
|
|
}})
|
|
if err != nil {
|
|
fail(step, err)
|
|
}
|
|
if callResult.IsError {
|
|
fail(step, fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var contactsPayload map[string]any
|
|
encoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(encoded, &contactsPayload); err != nil {
|
|
fail(step, fmt.Errorf("decode structuredContent %s: %w", encoded, err))
|
|
}
|
|
if contactsPayload["ok"] != true {
|
|
fail(step, fmt.Errorf("ok = %#v, want true", contactsPayload["ok"]))
|
|
}
|
|
contactsValue, ok := contactsPayload["contacts"].([]any)
|
|
if !ok {
|
|
fail(step, fmt.Errorf("contacts missing or not array: %s", encoded))
|
|
}
|
|
if len(contactsValue) != wantCount {
|
|
fail(step, fmt.Errorf("contacts count = %d, want %d: %s", len(contactsValue), wantCount, encoded))
|
|
}
|
|
if wantCount > 0 {
|
|
contact, ok := contactsValue[0].(map[string]any)
|
|
if !ok {
|
|
fail(step, fmt.Errorf("contact was not object: %#v", contactsValue[0]))
|
|
}
|
|
if contact["contact_id"] != "sender@imopenfire1-lanzhou" || contact["source"] != "local_readonly" {
|
|
fail(step, fmt.Errorf("unexpected contact: %#v", contact))
|
|
}
|
|
}
|
|
return len(contactsValue)
|
|
}
|
|
|
|
|
|
func verifySearchGroups(ctx context.Context, session *mcp.ClientSession, query string, step string, wantCount int) int {
|
|
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_search_groups", Arguments: map[string]any{
|
|
"query": query,
|
|
"source_preference": "local_readonly",
|
|
"include_archived": true,
|
|
"cursor": "",
|
|
"limit": 5,
|
|
}})
|
|
if err != nil {
|
|
fail(step, err)
|
|
}
|
|
if callResult.IsError {
|
|
fail(step, fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var groupsPayload map[string]any
|
|
encoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(encoded, &groupsPayload); err != nil {
|
|
fail(step, fmt.Errorf("decode structuredContent %s: %w", encoded, err))
|
|
}
|
|
if groupsPayload["ok"] != true {
|
|
fail(step, fmt.Errorf("ok = %#v, want true", groupsPayload["ok"]))
|
|
}
|
|
groupsValue, ok := groupsPayload["groups"].([]any)
|
|
if !ok {
|
|
fail(step, fmt.Errorf("groups missing or not array: %s", encoded))
|
|
}
|
|
if len(groupsValue) != wantCount {
|
|
fail(step, fmt.Errorf("groups count = %d, want %d: %s", len(groupsValue), wantCount, encoded))
|
|
}
|
|
if wantCount > 0 {
|
|
group, ok := groupsValue[0].(map[string]any)
|
|
if !ok {
|
|
fail(step, fmt.Errorf("group was not object: %#v", groupsValue[0]))
|
|
}
|
|
if group["group_id"] != "project-room@conference.imopenfire1-lanzhou" || group["source"] != "local_readonly" {
|
|
fail(step, fmt.Errorf("unexpected group: %#v", group))
|
|
}
|
|
}
|
|
return len(groupsValue)
|
|
}
|
|
|
|
|
|
func verifyReceiveFiles(ctx context.Context, session *mcp.ClientSession, nameContains string, step string, wantCount int, wantFileID string, wantFileName string) int {
|
|
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_receive_files", Arguments: map[string]any{
|
|
"mode": "list",
|
|
"name_contains": nameContains,
|
|
"source_preference": "local_readonly",
|
|
"preview": true,
|
|
"cursor": "",
|
|
"output_dir": "",
|
|
"limit": 5,
|
|
}})
|
|
if err != nil {
|
|
fail(step, err)
|
|
}
|
|
if callResult.IsError {
|
|
fail(step, fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
|
|
}
|
|
var filesPayload map[string]any
|
|
encoded, _ := json.Marshal(callResult.StructuredContent)
|
|
if err := json.Unmarshal(encoded, &filesPayload); err != nil {
|
|
fail(step, fmt.Errorf("decode structuredContent %s: %w", encoded, err))
|
|
}
|
|
if filesPayload["ok"] != true || filesPayload["mode"] != "list" {
|
|
fail(step, fmt.Errorf("ok/mode mismatch: %s", encoded))
|
|
}
|
|
filesValue, ok := filesPayload["files"].([]any)
|
|
if !ok {
|
|
fail(step, fmt.Errorf("files missing or not array: %s", encoded))
|
|
}
|
|
if len(filesValue) != wantCount {
|
|
fail(step, fmt.Errorf("files count = %d, want %d: %s", len(filesValue), wantCount, encoded))
|
|
}
|
|
if wantCount > 0 {
|
|
file, ok := filesValue[0].(map[string]any)
|
|
if !ok {
|
|
fail(step, fmt.Errorf("file was not object: %#v", filesValue[0]))
|
|
}
|
|
if file["file_id"] != wantFileID || file["file_name"] != wantFileName || file["source"] != "local_readonly" {
|
|
fail(step, fmt.Errorf("unexpected file: %#v", file))
|
|
}
|
|
if file["download_ref"] != nil || file["saved_path"] != nil || file["sha256"] != nil {
|
|
fail(step, fmt.Errorf("download/cache fields should be nil: %#v", file))
|
|
}
|
|
}
|
|
return len(filesValue)
|
|
}
|
|
|
|
func encryptPacketLogLineForHarness(plaintext string) (string, error) {
|
|
block, err := des.NewCipher([]byte("hyhccdtm"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
plain := padPKCS7ForHarness([]byte(plaintext), des.BlockSize)
|
|
ciphertext := make([]byte, len(plain))
|
|
iv := []byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}
|
|
cipher.NewCBCEncrypter(block, iv).CryptBlocks(ciphertext, plain)
|
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
|
}
|
|
|
|
func sha256HexForHarness(value string) string {
|
|
sum := sha256.Sum256([]byte(value))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func sha256FileHexForHarness(path string) string {
|
|
payload, err := os.ReadFile(path)
|
|
if err != nil {
|
|
fail("fixture/file_hash", err)
|
|
}
|
|
return sha256HexForHarness(string(payload))
|
|
}
|
|
|
|
func padPKCS7ForHarness(data []byte, blockSize int) []byte {
|
|
pad := blockSize - len(data)%blockSize
|
|
out := append([]byte(nil), data...)
|
|
for i := 0; i < pad; i++ {
|
|
out = append(out, byte(pad))
|
|
}
|
|
return out
|
|
}
|
|
|
|
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 10) "SDK harness tool_count mismatch: $($script:harnessJson.tool_count)"
|
|
Assert-True ($script:harnessJson.receive_message_count -eq 0) "SDK harness receive_message_count mismatch: $($script:harnessJson.receive_message_count)"
|
|
Assert-True ($script:harnessJson.contact_count -eq 0) "SDK harness contact_count mismatch: $($script:harnessJson.contact_count)"
|
|
Assert-True ($script:harnessJson.group_count -eq 0) "SDK harness group_count mismatch: $($script:harnessJson.group_count)"
|
|
Assert-True ($script:harnessJson.file_count -eq 0) "SDK harness file_count mismatch: $($script:harnessJson.file_count)"
|
|
Assert-True ($script:harnessJson.configured_receive_message_count -eq 1) "SDK harness configured_receive_message_count mismatch: $($script:harnessJson.configured_receive_message_count)"
|
|
Assert-True ($script:harnessJson.configured_contact_count -eq 1) "SDK harness configured_contact_count mismatch: $($script:harnessJson.configured_contact_count)"
|
|
Assert-True ($script:harnessJson.configured_group_count -eq 1) "SDK harness configured_group_count mismatch: $($script:harnessJson.configured_group_count)"
|
|
Assert-True ($script:harnessJson.configured_file_count -eq 1) "SDK harness configured_file_count mismatch: $($script:harnessJson.configured_file_count)"
|
|
Assert-True ($script:harnessJson.configured_dir_receive_message_count -eq 1) "SDK harness configured_dir_receive_message_count mismatch: $($script:harnessJson.configured_dir_receive_message_count)"
|
|
Assert-True ($script:harnessJson.configured_dir_contact_count -eq 1) "SDK harness configured_dir_contact_count mismatch: $($script:harnessJson.configured_dir_contact_count)"
|
|
Assert-True ($script:harnessJson.configured_dir_group_count -eq 1) "SDK harness configured_dir_group_count mismatch: $($script:harnessJson.configured_dir_group_count)"
|
|
Assert-True ($script:harnessJson.configured_dir_file_count -eq 1) "SDK harness configured_dir_file_count mismatch: $($script:harnessJson.configured_dir_file_count)"
|
|
Assert-True ($script:harnessJson.send_preview_tool_present -eq $true) "SDK harness send_preview_tool_present mismatch: $($script:harnessJson.send_preview_tool_present)"
|
|
Assert-True ($script:harnessJson.production_send_enabled -eq $false) "SDK harness production_send_enabled mismatch: $($script:harnessJson.production_send_enabled)"
|
|
Assert-True ($script:harnessJson.send_file_preview_tool_present -eq $true) "SDK harness send_file_preview_tool_present mismatch: $($script:harnessJson.send_file_preview_tool_present)"
|
|
Assert-True ($script:harnessJson.send_file_production_enabled -eq $false) "SDK harness send_file_production_enabled mismatch: $($script:harnessJson.send_file_production_enabled)"
|
|
|
|
[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
|
|
receive_message_count = $script:harnessJson.receive_message_count
|
|
contact_count = $script:harnessJson.contact_count
|
|
group_count = $script:harnessJson.group_count
|
|
file_count = $script:harnessJson.file_count
|
|
configured_receive_message_count = $script:harnessJson.configured_receive_message_count
|
|
configured_contact_count = $script:harnessJson.configured_contact_count
|
|
configured_group_count = $script:harnessJson.configured_group_count
|
|
configured_file_count = $script:harnessJson.configured_file_count
|
|
configured_dir_receive_message_count = $script:harnessJson.configured_dir_receive_message_count
|
|
configured_dir_contact_count = $script:harnessJson.configured_dir_contact_count
|
|
configured_dir_group_count = $script:harnessJson.configured_dir_group_count
|
|
configured_dir_file_count = $script:harnessJson.configured_dir_file_count
|
|
send_preview_tool_present = $script:harnessJson.send_preview_tool_present
|
|
production_send_enabled = $script:harnessJson.production_send_enabled
|
|
send_file_preview_tool_present = $script:harnessJson.send_file_preview_tool_present
|
|
send_file_production_enabled = $script:harnessJson.send_file_production_enabled
|
|
packet_log_file_configured = $script:harnessJson.packet_log_file_configured
|
|
packet_log_dir_configured = $script:harnessJson.packet_log_dir_configured
|
|
python_runtime_required = $false
|
|
real_isphere_login_required = $false
|
|
action_tools_present = $false
|
|
msglib_configured = $script:harnessJson.msglib_configured
|
|
} | 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
|
|
}
|
|
}
|