product: maestro audience: test-developer, operator, ai-assistant authority: normative
MCP Server — Live Events & Log Search
This document covers the Phase 3 MCP tools:
connect_live_events/poll_test_progress— real-time test progress via SignalRsearch_execution_logs— full-text search over indexed execution log chunks
Live Events
How it works
The Maestro API exposes a SignalR hub at /testExecutionHub. The MCP server maintains
a persistent connection to this hub and buffers incoming events in memory. Because MCP
uses a request-response transport (stdio), the AI polls for events rather than receiving
push notifications.
AI assistant McpServer API hub
│ │ │
│ connect_live_events │ HubConnection.StartAsync │
│──────────────────────────▶│──────────────────────────▶│
│ │ Connected │
│ │◀──────────────────────────│
│ │ │
│ │◀── TestStatusChangedEvent ─│ (broadcast)
│ │◀── StepProgressEvent ──────│ (broadcast)
│ poll_test_progress │ │
│──────────────────────────▶│ drains buffered events │
│◀── { statusEvents, stepEvents } ──────────────────────│
Tools
connect_live_events
Opens the SignalR connection. Safe to call multiple times — reconnects only if not already active.
| Parameter | Type | Required | Description |
|---|---|---|---|
| (none) |
Returns:
{
"station": "ST-01",
"connected": true,
"message": "Connected to station hub. Use poll_test_progress to receive events."
}
poll_test_progress
Drains and returns all events buffered since the last call. Returns immediately with empty arrays if no new events have arrived. Call at 1–5 second intervals while a test is running.
| Parameter | Type | Required | Description |
|---|---|---|---|
| (none) |
Returns:
{
"station": "ST-01",
"connected": true,
"statusEvents": [
{ "executionId": "…", "status": "RUNNING", "verdict": null },
{ "executionId": "…", "status": "COMPLETED", "verdict": "PASS" }
],
"stepEvents": [
{ "executionId": "…", "stepExecutionId": "…", "stepName": "Calibrate",
"currentStep": 1, "totalSteps": 5 }
]
}
Event types:
| Array | Trigger | Key fields |
|---|---|---|
statusEvents |
Test lifecycle changes (PENDING → RUNNING → COMPLETED / ABORTED) | status, verdict (non-null when COMPLETED) |
stepEvents |
Each step begins execution | stepName, currentStep, totalSteps |
Typical polling workflow
1. start_test(yamlFilePath=..., serialNumber=..., unattendedMode=true)
→ returns { executionId: "abc-123" }
2. connect_live_events()
→ connected: true
3. Loop until statusEvents contains status == "COMPLETED" or "ABORTED":
poll_test_progress()
→ inspect stepEvents for progress, statusEvents for completion
wait 2 seconds
4. get_test_status(executionId="abc-123")
→ final verdict and step summary
Buffer limits
The MCP server buffers up to 100 events per type. If the AI does not poll frequently enough and more than 100 events arrive, the oldest events are dropped. For tests with more than 100 steps poll every 1–2 seconds.
Log Search
How it works
After a test execution completes, EmbeddingBackgroundService in Orchestra indexes the
execution's log lines into the log_embeddings table. Each step's logs are chunked into
segments of up to 2 000 characters with 200-character overlap to prevent long error
messages from being split at a chunk boundary.
The search_execution_logs MCP tool calls GET /api/statistics/logs which runs a
PostgreSQL full-text search (plainto_tsquery) over the indexed chunks.
Test execution completes
│
▼ (within 65 seconds)
EmbeddingBackgroundService (Orchestra)
│ groups logs by step, chunks to 2000 chars
▼
log_embeddings table (PostgreSQL)
AI: search_execution_logs(query="timeout error")
│
▼
McpServer ──HTTP──▶ GET /api/statistics/logs?query=timeout+error
│ plainto_tsquery('english', ...)
▼
log_embeddings (full-text search)
│
▼
ranked hits → AI assistant
Indexing delay: Logs are indexed 5 minutes after execution completion (safety delay to avoid racing with in-flight persistence writes), then within the next 60-second poll cycle. Typical indexing latency: 5–65 seconds.
Tool: search_execution_logs
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | ✅ | — | Search phrase. Plain text recommended; supports PostgreSQL tsquery operators & (AND), \| (OR), ! (NOT). |
testExecutionId |
UUID | ❌ | null | Restrict search to a single execution. |
stepName |
string | ❌ | null | Restrict search to a specific step name. |
limit |
int | ❌ | 20 | Max hits per page (1–100). |
page |
int | ❌ | 1 | 1-based page number. |
Returns:
{
"station": "ST-01",
"results": {
"query": "timeout",
"testExecutionId": null,
"stepName": null,
"totalHits": 3,
"page": 1,
"pageSize": 20,
"hits": [
{
"logEmbeddingId": "…",
"testExecutionId": "…",
"stepExecutionId": "…",
"stepName": "CommunicationCheck",
"chunkIndex": 0,
"textSnippet": "connection timeout error while waiting for device…",
"embeddedAt": "2025-06-01T14:32:11Z",
"rank": 0.0
}
]
}
}
Note:
rankis always0.0in Phase 3a. Relevance ranking usingts_rankwill be added when pgvector semantic search is introduced in Phase 3b.
Example prompts
"Search the logs for test execution <id> for any timeout errors"
→ search_execution_logs(query="timeout error", testExecutionId="<id>")
"Did any steps mention a calibration failure?"
→ search_execution_logs(query="calibration failure")
"Find all log chunks from the 'RF Calibration' step that mention drift"
→ search_execution_logs(query="drift", stepName="RF Calibration")
"Show me the second page of results for 'voltage out of range'"
→ search_execution_logs(query="voltage out of range", page=2)
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
totalHits: 0 for a recent test |
Logs not yet indexed (< 65 s since completion) | Wait and retry |
totalHits: 0 for a test from yesterday |
Orchestra was not running | Restart Orchestra; it will index on next poll |
connected: false from poll_test_progress |
Hub disconnected | Call connect_live_events again |
No statusEvents during a test |
connect_live_events was not called first |
Call it before starting the test |