You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
@loopover/contract defines two halves of an error-envelope contract that nothing joins up:
toolErrorFields (packages/loopover-contract/src/shared.ts:38-45) — "Fields an error envelope
carries when a tool fails in a way the caller can act on. code is drawn from a closed,
developer-defined set rather than free text so telemetry can break failures down by cause
(mcp(observability): PostHog error tracking, tracing, and usage telemetry at every dispatch chokepoint — redacted, gated, registry-driven #9525)". It is exported from packages/loopover-contract/src/index.ts:28 and has zero
consumers: grep -rn "toolErrorFields" finds the definition, the re-export, and one prose
mention in a comment. No tool output schema spreads it.
resolveErrorCode (packages/loopover-contract/src/telemetry.ts:244-261) — "Map a thrown error or an error envelope onto the closed code set", whose first branch reads envelope.code and
matches it against MCP_TELEMETRY_ERROR_CODES. No caller ever passes an envelope.
The result is that the code dimension is dead on the failure path that actually exists:
Remote (src/mcp/dispatch-telemetry.ts:99-112): on result.isError === true it emits errorCode: "unknown_error" as const — a literal, never resolveErrorCode.
Stdio (packages/loopover-mcp/lib/telemetry.ts:125-134): on the failure path it calls recordStdioDispatchTelemetry without an error field, so resolveErrorCode(undefined) at lib/telemetry.ts:175 returns unknown_error.
Miner (packages/loopover-miner/bin/loopover-miner-mcp.ts:127-131): it builds a typed
envelope on line 128 — { error: { code: toolErrorCode(error), message } } — and then on line 129
throws that away and passes the raw error to recordMinerDispatchTelemetry, which re-guesses a
code from the message regexes. So the caller is told store_unavailable while telemetry records not_found (an ENOENT: no such file message matches the not found|no such regex at telemetry.ts:254) — two different classifications of one failure, produced by adjacent lines.
#9518's requirement 5 asked for exactly this to be closed: "Error envelopes unified... every tool on
every server returns the same schema-described error envelope." The miner half landed
(withMinerToolErrorHandling); the schema-description and the telemetry half did not.
Requirements
store_unavailable is added to MCP_TELEMETRY_ERROR_CODES
(packages/loopover-contract/src/telemetry.ts:28-39). It is a developer-defined closed set and
this is the code the miner already returns to callers; adding it is what lets one code serve both
the envelope and the telemetry event.
All eleven miner output schemas in packages/loopover-contract/src/tools/miner.ts spread toolErrorFields, so the advertised outputSchema describes the envelope withMinerToolErrorHandling really returns instead of only the success shape.
withMinerToolErrorHandling passes the envelope it built to recordMinerDispatchTelemetry, so resolveErrorCode reads the declared code rather than re-deriving one from the message.
instrumentToolDispatch (src/mcp/dispatch-telemetry.ts) replaces the hardcoded "unknown_error" with resolveErrorCode applied to the result's structuredContent.error, and wrapStdioToolHandler (packages/loopover-mcp/lib/telemetry.ts) passes that same value as its error field on the failure path. Both must keep returning unknown_error when no envelope is
present.
The pinned assertions that encode today's behaviour are updated deliberately, not deleted: test/unit/mcp-dispatch-telemetry.test.ts:229-238 ("treats an error envelope as a failed call")
keeps its unknown_error case for an envelope-less isError result and gains a new case for an
envelope carrying a declared code.
Delete the empty import type { } from "@loopover/contract"; at src/mcp/dispatch-telemetry-sink.ts:18 while you are in the file.
⚠️ Required pattern: resolveErrorCode in packages/loopover-contract/src/telemetry.ts:244-261 is
the single classifier — its existing envelope.code branch is what gets wired up. Adding a second
per-server code-mapping function, widening MCP_TELEMETRY_ERROR_CODES to accept a caller-derived
string, or reading code out of the envelope without validating it against the closed set, do NOT
satisfy this issue.
Deliverables
store_unavailable in MCP_TELEMETRY_ERROR_CODES, with the closed-set test at test/unit/mcp-dispatch-telemetry.test.ts:200-203 still passing
All 11 miner output schemas in packages/loopover-contract/src/tools/miner.ts spread toolErrorFields; a test asserts an error-envelope structuredContent validates against each
tool's advertised output schema
withMinerToolErrorHandling passes its own envelope to recordMinerDispatchTelemetry; a test
asserts a store failure records error_code: "store_unavailable" and not not_found
instrumentToolDispatch and wrapStdioToolHandler resolve the code from the result's structuredContent.error, with tests for: envelope carrying a declared code, envelope carrying
an undeclared code (falls back to unknown_error), and no envelope at all
The empty import type at src/mcp/dispatch-telemetry-sink.ts:18 is gone
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example wiring the three chokepoints to read the envelope while leaving toolErrorFields unused, so
no tool's advertised schema ever declares the field being read — does not resolve this issue.
Test Coverage Requirements
99%+ Codecov patch coverage, branch-counted, applies to everything this touches: src/mcp/dispatch-telemetry.ts, src/mcp/dispatch-telemetry-sink.ts, packages/loopover-contract/src/telemetry.ts, packages/loopover-contract/src/shared.ts, packages/loopover-contract/src/tools/miner.ts, packages/loopover-mcp/lib/telemetry.ts and packages/loopover-miner/bin/loopover-miner-mcp.ts are all in coverage.include
(vitest.config.ts:55-100). Every new conditional needs both arms (envelope present vs absent;
declared code vs undeclared code), plus a named regression test for the miner's store_unavailable/not_found mismatch.
Expected Outcome
A PostHog breakdown of usage_event by error_code distinguishes real failure causes instead of
reporting every non-throwing failure as unknown_error; the miner's caller-visible error code and its
telemetry code are the same value; and toolErrorFields describes a shape that is actually
advertised.
Context
@loopover/contractdefines two halves of an error-envelope contract that nothing joins up:toolErrorFields(packages/loopover-contract/src/shared.ts:38-45) — "Fields an error envelopecarries when a tool fails in a way the caller can act on.
codeis drawn from a closed,developer-defined set rather than free text so telemetry can break failures down by cause
(mcp(observability): PostHog error tracking, tracing, and usage telemetry at every dispatch chokepoint — redacted, gated, registry-driven #9525)". It is exported from
packages/loopover-contract/src/index.ts:28and has zeroconsumers:
grep -rn "toolErrorFields"finds the definition, the re-export, and one prosemention in a comment. No tool output schema spreads it.
resolveErrorCode(packages/loopover-contract/src/telemetry.ts:244-261) — "Map a thrown erroror an error envelope onto the closed code set", whose first branch reads
envelope.codeandmatches it against
MCP_TELEMETRY_ERROR_CODES. No caller ever passes an envelope.The result is that the code dimension is dead on the failure path that actually exists:
src/mcp/dispatch-telemetry.ts:99-112): onresult.isError === trueit emitserrorCode: "unknown_error" as const— a literal, neverresolveErrorCode.packages/loopover-mcp/lib/telemetry.ts:125-134): on the failure path it callsrecordStdioDispatchTelemetrywithout anerrorfield, soresolveErrorCode(undefined)atlib/telemetry.ts:175returnsunknown_error.packages/loopover-miner/bin/loopover-miner-mcp.ts:127-131): it builds a typedenvelope on line 128 —
{ error: { code: toolErrorCode(error), message } }— and then on line 129throws that away and passes the raw
errortorecordMinerDispatchTelemetry, which re-guesses acode from the message regexes. So the caller is told
store_unavailablewhile telemetry recordsnot_found(anENOENT: no such filemessage matches thenot found|no suchregex attelemetry.ts:254) — two different classifications of one failure, produced by adjacent lines.#9518's requirement 5 asked for exactly this to be closed: "Error envelopes unified... every tool on
every server returns the same schema-described error envelope." The miner half landed
(
withMinerToolErrorHandling); the schema-description and the telemetry half did not.Requirements
store_unavailableis added toMCP_TELEMETRY_ERROR_CODES(
packages/loopover-contract/src/telemetry.ts:28-39). It is a developer-defined closed set andthis is the code the miner already returns to callers; adding it is what lets one code serve both
the envelope and the telemetry event.
packages/loopover-contract/src/tools/miner.tsspreadtoolErrorFields, so the advertisedoutputSchemadescribes the envelopewithMinerToolErrorHandlingreally returns instead of only the success shape.withMinerToolErrorHandlingpasses the envelope it built torecordMinerDispatchTelemetry, soresolveErrorCodereads the declaredcoderather than re-deriving one from the message.instrumentToolDispatch(src/mcp/dispatch-telemetry.ts) replaces the hardcoded"unknown_error"withresolveErrorCodeapplied to the result'sstructuredContent.error, andwrapStdioToolHandler(packages/loopover-mcp/lib/telemetry.ts) passes that same value as itserrorfield on the failure path. Both must keep returningunknown_errorwhen no envelope ispresent.
test/unit/mcp-dispatch-telemetry.test.ts:229-238("treats an error envelope as a failed call")keeps its
unknown_errorcase for an envelope-lessisErrorresult and gains a new case for anenvelope carrying a declared
code.import type { } from "@loopover/contract";atsrc/mcp/dispatch-telemetry-sink.ts:18while you are in the file.Deliverables
store_unavailableinMCP_TELEMETRY_ERROR_CODES, with the closed-set test attest/unit/mcp-dispatch-telemetry.test.ts:200-203still passingpackages/loopover-contract/src/tools/miner.tsspreadtoolErrorFields; a test asserts an error-envelopestructuredContentvalidates against eachtool's advertised output schema
withMinerToolErrorHandlingpasses its own envelope torecordMinerDispatchTelemetry; a testasserts a store failure records
error_code: "store_unavailable"and notnot_foundinstrumentToolDispatchandwrapStdioToolHandlerresolve the code from the result'sstructuredContent.error, with tests for: envelope carrying a declared code, envelope carryingan undeclared code (falls back to
unknown_error), and no envelope at allimport typeatsrc/mcp/dispatch-telemetry-sink.ts:18is goneAll Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example wiring the three chokepoints to read the envelope while leaving
toolErrorFieldsunused, sono tool's advertised schema ever declares the field being read — does not resolve this issue.
Test Coverage Requirements
99%+ Codecov patch coverage, branch-counted, applies to everything this touches:
src/mcp/dispatch-telemetry.ts,src/mcp/dispatch-telemetry-sink.ts,packages/loopover-contract/src/telemetry.ts,packages/loopover-contract/src/shared.ts,packages/loopover-contract/src/tools/miner.ts,packages/loopover-mcp/lib/telemetry.tsandpackages/loopover-miner/bin/loopover-miner-mcp.tsare all incoverage.include(
vitest.config.ts:55-100). Every new conditional needs both arms (envelope present vs absent;declared code vs undeclared code), plus a named regression test for the miner's
store_unavailable/not_foundmismatch.Expected Outcome
A PostHog breakdown of
usage_eventbyerror_codedistinguishes real failure causes instead ofreporting every non-throwing failure as
unknown_error; the miner's caller-visible error code and itstelemetry code are the same value; and
toolErrorFieldsdescribes a shape that is actuallyadvertised.
Links & Resources
packages/loopover-contract/src/shared.ts:31-45,packages/loopover-contract/src/telemetry.ts:21-40, 238-261src/mcp/dispatch-telemetry.ts:88-133,src/mcp/dispatch-telemetry-sink.ts:18packages/loopover-mcp/lib/telemetry.ts:114-197packages/loopover-miner/bin/loopover-miner-mcp.ts:73-132test/unit/mcp-dispatch-telemetry.test.ts:196-255