-
Notifications
You must be signed in to change notification settings - Fork 61.2k
Add support for MCP(sse) in export mode #6481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@mokeyish is attempting to deploy a commit to the NextChat Team on Vercel. A member of the Team first needs to authorize it. |
""" WalkthroughThis update introduces dual client/server mode support for MCP-related logic, governed by a new global Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MCPMarketPage
participant MCPClient
participant ConfigStore
participant LocalStorage
participant FileSystem
User->>MCPMarketPage: Open MCP Market
MCPMarketPage->>ConfigStore: Load MCP Config
alt EXPORT_MODE enabled
ConfigStore->>LocalStorage: Read MCP Config
LocalStorage-->>ConfigStore: Config Data (possibly merged)
else Not EXPORT_MODE
ConfigStore->>FileSystem: Read MCP Config
FileSystem-->>ConfigStore: Config Data
end
MCPMarketPage->>MCPClient: List Tools (with config)
MCPClient->>MCPMarketPage: Tools List
MCPMarketPage-->>User: Render Tools Modal
Assessment against linked issues
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
app/components/mcp-market.tsx (1)
231-244
: 🛠️ Refactor suggestionLoading indicator never toggles during
loadTools
loadTools
fetches data asynchronously but never flipsisLoading
, so the modal may show “No tools available” before the request completes. A dedicated loading flag (or re-useisLoading
) fixes the UX:const loadTools = async (id: string) => { - try { + try { + setIsLoading(true); const result = await getClientTools(id); if (result) { - setTools(result); + setToolsResponse(result); } else { throw new Error("Failed to load tools"); } } catch (error) { @@ - setTools(null); + setToolsResponse(null); } finally { + setIsLoading(false); } };
🧹 Nitpick comments (5)
app/mcp/client.ts (1)
27-29
: Consider adding more context in the error message.The error message when attempting to use stdio transport in export mode could be more helpful by explaining alternatives.
- throw new Error("Cannot use stdio transport in export mode"); + throw new Error("Cannot use stdio transport in export mode. Please use SSE transport configuration instead.");app/components/mcp-market.tsx (2)
49-53
:tools
state now holds the whole response – consider renaming for clarity
tools
is no longer an array but aListToolsResponse
object. The plural name can mis-lead future readers into thinking it is an array, and makes the latertools.tools
access look odd.-const [tools, setTools] = useState<ListToolsResponse | null>(null); +const [toolsResponse, setToolsResponse] = useState<ListToolsResponse | null>(null);A small rename avoids the double-
tools
accessor and improves readability.
735-744
: Two minor list-rendering issues
index
is used askey
, risking re-ordering bugs; prefer a stable identifier (e.g.tool.name ?? index
).tool.name
andtool.description
are optional, so rendering may output empty<div>
s. Guard or fall back to placeholders.- tools.tools.map((tool: ToolSchema, index: number) => ( - <div key={index} className={styles["tool-item"]}> + toolsResponse.tools.map((tool: ToolSchema, index: number) => ( + <div + key={tool.name ?? index} + className={styles["tool-item"]} + > <div className={styles["tool-name"]}>{tool.name ?? "Unnamed Tool"}</div> <div className={styles["tool-description"]}> {tool.description ?? "No description"} </div> </div> ))app/mcp/types.ts (1)
71-76
:inputSchema?: object
is too looseUsing
object
loses type information and prevents validation. Prefer a more precise type:inputSchema?: Record<string, unknown>; // or a Zod schema if availableThis will still accept arbitrary JSON shapes while keeping
unknown
safety.app/mcp/actions.ts (1)
24-29
: Top-levelawait
inside constant initialisation can break older Node builds
await
at module scope requires ES 2022 modules; some bundlers/serverless targets still transpile to CommonJS. ComputeCONFIG_PATH
lazily inside an async helper to stay compatible.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/components/mcp-market.tsx
(4 hunks)app/config/build.ts
(1 hunks)app/mcp/actions.ts
(6 hunks)app/mcp/client.ts
(2 hunks)app/mcp/types.ts
(2 hunks)app/store/access.ts
(1 hunks)app/typing.ts
(1 hunks)next.config.mjs
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/components/mcp-market.tsx (1)
app/mcp/types.ts (3)
ListToolsResponse
(67-69)isServerStdioConfig
(118-119)ToolSchema
(71-76)
app/mcp/client.ts (1)
app/mcp/types.ts (1)
isServerSseConfig
(116-117)
🪛 Biome (1.9.4)
app/mcp/actions.ts
[error] 437-437: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.
(lint/complexity/noUselessCatch)
🔇 Additional comments (7)
app/typing.ts (1)
1-3
: Looks good: Global constant declaration for export mode.The global declaration of
EXPORT_MODE
as a boolean constant is a clean approach for making this build-mode flag available throughout the application. This will help conditionally switch between client-side and server-side logic.next.config.mjs (2)
9-10
: Good addition: Build mode constant definition.Creating a dedicated constant for export mode makes the code more readable and maintainable than checking
mode === "export"
throughout the configuration.
15-17
: Proper implementation: Injecting EXPORT_MODE through webpack.The webpack DefinePlugin is correctly used to make the EXPORT_MODE constant available to the application at compile time. This approach ensures the value is available as a global constant during runtime.
app/store/access.ts (1)
246-251
: Export mode state initialization is now properly handled.This change correctly handles state initialization in export mode by:
- Using client config directly instead of making a network request
- Setting fetchState to 2 (done) to avoid unnecessary fetch attempts
- Short-circuiting the fetch process when in export mode
This is consistent with the broader pattern of adapting behavior based on build mode.
app/mcp/client.ts (2)
3-8
: Good update: Enhanced type imports.The expanded import statement properly includes all necessary types and the type guard function needed for the transport selection logic.
19-46
: Well-implemented transport selection based on server config type.The refactored transport creation logic:
- Supports SSE transport for SSE server configurations
- Prevents stdio transport usage in export mode
- Uses dynamic imports to load only the necessary transport implementation
- Properly handles environment variables for stdio transport
This implementation elegantly handles different server configurations while respecting the export mode constraints.
app/config/build.ts (1)
46-48
:openaiUrl
silently falls back toBASE_URL
When
OPENAI_BASE_URL
is unset butBASE_URL
is, it might accidentally point OpenAI traffic to an unrelated backend. Consider explicit fallback order with a comment or validate that the chosen URL looks like an OpenAI endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
app/mcp/actions.ts (3)
1-3
: "use server" directive won't work conditionally.React/NextJS recognizes the directive only when it is the very first statement. Wrapping it in an
if
block makes it ineffective.Consider using separate entry files or the webpack alias approach, or split into
actions.client.ts
&actions.server.ts
with conditional imports.
347-354
: Fallback client search uses O(n²) scan and fragile name matching.
- Scanning all clients on every request is O(#clients × #tools).
- Comparing
t.name == request.params?.name
assumesparams.name
exists and is string-typed.Maintain a
Map<toolName, clientId>
during client initialization for O(1) lookup and add proper type guards.
369-393
: 🛠️ Refactor suggestionIncorrect merge logic between bundled config and
localStorage
override.The loop iterates over
config.mcpServers
, so any additional servers present only instoredConfig
are ignored.Apply this fix:
-for (const mcpId in config.mcpServers) { +for (const mcpId in storedConfig.mcpServers) { if (mcpId in mcpServers) { mcpServers[mcpId] = { ...mcpServers[mcpId], ...storedConfig.mcpServers[mcpId], }; } else { mcpServers[mcpId] = storedConfig.mcpServers[mcpId]; } }
🧹 Nitpick comments (1)
app/mcp/actions.ts (1)
417-419
: Useless catch clause that only rethrows the error.The catch block on line 417-419 simply rethrows the original error, which is redundant.
- try { // 确保目录存在 if (EXPORT_MODE) { const storage = localStorage; storage.setItem("McpConfig", JSON.stringify(config)); } else { const fs = await import("fs/promises"); const path = await import("path"); await fs.mkdir(path.dirname(CONFIG_PATH), { recursive: true }); await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2)); } - } catch (error) { - throw error; - }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/components/mcp-market.tsx
(4 hunks)app/config/build.ts
(1 hunks)app/mcp/actions.ts
(6 hunks)app/mcp/client.ts
(2 hunks)app/mcp/types.ts
(2 hunks)app/store/access.ts
(1 hunks)app/typing.ts
(1 hunks)next.config.mjs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- app/typing.ts
- next.config.mjs
- app/store/access.ts
- app/config/build.ts
- app/components/mcp-market.tsx
- app/mcp/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/mcp/client.ts (1)
app/mcp/types.ts (1)
isServerSseConfig
(116-117)
🪛 Biome (1.9.4)
app/mcp/actions.ts
[error] 435-435: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.
(lint/complexity/noUselessCatch)
🔇 Additional comments (5)
app/mcp/client.ts (2)
3-8
: LGTM! Good import organization.Your approach to expanding the imports to include the type guard and necessary types is clean and follows good practices.
19-46
:❌ Incorrect review comment
Well-implemented transport selection based on server config type.
The dynamic transport selection logic elegantly handles different server configurations and correctly uses the type guard for type safety. Dynamic imports are a good practice for code splitting.
I do have one concern:
The
EXPORT_MODE
constant is used but not imported. This appears to be a global constant injected at build time. Let's verify its definition:
🏁 Script executed:
#!/bin/bash # Check where EXPORT_MODE is defined in the codebase rg -A 1 "EXPORT_MODE" --type tsLength of output: 736
No import needed for EXPORT_MODE
EXPORT_MODE
is declared as a global constant in app/typing.ts (inside adeclare global
block), so it’s available at build time without an import.Likely an incorrect or invalid review comment.
app/mcp/actions.ts (3)
22-27
: Good dynamic path handling based on environment.The conditional path configuration logic handles both client and server environments well by using dynamic imports for Node.js modules only when needed in server mode.
407-417
: Clean environment-specific config persistence.The implementation properly handles configuration persistence on both client (localStorage) and server (filesystem) environments.
424-438
: Good prioritization of config sources.The function properly prioritizes the enableMcp flag from the loaded config before falling back to environment-specific configurations.
🧰 Tools
🪛 Biome (1.9.4)
[error] 435-435: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.(lint/complexity/noUselessCatch)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
app/mcp/actions.ts (3)
1-3
:⚠️ Potential issue"use server" directive will not be effective inside the conditional block
React/NextJS directive
"use server"
must be the very first statement in a file to work properly. Wrapping it in anif
block turns it into a regular string expression that won't be recognized by the compiler.Consider one of these alternatives:
- Use separate files (
actions.client.ts
andactions.server.ts
) with conditional imports- Use build-time code transformation in your Webpack/Next.js configuration to inject the directive correctly
347-354
: 🛠️ Refactor suggestionFallback client search uses O(n²) scan and fragile name matching
- Scanning all clients on every request is O(#clients × #tools).
- Comparing
t.name == request.params?.name
uses loose equality and assumesparams.name
exists.Consider these improvements:
- Maintain a
Map<toolName, clientId>
when clients are initialized for O(1) lookup.- Use strict equality (
===
) and proper type guards.let client = clientsMap.get(clientId); if (!client) { - client = [...clientsMap.values()].find( - (c) => - c.tools?.tools && - c.tools.tools.find((t) => t.name == request.params?.name), - ); + // Use type guard to ensure params.name exists and is a string + const toolName = typeof request.params?.name === "string" ? request.params.name : null; + if (toolName) { + client = [...clientsMap.values()].find( + (c) => + c.tools?.tools && + c.tools.tools.some((t) => t.name === toolName), + ); + } }
369-393
:⚠️ Potential issueIncorrect merge logic between bundled config and localStorage override
The loop iterates over
config.mcpServers
, so any additional servers present only instoredConfig
are ignored.Apply this fix to properly merge the configurations:
if (storedConfig_str) { const storedConfig: McpConfigData = JSON.parse(storedConfig_str); - const mcpServers = config.mcpServers; - if (storedConfig.mcpServers) { - for (const mcpId in config.mcpServers) { - if (mcpId in mcpServers) { - mcpServers[mcpId] = { - ...mcpServers[mcpId], - ...storedConfig.mcpServers[mcpId], - }; - } else { - mcpServers[mcpId] = storedConfig.mcpServers[mcpId]; - } - } - } - - config.mcpServers = mcpServers; + if (storedConfig.mcpServers) { + const merged = { ...config.mcpServers }; + for (const mcpId in storedConfig.mcpServers) { + merged[mcpId] = { + ...merged[mcpId], + ...storedConfig.mcpServers[mcpId] + }; + } + config.mcpServers = merged; + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/components/mcp-market.tsx
(4 hunks)app/config/build.ts
(1 hunks)app/mcp/actions.ts
(6 hunks)app/mcp/client.ts
(2 hunks)app/mcp/types.ts
(2 hunks)app/store/access.ts
(1 hunks)app/typing.ts
(1 hunks)next.config.mjs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- app/typing.ts
- next.config.mjs
- app/store/access.ts
- app/config/build.ts
- app/components/mcp-market.tsx
- app/mcp/types.ts
- app/mcp/client.ts
🧰 Additional context used
🪛 Biome (1.9.4)
app/mcp/actions.ts
[error] 435-435: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.
(lint/complexity/noUselessCatch)
🔇 Additional comments (1)
app/mcp/actions.ts (1)
425-437
: LGTM: Enhanced configuration access with appropriate fallbacksThe implementation correctly adds fallback behavior by checking config sources in a prioritized manner. It first checks the loaded config, then falls back to client or server config based on the environment.
🧰 Tools
🪛 Biome (1.9.4)
[error] 435-435: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.(lint/complexity/noUselessCatch)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
app/mcp/actions.ts (2)
1-3
:⚠️ Potential issueThe
"use server"
directive inside a conditional won't have the intended effectThis conditional directive won't be recognized by Next.js, as directives must be the first statement of a file. The compiler cannot evaluate the
EXPORT_MODE
condition at compilation time.Consider using separate files (e.g.,
actions.server.ts
andactions.client.ts
) with appropriate directives at the top of each file, and re-export from a common entry point based on the build config.
349-356
:⚠️ Potential issueFallback client search uses inefficient lookup and lacks type safety
While adding a fallback mechanism is good, the implementation has two issues:
- The lookup is inefficient, scanning all clients and tools on each request
- The comparison
t.name == request.params?.name
assumesparams.name
exists and is string-typedConsider implementing a tool-to-client mapping:
-let client = clientsMap.get(clientId); -if (!client) { - client = [...clientsMap.values()].find( - (c) => - c.tools?.tools && - c.tools.tools.find((t) => t.name == request.params?.name), - ); -} +let client = clientsMap.get(clientId); +if (!client && request.params?.name && typeof request.params.name === 'string') { + // Use a tool-to-client mapping that's maintained when tools are initialized + const toolName = request.params.name; + client = [...clientsMap.values()].find( + (c) => c.tools?.tools && c.tools.tools.some(t => t.name === toolName) + ); +}
🧹 Nitpick comments (2)
app/mcp/actions.ts (2)
371-392
: Config merging logic is improved but could be refinedThe logic for merging configurations has been improved compared to previous implementations. However, consider a more thorough approach to ensure no entries are missed.
-const merged = { ...config.mcpServers }; -if (storedConfig.mcpServers) { - for (const id in storedConfig.mcpServers) { - merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; - } -} +// Create a merged configuration that combines both sources +const merged = { ...config.mcpServers }; +if (storedConfig.mcpServers) { + // Ensure we process all servers from stored config + for (const id in storedConfig.mcpServers) { + merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; + } +}
422-424
: Unnecessary catch clauseThis catch clause simply rethrows the original error without any additional processing.
-try { // ... existing code ... -} catch (error) { - throw error; -} +// ... existing code ...
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/components/mcp-market.tsx
(4 hunks)app/config/build.ts
(1 hunks)app/mcp/actions.ts
(6 hunks)app/mcp/client.ts
(2 hunks)app/mcp/types.ts
(3 hunks)app/store/access.ts
(1 hunks)app/typing.ts
(1 hunks)next.config.mjs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- app/typing.ts
- next.config.mjs
- app/mcp/client.ts
- app/components/mcp-market.tsx
- app/store/access.ts
- app/config/build.ts
- app/mcp/types.ts
🧰 Additional context used
🪛 Biome (1.9.4)
app/mcp/actions.ts
[error] 440-440: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.
(lint/complexity/noUselessCatch)
🔇 Additional comments (3)
app/mcp/actions.ts (3)
22-29
: Good refactoring of path handling with async functionThe async function pattern improves on the previous top-level await approach, making initialization more predictable and maintainable.
403-421
: Good addition of error handling for localStorage operationsThe try-catch block for localStorage operations is a good improvement that addresses previous review comments.
430-442
: Improved MCP enabled check with better layeringGood enhancement of the
isMcpEnabled
function to first check the config before falling back to client/server config based on mode.🧰 Tools
🪛 Biome (1.9.4)
[error] 440-440: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.(lint/complexity/noUselessCatch)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
app/mcp/actions.ts (2)
1-3
:"use server"
directive inside a conditional will not work as expectedThe
"use server"
directive must be the very first statement in a file to be recognized by the NextJS compiler. Wrapping it in a conditional turns it into a regular string expression without any special meaning.Consider splitting this into separate files:
actions.server.ts
with"use server"
at the topactions.client.ts
without the directive- A common entry point that conditionally imports based on
EXPORT_MODE
349-360
: 🛠️ Refactor suggestionOptimize client search with a tool-to-client mapping
The current implementation performs an O(n²) operation by iterating through all clients and their tools for each request that doesn't specify a clientId. This can become inefficient as the number of clients and tools grows.
Consider maintaining a
Map<toolName, clientId>
when tools are initialized:// Add at file level + const toolToClientMap = new Map<string, string>(); // Update in places where tools are initialized (e.g., line 136) clientsMap.set(clientId, { client, tools, errorMsg: null }); + if (tools?.tools) { + for (const tool of tools.tools) { + if (tool.name) { + toolToClientMap.set(tool.name, clientId); + } + } + } // Then in executeMcpAction: if ( !client && request.params?.name && typeof request.params.name === "string" ) { // Use a direct lookup instead of scanning all clients + const toolClientId = toolToClientMap.get(request.params.name); + if (toolClientId) { + client = clientsMap.get(toolClientId); + } - const toolName = request.params.name; - client = [...clientsMap.values()].find( - (c) => c.tools?.tools && c.tools.tools.some((t) => t.name == toolName), - ); }Don't forget to update the map when clients/tools are removed or changed.
🧹 Nitpick comments (2)
app/mcp/actions.ts (2)
424-426
: Small suggestion: Consider using a constant for the formatting configurationThe indentation value '2' is hardcoded in the
JSON.stringify
call. For consistency, consider extracting this to a constant.+ const JSON_INDENT = 2; // Then use it in both places where JSON is formatted await fs.writeFile( await getConfigPath(), - JSON.stringify(config, null, 2), + JSON.stringify(config, null, JSON_INDENT), );
428-430
: Useless catch clause that only rethrows the errorThe catch clause simply rethrows the original error without any additional processing, making it redundant.
try { // ...code... - } catch (error) { - throw error; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/components/mcp-market.tsx
(4 hunks)app/config/build.ts
(1 hunks)app/mcp/actions.ts
(6 hunks)app/mcp/client.ts
(2 hunks)app/mcp/types.ts
(3 hunks)app/store/access.ts
(1 hunks)app/typing.ts
(1 hunks)next.config.mjs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- next.config.mjs
- app/typing.ts
- app/mcp/client.ts
- app/store/access.ts
- app/components/mcp-market.tsx
- app/config/build.ts
- app/mcp/types.ts
🧰 Additional context used
🪛 Biome (1.9.4)
app/mcp/actions.ts
[error] 446-446: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.
(lint/complexity/noUselessCatch)
🔇 Additional comments (3)
app/mcp/actions.ts (3)
436-448
: Good implementation of conditional config loadingThe function properly handles both export mode and server mode with appropriate dynamic imports. Good job implementing the fallbacks and type checks.
🧰 Tools
🪛 Biome (1.9.4)
[error] 446-446: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.(lint/complexity/noUselessCatch)
375-398
: Good improvement to the config merge logicThe merge logic now correctly processes all servers from stored config, addressing previous issues. The conditional handling of
EXPORT_MODE
is well implemented.
409-427
: Well-implemented error handling for localStorage operationsThe try-catch block for localStorage operations is a good safeguard against environments where storage might be unavailable. The warning logged without stopping execution is the right approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/mcp/actions.ts (1)
1-3
:⚠️ Potential issueThe "use server" directive will be ineffective due to conditional wrapping
Next.js requires the "use server" directive to be the very first statement in a file to be recognized. Placing it inside a conditional statement effectively turns it into a no-op string expression.
Consider separating the client and server code into different files, such as
actions.client.ts
andactions.server.ts
, and use dynamic imports to load the appropriate file based onEXPORT_MODE
.
🧹 Nitpick comments (1)
app/mcp/actions.ts (1)
263-269
: Consider extracting duplicate mapping logic to a functionThis code block duplicates the logic from lines 139-145 for populating the tool-to-client map.
+// Helper function to populate tool to client map +function populateToolToClientMap(tools: any, clientId: string) { + if (tools?.tools) { + for (const tool of tools.tools) { + if (tool.name) { + toolToClientMap.set(tool.name, clientId); + } + } + } +} // In initializeSingleClient -if (tools?.tools) { - for (const tool of tools.tools) { - if (tool.name) { - toolToClientMap.set(tool.name, clientId); - } - } -} +populateToolToClientMap(tools, clientId); // In resumeMcpServer -if (tools?.tools) { - for (const tool of tools.tools) { - if (tool.name) { - toolToClientMap.set(tool.name, clientId); - } - } -} +populateToolToClientMap(tools, clientId);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/components/mcp-market.tsx
(5 hunks)app/config/build.ts
(1 hunks)app/mcp/actions.ts
(7 hunks)app/mcp/client.ts
(2 hunks)app/mcp/types.ts
(3 hunks)app/store/access.ts
(1 hunks)app/typing.ts
(1 hunks)next.config.mjs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- next.config.mjs
- app/typing.ts
- app/store/access.ts
- app/mcp/client.ts
- app/components/mcp-market.tsx
- app/mcp/types.ts
- app/config/build.ts
🔇 Additional comments (6)
app/mcp/actions.ts (6)
24-31
: Good refactoring of config path handlingThe asynchronous
getConfigPath
function is a much better approach than the previous top-level await in an IIFE pattern. This implementation provides cleaner separation between client and server modes.
34-34
: Great addition of tool-to-client mappingAdding a
toolToClientMap
addresses the previous O(n²) scan issue, providing O(1) lookup time when searching for a client by tool name.
139-145
: Good implementation of tool-to-client mapping populationThe code properly checks for the existence of tool fields before attempting to access them and correctly populates the mapping.
366-378
: Excellent enhancement to client lookup logicThis implementation correctly addresses the previous review comment about O(n²) scan and fragile name matching. The code properly handles type checking and provides a fallback mechanism when a client isn't found by ID.
425-433
: Good error handling for localStorage operationsThe try-catch block for localStorage operations is a good improvement over the previous implementation, as it allows the application to continue even if localStorage is not available.
448-460
: Well-structured conditional configuration loadingThe implementation correctly checks the loaded config first before falling back to conditionally imported configurations, providing a clean separation between client and server modes.
app/mcp/actions.ts
Outdated
if (EXPORT_MODE) { | ||
const res = await fetch(await getConfigPath()); | ||
const config: McpConfigData = await res.json(); | ||
const storage = localStorage; | ||
const storedConfig_str = storage.getItem("McpConfig"); | ||
if (storedConfig_str) { | ||
const storedConfig: McpConfigData = JSON.parse(storedConfig_str); | ||
// Create a merged configuration that combines both sources | ||
const merged = { ...config.mcpServers }; | ||
if (storedConfig.mcpServers) { | ||
// Ensure we process all servers from stored config | ||
for (const id in storedConfig.mcpServers) { | ||
merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; | ||
} | ||
} | ||
|
||
config.mcpServers = merged; | ||
} | ||
return config; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for localStorage and JSON parsing operations
While fetching and merging configs looks good, there's no error handling for localStorage access or JSON parsing, which could fail in various scenarios.
if (EXPORT_MODE) {
const res = await fetch(await getConfigPath());
+ if (!res.ok) {
+ throw new Error(`Failed to fetch config: ${res.status} ${res.statusText}`);
+ }
const config: McpConfigData = await res.json();
- const storage = localStorage;
- const storedConfig_str = storage.getItem("McpConfig");
- if (storedConfig_str) {
- const storedConfig: McpConfigData = JSON.parse(storedConfig_str);
+ try {
+ const storage = localStorage;
+ const storedConfigStr = storage.getItem("McpConfig");
+ if (storedConfigStr) {
+ try {
+ const storedConfig: McpConfigData = JSON.parse(storedConfigStr);
+ // Create a merged configuration that combines both sources
+ const merged = { ...config.mcpServers };
+ if (storedConfig.mcpServers) {
+ // Ensure we process all servers from stored config
+ for (const id in storedConfig.mcpServers) {
+ merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] };
+ }
+ }
+ config.mcpServers = merged;
+ } catch (parseError) {
+ logger.warn(`Failed to parse stored MCP config: ${parseError}`);
+ // Continue with default config
+ }
+ }
+ } catch (storageError) {
+ logger.warn(`Failed to access localStorage: ${storageError}`);
+ // Continue with default config
+ }
return config;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (EXPORT_MODE) { | |
const res = await fetch(await getConfigPath()); | |
const config: McpConfigData = await res.json(); | |
const storage = localStorage; | |
const storedConfig_str = storage.getItem("McpConfig"); | |
if (storedConfig_str) { | |
const storedConfig: McpConfigData = JSON.parse(storedConfig_str); | |
// Create a merged configuration that combines both sources | |
const merged = { ...config.mcpServers }; | |
if (storedConfig.mcpServers) { | |
// Ensure we process all servers from stored config | |
for (const id in storedConfig.mcpServers) { | |
merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; | |
} | |
} | |
config.mcpServers = merged; | |
} | |
return config; | |
} else { | |
if (EXPORT_MODE) { | |
const res = await fetch(await getConfigPath()); | |
if (!res.ok) { | |
throw new Error(`Failed to fetch config: ${res.status} ${res.statusText}`); | |
} | |
const config: McpConfigData = await res.json(); | |
try { | |
const storage = localStorage; | |
const storedConfigStr = storage.getItem("McpConfig"); | |
if (storedConfigStr) { | |
try { | |
const storedConfig: McpConfigData = JSON.parse(storedConfigStr); | |
// Create a merged configuration that combines both sources | |
const merged = { ...config.mcpServers }; | |
if (storedConfig.mcpServers) { | |
// Ensure we process all servers from stored config | |
for (const id in storedConfig.mcpServers) { | |
merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; | |
} | |
} | |
config.mcpServers = merged; | |
} catch (parseError) { | |
logger.warn(`Failed to parse stored MCP config: ${parseError}`); | |
// Continue with default config | |
} | |
} | |
} catch (storageError) { | |
logger.warn(`Failed to access localStorage: ${storageError}`); | |
// Continue with default config | |
} | |
return config; | |
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
app/mcp/actions.base.ts (1)
109-153
:initializeSingleClient
resolves before the client is readyThe function is declared
async
yet it neverawait
s thecreateClient
chain—it sets up a.then
/.catch
handler and returns immediately.
Callers likeinitializeMcpSystem
therefore “await” a promise that is already resolved, giving a false sense of completion while clients are still starting in the background.If the intent is fire-and-forget, mark the function
void
and dropasync
to avoid confusion.
Otherwise, return the inner promise to let callers truly wait:- createClient(clientId, serverConfig) + return createClient(clientId, serverConfig) .then(async (client) => { /* … */ }) .catch((error) => { /* … */ });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/mcp/actions.base.ts
(1 hunks)app/mcp/actions.client.ts
(1 hunks)app/mcp/actions.server.ts
(1 hunks)app/mcp/actions.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- app/mcp/actions.client.ts
- app/mcp/actions.server.ts
export async function getMcpConfigFromFile(): Promise<McpConfigData> { | ||
try { | ||
if (EXPORT_MODE) { | ||
const res = await fetch(await getConfigPath()); | ||
const config: McpConfigData = await res.json(); | ||
const storage = localStorage; | ||
const storedConfig_str = storage.getItem("McpConfig"); | ||
if (storedConfig_str) { | ||
const storedConfig: McpConfigData = JSON.parse(storedConfig_str); | ||
// Create a merged configuration that combines both sources | ||
const merged = { ...config.mcpServers }; | ||
if (storedConfig.mcpServers) { | ||
// Ensure we process all servers from stored config | ||
for (const id in storedConfig.mcpServers) { | ||
merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; | ||
} | ||
} | ||
|
||
config.mcpServers = merged; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Local-storage failure currently discards a successfully fetched config
If localStorage
is unavailable (e.g. browser privacy mode) or the stored JSON is malformed, the catch
at 388–418 falls back to DEFAULT_MCP_CONFIG
, losing the remote /mcp/config.json
that was already fetched successfully.
A more resilient flow:
const config: McpConfigData = await res.json();
- const storage = localStorage;
- const storedConfig_str = storage.getItem("McpConfig");
- if (storedConfig_str) {
- const storedConfig: McpConfigData = JSON.parse(storedConfig_str);
+ try {
+ const storedConfigStr = localStorage.getItem("McpConfig");
+ if (storedConfigStr) {
+ const storedConfig: McpConfigData = JSON.parse(storedConfigStr);
// merge logic …
- }
- return config;
+ }
+ } catch (lsError) {
+ logger.warn(`Ignoring invalid localStorage MCP config: ${lsError}`);
+ }
+ return config; // always return the remote (possibly merged) config
This way a transient storage issue does not wipe out the authoritative server configuration.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export async function getMcpConfigFromFile(): Promise<McpConfigData> { | |
try { | |
if (EXPORT_MODE) { | |
const res = await fetch(await getConfigPath()); | |
const config: McpConfigData = await res.json(); | |
const storage = localStorage; | |
const storedConfig_str = storage.getItem("McpConfig"); | |
if (storedConfig_str) { | |
const storedConfig: McpConfigData = JSON.parse(storedConfig_str); | |
// Create a merged configuration that combines both sources | |
const merged = { ...config.mcpServers }; | |
if (storedConfig.mcpServers) { | |
// Ensure we process all servers from stored config | |
for (const id in storedConfig.mcpServers) { | |
merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; | |
} | |
} | |
config.mcpServers = merged; | |
} | |
export async function getMcpConfigFromFile(): Promise<McpConfigData> { | |
try { | |
if (EXPORT_MODE) { | |
const res = await fetch(await getConfigPath()); | |
const config: McpConfigData = await res.json(); | |
// Only the local-storage merge is now wrapped in its own try/catch | |
try { | |
const storedConfigStr = localStorage.getItem("McpConfig"); | |
if (storedConfigStr) { | |
const storedConfig: McpConfigData = JSON.parse(storedConfigStr); | |
// Create a merged configuration that combines both sources | |
const merged = { ...config.mcpServers }; | |
if (storedConfig.mcpServers) { | |
for (const id in storedConfig.mcpServers) { | |
merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; | |
} | |
} | |
config.mcpServers = merged; | |
} | |
} catch (lsError) { | |
logger.warn(`Ignoring invalid localStorage MCP config: ${lsError}`); | |
} | |
// Always return at least what we fetched from the server | |
return config; | |
} | |
} catch (error) { | |
// Fallback only if the fetch/json itself failed | |
return DEFAULT_MCP_CONFIG; | |
} | |
} |
cd5824e
to
cf050d5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (5)
app/mcp/actions.base.ts (5)
31-32
:⚠️ Potential issueThe toolToClientMap lacks cleanup when clients are removed or restarted.
When a client is removed, paused, or all clients are restarted, the entries in
toolToClientMap
are not cleaned up. This can lead to stale mappings that cause requests to be sent to non-existent clients.Implement a helper function to purge tool mappings:
const toolToClientMap = new Map<string, string>(); +// Helper function to purge tool mappings for a specific client +function purgeToolMappings(clientId: string) { + for (const [tool, mappedId] of toolToClientMap.entries()) { + if (mappedId === clientId) { + toolToClientMap.delete(tool); + } + } +}Then call this function in
pauseMcpServer
,removeMcpServer
, and usetoolToClientMap.clear()
inrestartAllClients
.
388-418
: 🛠️ Refactor suggestionImprove localStorage error handling with nested try-catch blocks.
The current implementation falls back to the default config if either fetch fails or localStorage operations fail. It should preserve the successfully fetched config even if localStorage operations fail.
if (EXPORT_MODE) { const res = await fetch(await getConfigPath()); + if (!res.ok) { + throw new Error(`Failed to fetch config: ${res.status} ${res.statusText}`); + } const config: McpConfigData = await res.json(); - const storage = localStorage; - const storedConfig_str = storage.getItem("McpConfig"); - if (storedConfig_str) { - const storedConfig: McpConfigData = JSON.parse(storedConfig_str); + try { + const storedConfigStr = localStorage.getItem("McpConfig"); + if (storedConfigStr) { + try { + const storedConfig: McpConfigData = JSON.parse(storedConfigStr); // Create a merged configuration that combines both sources const merged = { ...config.mcpServers }; if (storedConfig.mcpServers) { // Ensure we process all servers from stored config for (const id in storedConfig.mcpServers) { merged[id] = { ...merged[id], ...storedConfig.mcpServers[id] }; } } config.mcpServers = merged; + } catch (parseError) { + logger.warn(`Failed to parse stored MCP config: ${parseError}`); + // Continue with default config + } } + } catch (storageError) { + logger.warn(`Failed to access localStorage: ${storageError}`); + // Continue with default config + } return config;
318-323
:⚠️ Potential issueAdd tool mapping cleanup when removing a server.
When a server is removed, the tool mappings for that client should be removed to prevent routing requests to a non-existent client.
clientsMap.delete(clientId); +// Purge tool mappings for this client +for (const [tool, mappedId] of toolToClientMap.entries()) { + if (mappedId === clientId) { + toolToClientMap.delete(tool); + } +}
343-344
:⚠️ Potential issueClear tool-to-client mappings when restarting all clients.
When all clients are restarted, the tool-to-client mappings should be cleared to prevent routing requests to non-existent clients.
// 清空状态 clientsMap.clear(); +toolToClientMap.clear();
232-237
:⚠️ Potential issueAdd tool mapping cleanup when pausing a server.
When a server is paused, the tool mappings for that client should be removed to prevent routing requests to a non-existent client.
clientsMap.delete(clientId); +// Purge tool mappings for this client +for (const [tool, mappedId] of toolToClientMap.entries()) { + if (mappedId === clientId) { + toolToClientMap.delete(tool); + } +}
🧹 Nitpick comments (2)
app/mcp/actions.ts (2)
3-3
: Consider adding type guard for consistent error handling.The variable
actionsHost
is initialized with a type but no value. When accessing this variable before initialization, TypeScript's type checking won't catch potential issues.-let actionsHost: typeof import("./actions.base"); +let actionsHost: typeof import("./actions.base") | undefined;
48-53
: Add error handling for the dynamic import chain.The current implementation doesn't handle potential failures in the dynamic import chain. Consider adding error handling to provide better diagnostics.
export const executeMcpAction = async ( clientId: string, request: McpRequestMessage, ) => { - return (await actions()).executeMcpAction(clientId, request); + try { + return (await actions()).executeMcpAction(clientId, request); + } catch (error) { + console.error(`Failed to execute MCP action: ${error}`); + throw error; + } };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/mcp/actions.base.ts
(1 hunks)app/mcp/actions.client.ts
(1 hunks)app/mcp/actions.server.ts
(1 hunks)app/mcp/actions.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/mcp/actions.client.ts
- app/mcp/actions.server.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/mcp/actions.ts (2)
app/mcp/actions.base.ts (12)
getAvailableClientsCount
(90-94)isMcpEnabled
(443-462)initializeMcpSystem
(156-175)addMcpServer
(178-207)getClientsStatus
(34-82)getClientTools
(85-87)getMcpConfigFromFile
(388-418)pauseMcpServer
(210-243)restartAllClients
(332-355)resumeMcpServer
(246-304)executeMcpAction
(358-385)getAllTools
(97-106)app/mcp/types.ts (2)
ServerConfig
(122-122)McpRequestMessage
(6-14)
🔇 Additional comments (7)
app/mcp/actions.ts (3)
1-15
: Good implementation of dynamic module loading based on EXPORT_MODE.The code properly implements a lazy-loading pattern that defers the import of environment-specific modules until needed. This approach is efficient and maintainable.
17-22
: Well-implemented function wrapper pattern.The pattern of exporting thin wrapper functions that delegate to the dynamically imported module is clean and maintainable. This preserves the original API surface while allowing for environment-specific implementations.
7-7
:❌ Incorrect review comment
Missing EXPORT_MODE import or declaration.
The global constant
EXPORT_MODE
is used without being imported or declared in this file.
🏁 Script executed:
#!/bin/bash # Check for EXPORT_MODE definition and usage echo "Checking for EXPORT_MODE declaration or import:" rg -A 2 "EXPORT_MODE" --type tsLength of output: 1264
🏁 Script executed:
#!/bin/bash # Display context around EXPORT_MODE declaration in app/typing.ts rg -C 3 "EXPORT_MODE" app/typing.tsLength of output: 149
No import needed for EXPORT_MODE
The global constant
EXPORT_MODE
is declared via an ambient declaration inapp/typing.ts
, so it’s available throughout the project without an explicit import. You can ignore the “Missing EXPORT_MODE import or declaration” comment.Likely an incorrect or invalid review comment.
app/mcp/actions.base.ts (4)
109-153
: Good implementation of client initialization with robust error handling.The client initialization process is well-implemented with proper error handling and logging. It correctly manages client state transitions and updates the tool-to-client mapping when tools are discovered.
358-385
: Effective tool-based client resolution with proper error handling.The implementation correctly resolves the appropriate client based on either client ID or tool name, with proper type checking for the tool name parameter. The error handling is robust with appropriate logging.
421-440
: Good implementation of environment-aware configuration updates.The implementation correctly handles configuration updates in both export and non-export modes, with proper error handling for localStorage operations and filesystem operations.
443-462
: Comprehensive MCP enablement check with fallbacks.The function properly checks multiple sources to determine if MCP is enabled, with appropriate fallbacks and error handling. It first checks the config file, then falls back to client or server config depending on the environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/mcp/actions.base.ts (1)
417-447
: 🛠️ Refactor suggestionFragile localStorage error handling.
This implementation has the same issue that was flagged in a previous review. If
localStorage
is unavailable or the stored JSON is malformed, you'll lose the remote config data.Apply the solution from the previous review to ensure resilient config handling:
const res = await fetch(await getConfigPath()); const config: McpConfigData = await res.json(); - const storage = localStorage; - const storedConfig_str = storage.getItem("McpConfig"); - if (storedConfig_str) { - const storedConfig: McpConfigData = JSON.parse(storedConfig_str); + try { + const storedConfigStr = localStorage.getItem("McpConfig"); + if (storedConfigStr) { + const storedConfig: McpConfigData = JSON.parse(storedConfigStr); // merge logic … - } - return config; + } + } catch (lsError) { + logger.warn(`Ignoring invalid localStorage MCP config: ${lsError}`); + } + return config; // always return the remote (possibly merged) config
🧹 Nitpick comments (1)
app/mcp/actions.base.ts (1)
218-248
: Potential issue with config status handling.In the
addMcpServer
function, there's a checkif (isNewServer && !config.status)
that only sets default status for new servers. This might cause issues if an existing server's config is provided without a status.Consider adding a default status for all servers without status, not just new ones:
- // 如果是新服务器,设置默认状态为 active - if (isNewServer && !config.status) { + // 设置默认状态为 active(如果未指定) + if (!config.status) { config.status = "active"; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/mcp/actions.base.ts
(1 hunks)app/mcp/actions.client.ts
(1 hunks)app/mcp/actions.server.ts
(1 hunks)app/mcp/actions.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- app/mcp/actions.server.ts
- app/mcp/actions.client.ts
- app/mcp/actions.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/mcp/actions.base.ts (4)
app/mcp/logger.ts (2)
MCPClientLogger
(12-65)error
(32-34)app/mcp/types.ts (6)
McpClientData
(79-82)ServerStatusResponse
(110-113)ServerConfig
(122-122)McpConfigData
(139-143)McpRequestMessage
(6-14)DEFAULT_MCP_CONFIG
(145-147)app/mcp/client.ts (4)
createClient
(13-61)listTools
(68-70)removeClient
(63-66)executeRequest
(72-77)app/config/server.ts (1)
getServerSideConfig
(128-269)
🔇 Additional comments (12)
app/mcp/actions.base.ts (12)
22-28
: Good implementation of dual-mode path handling.The
getConfigPath
function correctly handles different environments by returning the appropriate path based on theEXPORT_MODE
global constant. This allows for seamless operation in both client-side (using relative URL) and server-side (using filesystem path) contexts.
30-77
: Good implementation addressing the previously identified stale mapping issue.The
ClientsMap
class properly implements the suggestedpurgeToolMappings
functionality from the previous review and ensures it's called in all appropriate places: theset
method (when tools are null), thedelete
method, and theclear
method. This prevents the leaked stale entries issue that was identified in the previous review.
81-130
: LGTM: Thorough status checking implementation.The
getClientsStatus
function thoroughly checks all possible states of a client (undefined, paused, initializing, error, active) and returns appropriate status responses. It provides a comprehensive view of the system state.
156-194
: Robust client initialization with proper error handling.The
initializeSingleClient
function initializes clients asynchronously with good error handling. It properly sets initial state, skips paused clients, and updates client state with tools or error information appropriately.
197-216
: Good system initialization with existing client detection.The
initializeMcpSystem
function correctly checks for existing clients before initialization and properly initializes all clients from configuration.
251-284
: Server pause implementation follows best practices.The
pauseMcpServer
function correctly updates the configuration first, then handles the cleanup of the client, following the pattern established in the past review comments that suggested purging tool mappings.
340-363
: LGTM: Server removal with proper cleanup.The
removeMcpServer
function correctly removes the server from configuration and cleans up the client and its tool mappings.
365-389
: LGTM: Comprehensive client restart implementation.The
restartAllClients
function properly shuts down all clients, clears the clients map (including tool mappings), and reinitializes all clients from the latest configuration.
391-414
: LGTM: Action execution with tool-based client resolution.The
executeMcpAction
function correctly resolves the client either by client ID or by tool name, which enables flexible invocation patterns.
449-469
: Efficient configuration update handling.The
updateMcpConfig
function appropriately handles both export mode (using localStorage) and server mode (using filesystem), with proper directory creation and error handling.
471-491
: Comprehensive MCP enablement check.The
isMcpEnabled
function correctly prioritizes the explicit config setting, then falls back to environment-specific configuration checks, making it robust across different deployment scenarios.
63-68
:✅ Verification successful
Verify error handling in the
getByToolName
method.The method returns
undefined
implicitly when a tool isn't found, but doesn't validate if the client exists in the map after finding a tool mapping. Consider adding a check to ensure the client exists.
🏁 Script executed:
#!/bin/bash # Check for places where getByToolName is used without explicit null/undefined checks rg -A 3 "getByToolName" --type tsLength of output: 548
No action needed:
getByToolName
result is already validated
The lookup is followed by a null‐safe check (if (!client?.client) throw …
), which covers both missing mappings and missing client entries.
export async function resumeMcpServer(clientId: string): Promise<void> { | ||
try { | ||
const currentConfig = await getMcpConfigFromFile(); | ||
const serverConfig = currentConfig.mcpServers[clientId]; | ||
if (!serverConfig) { | ||
throw new Error(`Server ${clientId} not found`); | ||
} | ||
|
||
// 先尝试初始化客户端 | ||
logger.info(`Trying to initialize client [${clientId}]...`); | ||
try { | ||
const client = await createClient(clientId, serverConfig); | ||
const tools = await listTools(client); | ||
clientsMap.set(clientId, { client, tools, errorMsg: null }); | ||
logger.success(`Client [${clientId}] initialized successfully`); | ||
|
||
// 初始化成功后更新配置 | ||
const newConfig: McpConfigData = { | ||
...currentConfig, | ||
mcpServers: { | ||
...currentConfig.mcpServers, | ||
[clientId]: { | ||
...serverConfig, | ||
status: "active" as const, | ||
}, | ||
}, | ||
}; | ||
await updateMcpConfig(newConfig); | ||
} catch (error) { | ||
const currentConfig = await getMcpConfigFromFile(); | ||
const serverConfig = currentConfig.mcpServers[clientId]; | ||
|
||
// 如果配置中存在该服务器,则更新其状态为 error | ||
if (serverConfig) { | ||
serverConfig.status = "error"; | ||
await updateMcpConfig(currentConfig); | ||
} | ||
|
||
// 初始化失败 | ||
clientsMap.set(clientId, { | ||
client: null, | ||
tools: null, | ||
errorMsg: error instanceof Error ? error.message : String(error), | ||
}); | ||
logger.error(`Failed to initialize client [${clientId}]: ${error}`); | ||
throw error; | ||
} | ||
} catch (error) { | ||
logger.error(`Failed to resume server [${clientId}]: ${error}`); | ||
throw error; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Potential race condition in resumeMcpServer
.
The function updates the server status to "active" only after successful initialization, which is good. However, if initialization fails, it updates the status to "error" by modifying the original config object directly, which might cause issues if the config is used elsewhere.
- // 如果配置中存在该服务器,则更新其状态为 error
- if (serverConfig) {
- serverConfig.status = "error";
- await updateMcpConfig(currentConfig);
- }
+ // 如果配置中存在该服务器,则更新其状态为 error
+ if (serverConfig) {
+ const updatedConfig = {
+ ...currentConfig,
+ mcpServers: {
+ ...currentConfig.mcpServers,
+ [clientId]: {
+ ...serverConfig,
+ status: "error" as const,
+ },
+ },
+ };
+ await updateMcpConfig(updatedConfig);
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export async function resumeMcpServer(clientId: string): Promise<void> { | |
try { | |
const currentConfig = await getMcpConfigFromFile(); | |
const serverConfig = currentConfig.mcpServers[clientId]; | |
if (!serverConfig) { | |
throw new Error(`Server ${clientId} not found`); | |
} | |
// 先尝试初始化客户端 | |
logger.info(`Trying to initialize client [${clientId}]...`); | |
try { | |
const client = await createClient(clientId, serverConfig); | |
const tools = await listTools(client); | |
clientsMap.set(clientId, { client, tools, errorMsg: null }); | |
logger.success(`Client [${clientId}] initialized successfully`); | |
// 初始化成功后更新配置 | |
const newConfig: McpConfigData = { | |
...currentConfig, | |
mcpServers: { | |
...currentConfig.mcpServers, | |
[clientId]: { | |
...serverConfig, | |
status: "active" as const, | |
}, | |
}, | |
}; | |
await updateMcpConfig(newConfig); | |
} catch (error) { | |
const currentConfig = await getMcpConfigFromFile(); | |
const serverConfig = currentConfig.mcpServers[clientId]; | |
// 如果配置中存在该服务器,则更新其状态为 error | |
if (serverConfig) { | |
serverConfig.status = "error"; | |
await updateMcpConfig(currentConfig); | |
} | |
// 初始化失败 | |
clientsMap.set(clientId, { | |
client: null, | |
tools: null, | |
errorMsg: error instanceof Error ? error.message : String(error), | |
}); | |
logger.error(`Failed to initialize client [${clientId}]: ${error}`); | |
throw error; | |
} | |
} catch (error) { | |
logger.error(`Failed to resume server [${clientId}]: ${error}`); | |
throw error; | |
} | |
} | |
} catch (error) { | |
const currentConfig = await getMcpConfigFromFile(); | |
const serverConfig = currentConfig.mcpServers[clientId]; | |
// 如果配置中存在该服务器,则更新其状态为 error | |
if (serverConfig) { | |
const updatedConfig = { | |
...currentConfig, | |
mcpServers: { | |
...currentConfig.mcpServers, | |
[clientId]: { | |
...serverConfig, | |
status: "error" as const, | |
}, | |
}, | |
}; | |
await updateMcpConfig(updatedConfig); | |
} | |
// 初始化失败 | |
clientsMap.set(clientId, { | |
client: null, | |
tools: null, | |
errorMsg: error instanceof Error ? error.message : String(error), | |
}); | |
logger.error(`Failed to initialize client [${clientId}]: ${error}`); | |
throw error; | |
} |
closes #6473,#6448, #6437,#6323,#6154
💻 变更类型 | Change Type
🔀 变更说明 | Description of Change
📝 补充信息 | Additional Information
Summary by CodeRabbit
Summary by CodeRabbit
New Features
Improvements
Bug Fixes