docs: verify five-tool mcp surface

This commit is contained in:
zhaoyilun
2026-07-09 23:16:02 +08:00
parent 57eb7a5fc7
commit 220f3f68c1
4 changed files with 147 additions and 40 deletions

View File

@@ -14,21 +14,22 @@ Remote base before local roadmap commits: `b2d839e Merge branch 'codex/n15-repor
## Current completed state ## Current completed state
- Go MCP foundation exists: `cmd/isphere-mcp`, `internal/mcpserver`, `internal/helperclient`, and `internal/tools` build a stdio MCP server. - Go MCP foundation exists: `cmd/isphere-mcp`, `internal/mcpserver`, `internal/helperclient`, and `internal/tools` build a stdio MCP server.
- The current MCP surface exposes only four read-only helper tools: - The current MCP surface exposes five read-oriented tools:
- `win_helper_version` - `win_helper_version`
- `win_helper_self_check` - `win_helper_self_check`
- `win_helper_scan_windows` - `win_helper_scan_windows`
- `win_helper_dump_uia` - `win_helper_dump_uia`
- `isphere_receive_messages`
- C# `ISphereWinHelper` exists under `native/ISphereWinHelper` and uses the `isphere.helper.v1` stdin/stdout JSON contract. - C# `ISphereWinHelper` exists under `native/ISphereWinHelper` and uses the `isphere.helper.v1` stdin/stdout JSON contract.
- `scripts/verify-win-helper.ps1` and `scripts/verify-go-mcp.ps1` provide repeatable local checks for the helper and first-phase Go MCP server. - `scripts/verify-win-helper.ps1` and `scripts/verify-go-mcp.ps1` provide repeatable local checks for the helper, five-tool MCP surface, and empty-source `isphere_receive_messages` smoke call.
- N12-pre and N12R documents define safe offline evidence intake and internal-sandbox live UIA capture procedures. - N12-pre and N12R documents define safe offline evidence intake and internal-sandbox live UIA capture procedures.
- N13/N14/N15 produced selector catalog/report validation infrastructure, but those are supporting validation assets only. - N13/N14/N15 produced selector catalog/report validation infrastructure, but those are supporting validation assets only.
## What cannot be claimed externally ## What cannot be claimed externally
- The project has not yet implemented business MCP tools for contact search, group search, message receiving, message sending, file receiving, or file sending. - The project has not yet implemented business MCP tools for contact search, group search, message sending, file receiving, or file sending; `isphere_receive_messages` is registered but still lacks a production loader/configuration path.
- The project has not completed production iSphere integration. - The project has not completed production iSphere integration.
- The project has not proven a stable real message source for the full business workflow. - The project has not proven a stable real message source for the full business workflow beyond decrypted PacketReader log evidence and synthetic/redacted tests.
- The project has not implemented real message sending or real file upload. - The project has not implemented real message sending or real file upload.
- N12-pre offline evidence is not N12 pass. - N12-pre offline evidence is not N12 pass.
- N13/N14/N15 selector/report work is pre-business evidence, not finished contact/message/file capability. - N13/N14/N15 selector/report work is pre-business evidence, not finished contact/message/file capability.
@@ -38,8 +39,8 @@ Remote base before local roadmap commits: `b2d839e Merge branch 'codex/n15-repor
- The repository has a Go MCP + C# WinHelper foundation for controlled Windows-side read-only observation. - The repository has a Go MCP + C# WinHelper foundation for controlled Windows-side read-only observation.
- The helper protocol boundary is explicit and keeps Windows/UIA work separate from the Go MCP service layer. - The helper protocol boundary is explicit and keeps Windows/UIA work separate from the Go MCP service layer.
- The current code can support future MCP business tools by routing low-level Windows/UIA fallback work through a bounded helper layer. - The current code can support future MCP business tools by routing low-level Windows/UIA fallback work through a bounded helper layer.
- The project has repeatable verification scripts for the first-phase helper/MCP foundation. - The project has repeatable verification scripts for the helper/MCP foundation and the current five-tool surface.
- The project now has a corrected business contract and plan for core MCP communication tools. - The project now has a corrected business contract and plan for core MCP communication tools, plus a registered first read tool for receiving messages from a future configured source.
## N13/N14/N15 positioning ## N13/N14/N15 positioning
@@ -47,7 +48,7 @@ N13/N14/N15 are pre-business validation results. They can help identify UI eleme
## Current loop ## Current loop
Current loop: `Stage C / Loop C1 - log decryptor and PacketReader parser`. Current loop: `Stage C / Loop C4 - operator verification and docs`.
Plan file: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`. Plan file: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`.
@@ -58,18 +59,17 @@ Loop rule:
3. Verify, commit exact paths, then update the next loop based on actual results. 3. Verify, commit exact paths, then update the next loop based on actual results.
4. Continue automatically while the next step is clear; ask the user only when blocked or when business direction changes. 4. Continue automatically while the next step is clear; ask the user only when blocked or when business direction changes.
Current loop output: Current loop output so far:
1. `internal/isphere/logcodec.DecryptLine` for DES/CBC/PKCS7 Base64 log lines. 1. C1: `internal/isphere/logcodec.DecryptLine` and `internal/isphere/packetlog.ParseMessageLog` for redacted PacketReader message logs.
2. `internal/isphere/packetlog.ParseMessageLog` for redacted `PacketReader.ProcessPacket` XMPP `<message>` stanzas. 2. C2: `internal/isphere.EncryptedPacketLogSource.ReceiveMessages` source abstraction with synthetic/redacted encrypted fixtures.
3. Passing unit tests with synthetic/redacted fixtures only. 3. C3: `isphere_receive_messages` MCP tool registration and server tool list update.
4. Updated Loop C2 plan before source abstraction starts. 4. C4: updating repeatable operator verification and docs for the five-tool surface.
## Next business mainline ## Next business mainline
1. Complete Stage C Loop C1: log decryptor + PacketReader parser. 1. Finish Stage C Loop C4: verify and document the five-tool MCP surface.
2. Rewrite and execute Loop C2: log-backed `ReceiveMessages` source abstraction. 2. Implement the next loop: safe configurable source loader or fixture path for `isphere_receive_messages`, without committing raw evidence.
3. Register `isphere_receive_messages` only after parser/source tests pass. 3. Add contact/group/file read tools after their source decisions are recorded.
4. Add contact/group/file read tools after their source decisions are recorded. 4. Add `isphere_send_message` and `isphere_send_file` after source discovery and connector selection.
5. Add `isphere_send_message` and `isphere_send_file` after source discovery and connector selection. 5. Keep UIA selector/report work as fallback support, not as the primary roadmap.
6. Keep UIA selector/report work as fallback support, not as the primary roadmap.

View File

@@ -2,7 +2,7 @@
This runbook is for the first Go MCP phase of `isphere-ai-bridge`. It lets an operator build and verify the Windows helper and the Go MCP server without reading the source code. This runbook is for the first Go MCP phase of `isphere-ai-bridge`. It lets an operator build and verify the Windows helper and the Go MCP server without reading the source code.
Current scope: expose four WinHelper observation operations through Go MCP. Business contact/group/message/file tools are defined in the core MCP contract and implemented in later stages. Current scope: expose four WinHelper observation operations plus the first log-backed business read tool, `isphere_receive_messages`, through Go MCP. The receive tool is registered with an empty default source until a safe evidence loader is wired.
## 1. Prerequisites ## 1. Prerequisites
@@ -90,10 +90,11 @@ The verification confirms:
- C# helper can be built. - C# helper can be built.
- Go MCP binary can be built. - Go MCP binary can be built.
- MCP initialize/list/call flow works through the SDK harness. - MCP initialize/list/call flow works through the SDK harness.
- Exactly four tools are exposed. - Exactly five tools are exposed: four WinHelper observation tools plus `isphere_receive_messages`.
- `win_helper_version` returns `ISphereWinHelper`. - `win_helper_version` returns `ISphereWinHelper`.
- Verification uses synthetic/local helper checks. - `isphere_receive_messages` is callable and returns an empty `messages` array from the default empty source.
- Message/file/search business tools are covered by the core MCP contract, not this runbook phase. - Verification uses synthetic/local helper checks and does not load raw N12-pre evidence.
- Contact/group/file/search/send business tools remain later-stage work.
## 6. Configure MCP client command ## 6. Configure MCP client command
@@ -132,14 +133,15 @@ E:\coding\codex\isphere-ai-bridge\isphere-mcp.exe
## 7. Allowed tools ## 7. Allowed tools
Only these four tools are allowed in the first phase: These five tools are currently allowed:
| Tool | Helper op | Purpose | | Tool | Source/op | Purpose |
| --- | --- | --- | | --- | --- | --- |
| `win_helper_version` | `version` | Read helper version and protocol metadata. | | `win_helper_version` | helper `version` | Read helper version and protocol metadata. |
| `win_helper_self_check` | `self_check` | Read desktop/UIA availability status. | | `win_helper_self_check` | helper `self_check` | Read desktop/UIA availability status. |
| `win_helper_scan_windows` | `scan_windows` | Read visible window metadata or likely iSphere candidates. | | `win_helper_scan_windows` | helper `scan_windows` | Read visible window metadata or likely iSphere candidates. |
| `win_helper_dump_uia` | `dump_uia` | Read a UI Automation tree for a specified window handle. | | `win_helper_dump_uia` | helper `dump_uia` | Read a UI Automation tree for a specified window handle. |
| `isphere_receive_messages` | log-backed `EncryptedPacketLogSource` | Read normalized messages from the configured PacketReader log source. The default server source is empty until a loader is wired. |
Allowed parameters: Allowed parameters:
@@ -147,15 +149,19 @@ Allowed parameters:
- `win_helper_self_check`: zero business parameters. - `win_helper_self_check`: zero business parameters.
- `win_helper_scan_windows`: `include_all_visible` only. - `win_helper_scan_windows`: `include_all_visible` only.
- `win_helper_dump_uia`: `hwnd`, `max_depth`, `include_text`, `max_children` only. - `win_helper_dump_uia`: `hwnd`, `max_depth`, `include_text`, `max_children` only.
- `isphere_receive_messages`: `limit` only.
## 8. Current tool surface and next-stage route ## 8. Current tool surface and next-stage route
The current runbook verifies four WinHelper observation tools: The current runbook verifies five tools:
- `win_helper_version` - `win_helper_version`
- `win_helper_self_check` - `win_helper_self_check`
- `win_helper_scan_windows` - `win_helper_scan_windows`
- `win_helper_dump_uia` - `win_helper_dump_uia`
- `isphere_receive_messages`
`isphere_receive_messages` currently has parser/source/tool registration and an empty default server source. Raw N12-pre evidence remains under ignored `runs/` paths and is not committed. The next implementation slice should wire a safe configurable loader or fixture path before claiming production message ingestion.
Core business tools for contacts, groups, messages, and files are defined in `docs/mcp-core-tools-contract.md`. Send/file capabilities enter the roadmap through that contract after source discovery, connector selection, preview metadata, execution metadata, and audit records are in place. Core business tools for contacts, groups, messages, and files are defined in `docs/mcp-core-tools-contract.md`. Send/file capabilities enter the roadmap through that contract after source discovery, connector selection, preview metadata, execution metadata, and audit records are in place.
@@ -207,7 +213,7 @@ iSphere is internal-network only. If the current repository environment is outsi
1. **N12-pre offline evidence** for copied files and static analysis. Follow `docs/offline-evidence-intake-plan.md` and place packages under `runs\offline-evidence-intake\<evidence-id>\`. 1. **N12-pre offline evidence** for copied files and static analysis. Follow `docs/offline-evidence-intake-plan.md` and place packages under `runs\offline-evidence-intake\<evidence-id>\`.
2. **N12R internal-sandbox live capture** for real logged-in UIA evidence. Follow `docs/internal-sandbox-live-capture-plan.md` and `docs/internal-sandbox-operator-runbook.md`; place returned packages under `runs\internal-sandbox-live-capture\<capture-id>\`. 2. **N12R internal-sandbox live capture** for real logged-in UIA evidence. Follow `docs/internal-sandbox-live-capture-plan.md` and `docs/internal-sandbox-operator-runbook.md`; place returned packages under `runs\internal-sandbox-live-capture\<capture-id>\`.
N12R is the active live-capture route when login is only possible in an internal-network test sandbox. It still allows only the four first-phase read-only tools: N12R is the active live-capture route when login is only possible in an internal-network test sandbox. The UIA capture procedure still uses only the four helper observation tools:
```text ```text
win_helper_version win_helper_version
@@ -216,6 +222,8 @@ win_helper_scan_windows
win_helper_dump_uia win_helper_dump_uia
``` ```
The MCP server also registers `isphere_receive_messages`, but its default source is empty and it should not be used as N12R UIA evidence collection.
After receiving an N12R package, validate it with `docs/n12r-return-package-validation-checklist.md` before using it as evidence for selector-design review. Prefer the scripted read-only validator: After receiving an N12R package, validate it with `docs/n12r-return-package-validation-checklist.md` before using it as evidence for selector-design review. Prefer the scripted read-only validator:
```powershell ```powershell

View File

@@ -725,7 +725,7 @@ Implemented and verified in this loop:
- Default `isphere_receive_messages` source: `isphere.EncryptedPacketLogSource{}` with no configured lines, so the operator smoke call should return `messages: []` until a real evidence loader is wired. - Default `isphere_receive_messages` source: `isphere.EncryptedPacketLogSource{}` with no configured lines, so the operator smoke call should return `messages: []` until a real evidence loader is wired.
- Verification script should still call `win_helper_version`, and should additionally call `isphere_receive_messages` with a small `limit` and assert the returned `messages` field is an array. - Verification script should still call `win_helper_version`, and should additionally call `isphere_receive_messages` with a small `limit` and assert the returned `messages` field is an array.
- [ ] **Step 1: Capture current verification red state** - [x] **Step 1: Capture current verification red state**
Run before editing the script: Run before editing the script:
@@ -735,15 +735,15 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1
Expected: FAIL because the existing harness still expects exactly four tools and treats `receive` as a forbidden term. Expected: FAIL because the existing harness still expects exactly four tools and treats `receive` as a forbidden term.
- [ ] **Step 2: Update `scripts/verify-go-mcp.ps1`** - [x] **Step 2: Update `scripts/verify-go-mcp.ps1`**
Change expected tools to five, remove `receive` from the forbidden write/scope terms, and add an SDK call to `isphere_receive_messages` with `{ "limit": 5 }`. Assert `messages` exists and is an array; for the default empty source, assert the smoke result has zero messages. Change expected tools to five, remove `receive` from the forbidden write/scope terms, and add an SDK call to `isphere_receive_messages` with `{ "limit": 5 }`. Assert `messages` exists and is an array; for the default empty source, assert the smoke result has zero messages.
- [ ] **Step 3: Update operator docs** - [x] **Step 3: Update operator docs**
Update `docs/go-mcp-runbook.md` and `docs/current-status-card.md` so they describe the current five-tool surface, the empty default read source, and the fact that raw N12-pre evidence still stays under ignored `runs/` paths. Update `docs/go-mcp-runbook.md` and `docs/current-status-card.md` so they describe the current five-tool surface, the empty default read source, and the fact that raw N12-pre evidence still stays under ignored `runs/` paths.
- [ ] **Step 4: Run full verification** - [x] **Step 4: Run full verification**
Run: Run:
@@ -756,12 +756,86 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1
Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build. Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build.
- [ ] **Step 5: Commit Loop C4 and rewrite next loop** - [x] **Step 5: Commit Loop C4 and rewrite next loop**
Commit exact C4 paths only. Rewrite the next loop around the remaining gap: a safe configurable source loader or fixture path for `isphere_receive_messages`, without committing raw evidence. Commit exact C4 paths only. Rewrite the next loop around the remaining gap: a safe configurable source loader or fixture path for `isphere_receive_messages`, without committing raw evidence.
--- ---
## Loop C4 Result
Implemented and verified in this loop:
- `scripts/verify-go-mcp.ps1` now expects five tools and treats `isphere_receive_messages` as an allowed read capability rather than a forbidden action term.
- The SDK harness still verifies `win_helper_version`, and now also calls `isphere_receive_messages` with `{ "limit": 5 }`.
- Default server wiring returns `messages: []` for `isphere_receive_messages`, proving the tool is callable without loading raw evidence.
- `docs/go-mcp-runbook.md` and `docs/current-status-card.md` now describe the five-tool surface and the empty default source behavior.
- Verification completed for C4 with `git diff --check`, `go test ./...`, `go build ./cmd/isphere-mcp`, and `powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1`; the generated root `isphere-mcp.exe` build artifact was removed.
---
## Loop C5: Safe configurable PacketReader log source loader
**Goal:** Add a safe configuration path so `isphere_receive_messages` can read encrypted PacketReader log lines from a local operator-supplied file without committing raw evidence and while preserving the empty default source.
**Planned files:**
- Create: `internal/isphere/file_source.go`
- Create: `internal/isphere/file_source_test.go`
- Modify: `internal/mcpserver/server.go`
- Modify: `internal/mcpserver/server_test.go`
- Modify: `cmd/isphere-mcp/main.go`
- Modify: `scripts/verify-go-mcp.ps1`
- Modify: `docs/go-mcp-runbook.md`
- Modify: `docs/current-status-card.md`
- Modify: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`
**Planned interface:**
- Environment variable: `ISPHERE_PACKET_LOG_FILE`.
- Default behavior: env var unset means `isphere_receive_messages` remains an empty-source read tool returning `messages: []`.
- Configured behavior: env var set to a local text file means the server reads non-empty encrypted Base64 lines from that file into `isphere.EncryptedPacketLogSource{Lines: lines}`.
- Evidence rule: the configured file should live under ignored `runs/` or another operator-local path; the loader must not require or commit raw evidence.
- [ ] **Step 1: Write failing file loader test**
Create `internal/isphere/file_source_test.go` first and run:
```powershell
go test ./internal/isphere -run TestLoadEncryptedPacketLogSourceFromFileReadsNonEmptyLines -v
```
Expected initial failure: loader function/type does not exist.
- [ ] **Step 2: Implement file loader**
Implement a small loader that reads a UTF-8 text file, trims whitespace, skips blank lines, and returns `EncryptedPacketLogSource{Lines: lines}`. It should return clear path/context errors for missing or unreadable files.
- [ ] **Step 3: Write failing env-wired server test**
Update `internal/mcpserver/server_test.go` with a temp encrypted fixture file and a test that sets `ISPHERE_PACKET_LOG_FILE`, constructs the env-wired server, calls `isphere_receive_messages`, and expects one redacted message. Run the specific test before implementation.
- [ ] **Step 4: Implement env-wired server construction**
Add `NewServerFromEnv()` or equivalent env-aware construction. Keep `NewServer()` as the empty default for existing tests. Update `cmd/isphere-mcp/main.go` to use the env-aware server.
- [ ] **Step 5: Update operator verification/docs**
Extend `scripts/verify-go-mcp.ps1` so default verification still asserts zero messages when no env var is set. Document optional `ISPHERE_PACKET_LOG_FILE` usage in `docs/go-mcp-runbook.md` and update the status card.
- [ ] **Step 6: Run full verification and commit**
Run:
```powershell
git diff --check
go test ./...
go build ./cmd/isphere-mcp
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1
```
Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build. Commit exact C5 paths only and rewrite the next loop from actual results.
---
## Self-Review ## Self-Review
- Spec coverage: the plan follows the user's cyclic requirement: plan first, implement one loop at a time, update the next loop after each result, and ask only on blockers. - Spec coverage: the plan follows the user's cyclic requirement: plan first, implement one loop at a time, update the next loop after each result, and ask only on blockers.

View File

@@ -6,7 +6,8 @@ $expectedTools = @(
"win_helper_version", "win_helper_version",
"win_helper_self_check", "win_helper_self_check",
"win_helper_scan_windows", "win_helper_scan_windows",
"win_helper_dump_uia" "win_helper_dump_uia",
"isphere_receive_messages"
) )
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("isphere-go-mcp-verify-" + [guid]::NewGuid().ToString("N")) $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("isphere-go-mcp-verify-" + [guid]::NewGuid().ToString("N"))
@@ -74,9 +75,10 @@ var expectedTools = []string{
"win_helper_self_check", "win_helper_self_check",
"win_helper_scan_windows", "win_helper_scan_windows",
"win_helper_dump_uia", "win_helper_dump_uia",
"isphere_receive_messages",
} }
var forbiddenTerms = []string{"send", "search", "file", "login", "conversation", "upload", "download", "receive", "autologin"} var forbiddenTerms = []string{"send", "search", "file", "login", "conversation", "upload", "download", "autologin"}
func main() { func main() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
@@ -122,7 +124,7 @@ func main() {
sort.Strings(toolNames) sort.Strings(toolNames)
want := append([]string(nil), expectedTools...) want := append([]string(nil), expectedTools...)
sort.Strings(want) sort.Strings(want)
if len(toolNames) != 4 || !reflect.DeepEqual(toolNames, want) { if len(toolNames) != 5 || !reflect.DeepEqual(toolNames, want) {
fail("tools/list", fmt.Errorf("tool names = %#v, want %#v", toolNames, want)) fail("tools/list", fmt.Errorf("tool names = %#v, want %#v", toolNames, want))
} }
@@ -153,6 +155,26 @@ func main() {
fail("tools/call win_helper_version", fmt.Errorf("helper_name = %q, want ISphereWinHelper", helperName)) 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{"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))
}
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)))
}
result := map[string]any{ result := map[string]any{
"ok": true, "ok": true,
"transport": "sdk_in_memory", "transport": "sdk_in_memory",
@@ -160,6 +182,7 @@ func main() {
"tool_count": len(toolNames), "tool_count": len(toolNames),
"tools": toolNames, "tools": toolNames,
"helper_name": helperName, "helper_name": helperName,
"receive_message_count": len(messagesValue),
"real_isphere_login_required": false, "real_isphere_login_required": false,
"python_runtime_required": false, "python_runtime_required": false,
"action_tools_present": false, "action_tools_present": false,
@@ -195,7 +218,8 @@ func fail(step string, err error) {
Assert-True ($script:harnessJson.ok -eq $true) "SDK harness did not return ok=true" 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.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)" Assert-True ($script:harnessJson.tool_count -eq 5) "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)"
[ordered]@{ [ordered]@{
ok = $true ok = $true
@@ -204,6 +228,7 @@ func fail(step string, err error) {
helper_name = $script:harnessJson.helper_name helper_name = $script:harnessJson.helper_name
tool_count = $script:harnessJson.tool_count tool_count = $script:harnessJson.tool_count
tools = $script:harnessJson.tools tools = $script:harnessJson.tools
receive_message_count = $script:harnessJson.receive_message_count
python_runtime_required = $false python_runtime_required = $false
real_isphere_login_required = $false real_isphere_login_required = $false
action_tools_present = $false action_tools_present = $false