different-ai/openwork · error · ProbeFailure

MCP_INITIALIZED

MCP_INITIALIZED

Error message

Initialized notification returned HTTP ${initializedResponse.status}

What it means

Thrown when the notifications/initialized request returns any status other than the expected 202 (and not 404, which is classified as session expiry instead). MCP streamable-HTTP servers must acknowledge notifications with 202 Accepted; any other status means the lifecycle step did not complete as required.

Source

Thrown at packages/enterprise-mcp-mock-server/src/testing/probe.ts:789

    recordPassed(phases, "MCP_INITIALIZE", startedAt, "MCP version, capabilities, and session negotiated")

    const sessionHeaders = {
      ...rpcHeaders,
      "mcp-session-id": sessionId,
      "mcp-protocol-version": initialize.protocolVersion,
    }
    startedAt = Date.now()
    const initializedResponse = await fetchStep(mcpUrl, {
      method: "POST",
      headers: sessionHeaders,
      body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
    }, "MCP_INITIALIZED", overallDeadline)
    await discardResponseBody(initializedResponse, "MCP_INITIALIZED", "mcp_lifecycle")
    if (initializedResponse.status === 404) {
      throw new ProbeFailure("CONTINUITY_SESSION", "mcp_session_expired", "MCP session expired after initialize")
    }
    if (initializedResponse.status !== 202) {
      throw new ProbeFailure("MCP_INITIALIZED", "mcp_lifecycle", `Initialized notification returned HTTP ${initializedResponse.status}`)
    }
    recordPassed(phases, "MCP_INITIALIZED", startedAt, "Initialized notification received HTTP 202")

    startedAt = Date.now()
    const toolNames = new Set<string>()
    const discoveredTools: MockTool[] = []
    const cursors = new Set<string>()
    let cursor: string | undefined
    let catalogComplete = false
    for (let page = 0; page < 25; page += 1) {
      if (cursor) {
        if (cursors.has(cursor)) throw new ProbeFailure("MCP_TOOL_DISCOVERY", "mcp_pagination_loop", "Tool catalog repeated a cursor")
        cursors.add(cursor)
      }
      const listRawResponse = await fetchStep(mcpUrl, {
          method: "POST",
          headers: sessionHeaders,
          body: JSON.stringify({ jsonrpc: "2.0", id: 10 + page, method: "tools/list", params: cursor ? { cursor } : {} }),

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Update the server to return 202 Accepted for JSON-RPC notifications (no id)
  2. Check gateway/proxy rules that may reject id-less POST bodies
  3. Inspect the response body/status server-side for an internal error and fix it
  4. Confirm the server implements the streamable-HTTP transport, not an older one

Example fix

// before (server)
handleJsonRpc(body); res.status(200).json(result)
// after (server)
if (!("id" in body)) { handleNotification(body); res.status(202).end() }
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  await probeEnterpriseMcpMockServer(scenario)
} catch (e) {
  if (e instanceof ProbeFailure && e.code === "MCP_INITIALIZED") {
    console.error("Initialized notification rejected:", e.message)
  }
}

Prevention

When it happens

Trigger: The initialized notification POST returns 200, 400, 405, 500, etc. — e.g. the server treats notifications as regular JSON-RPC requests, rejects the body, or errors internally.

Common situations: Server implemented against an older HTTP transport that replies 200 to notifications; a gateway that rejects POSTs without an id field (notifications have no id); server bug returning 500 on notification handling; wrong content-type negotiation causing 415.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/103f29873e188d08. Report an issue: GitHub.