From bc9eb5dc57022df12bbdc0186c122c70914f368c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 20 Jun 2026 08:19:09 +0000 Subject: [PATCH] Add 30s request timeouts to SupermemoryClient outbound calls - Pass explicit timeout: 30_000 to the Supermemory SDK constructor so all SDK-backed calls (search, add, forget, profile) are bounded to 30 seconds - Add AbortSignal.timeout(FETCH_TIMEOUT_MS) to the manual fetch calls in getProjects() and getDocuments() which had no timeout before - Accept an optional { signal?: AbortSignal } argument on both methods so callers can thread a request-scoped signal when one is available - Catch AbortError / TimeoutError in handleError() and surface a friendly 'Request to Supermemory API timed out' message instead of an opaque crash - Follow existing codebase convention (FETCH_TIMEOUT_MS constant, same value used in apps/api/src/services/extraction/extractors/image.ts) Co-authored-by: Dhravya Shah --- apps/mcp/src/client.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/mcp/src/client.ts b/apps/mcp/src/client.ts index ee35fcf1..2ce8d41a 100644 --- a/apps/mcp/src/client.ts +++ b/apps/mcp/src/client.ts @@ -2,6 +2,7 @@ import Supermemory from "supermemory" const MAX_CHARS = 200000 // ~50k tokens (character-based limit) const DEFAULT_PROJECT_ID = "sm_project_default" +const FETCH_TIMEOUT_MS = 30_000 interface MemoryRichFields { metadata?: Record | null @@ -142,6 +143,7 @@ export class SupermemoryClient { this.client = new Supermemory({ apiKey: bearerToken, baseURL: apiUrl, + timeout: FETCH_TIMEOUT_MS, }) this.containerTag = containerTag || DEFAULT_PROJECT_ID } @@ -328,14 +330,16 @@ export class SupermemoryClient { } // Get projects list - async getProjects(): Promise { + async getProjects(options?: { signal?: AbortSignal }): Promise { try { + const signal = options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS) const response = await fetch(`${this.apiUrl}/v3/projects`, { method: "GET", headers: { Authorization: `Bearer ${this.bearerToken}`, "Content-Type": "application/json", }, + signal, }) if (!response.ok) { @@ -359,8 +363,10 @@ export class SupermemoryClient { containerTags?: string[], page = 1, limit = 10, + options?: { signal?: AbortSignal }, ): Promise { try { + const signal = options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS) const response = await fetch(`${this.apiUrl}/v3/documents/documents`, { method: "POST", headers: { @@ -374,6 +380,7 @@ export class SupermemoryClient { order: "desc", containerTags, }), + signal, }) if (!response.ok) { throw Object.assign(new Error("Failed to fetch documents"), { @@ -387,6 +394,14 @@ export class SupermemoryClient { } private handleError(error: unknown): never { + // Handle request timeout (AbortSignal.timeout or explicit abort) + if ( + error instanceof Error && + (error.name === "AbortError" || error.name === "TimeoutError") + ) { + throw new Error("Request to Supermemory API timed out") + } + // Handle network/fetch errors if (error instanceof TypeError) { if (