fix(opencode): recover expired MCP sessions (#32088)
This commit is contained in:
parent
45e4606fa4
commit
c7dee9c609
5 changed files with 481 additions and 1 deletions
50
packages/opencode/test/fixture/mcp-session-recovery.ts
Normal file
50
packages/opencode/test/fixture/mcp-session-recovery.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js"
|
||||
|
||||
const posts: Array<{ method: string; session: string | null }> = []
|
||||
let initializeCount = 0
|
||||
let pingCount = 0
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
async fetch(request) {
|
||||
if (request.method === "GET") return new Response(null, { status: 405 })
|
||||
if (request.method === "DELETE") return new Response(null, { status: 200 })
|
||||
|
||||
const message = (await request.json()) as { id?: number; method: string }
|
||||
const session = request.headers.get("mcp-session-id")
|
||||
posts.push({ method: message.method, session })
|
||||
|
||||
if (message.method === "initialize") {
|
||||
initializeCount++
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: message.id,
|
||||
result: {
|
||||
protocolVersion: LATEST_PROTOCOL_VERSION,
|
||||
capabilities: {},
|
||||
serverInfo: { name: "test", version: "1" },
|
||||
},
|
||||
},
|
||||
{ headers: { "mcp-session-id": initializeCount === 1 ? "expired" : "replacement" } },
|
||||
)
|
||||
}
|
||||
|
||||
if (message.method === "notifications/initialized") return new Response(null, { status: 202 })
|
||||
|
||||
pingCount++
|
||||
if (pingCount === 1) return new Response("Session not found", { status: 404 })
|
||||
return Response.json({ jsonrpc: "2.0", id: message.id, result: {} })
|
||||
},
|
||||
})
|
||||
const client = new Client({ name: "test", version: "1" })
|
||||
|
||||
try {
|
||||
await client.connect(new StreamableHTTPClientTransport(server.url))
|
||||
await client.ping()
|
||||
process.stdout.write(JSON.stringify(posts))
|
||||
} finally {
|
||||
await client.close()
|
||||
server.stop(true)
|
||||
}
|
||||
27
packages/opencode/test/mcp/session-recovery.test.ts
Normal file
27
packages/opencode/test/mcp/session-recovery.test.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import path from "node:path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
describe("mcp session recovery", () => {
|
||||
test("reinitializes and retries once after a session-bound POST returns 404", async () => {
|
||||
const child = Bun.spawn([process.execPath, path.join(import.meta.dir, "../fixture/mcp-session-recovery.ts")], {
|
||||
cwd: path.join(import.meta.dir, "../.."),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
const [code, stdout, stderr] = await Promise.all([
|
||||
child.exited,
|
||||
Bun.readableStreamToText(child.stdout),
|
||||
Bun.readableStreamToText(child.stderr),
|
||||
])
|
||||
|
||||
expect(code, stderr).toBe(0)
|
||||
expect(JSON.parse(stdout)).toEqual([
|
||||
{ method: "initialize", session: null },
|
||||
{ method: "notifications/initialized", session: "expired" },
|
||||
{ method: "ping", session: "expired" },
|
||||
{ method: "initialize", session: null },
|
||||
{ method: "notifications/initialized", session: "replacement" },
|
||||
{ method: "ping", session: "replacement" },
|
||||
])
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue