param( [string]$SidecarExe = "runs/msglib-sidecar/MsgLibReadSidecar.exe", [string]$SQLiteDllPath = $env:ISPHERE_MSGLIB_SQLITE_DLL, [string]$DbPath = $env:ISPHERE_MSGLIB_DB, [switch]$SkipBuild ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path $sidecarPath = Join-Path $repo $SidecarExe $harnessDir = Join-Path $repo ("runs\msglib-mcp-enrichment-harness-" + [guid]::NewGuid().ToString("N")) $harnessMain = Join-Path $harnessDir "main.go" function Assert-True([bool]$Condition, [string]$Message) { if (-not $Condition) { throw $Message } } if (-not $SQLiteDllPath -or -not (Test-Path -LiteralPath $SQLiteDllPath)) { throw "SQLite DLL not found; set ISPHERE_MSGLIB_SQLITE_DLL or pass -SQLiteDllPath" } if (-not $DbPath -or -not (Test-Path -LiteralPath $DbPath)) { throw "MsgLib DB not found; set ISPHERE_MSGLIB_DB or pass -DbPath" } try { Push-Location $repo try { if (-not $SkipBuild) { $buildOutput = & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repo "scripts\build-msglib-sidecar.ps1") 2>&1 if ($LASTEXITCODE -ne 0) { throw "build-msglib-sidecar.ps1 failed with exit code $LASTEXITCODE" } } Assert-True (Test-Path -LiteralPath $sidecarPath) "sidecar not found: $sidecarPath" New-Item -ItemType Directory -Force -Path $harnessDir | Out-Null @' package main import ( "context" "crypto/cipher" "crypto/des" "encoding/base64" "encoding/json" "fmt" "os" "sort" "strings" "time" "github.com/modelcontextprotocol/go-sdk/mcp" "isphere-ai-bridge/internal/mcpserver" ) type summary struct { Count int `json:"count"` Sources []string `json:"sources"` FirstID string FirstDisplayName string } type receiveDisplaySummary struct { MessageCount int SenderNamePopulated bool ConversationDisplayPopulated bool } type msgLibReceiveSummary struct { MessageCount int Sources []string BodyValuesPresent bool } func main() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() 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-msglib-mcp-enrichment", Version: "0.1.0"}, nil) session, err := client.Connect(ctx, clientTransport, nil) if err != nil { fail("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")) } }() contacts := callSearch(ctx, session, "isphere_search_contacts", "contacts") groups := callSearch(ctx, session, "isphere_search_groups", "groups") if contacts.Count == 0 { fail("contacts", fmt.Errorf("no msglib_readonly contacts returned")) } if groups.Count == 0 { fail("groups", fmt.Errorf("no msglib_readonly groups returned")) } if contacts.FirstID == "" || contacts.FirstDisplayName == "" { fail("contacts", fmt.Errorf("contact display fixture unavailable")) } if groups.FirstID == "" || groups.FirstDisplayName == "" { fail("groups", fmt.Errorf("group display fixture unavailable")) } dbReceive := verifyMsgLibReceive(ctx, session) if dbReceive.MessageCount == 0 { fail("msglib_receive", fmt.Errorf("explicit msglib_readonly receive returned no messages")) } receiveDisplay := verifyReceiveDisplay(ctx, contacts, groups) if receiveDisplay.MessageCount == 0 { fail("receive_display", fmt.Errorf("receive message fixture returned no messages")) } if !receiveDisplay.SenderNamePopulated { fail("receive_display", fmt.Errorf("sender display was not populated")) } if !receiveDisplay.ConversationDisplayPopulated { fail("receive_display", fmt.Errorf("conversation display was not populated")) } out := map[string]any{ "ok": true, "verification": "msglib_mcp_enrichment", "contact_count": contacts.Count, "contact_sources": contacts.Sources, "group_count": groups.Count, "group_sources": groups.Sources, "db_receive_smoke": true, "db_receive_message_count": dbReceive.MessageCount, "db_receive_sources": dbReceive.Sources, "db_receive_body_values_present": dbReceive.BodyValuesPresent, "receive_display_smoke": true, "receive_message_count": receiveDisplay.MessageCount, "receive_sender_name_populated": receiveDisplay.SenderNamePopulated, "receive_conversation_display_populated": receiveDisplay.ConversationDisplayPopulated, "printed_entity_values": false, "message_body_values_printed": false, "message_bodies_returned": false, "file_paths_returned": false, "raw_rows_returned": false, } encoded, _ := json.Marshal(out) fmt.Println(string(encoded)) } func callSearch(ctx context.Context, session *mcp.ClientSession, tool string, field string) summary { result, err := session.CallTool(ctx, &mcp.CallToolParams{Name: tool, Arguments: map[string]any{ "query": "", "source_preference": "auto", "cursor": "", "limit": 5, }}) if err != nil { fail(tool, err) } if result.IsError { fail(tool, fmt.Errorf("tool returned isError=true")) } payload, _ := json.Marshal(result.StructuredContent) var decoded map[string]any if err := json.Unmarshal(payload, &decoded); err != nil { fail(tool+"_decode", err) } values, ok := decoded[field].([]any) if !ok { fail(tool+"_shape", fmt.Errorf("result field missing or not array")) } idKey := "contact_id" if field == "groups" { idKey = "group_id" } sourceSet := map[string]bool{} count := 0 out := summary{} for _, value := range values { item, ok := value.(map[string]any) if !ok { continue } source, _ := item["source"].(string) if source != "msglib_readonly" { continue } rawRef, _ := item["raw_ref"].(string) if rawRef != "" { sourceSet[rawRef] = true } id, _ := item[idKey].(string) displayName, _ := item["display_name"].(string) if out.FirstID == "" && strings.TrimSpace(id) != "" && strings.TrimSpace(displayName) != "" { out.FirstID = strings.TrimSpace(id) out.FirstDisplayName = strings.TrimSpace(displayName) } count++ } sources := make([]string, 0, len(sourceSet)) for source := range sourceSet { sources = append(sources, source) } sort.Strings(sources) out.Count = count out.Sources = sources return out } func verifyMsgLibReceive(ctx context.Context, session *mcp.ClientSession) msgLibReceiveSummary { result, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{ "source_preference": "msglib_readonly", "preview": true, "cursor": "", "include_attachment_metadata": false, "limit": 5, }}) if err != nil { fail("msglib_receive_call", err) } if result.IsError { fail("msglib_receive_call", fmt.Errorf("tool returned isError=true")) } payload, _ := json.Marshal(result.StructuredContent) var decoded map[string]any if err := json.Unmarshal(payload, &decoded); err != nil { fail("msglib_receive_decode", err) } messages, ok := decoded["messages"].([]any) if !ok { fail("msglib_receive_shape", fmt.Errorf("messages missing or not array")) } sourceSet := map[string]bool{} out := msgLibReceiveSummary{MessageCount: len(messages)} for _, value := range messages { message, ok := value.(map[string]any) if !ok { continue } rawRef, _ := message["raw_ref"].(string) if strings.HasPrefix(rawRef, "msglib:") { sourceSet[rawRef] = true } text, _ := message["text"].(string) contentText, _ := message["content_text"].(string) if strings.TrimSpace(text) != "" || strings.TrimSpace(contentText) != "" { out.BodyValuesPresent = true } } sources := make([]string, 0, len(sourceSet)) for source := range sourceSet { sources = append(sources, source) } sort.Strings(sources) out.Sources = sources return out } func verifyReceiveDisplay(ctx context.Context, contact summary, group summary) receiveDisplaySummary { groupPlaintext := packetFixturePlaintext("c33-group-display-fixture", group.FirstID+"/imp_pc_4.1.2.6842", contact.FirstID, "groupchat", "redacted-group-body") directPlaintext := packetFixturePlaintext("c33-contact-display-fixture", contact.FirstID+"/imp_pc_4.1.2.6842", "receiver-redacted@imopenfire1-lanzhou", "chat", "redacted-contact-body") groupLine, err := encryptPacketLogLineForHarness(groupPlaintext) if err != nil { fail("receive_display_encrypt", err) } directLine, err := encryptPacketLogLineForHarness(directPlaintext) if err != nil { fail("receive_display_encrypt", err) } file, err := os.CreateTemp("", "isphere-msglib-receive-display-*.txt") if err != nil { fail("receive_display_fixture", err) } fixturePath := file.Name() defer os.Remove(fixturePath) if _, err := file.WriteString("\n" + groupLine + "\n" + directLine + "\n"); err != nil { _ = file.Close() fail("receive_display_fixture", err) } if err := file.Close(); err != nil { fail("receive_display_fixture", err) } oldFileValue, hadOldFileValue := os.LookupEnv("ISPHERE_PACKET_LOG_FILE") oldDirValue, hadOldDirValue := os.LookupEnv("ISPHERE_PACKET_LOG_DIR") _ = os.Unsetenv("ISPHERE_PACKET_LOG_DIR") if err := os.Setenv("ISPHERE_PACKET_LOG_FILE", fixturePath); err != nil { fail("receive_display_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("receive_display_server", err) } serverErr := make(chan error, 1) go func() { serverErr <- server.Run(serverCtx, serverTransport) }() client := mcp.NewClient(&mcp.Implementation{Name: "verify-msglib-mcp-receive-display", Version: "0.1.0"}, nil) session, err := client.Connect(serverCtx, clientTransport, nil) if err != nil { cancel() fail("receive_display_connect", err) } defer func() { _ = session.Close() cancel() select { case <-serverErr: case <-time.After(2*time.Second): fail("receive_display_shutdown", fmt.Errorf("server did not stop")) } }() result, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{ "source_preference": "auto", "preview": true, "cursor": "", "include_attachment_metadata": false, "limit": 5, }}) if err != nil { fail("receive_display_call", err) } if result.IsError { fail("receive_display_call", fmt.Errorf("tool returned isError=true")) } payload, _ := json.Marshal(result.StructuredContent) var decoded map[string]any if err := json.Unmarshal(payload, &decoded); err != nil { fail("receive_display_decode", err) } messages, ok := decoded["messages"].([]any) if !ok { fail("receive_display_shape", fmt.Errorf("messages missing or not array")) } out := receiveDisplaySummary{MessageCount: len(messages)} if conversation, ok := decoded["conversation"].(map[string]any); ok { displayName, _ := conversation["display_name"].(string) out.ConversationDisplayPopulated = displayName == group.FirstDisplayName } for _, value := range messages { message, ok := value.(map[string]any) if !ok { continue } senderID, _ := message["sender_id"].(string) senderName, _ := message["sender_name"].(string) if senderID == contact.FirstID && senderName == contact.FirstDisplayName { out.SenderNamePopulated = true } } return out } func packetFixturePlaintext(id string, from string, to string, messageType string, body string) string { return "--------------------------------------------------------------------------------------------------------------------------------------------\n" + "2026/7/10 09:30:00\n" + fmt.Sprintf("\n", xmlAttr(id), xmlAttr(from), xmlAttr(to), xmlAttr(messageType)) + fmt.Sprintf(" %s\n", xmlText(body)) + " \n" + " DISPLAY_SMOKE\n" + " \n" + " \n" + "" } func xmlAttr(value string) string { return strings.NewReplacer("&", "&", "\"", """, "<", "<", ">", ">").Replace(value) } func xmlText(value string) string { return strings.NewReplacer("&", "&", "<", "<", ">", ">").Replace(value) } 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 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) { encoded, _ := json.Marshal(map[string]any{"ok": false, "step": step, "error": err.Error()}) fmt.Fprintln(os.Stderr, string(encoded)) os.Exit(1) } '@ | Set-Content -LiteralPath $harnessMain -Encoding UTF8 $oldSidecar = $env:ISPHERE_MSGLIB_SIDECAR_EXE $oldDll = $env:ISPHERE_MSGLIB_SQLITE_DLL $oldDb = $env:ISPHERE_MSGLIB_DB $env:ISPHERE_MSGLIB_SIDECAR_EXE = (Resolve-Path -LiteralPath $sidecarPath).Path $env:ISPHERE_MSGLIB_SQLITE_DLL = (Resolve-Path -LiteralPath $SQLiteDllPath).Path $env:ISPHERE_MSGLIB_DB = (Resolve-Path -LiteralPath $DbPath).Path try { Push-Location $harnessDir try { $output = & go run . if ($LASTEXITCODE -ne 0) { throw "go run MCP enrichment harness failed with exit code $LASTEXITCODE" } $output | ForEach-Object { Write-Host $_ } $lastLine = @($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) | Select-Object -Last 1 $json = $lastLine | ConvertFrom-Json } finally { Pop-Location } } finally { $env:ISPHERE_MSGLIB_SIDECAR_EXE = $oldSidecar $env:ISPHERE_MSGLIB_SQLITE_DLL = $oldDll $env:ISPHERE_MSGLIB_DB = $oldDb } Assert-True ($json.ok -eq $true) "harness did not return ok=true" Assert-True ($json.printed_entity_values -eq $false) "harness must not print entity values" Assert-True ($json.contact_count -gt 0) "contact_count should be > 0" Assert-True ($json.group_count -gt 0) "group_count should be > 0" Assert-True (($json.PSObject.Properties.Name -contains "db_receive_smoke") -and $json.db_receive_smoke -eq $true) "db receive smoke should be true" Assert-True (($json.PSObject.Properties.Name -contains "db_receive_message_count") -and $json.db_receive_message_count -gt 0) "db_receive_message_count should be > 0" Assert-True (($json.PSObject.Properties.Name -contains "message_body_values_printed") -and $json.message_body_values_printed -eq $false) "message body values must not be printed" Assert-True (($json.PSObject.Properties.Name -contains "receive_display_smoke") -and $json.receive_display_smoke -eq $true) "receive display smoke should be true" Assert-True (($json.PSObject.Properties.Name -contains "receive_sender_name_populated") -and $json.receive_sender_name_populated -eq $true) "receive sender_name should be populated" Assert-True (($json.PSObject.Properties.Name -contains "receive_conversation_display_populated") -and $json.receive_conversation_display_populated -eq $true) "receive conversation.display_name should be populated" } finally { Pop-Location } } finally { if (Test-Path -LiteralPath $harnessDir) { Remove-Item -LiteralPath $harnessDir -Recurse -Force } }