Skip to content

Fix: Improve Custom MCP node performance by caching toolkits #4809

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

toi500
Copy link
Contributor

@toi500 toi500 commented Jul 7, 2025

As discussed, this PR fixes a critical performance issue in the Custom MCP node caused by redundant process spawning. The STDIO transport is re-spawning a child process on every UI refresh, leading to significant overhead.

Solution:

This is a single file solution implemented entirely within customMCP.ts.

  1. A static toolkitCache is used to store and reuse MCPToolkit instances, preventing the expensive process creation on every refresh.
  2. The cache key is generated by parsing the mcpServerConfig JSON. This makes the cache immune to formatting differences (spaces, newlines) and prevents spawning duplicate processes for the same logical configuration.

chrome_6CVrqvDHHL

Trade-off:

Since we are not modifying core.ts and the node's destroy() method has proven unreliable for cleanup (I have tried to implement it without any success), this solution will leak one process per unique server configuration. This is a necessary trade-off to fix the critical performance issue immediately while keeping the changes isolated to this single file.

@toi500
Copy link
Contributor Author

toi500 commented Jul 7, 2025

@HenryHengZJ, this is working very well for me now, very smooth.

I really hope you can find a solution to flush the cache when the custom node is deleted. I tried to implement this myself but without any success.

I will definitely be keeping this change for my own use, as it is much better to have a minor memory leak (especially since the current MCP implementation has some leaks anyway) than to have the constant process churn overload my server and force me to spend 20 minutes filling in actions on a single MCP node.

@HenryHengZJ HenryHengZJ requested a review from Copilot July 9, 2025 08:43
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Implements caching for MCPToolkit instances to eliminate redundant process spawning on UI refresh, boosting performance.

  • Introduces a static toolkitCache keyed by workspace, parsed config, and sandbox.
  • Parses and canonicalizes mcpServerConfig to generate consistent cache keys.
  • Clears out invalid mcpActions after loading a new toolkit to keep the UI in sync.
Comments suppressed due to low confidence (1)

packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts:161

  • Add unit tests to verify that cached toolkits are returned correctly and that the stale-action clearing logic behaves as expected to prevent regressions.
        if (Custom_MCP.toolkitCache.has(cacheKey)) {

// If parsing fails (e.g., invalid JSON), use the raw string as a fallback.
canonicalConfig = mcpServerConfig
}
const cacheKey = JSON.stringify({ workspaceId, canonicalConfig, sandbox })
Copy link
Preview

Copilot AI Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serializing the entire sandbox object for the cache key can be expensive and may fail on circular references. Consider restricting the key to only the relevant sandbox properties (e.g. $vars), or use a lightweight hash of workspaceId and canonicalConfig instead.

Suggested change
const cacheKey = JSON.stringify({ workspaceId, canonicalConfig, sandbox })
const sandboxVars = sandbox['$vars'] || {};
const cacheKey = JSON.stringify({ workspaceId, canonicalConfig, sandboxVars });

Copilot uses AI. Check for mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toi500 im quite concerned about stringify the whole canonicalConfig and sandboxVars because users can enter anything that could cause problem.

all we need is a unique key right?
how bout workspaceId_chatflowId_nodeId ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I think the key must be unique depending on the mcpServerConfig..

How do we clear the cache though?

@ahmedrowaihi
Copy link
Contributor

does this takes care of tool availability changes ? when custom mcps are used via npx the mcp tools might change ( which is desired if user wants new version of that mcp server ), but I wonder this cache will not affect that ?

@toi500
Copy link
Contributor Author

toi500 commented Jul 10, 2025

does this takes care of tool availability changes ? when custom mcps are used via npx the mcp tools might change ( which is desired if user wants new version of that mcp server ), but I wonder this cache will not affect that ?

Not sure if I am following you here. Every time you add a version, it uses a new mcpServerConfig, and you will fetch whatever actions that version has. That version is then cached until Flowise restarts.

If what you mean is that you will lose the convenience of using "@latest" in some cases, it is a trade-off I would definitely pay for not spinning up the same server 10 times to add 10 actions and spending 10 minutes on it.

image

@toi500
Copy link
Contributor Author

toi500 commented Jul 11, 2025

image

@toi500 toi500 marked this pull request as ready for review July 11, 2025 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants