mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
57775d6ba2
Telemetry follow-ups from the 7d tool-quality review: - tool_call log lines now carry a capped errorMessage (thrown message or leading toolError text) so error drilldowns name the failure class instead of guessing from args; absent on success. - get_rule_stats omits per-rule daily trend rows by default (they drove 50% of calls over the response cap at platform scope); new includeTrends arg opts back in, trendsNote marks the omission. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.5 KiB
4.5 KiB
type, title, description, resource, tags, timestamp
| type | title | description | resource | tags | timestamp | ||||
|---|---|---|---|---|---|---|---|---|---|
| concept | MCP Tool Telemetry | The three telemetry layers for judging MCP tool quality — backend App Insights via X-MCP-Tool-Name, the per-user usage table, and the MCP_TOOL_LOGGING stderr lines that carry the signals the backend cannot see (soft errors, result size vs. cap, zero-hit searches, Zod rejections). | src/McpServer/autopilot-monitor-mcp/src/telemetry.ts |
|
2026-08-05 |
Schema
Three layers answer "are the MCP tools doing their job, and are tools missing?":
- Backend App Insights — every tool handler runs inside
withToolTelemetry(src/telemetry.ts), which propagates the tool name via AsyncLocalStorage soapiFetchstampsX-MCP-Tool-Nameon each backend request.RequestTelemetryMiddlewarecopies it into the request telemetry asMcpToolName: per-tool call volume, latency percentiles and HTTP failure rates by KQL. Blind spot: tools that never call the backend, and handler-level soft errors (see layer 3). - Usage table —
McpQuotaEnforcementMiddleware.TrackUsagecounts per user/day under the keytoolname:endpoint; surfaced by theget_api_usagetool. Counts only, every call counts as success. - stderr JSON lines (
MCP_TOOL_LOGGING=true, queryable via Container App Logs) — the layer that carries what the other two structurally cannot:tool_call: duration,isErrorincluding soft errors (handlers never throw —toolErrorreturnsisError: true, so backend-only telemetry records those calls as successes),errorMessage(capped; the thrown message or the first lines of thetoolErrortext, which name the error class — HTTP status / timeout / auth / not found; absent on success),resultChars+overCap(result exceeds the tool'santhropic/maxResultSizeCharshint → the host truncates it; a frequently-overCap tool needs tighter defaults or projections), a size-bounded args summary, and the caller scope (ga/delegated/tenant). This is the ONLY telemetry for backend-free tools:search_docs,validate_rule,get_resource, the local ranking ofsearch_knowledge.search_zero_hit: query text (capped) wheneversearch_events/search_knowledge/search_docsreturn zero results — the most direct "missing docs / missing knowledge / missing tool?" demand signal.tool_call_rejected: rejections the SDK answers before any handler runs (Zod argument validation, unknown/disabled tool). Two non-obvious facts, both found by live verification: SDK 1.30 wraps these McpErrors as a successful JSON-RPC response whose result is{ isError: true, text: "MCP error <code>: …" }— a JSON-RPC error envelope never exists for them — and the Hono bridge (getRequestListener) may stream the body asres.writechunks with a bareres.end(). The sniffer in the/mcproute therefore wraps bothwriteandend, reassembles up to 10 KB and matches both shapes (attachToolCallRejectionSnifferintelemetry.ts). Handler soft errors never carry theMCP errorprefix, so they are not double-logged. This is the strongest "the schema or description confuses the model" signal and was previously invisible everywhere.
The env flag defaults to true in infra/mcp-server.bicep (documented desired
state — the template is not routinely deployed; the live flag is set with
az containerapp update --set-env-vars MCP_TOOL_LOGGING=true).
Examples
Count zero-hit queries to find unmet demand (Log Analytics):
ContainerAppConsoleLogs_CL
| where Log_s has '"type":"search_zero_hit"'
| extend d = parse_json(Log_s)
| summarize count() by tostring(d.tool), tostring(d.query)
Per-tool soft-error and truncation rates:
ContainerAppConsoleLogs_CL
| where Log_s has '"type":"tool_call"'
| extend d = parse_json(Log_s)
| summarize calls = count(), errors = countif(d.isError == true),
overCap = countif(d.overCap == true), p95ms = percentile(tolong(d.durationMs), 95)
by tool = tostring(d.tool)
Citations
src/McpServer/autopilot-monitor-mcp/src/telemetry.ts— wrapper + log emitterssrc/McpServer/autopilot-monitor-mcp/src/index.ts—/mcprejection sniffersrc/Backend/AutopilotMonitor.Functions/Middleware/RequestTelemetryMiddleware.cs—McpToolNamestampingsrc/Backend/AutopilotMonitor.Functions/Middleware/McpQuotaEnforcementMiddleware.cs— usage-table counting