Summary
The observability MCP server (https://observability.mcp.cloudflare.com/mcp) cannot return any cron-triggered Worker event. Its response schema requires $workers.outcome to be a string, but the underlying Cloudflare telemetry API omits that field entirely for eventType: "cron" events. The tool therefore rejects valid data returned by the very API it wraps, and cron/scheduled Worker invocations become unqueryable through MCP.
Reproduce
Any Worker with a [triggers] crons schedule and observability enabled. Call query_worker_observability:
{
"view": "events",
"queryId": "repro",
"limit": 20,
"dry": false,
"parameters": {
"datasets": ["cloudflare-workers"],
"filters": [
{ "key": "$metadata.service", "operation": "eq", "type": "string", "value": "<worker-name>" },
{ "key": "$metadata.origin", "operation": "eq", "type": "string", "value": "cron" }
]
},
"timeframe": { "reference": "<now>", "offset": "-24h" }
}
Actual
Error analyzing worker logs: [
{
"code": "invalid_union",
"errors": [[{ "expected": "string", "code": "invalid_type",
"path": ["outcome"],
"message": "Invalid input: expected string, received undefined" }]],
"path": ["result", "events", "events", 0, "$workers"],
"message": "Invalid input"
},
...
]
Note the error names events[0], events[2], etc. — the events were retrieved; only deserialization fails.
Expected
The cron events are returned, as they are via the REST API.
Root cause
POST /accounts/{account_id}/workers/observability/telemetry/query returns different $workers shapes by event type. Same Worker, same account, same timeframe:
| eventType |
$workers keys |
outcome |
cron |
truncated, event, scriptName, eventType, scriptVersion, executionModel, requestId |
absent |
fetch |
event, truncated, scriptName, outcome, eventType, executionModel, scriptVersion, requestId, cpuTimeMs, wallTimeMs |
"ok" |
Cron events also omit cpuTimeMs and wallTimeMs.
The MCP layer's zod schema for $workers requires outcome, so every cron event fails validation. The REST endpoint returns these same events without complaint, which is the workaround (see below).
Suggested fix
Make outcome optional in the $workers schema (and likely cpuTimeMs / wallTimeMs too), or discriminate the schema on eventType.
Impact
Cron-triggered Workers cannot be observed through MCP at all. This matters for scheduled jobs, where the log line emitted by the scheduled() handler is often the only record that the job ran and what it decided — there is no request/response to inspect, and wrangler tail only shows live traffic, so a missed window is unrecoverable.
Secondary note
$metadata.origin for cron-triggered events is "cron", not "scheduled". Filtering on "scheduled" returns an empty array rather than an error, which is indistinguishable from "the cron never fired" — a misleading result when debugging a scheduled job. Worth documenting in the tool description alongside the existing $metadata.origin guidance.
Workaround
Query the REST API directly; it returns the events correctly:
POST /accounts/{account_id}/workers/observability/telemetry/query
{
"queryId": "…", "view": "events", "limit": 25, "dry": false,
"parameters": {
"datasets": ["cloudflare-workers"],
"filters": [
{ "id": "1", "key": "$metadata.service", "operation": "eq", "type": "string", "value": "<worker>" },
{ "id": "2", "key": "$metadata.origin", "operation": "eq", "type": "string", "value": "cron" }
],
"calculations": [], "groupBys": [], "havings": []
},
"timeframe": { "from": <epoch-ms>, "to": <epoch-ms> }
}
(The REST endpoint wants numeric epoch-ms from/to and id on each filter; the MCP tool accepts ISO strings and omits id.)
Summary
The observability MCP server (
https://observability.mcp.cloudflare.com/mcp) cannot return any cron-triggered Worker event. Its response schema requires$workers.outcometo be a string, but the underlying Cloudflare telemetry API omits that field entirely foreventType: "cron"events. The tool therefore rejects valid data returned by the very API it wraps, and cron/scheduled Worker invocations become unqueryable through MCP.Reproduce
Any Worker with a
[triggers] cronsschedule and observability enabled. Callquery_worker_observability:{ "view": "events", "queryId": "repro", "limit": 20, "dry": false, "parameters": { "datasets": ["cloudflare-workers"], "filters": [ { "key": "$metadata.service", "operation": "eq", "type": "string", "value": "<worker-name>" }, { "key": "$metadata.origin", "operation": "eq", "type": "string", "value": "cron" } ] }, "timeframe": { "reference": "<now>", "offset": "-24h" } }Actual
Note the error names
events[0],events[2], etc. — the events were retrieved; only deserialization fails.Expected
The cron events are returned, as they are via the REST API.
Root cause
POST /accounts/{account_id}/workers/observability/telemetry/queryreturns different$workersshapes by event type. Same Worker, same account, same timeframe:$workerskeysoutcomecrontruncated, event, scriptName, eventType, scriptVersion, executionModel, requestIdfetchevent, truncated, scriptName, outcome, eventType, executionModel, scriptVersion, requestId, cpuTimeMs, wallTimeMs"ok"Cron events also omit
cpuTimeMsandwallTimeMs.The MCP layer's zod schema for
$workersrequiresoutcome, so every cron event fails validation. The REST endpoint returns these same events without complaint, which is the workaround (see below).Suggested fix
Make
outcomeoptional in the$workersschema (and likelycpuTimeMs/wallTimeMstoo), or discriminate the schema oneventType.Impact
Cron-triggered Workers cannot be observed through MCP at all. This matters for scheduled jobs, where the log line emitted by the
scheduled()handler is often the only record that the job ran and what it decided — there is no request/response to inspect, andwrangler tailonly shows live traffic, so a missed window is unrecoverable.Secondary note
$metadata.originfor cron-triggered events is"cron", not"scheduled". Filtering on"scheduled"returns an empty array rather than an error, which is indistinguishable from "the cron never fired" — a misleading result when debugging a scheduled job. Worth documenting in the tool description alongside the existing$metadata.originguidance.Workaround
Query the REST API directly; it returns the events correctly:
(The REST endpoint wants numeric epoch-ms
from/toandidon each filter; the MCP tool accepts ISO strings and omitsid.)