Skip to content

Commit 5d0f866

Browse files
authored
fix(mcp): stop idle OAuth callback server (anomalyco#32245)
1 parent 98d66e9 commit 5d0f866

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

packages/opencode/src/mcp/oauth-callback.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ function cleanupStateIndex(oauthState: string) {
7171
}
7272
}
7373

74+
function stopIfIdle() {
75+
if (pendingAuths.size > 0 || !server) return
76+
77+
server.close()
78+
server = undefined
79+
}
80+
7481
function handleRequest(req: import("http").IncomingMessage, res: import("http").ServerResponse) {
7582
const url = new URL(req.url || "/", `http://localhost:${currentPort}`)
7683

@@ -104,6 +111,7 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http").
104111
}
105112
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
106113
res.end(HTML_ERROR(errorMsg))
114+
stopIfIdle()
107115
return
108116
}
109117

@@ -130,6 +138,7 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http").
130138

131139
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
132140
res.end(HTML_SUCCESS)
141+
stopIfIdle()
133142
}
134143

135144
export async function ensureRunning(redirectUri?: string): Promise<void> {
@@ -168,6 +177,7 @@ export function waitForCallback(oauthState: string, mcpName?: string): Promise<s
168177
pendingAuths.delete(oauthState)
169178
if (mcpName) mcpNameToState.delete(mcpName)
170179
reject(new Error("OAuth callback timeout - authorization took too long"))
180+
stopIfIdle()
171181
}
172182
}, CALLBACK_TIMEOUT_MS)
173183

@@ -185,6 +195,7 @@ export function cancelPending(mcpName: string): void {
185195
pendingAuths.delete(key)
186196
mcpNameToState.delete(mcpName)
187197
pending.reject(new Error("Authorization cancelled"))
198+
stopIfIdle()
188199
}
189200
}
190201

packages/opencode/test/mcp/oauth-callback.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ describe("McpOAuthCallback.ensureRunning", () => {
3232
expect(McpOAuthCallback.isRunning()).toBe(true)
3333
})
3434

35+
test("stops after the callback completes", async () => {
36+
const redirectUri = "http://127.0.0.1:18003/custom/callback"
37+
await McpOAuthCallback.ensureRunning(redirectUri)
38+
const callback = McpOAuthCallback.waitForCallback("success")
39+
40+
const response = await fetch(`${redirectUri}?code=code&state=success`)
41+
42+
expect(response.status).toBe(200)
43+
expect(await callback).toBe("code")
44+
expect(McpOAuthCallback.isRunning()).toBe(false)
45+
})
46+
3547
test("escapes provider error markup in callback HTML", async () => {
3648
const redirectUri = "http://127.0.0.1:18001/custom/callback"
3749
await McpOAuthCallback.ensureRunning(redirectUri)

0 commit comments

Comments
 (0)