From f9081644035dbdcdff0464d55543a55620a34a23 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Jun 2026 10:19:48 +0000 Subject: [PATCH] feat(mcp): add 30s request timeouts to prevent Durable Object stalls - Add AbortSignal.timeout(30_000) to fetch calls in getProjects() and getDocuments() in apps/mcp/src/client.ts - Pass timeout: 30_000 to the Supermemory SDK constructor so SDK calls (search, add, forget, profile) are bounded - Add AbortSignal.timeout(30_000) to fetch in validateApiKey() and validateOAuthToken() in apps/mcp/src/auth.ts - Add AbortSignal.timeout(30_000) to the /.well-known/oauth-authorization-server proxy fetch in apps/mcp/src/index.ts - Add AbortError/TimeoutError guard in handleError() that maps timeout errors to a clear user-facing message Fixes unbounded upstream fetches that blocked the SupermemoryMCP Durable Object event loop causing durable_object_subrequest wall-time spikes. Co-authored-by: Dhravya Shah --- apps/mcp/src/auth.ts | 2 ++ apps/mcp/src/client.ts | 16 ++++++++++++++++ apps/mcp/src/index.ts | 1 + 3 files changed, 19 insertions(+) diff --git a/apps/mcp/src/auth.ts b/apps/mcp/src/auth.ts index 7bd3b946..ee0fa5a1 100644 --- a/apps/mcp/src/auth.ts +++ b/apps/mcp/src/auth.ts @@ -32,6 +32,7 @@ export async function validateApiKey( headers: { Authorization: `Bearer ${apiKey}`, }, + signal: AbortSignal.timeout(30_000), }) if (!sessionResponse.ok) { @@ -103,6 +104,7 @@ export async function validateOAuthToken( headers: { Authorization: `Bearer ${token}`, }, + signal: AbortSignal.timeout(30_000), }) if (!sessionResponse.ok) { diff --git a/apps/mcp/src/client.ts b/apps/mcp/src/client.ts index ee35fcf1..6018d593 100644 --- a/apps/mcp/src/client.ts +++ b/apps/mcp/src/client.ts @@ -142,6 +142,7 @@ export class SupermemoryClient { this.client = new Supermemory({ apiKey: bearerToken, baseURL: apiUrl, + timeout: 30_000, }) this.containerTag = containerTag || DEFAULT_PROJECT_ID } @@ -336,6 +337,7 @@ export class SupermemoryClient { Authorization: `Bearer ${this.bearerToken}`, "Content-Type": "application/json", }, + signal: AbortSignal.timeout(30_000), }) if (!response.ok) { @@ -374,6 +376,7 @@ export class SupermemoryClient { order: "desc", containerTags, }), + signal: AbortSignal.timeout(30_000), }) if (!response.ok) { throw Object.assign(new Error("Failed to fetch documents"), { @@ -387,6 +390,19 @@ export class SupermemoryClient { } private handleError(error: unknown): never { + // Handle request timeout errors (AbortError from AbortSignal.timeout or SDK TimeoutError) + if (error instanceof Error) { + if ( + error.name === "AbortError" || + error.name === "TimeoutError" || + error.message.toLowerCase().includes("timeout") + ) { + throw new Error( + "Request to Supermemory API timed out. Please try again.", + ) + } + } + // Handle network/fetch errors if (error instanceof TypeError) { if ( diff --git a/apps/mcp/src/index.ts b/apps/mcp/src/index.ts index 846064e1..45731982 100644 --- a/apps/mcp/src/index.ts +++ b/apps/mcp/src/index.ts @@ -89,6 +89,7 @@ app.get("/.well-known/oauth-authorization-server", async (c) => { // Fetch the authorization server metadata from the main API const response = await fetch( `${apiUrl}/.well-known/oauth-authorization-server`, + { signal: AbortSignal.timeout(30_000) }, ) if (!response.ok) {