From 4aa044fe55729f4f97b60f7070350de3724994f0 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:01:51 +0000 Subject: [PATCH] fix(mcp): respect readable scope for unscoped recall (#1357) Unscoped recall forced sm_project_default even when the caller could only read another organization space, causing a misleading 403 while list and graph operations succeeded. Let the search API choose the caller's readable scope, skip profile enrichment when no concrete scope is selected, and retain upstream error details. Validated with Biome, Vite production build, and Wrangler deploy dry-run. --- apps/mcp/src/client.ts | 48 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/apps/mcp/src/client.ts b/apps/mcp/src/client.ts index 2ce8d41a..4e01a479 100644 --- a/apps/mcp/src/client.ts +++ b/apps/mcp/src/client.ts @@ -130,6 +130,7 @@ interface SDKResult { export class SupermemoryClient { private client: Supermemory private containerTag: string + private hasExplicitContainerTag: boolean private bearerToken: string private apiUrl: string @@ -145,6 +146,7 @@ export class SupermemoryClient { baseURL: apiUrl, timeout: FETCH_TIMEOUT_MS, }) + this.hasExplicitContainerTag = Boolean(containerTag) this.containerTag = containerTag || DEFAULT_PROJECT_ID } @@ -166,7 +168,7 @@ export class SupermemoryClient { containerTag: this.containerTag, } } catch (error) { - this.handleError(error) + this.handleOperationError("Create memory request", error) } } @@ -201,7 +203,13 @@ export class SupermemoryClient { // Fallback to semantic search if exact match fails const SIMILARITY_THRESHOLD = 0.85 // High threshold - only very similar memories - const searchResult = await this.search(content, 5, SIMILARITY_THRESHOLD) + const searchResult = await this.search( + content, + 5, + SIMILARITY_THRESHOLD, + undefined, + this.containerTag, + ) if (searchResult.results.length === 0) { return { @@ -236,7 +244,7 @@ export class SupermemoryClient { containerTag: this.containerTag, } } catch (error) { - this.handleError(error) + this.handleOperationError("Forget memory request", error) } } @@ -246,12 +254,16 @@ export class SupermemoryClient { limit = 10, threshold?: number, options?: SearchOptions, + containerTagOverride?: string, ): Promise { try { + const containerTag = + containerTagOverride ?? + (this.hasExplicitContainerTag ? this.containerTag : undefined) const result = await this.client.search.memories({ q: query, limit, - containerTag: this.containerTag, + ...(containerTag ? { containerTag } : {}), searchMode: options?.searchMode ?? "hybrid", threshold, // Optional threshold parameter rerank: options?.rerank, @@ -284,12 +296,21 @@ export class SupermemoryClient { timing: result.timing, } } catch (error) { - this.handleError(error) + this.handleOperationError("Search request", error) } } // Get user profile using SDK async getProfile(query?: string): Promise { + if (!this.hasExplicitContainerTag) { + return { + profile: { + static: [], + dynamic: [], + }, + } + } + try { const result = await this.client.profile({ containerTag: this.containerTag, @@ -325,7 +346,7 @@ export class SupermemoryClient { return response } catch (error) { - this.handleError(error) + this.handleOperationError("Profile request", error) } } @@ -432,7 +453,8 @@ export class SupermemoryClient { throw new Error("Memory limit reached. Upgrade at supermemory.ai") case 403: throw new Error( - "Access forbidden. Your account may be restricted or blocked.", + message || + "Access forbidden. Your account may be restricted or blocked.", ) case 404: throw new Error("Memory not found. It may have been deleted.") @@ -457,4 +479,16 @@ export class SupermemoryClient { // Wrap unknown errors throw new Error(`An unexpected error occurred: ${String(error)}`) } + + private handleOperationError(operation: string, error: unknown): never { + try { + this.handleError(error) + } catch (handledError) { + const message = + handledError instanceof Error + ? handledError.message + : String(handledError) + throw new Error(`${operation} failed: ${message}`) + } + } }