diff --git a/.changeset/fix-workers-observability-outcome-optional.md b/.changeset/fix-workers-observability-outcome-optional.md new file mode 100644 index 00000000..1ff6f761 --- /dev/null +++ b/.changeset/fix-workers-observability-outcome-optional.md @@ -0,0 +1,7 @@ +--- +'@repo/mcp-common': patch +--- + +fix(mcp-common): make `outcome` optional in the Workers Observability response schema + +`zCloudflareMiniEvent` (`packages/mcp-common/src/types/workers-logs.types.ts`) required every event to carry `outcome`, but `outcome` only describes a whole invocation and is absent on the `console.log` lines emitted inside one. Because `query_worker_observability` validates the entire event array in a single `.parse()`, any response containing one of these non-invocation events was rejected outright and the tool returned no logs at all. `outcome` is now optional so responses with a mix of invocation and non-invocation events parse correctly. diff --git a/apps/workers-observability/src/types/workers-logs.types.spec.ts b/apps/workers-observability/src/types/workers-logs.types.spec.ts new file mode 100644 index 00000000..185ad179 --- /dev/null +++ b/apps/workers-observability/src/types/workers-logs.types.spec.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest' + +import { zCloudflareMiniEvent, zReturnedQueryRunEvents } from './workers-logs.types' + +describe('zCloudflareMiniEvent', () => { + it('parses a console.log event with no outcome', () => { + const event = { + event: {}, + scriptName: 'my-worker', + eventType: 'cron', + requestId: '1RI7X6A7OCMC159U', + } + expect(() => zCloudflareMiniEvent.parse(event)).not.toThrow() + }) + + it('still parses an invocation-summary event with an outcome', () => { + const event = { + event: {}, + scriptName: 'my-worker', + eventType: 'cron', + requestId: '1RI7X6A7OCMC159U', + outcome: 'ok', + } + expect(zCloudflareMiniEvent.parse(event).outcome).toBe('ok') + }) +}) + +describe('zReturnedQueryRunEvents', () => { + it('does not discard the batch when one event has no outcome', () => { + const telemetryEvent = (workers: Record) => ({ + dataset: 'cloudflare-workers', + timestamp: 1784222146000, + source: 'log line', + $workers: { + event: {}, + scriptName: 'my-worker', + eventType: 'cron', + requestId: '1RI7X6A7OCMC159U', + ...workers, + }, + $metadata: { id: 'evt-1' }, + }) + const result = zReturnedQueryRunEvents.parse({ + events: [telemetryEvent({}), telemetryEvent({ outcome: 'ok' })], + }) + expect(result.events).toHaveLength(2) + }) +}) diff --git a/apps/workers-observability/src/types/workers-logs.types.ts b/apps/workers-observability/src/types/workers-logs.types.ts index 226bdc1f..ca643cda 100644 --- a/apps/workers-observability/src/types/workers-logs.types.ts +++ b/apps/workers-observability/src/types/workers-logs.types.ts @@ -248,7 +248,7 @@ const zCloudflareMiniEventDetails = z.object({ export const zCloudflareMiniEvent = z.object({ event: zCloudflareMiniEventDetails, scriptName: z.string(), - outcome: z.string(), + outcome: z.string().optional(), eventType: z.enum([ 'fetch', 'scheduled',