Skip to content

Commit c292149

Browse files
waleedlatif1claude
andcommitted
fix(mcp): release probe session, clean up refresh-lock chain
- detectMcpAuthType now reads the Mcp-Session-Id response header and fires a best-effort DELETE to release the streamable-HTTP session the probe just allocated. Failures are ignored; servers will time the session out anyway. - Rewrite the refresh-lock chain to `prev.catch(() => undefined).then(() => fn())` so the predecessor's rejection reason is explicitly discarded rather than threaded into fn as an ignored argument. Each caller's outcome stays its own. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 08fe845 commit c292149

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

apps/sim/lib/mcp/oauth/probe.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export async function detectMcpAuthType(url: string): Promise<McpAuthType> {
4242
signal: controller.signal,
4343
})
4444

45+
const sessionId = res.headers.get('mcp-session-id')
46+
if (sessionId) {
47+
void closeMcpSession(url, sessionId)
48+
}
49+
4550
if (res.status === 401) {
4651
const params = extractWWWAuthenticateParams(res)
4752
if (params.resourceMetadataUrl || params.scope || params.error) {
@@ -59,3 +64,25 @@ export async function detectMcpAuthType(url: string): Promise<McpAuthType> {
5964
clearTimeout(timer)
6065
}
6166
}
67+
68+
/**
69+
* Best-effort DELETE to release the streamable-HTTP session the probe just
70+
* allocated. Failures are ignored — the session will expire on the server side.
71+
*/
72+
async function closeMcpSession(url: string, sessionId: string): Promise<void> {
73+
try {
74+
const controller = new AbortController()
75+
const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS)
76+
try {
77+
await fetch(url, {
78+
method: 'DELETE',
79+
headers: { 'Mcp-Session-Id': sessionId },
80+
signal: controller.signal,
81+
})
82+
} finally {
83+
clearTimeout(timer)
84+
}
85+
} catch {
86+
// Ignore — best-effort cleanup
87+
}
88+
}

apps/sim/lib/mcp/oauth/storage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ const refreshLocks = new Map<string, Promise<unknown>>()
232232

233233
export async function withMcpOauthRefreshLock<T>(rowId: string, fn: () => Promise<T>): Promise<T> {
234234
const prev = refreshLocks.get(rowId) ?? Promise.resolve()
235-
const next = prev.then(fn, fn)
235+
// Wait for the predecessor to settle (success or failure), discard its
236+
// value/error, then run fn. Each caller awaits its own fn's outcome — errors
237+
// do not propagate across callers in the chain.
238+
const next = prev.catch(() => undefined).then(() => fn())
236239
refreshLocks.set(rowId, next)
237240
const cleanup = () => {
238241
if (refreshLocks.get(rowId) === next) refreshLocks.delete(rowId)

0 commit comments

Comments
 (0)