From 9f650115bbe8e709dbf8878109ac15f101cf8e70 Mon Sep 17 00:00:00 2001 From: wishhyt <24300810017@m.fudan.edu.cn> Date: Wed, 18 Mar 2026 16:58:43 +0800 Subject: [PATCH 1/6] fix: use for...of to iterate Map in streamableHttp shutdown handler `for...in` iterates enumerable own properties, which a Map does not expose for its entries. The SIGINT handler's cleanup loop therefore never executed, leaking active transports on shutdown. Replaced with `for...of` which correctly iterates Map entries. Made-with: Cursor --- src/everything/transports/streamableHttp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/everything/transports/streamableHttp.ts b/src/everything/transports/streamableHttp.ts index 2e79abc554..2580c4ca29 100644 --- a/src/everything/transports/streamableHttp.ts +++ b/src/everything/transports/streamableHttp.ts @@ -225,10 +225,10 @@ process.on("SIGINT", async () => { console.log("Shutting down server..."); // Close all active transports to properly clean up resources - for (const sessionId in transports) { + for (const [sessionId, transport] of transports) { try { console.log(`Closing transport for session ${sessionId}`); - await transports.get(sessionId)!.close(); + await transport.close(); transports.delete(sessionId); } catch (error) { console.log(`Error closing transport for session ${sessionId}:`, error); From 00a93ed771f3aceeb7712a9cf7389b0961867a22 Mon Sep 17 00:00:00 2001 From: wishhyt <24300810017@m.fudan.edu.cn> Date: Wed, 18 Mar 2026 16:59:02 +0800 Subject: [PATCH 2/6] fix: re-throw access-denied error in validatePath inner catch The bare catch block on the ENOENT path for new files was catching all errors, including the "Access denied" error thrown when the parent directory resolves outside allowed directories. This replaced a security-relevant error with a misleading "Parent directory does not exist" message. Now the inner catch re-throws access-denied errors. Made-with: Cursor --- src/filesystem/lib.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts index 17e4654cd5..86bfee53c0 100644 --- a/src/filesystem/lib.ts +++ b/src/filesystem/lib.ts @@ -131,7 +131,10 @@ export async function validatePath(requestedPath: string): Promise { throw new Error(`Access denied - parent directory outside allowed directories: ${realParentPath} not in ${allowedDirectories.join(', ')}`); } return absolute; - } catch { + } catch (innerError) { + if (innerError instanceof Error && innerError.message.startsWith('Access denied')) { + throw innerError; + } throw new Error(`Parent directory does not exist: ${parentDir}`); } } From 5d0cf9ae8ec9949c2b1df03d0ac602a774b02c86 Mon Sep 17 00:00:00 2001 From: wishhyt <24300810017@m.fudan.edu.cn> Date: Wed, 18 Mar 2026 16:59:16 +0800 Subject: [PATCH 3/6] fix: correct always-false guard condition in parseResourceId The condition used && between two startsWith checks for different URI prefixes (text vs blob), which can never both be true for the same URI. The intent is to reject URIs matching neither prefix. Changed to negate both checks so the guard correctly throws for unknown URIs. Made-with: Cursor --- src/everything/resources/templates.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/everything/resources/templates.ts b/src/everything/resources/templates.ts index 6d4903f74c..49043c201c 100644 --- a/src/everything/resources/templates.ts +++ b/src/everything/resources/templates.ts @@ -139,8 +139,8 @@ export const blobResourceUri = (resourceId: number) => const parseResourceId = (uri: URL, variables: Record) => { const uriError = `Unknown resource: ${uri.toString()}`; if ( - uri.toString().startsWith(textUriBase) && - uri.toString().startsWith(blobUriBase) + !uri.toString().startsWith(textUriBase) && + !uri.toString().startsWith(blobUriBase) ) { throw new Error(uriError); } else { From 97f6ec4f6deb849b863f54915ca26cfec77aa2b1 Mon Sep 17 00:00:00 2001 From: wishhyt <24300810017@m.fudan.edu.cn> Date: Wed, 18 Mar 2026 16:59:30 +0800 Subject: [PATCH 4/6] fix: set idempotentHint to false for sequential thinking tool The tool appends to thoughtHistory on every invocation, so repeated calls with identical arguments produce different results (thoughtHistoryLength increments and duplicate entries appear). This makes the tool non-idempotent, and clients relying on the hint for automatic retries could corrupt the thinking chain. Made-with: Cursor --- src/sequentialthinking/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts index 217845bb3d..a668aed184 100644 --- a/src/sequentialthinking/index.ts +++ b/src/sequentialthinking/index.ts @@ -94,7 +94,7 @@ You should: annotations: { readOnlyHint: true, destructiveHint: false, - idempotentHint: true, + idempotentHint: false, openWorldHint: false, }, outputSchema: { From cb76ad14807e5c243e735cf98fa6b40131670e37 Mon Sep 17 00:00:00 2001 From: wishhyt <24300810017@m.fudan.edu.cn> Date: Wed, 18 Mar 2026 16:59:50 +0800 Subject: [PATCH 5/6] fix: prevent migration rename errors from being silently swallowed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nested try/catch structure meant that if fs.rename() failed during memory.json → memory.jsonl migration (e.g. permission error), the exception would bubble into the outer catch which silently returned the new path. The server would then start with a non-existent file and overwrite the user's data on the first write. Restructured to separate the access checks so rename failures propagate to the caller. Made-with: Cursor --- src/memory/index.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/memory/index.ts b/src/memory/index.ts index b560bf1e53..64ec87f866 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -24,23 +24,24 @@ export async function ensureMemoryFilePath(): Promise { const newMemoryPath = defaultMemoryPath; try { - // Check if old file exists and new file doesn't await fs.access(oldMemoryPath); - try { - await fs.access(newMemoryPath); - // Both files exist, use new one (no migration needed) - return newMemoryPath; - } catch { - // Old file exists, new file doesn't - migrate - console.error('DETECTED: Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility'); - await fs.rename(oldMemoryPath, newMemoryPath); - console.error('COMPLETED: Successfully migrated memory.json to memory.jsonl'); - return newMemoryPath; - } } catch { // Old file doesn't exist, use new path return newMemoryPath; } + + try { + await fs.access(newMemoryPath); + // Both files exist, use new one (no migration needed) + return newMemoryPath; + } catch { + // Old file exists, new file doesn't - migrate + // Let rename errors propagate so migration failures are not silently ignored + console.error('DETECTED: Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility'); + await fs.rename(oldMemoryPath, newMemoryPath); + console.error('COMPLETED: Successfully migrated memory.json to memory.jsonl'); + return newMemoryPath; + } } // Initialize memory file path (will be set during startup) From ec9402b98d527fa24e4da9d7c6c27f5d554d1a80 Mon Sep 17 00:00:00 2001 From: wishhyt <24300810017@m.fudan.edu.cn> Date: Wed, 18 Mar 2026 17:00:12 +0800 Subject: [PATCH 6/6] fix: sync elicitation response handler with request schema fields The response handler referenced 'color' and 'petType' fields that do not exist in the elicitation request schema, so they would never have values. Meanwhile, fields actually defined in the schema (firstLine, untitledSingleSelectEnum, untitledMultipleSelectEnum, titledSingleSelectEnum, titledMultipleSelectEnum, legacyTitledEnum) were never displayed. Removed the dead references and added handlers for all schema-defined fields. Made-with: Cursor --- .../tools/trigger-elicitation-request.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/everything/tools/trigger-elicitation-request.ts b/src/everything/tools/trigger-elicitation-request.ts index 4de7993e9a..fdf2cb8cca 100644 --- a/src/everything/tools/trigger-elicitation-request.ts +++ b/src/everything/tools/trigger-elicitation-request.ts @@ -189,7 +189,8 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => { if (userData.name) lines.push(`- Name: ${userData.name}`); if (userData.check !== undefined) lines.push(`- Agreed to terms: ${userData.check}`); - if (userData.color) lines.push(`- Favorite Color: ${userData.color}`); + if (userData.firstLine) + lines.push(`- Favorite First Line: ${userData.firstLine}`); if (userData.email) lines.push(`- Email: ${userData.email}`); if (userData.homepage) lines.push(`- Homepage: ${userData.homepage}`); if (userData.birthdate) @@ -198,7 +199,16 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => { lines.push(`- Favorite Integer: ${userData.integer}`); if (userData.number !== undefined) lines.push(`- Favorite Number: ${userData.number}`); - if (userData.petType) lines.push(`- Pet Type: ${userData.petType}`); + if (userData.untitledSingleSelectEnum) + lines.push(`- Favorite Friend: ${userData.untitledSingleSelectEnum}`); + if (userData.untitledMultipleSelectEnum) + lines.push(`- Favorite Instruments: ${userData.untitledMultipleSelectEnum}`); + if (userData.titledSingleSelectEnum) + lines.push(`- Favorite Hero: ${userData.titledSingleSelectEnum}`); + if (userData.titledMultipleSelectEnum) + lines.push(`- Favorite Fish: ${userData.titledMultipleSelectEnum}`); + if (userData.legacyTitledEnum) + lines.push(`- Favorite Pet: ${userData.legacyTitledEnum}`); content.push({ type: "text",