koala73/worldmonitor · error

get-china-decision-signals returned no canonical payload

Error message

get-china-decision-signals returned no canonical payload

What it means

Plain Error thrown in the get-china-decision-signals tool (api/mcp/registry/rpc-tools.ts:703): the downstream fetch passed auth and returned 200 (assertMcpToolFetchOk did not throw), but response.json() lacks a string payloadJson field. This is a contract violation between the MCP tool and the GET /api/intelligence/v1/get-china-decision-signals handler — the canonical wire shape is { payloadJson: string } and anything else (error envelope with 200, unexpected JSON, missing field) fails fast here rather than corrupting downstream snapshot logic.

Solutions

  1. curl GET /api/intelligence/v1/get-china-decision-signals directly with valid auth and inspect the JSON — confirm payloadJson exists and is a string
  2. If the handler's response shape changed, update the wire expectation and the isChinaDecisionSignalSnapshot guard together, then redeploy both surfaces
  3. Check for intermediaries returning 200-wrapped error bodies (gateway/proxy config)
  4. Redeploy the lagging surface so registry and handler are version-aligned
Defensive patterns

Strategy: try-catch

Type guard

function isMissingCanonicalPayload(e) {
  return e instanceof Error
    && e.message === 'get-china-decision-signals returned no canonical payload';
}

Try / catch

try {
  const snapshot = await fetchChinaDecisionSignals(context, execution);
} catch (e) {
  if (isMissingCanonicalPayload(e)) {
    // Contract drift between registry and handler — not transient. Probe once to confirm:
    const probe = await fetch(url, { headers: auth }).then(r => r.json());
    logContractMismatch('get-china-decision-signals', Object.keys(probe));
    throw new Error('Downstream contract drift; payloadJson missing', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Deploying the MCP registry and the intelligence API handler out of sync: handler version that does not yet emit payloadJson, an intermediary (proxy/WAF) returning a 200 JSON body of its own, or a handler bug returning an error envelope without the right status code.

Common situations: Partial deploy where api/ routes lag the MCP registry (or vice versa). Staged rollout hitting mixed versions. A gateway rewrite that swallows the handler's error status and returns 200 with an error body. Local dev proxying to a stale backend.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-21). Data as JSON: /api/errors/63a506120cde3161. Report an issue: GitHub.

Appendix: source

Thrown at api/mcp/registry/rpc-tools.ts:1126

      },
    },
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    _execute: async (_params, base, context, execution) => {
      const url = `${base}/api/intelligence/v1/get-china-decision-signals`;
      const auth = await buildAuthHeaders(context, 'GET', url, null);
      const response = await fetchMcpDownstream(url, {
        headers: { ...auth, 'User-Agent': 'worldmonitor-mcp-edge/1.0' },
        signal: AbortSignal.timeout(12_000),
      }, execution);
      await assertMcpToolFetchOk(response, {
        operation: 'get-china-decision-signals',
        tool: 'get_china_decision_signals',
        auth: context,
        execution,
      });
      const wire = await response.json() as { payloadJson?: unknown };
      if (typeof wire.payloadJson !== 'string') {
        throw new Error('get-china-decision-signals returned no canonical payload');
      }
      const payload = JSON.parse(wire.payloadJson) as unknown;
      if (!isChinaDecisionSignalSnapshot(payload)) {
        throw new Error('get-china-decision-signals returned an invalid canonical payload');
      }
      return payload;
    },
    _coverageKeys: [
      'china:policy-events:v1',
      'military:cross-strait-activity:v1',
      'military:cross-strait-activity-bootstrap:v1',
      'market:china:corporate-disclosures:v1',
      'intelligence:china-decision-signals:v1',
    ],
    _apiPaths: [
      'GET /api/intelligence/v1/get-china-decision-signals',
    ],
  },

View on GitHub (pinned to e586b8b4b8)