package main import ( "context" "crypto/cipher" "crypto/des" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "os" "path/filepath" "reflect" "sort" "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_message", } type smokeResult struct { OK bool `json:"ok"` Package string `json:"package"` Transport string `json:"transport"` Initialized bool `json:"initialized"` ToolCount int `json:"tool_count"` Tools []string `json:"tools"` HelperName string `json:"helper_name"` ReceiveMessageCount int `json:"receive_message_count"` ContactCount int `json:"contact_count"` GroupCount int `json:"group_count"` FileCount int `json:"file_count"` FixtureReceiveMessageCount int `json:"fixture_receive_message_count"` FixtureContactCount int `json:"fixture_contact_count"` FixtureGroupCount int `json:"fixture_group_count"` FixtureFileCount int `json:"fixture_file_count"` FixtureDirReceiveMessageCount int `json:"fixture_dir_receive_message_count"` FixtureDirContactCount int `json:"fixture_dir_contact_count"` FixtureDirGroupCount int `json:"fixture_dir_group_count"` FixtureDirFileCount int `json:"fixture_dir_file_count"` SendPreviewToolPresent bool `json:"send_preview_tool_present"` ProductionSendEnabled bool `json:"production_send_enabled"` LocalLoginRequired bool `json:"local_login_required"` RealISphereLoginRequired bool `json:"real_isphere_login_required"` PythonRuntimeRequired bool `json:"python_runtime_required"` NoRealSend bool `json:"no_real_send"` ActionToolsPresent bool `json:"action_tools_present"` PacketLogFileConfiguredAfterRun bool `json:"packet_log_file_configured_after_run"` PacketLogDirConfiguredAfterRun bool `json:"packet_log_dir_configured_after_run"` MsgLibConfigured bool `json:"msglib_configured"` SendAuditPath string `json:"send_audit_path"` } func main() { if err := run(); err != nil { fail("capability_smoke", err) } } func run() error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() clearRuntimeEnv() auditPath := filepath.Join("runs", "capability-smoke", "send-preview.jsonl") if err := os.Setenv("ISPHERE_SEND_AUDIT_PATH", auditPath); err != nil { return err } session, cleanup, err := newSession(ctx, "isphere-capability-smoke") if err != nil { return err } defer cleanup() toolNames, err := listToolNames(ctx, session) if err != nil { return err } if err := requireExpectedTools(toolNames); err != nil { return err } helperName, err := verifyHelperVersion(ctx, session) if err != nil { return err } receiveCount, err := verifyReceiveMessages(ctx, session, 0) if err != nil { return err } contactCount, err := verifySearchContacts(ctx, session, "sender", 0) if err != nil { return err } groupCount, err := verifySearchGroups(ctx, session, "project", 0) if err != nil { return err } fileCount, err := verifyReceiveFiles(ctx, session, "report", 0, "", "") if err != nil { return err } sendPreviewOK, productionSendEnabled, err := verifySendMessage(ctx, session) if err != nil { return err } fixtureReceiveCount, fixtureContactCount, fixtureGroupCount, fixtureFileCount, err := verifyPacketLogFileFixture(ctx) if err != nil { return err } fixtureDirReceiveCount, fixtureDirContactCount, fixtureDirGroupCount, fixtureDirFileCount, err := verifyPacketLogDirFixture(ctx) if err != nil { return err } result := smokeResult{ OK: true, Package: "send-capability-test", Transport: "sdk_in_memory", Initialized: true, ToolCount: len(toolNames), Tools: toolNames, HelperName: helperName, ReceiveMessageCount: receiveCount, ContactCount: contactCount, GroupCount: groupCount, FileCount: fileCount, FixtureReceiveMessageCount: fixtureReceiveCount, FixtureContactCount: fixtureContactCount, FixtureGroupCount: fixtureGroupCount, FixtureFileCount: fixtureFileCount, FixtureDirReceiveMessageCount: fixtureDirReceiveCount, FixtureDirContactCount: fixtureDirContactCount, FixtureDirGroupCount: fixtureDirGroupCount, FixtureDirFileCount: fixtureDirFileCount, SendPreviewToolPresent: sendPreviewOK, ProductionSendEnabled: productionSendEnabled, LocalLoginRequired: false, RealISphereLoginRequired: false, PythonRuntimeRequired: false, NoRealSend: true, ActionToolsPresent: false, PacketLogFileConfiguredAfterRun: os.Getenv("ISPHERE_PACKET_LOG_FILE") != "", PacketLogDirConfiguredAfterRun: os.Getenv("ISPHERE_PACKET_LOG_DIR") != "", MsgLibConfigured: false, SendAuditPath: auditPath, } encoded, err := json.Marshal(result) if err != nil { return err } fmt.Println(string(encoded)) return nil } func clearRuntimeEnv() { 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", } { _ = os.Unsetenv(key) } } func newSession(ctx context.Context, name string) (*mcp.ClientSession, func(), error) { serverTransport, clientTransport := mcp.NewInMemoryTransports() server, err := mcpserver.NewServerFromEnv() if err != nil { return nil, nil, fmt.Errorf("server config: %w", err) } serverCtx, cancel := context.WithCancel(ctx) serverErr := make(chan error, 1) go func() { serverErr <- server.Run(serverCtx, serverTransport) }() client := mcp.NewClient(&mcp.Implementation{Name: name, Version: "0.1.0"}, nil) session, err := client.Connect(serverCtx, clientTransport, nil) if err != nil { cancel() return nil, nil, fmt.Errorf("connect: %w", err) } cleanup := func() { _ = session.Close() cancel() select { case <-serverErr: case <-time.After(2 * time.Second): fmt.Fprintln(os.Stderr, `{"ok":false,"step":"server_shutdown","error":"server did not stop before timeout"}`) } } return session, cleanup, nil } func listToolNames(ctx context.Context, session *mcp.ClientSession) ([]string, error) { result, err := session.ListTools(ctx, &mcp.ListToolsParams{}) if err != nil { return nil, fmt.Errorf("list tools: %w", err) } names := make([]string, 0, len(result.Tools)) for _, tool := range result.Tools { names = append(names, tool.Name) } sort.Strings(names) return names, nil } func requireExpectedTools(got []string) error { want := append([]string(nil), expectedTools...) sort.Strings(want) if !reflect.DeepEqual(got, want) { return fmt.Errorf("tool names = %#v, want %#v", got, want) } return nil } func verifyHelperVersion(ctx context.Context, session *mcp.ClientSession) (string, error) { payload, err := callToolObject(ctx, session, "win_helper_version", map[string]any{}) if err != nil { return "", err } if payload["ok"] != true { return "", fmt.Errorf("win_helper_version ok = %#v, want true", payload["ok"]) } data, ok := payload["data"].(map[string]any) if !ok { return "", fmt.Errorf("win_helper_version data missing: %#v", payload["data"]) } helperName, _ := data["helper_name"].(string) if helperName != "ISphereWinHelper" { return "", fmt.Errorf("helper_name = %q, want ISphereWinHelper", helperName) } return helperName, nil } func verifyReceiveMessages(ctx context.Context, session *mcp.ClientSession, wantCount int) (int, error) { payload, err := callToolObject(ctx, session, "isphere_receive_messages", map[string]any{ "source_preference": "local_readonly", "preview": true, "cursor": "", "include_attachment_metadata": true, "limit": 5, }) if err != nil { return 0, err } if payload["ok"] != true { return 0, fmt.Errorf("isphere_receive_messages ok = %#v, want true", payload["ok"]) } messages, ok := payload["messages"].([]any) if !ok { return 0, fmt.Errorf("messages missing or not array: %#v", payload["messages"]) } if len(messages) != wantCount { return 0, fmt.Errorf("message count = %d, want %d", len(messages), wantCount) } return len(messages), nil } func verifySearchContacts(ctx context.Context, session *mcp.ClientSession, query string, wantCount int) (int, error) { payload, err := callToolObject(ctx, session, "isphere_search_contacts", map[string]any{ "query": query, "source_preference": "local_readonly", "include_inactive": true, "cursor": "", "limit": 5, }) if err != nil { return 0, err } if payload["ok"] != true { return 0, fmt.Errorf("isphere_search_contacts ok = %#v, want true", payload["ok"]) } contacts, ok := payload["contacts"].([]any) if !ok { return 0, fmt.Errorf("contacts missing or not array: %#v", payload["contacts"]) } if len(contacts) != wantCount { return 0, fmt.Errorf("contact count = %d, want %d", len(contacts), wantCount) } return len(contacts), nil } func verifySearchGroups(ctx context.Context, session *mcp.ClientSession, query string, wantCount int) (int, error) { payload, err := callToolObject(ctx, session, "isphere_search_groups", map[string]any{ "query": query, "source_preference": "local_readonly", "include_archived": true, "cursor": "", "limit": 5, }) if err != nil { return 0, err } if payload["ok"] != true { return 0, fmt.Errorf("isphere_search_groups ok = %#v, want true", payload["ok"]) } groups, ok := payload["groups"].([]any) if !ok { return 0, fmt.Errorf("groups missing or not array: %#v", payload["groups"]) } if len(groups) != wantCount { return 0, fmt.Errorf("group count = %d, want %d", len(groups), wantCount) } return len(groups), nil } func verifyReceiveFiles(ctx context.Context, session *mcp.ClientSession, nameContains string, wantCount int, wantFileID string, wantFileName string) (int, error) { payload, err := callToolObject(ctx, session, "isphere_receive_files", map[string]any{ "mode": "list", "name_contains": nameContains, "source_preference": "local_readonly", "preview": true, "cursor": "", "output_dir": "", "limit": 5, }) if err != nil { return 0, err } if payload["ok"] != true { return 0, fmt.Errorf("isphere_receive_files ok = %#v, want true", payload["ok"]) } files, ok := payload["files"].([]any) if !ok { return 0, fmt.Errorf("files missing or not array: %#v", payload["files"]) } if len(files) != wantCount { return 0, fmt.Errorf("file count = %d, want %d", len(files), wantCount) } if wantCount > 0 { first, ok := files[0].(map[string]any) if !ok { return 0, fmt.Errorf("first file not object: %#v", files[0]) } if first["file_id"] != wantFileID || first["file_name"] != wantFileName { return 0, fmt.Errorf("unexpected file: %#v", first) } if first["download_ref"] != nil || first["saved_path"] != nil || first["sha256"] != nil { return 0, fmt.Errorf("file list should not download or save: %#v", first) } } return len(files), nil } func verifySendMessage(ctx context.Context, session *mcp.ClientSession) (bool, bool, error) { content := "redacted preview body" contentHash := sha256Hex(content) preview, err := callToolObject(ctx, session, "isphere_send_message", map[string]any{ "target_type": "direct", "target_id": "sender@imopenfire1-lanzhou", "content_text": content, "content_sha256": contentHash, "idempotency_key": "capability-preview-idempotency-key", "execution_mode": "preview", }) if err != nil { return false, false, err } if preview["ok"] != true || preview["send_status"] != "planned" || preview["connector_stage"] != "preview" { return false, false, fmt.Errorf("unexpected send preview payload: %#v", preview) } sideEffects, ok := preview["side_effects"].(map[string]any) if !ok || sideEffects["sent_message"] != false || sideEffects["clicked_ui"] != false || sideEffects["typed_text"] != false { return false, false, fmt.Errorf("send preview side effects mismatch: %#v", preview["side_effects"]) } production, err := callToolObject(ctx, session, "isphere_send_message", map[string]any{ "target_type": "direct", "target_id": "sender@imopenfire1-lanzhou", "content_text": content, "content_sha256": contentHash, "idempotency_key": "capability-production-idempotency-key", "execution_mode": "production", }) if err != nil { return false, false, err } if production["ok"] != false || production["send_status"] != "blocked" || production["connector_stage"] != "blocked" { return false, false, fmt.Errorf("production send was not blocked: %#v", production) } productionSideEffects, ok := production["side_effects"].(map[string]any) if !ok || productionSideEffects["sent_message"] != false || productionSideEffects["uploaded_file"] != false || productionSideEffects["captured_network"] != false { return false, false, fmt.Errorf("production side effects mismatch: %#v", production["side_effects"]) } return true, false, nil } func verifyPacketLogFileFixture(ctx context.Context) (int, int, int, int, error) { plaintext := fixturePacketLogPlaintext("msg-fixture-1", "redacted-report.docx") line, err := encryptPacketLogLine(plaintext) if err != nil { return 0, 0, 0, 0, err } file, err := os.CreateTemp("", "isphere-capability-packet-*.log") if err != nil { return 0, 0, 0, 0, err } fixturePath := file.Name() defer os.Remove(fixturePath) if _, err := file.WriteString("\n" + line + "\n"); err != nil { _ = file.Close() return 0, 0, 0, 0, err } if err := file.Close(); err != nil { return 0, 0, 0, 0, err } restore := replaceEnv("ISPHERE_PACKET_LOG_FILE", fixturePath) defer restore() _ = os.Unsetenv("ISPHERE_PACKET_LOG_DIR") session, cleanup, err := newSession(ctx, "isphere-capability-smoke-file-fixture") if err != nil { return 0, 0, 0, 0, err } defer cleanup() receiveCount, err := verifyReceiveMessages(ctx, session, 1) if err != nil { return 0, 0, 0, 0, err } contactCount, err := verifySearchContacts(ctx, session, "sender", 1) if err != nil { return 0, 0, 0, 0, err } groupCount, err := verifySearchGroups(ctx, session, "project", 1) if err != nil { return 0, 0, 0, 0, err } fileCount, err := verifyReceiveFiles(ctx, session, "report", 1, "msg-fixture-1:redacted-report.docx", "redacted-report.docx") if err != nil { return 0, 0, 0, 0, err } return receiveCount, contactCount, groupCount, fileCount, nil } func verifyPacketLogDirFixture(ctx context.Context) (int, int, int, int, error) { plaintext := fixturePacketLogPlaintext("msg-dir-fixture-1", "redacted-dir-report.docx") line, err := encryptPacketLogLine(plaintext) if err != nil { return 0, 0, 0, 0, err } dir, err := os.MkdirTemp("", "isphere-capability-packet-dir-*") if err != nil { return 0, 0, 0, 0, err } defer os.RemoveAll(dir) if err := os.WriteFile(filepath.Join(dir, "a-packet-reader.log"), []byte("\n"+line+"\n"), 0o600); err != nil { return 0, 0, 0, 0, err } if err := os.WriteFile(filepath.Join(dir, "ignored.tmp"), []byte("ignored\n"), 0o600); err != nil { return 0, 0, 0, 0, err } restoreFile := replaceEnv("ISPHERE_PACKET_LOG_FILE", "") restoreDir := replaceEnv("ISPHERE_PACKET_LOG_DIR", dir) defer restoreFile() defer restoreDir() session, cleanup, err := newSession(ctx, "isphere-capability-smoke-dir-fixture") if err != nil { return 0, 0, 0, 0, err } defer cleanup() receiveCount, err := verifyReceiveMessages(ctx, session, 1) if err != nil { return 0, 0, 0, 0, err } contactCount, err := verifySearchContacts(ctx, session, "sender", 1) if err != nil { return 0, 0, 0, 0, err } groupCount, err := verifySearchGroups(ctx, session, "project", 1) if err != nil { return 0, 0, 0, 0, err } fileCount, err := verifyReceiveFiles(ctx, session, "dir-report", 1, "msg-dir-fixture-1:redacted-dir-report.docx", "redacted-dir-report.docx") if err != nil { return 0, 0, 0, 0, err } return receiveCount, contactCount, groupCount, fileCount, nil } func callToolObject(ctx context.Context, session *mcp.ClientSession, name string, args map[string]any) (map[string]any, error) { result, err := session.CallTool(ctx, &mcp.CallToolParams{Name: name, Arguments: args}) if err != nil { return nil, fmt.Errorf("%s: %w", name, err) } if result.IsError { return nil, fmt.Errorf("%s returned IsError=true: %#v", name, result.Content) } payload, err := json.Marshal(result.StructuredContent) if err != nil { return nil, fmt.Errorf("%s marshal structuredContent: %w", name, err) } var object map[string]any if err := json.Unmarshal(payload, &object); err != nil { return nil, fmt.Errorf("%s decode structuredContent %s: %w", name, payload, err) } return object, nil } func fixturePacketLogPlaintext(messageID string, body string) string { return "--------------------------------------------------------------------------------------------------------------------------------------------\n" + "2026/7/7 15:30:07\n" + "\n" + " " + body + "\n" + " \n" + " FILE_TRANSFER_CANCEL\n" + " \n" + " \n" + "" } func encryptPacketLogLine(plaintext string) (string, error) { block, err := des.NewCipher([]byte("hyhccdtm")) if err != nil { return "", err } plain := padPKCS7([]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 padPKCS7(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 sha256Hex(value string) string { sum := sha256.Sum256([]byte(value)) return hex.EncodeToString(sum[:]) } func replaceEnv(key string, value string) func() { oldValue, hadOldValue := os.LookupEnv(key) if value == "" { _ = os.Unsetenv(key) } else { _ = os.Setenv(key, value) } return func() { if hadOldValue { _ = os.Setenv(key, oldValue) } else { _ = os.Unsetenv(key) } } } 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) }