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 <dhravya@supermemory.com>
This commit is contained in:
Cursor Agent 2026-06-22 10:19:48 +00:00
parent cf47d73126
commit f908164403
No known key found for this signature in database
3 changed files with 19 additions and 0 deletions

View file

@ -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) {

View file

@ -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 (

View file

@ -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) {