mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-10 03:28:14 +00:00
fix(mcp): add 30s fetch timeouts to prevent MCP stalls
Add FETCH_TIMEOUT_MS = 30_000 constant and apply AbortSignal.timeout to all outbound fetch calls from the supermemory-mcp Durable Object: - client.ts: SDK constructor timeout + getProjects/getDocuments signals - client.ts: handleError catches AbortError/TimeoutError with friendly msg - auth.ts: validateOAuthToken fetch signal - index.ts: oauth-authorization-server proxy fetch signal Fixes stalls where downstream services (Turbopuffer/Gemini) blocked DO subrequests for minutes with no client-side timeout. Co-authored-by: Dhravya Shah <dhravya@supermemory.com>
This commit is contained in:
parent
cf47d73126
commit
3d5579a006
3 changed files with 18 additions and 1 deletions
|
|
@ -103,6 +103,7 @@ export async function validateOAuthToken(
|
|||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
})
|
||||
|
||||
if (!sessionResponse.ok) {
|
||||
|
|
|
|||
|
|
@ -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<string, unknown> | 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<string[]> {
|
||||
async getProjects(options?: { signal?: AbortSignal }): Promise<string[]> {
|
||||
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<DocumentsApiResponse> {
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue