different-ai/openwork · warning

MCP_SESSION_NOT_FOUND

MCP_SESSION_NOT_FOUND

Error message

MCP_SESSION_NOT_FOUND

What it means

MCP_SESSION_NOT_FOUND is produced when an MCP request returns HTTP 404 while the gateway knows a session had been established (input.hasSession). The classifier treats this as a CONTINUITY_SESSION / mcp_session_expired problem: the session id the client is presenting is no longer recognized by the server. Unlike a plain endpoint 404, it is retryable because reinitializing the session should restore operation.

Source

Thrown at ee/apps/den-api/src/capability-sources/external-mcp-diagnostics.ts:899

    ...(input.providerRequestId ? { providerRequestId: input.providerRequestId } : {}),
  })
}

function httpClassification(input: {
  phase: ExternalMcpDiagnosticPhase
  status: number
  hasAuthorization: boolean
  bearerChallenge: boolean
  insufficientScope: boolean
  hasSession: boolean
  contentType: string
}): Classification | null {
  const { phase, status } = input
  if (status === 404 && input.hasSession) {
    return {
      phase: "CONTINUITY_SESSION",
      category: "mcp_session_expired",
      code: "MCP_SESSION_NOT_FOUND",
      retryable: true,
      actionOwner: "openwork",
      operatorAction: "Reinitialize the MCP session, then retry the operation once.",
    }
  }
  if (status === 404 && phase.startsWith("MCP_")) {
    return {
      phase: "HTTP_ROUTING",
      category: "endpoint_not_found",
      code: "MCP_HTTP_404",
      retryable: false,
      actionOwner: "organization_admin",
      operatorAction: "Verify the complete MCP endpoint path, including any provider tenant or instance prefix.",
    }
  }
  if ((status === 406 || status === 415) && phase.startsWith("MCP_")) {
    return {
      phase: "MCP_TRANSPORT",

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Reinitialize the MCP session (new initialize handshake to obtain a fresh Mcp-Session-Id).
  2. Retry the failed operation once with the new session, as the diagnostic prescribes.
  3. If it recurs, shorten the interval between calls or enable session keep-alive; verify the load balancer is not splitting session traffic.

Example fix

// before: reusing a stale session id
const res = await fetch(mcpUrl, { headers: { 'Mcp-Session-Id': staleId, ... } })
// after: reinitialize and reuse the new session id
const init = await initialize(mcpUrl)
const res = await fetch(mcpUrl, { headers: { 'Mcp-Session-Id': init.sessionId, ... } })
Defensive patterns

Strategy: retry

Validate before calling

// check session freshness before reuse
async function sessionValid(url: string, sessionId: string): Promise<boolean> {
  const res = await fetch(url, { method: 'GET', headers: { 'Mcp-Session-Id': sessionId } })
  return res.status !== 404
}

Type guard

function isSessionExpired(d: { code: string; category?: string }): boolean {
  return d.code === 'MCP_SESSION_NOT_FOUND' || d.category === 'mcp_session_expired'
}

Try / catch

try {
  return await withSession(sessionId, () => client.callTool(req))
} catch (e) {
  if (isSessionExpired(e.diagnostic)) {
    const fresh = await initialize(mcpUrl) // one reinitialize + single retry
    return await withSession(fresh.sessionId, () => client.callTool(req))
  }
  throw e
}

Prevention

When it happens

Trigger: Any request sent during an MCP phase (e.g. MCP_TOOL_EXECUTION or MCP_TOOL_DISCOVERY) with a previously negotiated Mcp-Session-Id header where the server responds 404, and hasSession is true. Typical call: client.callTool(...) on a streamable-HTTP MCP connection after the server dropped the session.

Common situations: Server restart or redeploy between calls invalidating in-memory sessions; server-side session TTL/timeout expiring during long pauses; load balancer routing to a replica without the session state; provider upgraded and rotated session stores.

Related errors


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