From 1710c56e82b7da115366d960ee684f786ea3c29a Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:33:24 -0300 Subject: [PATCH] fix: changed truncation length for MCP tools and projects (#8494) * updated mcp backend to truncate tools name to 30 chars * updated frontend to truncate tools with 30 chars and truncate project name with 26 chars * changed it to not exceed 25 * updated one click install to truncate at 26 * updated name * used constants and helper functions to refactor code * updated constants --------- Co-authored-by: Edwin Jose --- src/backend/base/langflow/api/v1/mcp.py | 22 +++++++++++++++---- .../base/langflow/api/v1/mcp_projects.py | 22 ++++++++++++++----- .../base/langflow/base/mcp/constants.py | 2 ++ src/backend/base/langflow/base/mcp/util.py | 14 ++++++++++++ .../database/models/folder/constants.py | 1 + src/frontend/src/constants/constants.ts | 2 ++ .../src/modals/addMcpServerModal/index.tsx | 6 ++--- .../homePage/components/McpServerTab.tsx | 7 +++++- src/frontend/src/utils/mcpUtils.ts | 2 +- 9 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 src/backend/base/langflow/base/mcp/constants.py diff --git a/src/backend/base/langflow/api/v1/mcp.py b/src/backend/base/langflow/api/v1/mcp.py index d7029fcf6..3b6296130 100644 --- a/src/backend/base/langflow/api/v1/mcp.py +++ b/src/backend/base/langflow/api/v1/mcp.py @@ -19,6 +19,7 @@ from sqlmodel import select from langflow.api.v1.endpoints import simple_run_flow from langflow.api.v1.schemas import SimplifiedAPIRequest +from langflow.base.mcp.constants import MAX_MCP_TOOL_NAME_LENGTH from langflow.base.mcp.util import get_flow_snake_case from langflow.helpers.flow import json_schema_from_flow from langflow.schema.message import Message @@ -180,22 +181,35 @@ async def handle_list_tools(): async with db_service.with_session() as session: flows = (await session.exec(select(Flow))).all() + existing_names = set() for flow in flows: if flow.user_id is None: continue - flow_name = "_".join(flow.name.lower().split()) + base_name = "_".join(flow.name.lower().split()) + name = base_name[:MAX_MCP_TOOL_NAME_LENGTH] + if name in existing_names: + i = 1 + while True: + suffix = f"_{i}" + truncated_base = base_name[: MAX_MCP_TOOL_NAME_LENGTH - len(suffix)] + candidate = f"{truncated_base}{suffix}" + if candidate not in existing_names: + name = candidate + break + i += 1 try: tool = types.Tool( - name=flow_name, + name=name, description=f"{flow.id}: {flow.description}" if flow.description - else f"Tool generated from flow: {flow_name}", + else f"Tool generated from flow: {name}", inputSchema=json_schema_from_flow(flow), ) tools.append(tool) + existing_names.add(name) except Exception as e: # noqa: BLE001 - msg = f"Error in listing tools: {e!s} from flow: {flow_name}" + msg = f"Error in listing tools: {e!s} from flow: {base_name}" logger.warning(msg) continue except Exception as e: diff --git a/src/backend/base/langflow/api/v1/mcp_projects.py b/src/backend/base/langflow/api/v1/mcp_projects.py index de1ba33a0..4b7ea39fe 100644 --- a/src/backend/base/langflow/api/v1/mcp_projects.py +++ b/src/backend/base/langflow/api/v1/mcp_projects.py @@ -30,11 +30,13 @@ from langflow.api.v1.mcp import ( with_db_session, ) from langflow.api.v1.schemas import MCPInstallRequest, MCPSettings, SimplifiedAPIRequest -from langflow.base.mcp.util import get_flow_snake_case +from langflow.base.mcp.constants import MAX_MCP_SERVER_NAME_LENGTH, MAX_MCP_TOOL_NAME_LENGTH +from langflow.base.mcp.util import get_flow_snake_case, get_unique_name from langflow.helpers.flow import json_schema_from_flow from langflow.schema.message import Message from langflow.services.auth.utils import get_current_active_user from langflow.services.database.models import Flow, Folder, User +from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME, NEW_FOLDER_NAME from langflow.services.deps import get_settings_service, get_storage_service, session_scope from langflow.services.storage.utils import build_content_type_from_extension @@ -389,9 +391,17 @@ async def install_mcp_config( args = ["/c", "uvx", "mcp-proxy", sse_url] logger.debug("Windows detected, using cmd command") + name = project.name + name = NEW_FOLDER_NAME if name == DEFAULT_FOLDER_NAME else name + # Create the MCP configuration mcp_config = { - "mcpServers": {f"lf-{project.name.lower().replace(' ', '_')[:11]}": {"command": command, "args": args}} + "mcpServers": { + f"lf-{name.lower().replace(' ', '_')[: (MAX_MCP_SERVER_NAME_LENGTH - 4)]}": { + "command": command, + "args": args, + } + } } # Determine the config file path based on the client and OS @@ -456,7 +466,7 @@ async def check_installed_mcp_servers( raise HTTPException(status_code=404, detail="Project not found") # Project server name pattern - project_server_name = f"lf-{project.name.lower().replace(' ', '_')[:11]}" + project_server_name = f"lf-{project.name.lower().replace(' ', '_')[: (MAX_MCP_SERVER_NAME_LENGTH - 4)]}" # Check configurations for different clients results = [] @@ -517,13 +527,14 @@ class ProjectMCPServer: select(Flow).where(Flow.mcp_enabled == True, Flow.folder_id == self.project_id) # noqa: E712 ) ).all() - + existing_names = set() for flow in flows: if flow.user_id is None: continue # Use action_name if available, otherwise construct from flow name - name = flow.action_name or "_".join(flow.name.lower().split()) + base_name = flow.action_name or "_".join(flow.name.lower().split()) + name = get_unique_name(base_name, MAX_MCP_TOOL_NAME_LENGTH, existing_names) # Use action_description if available, otherwise use defaults description = flow.action_description or ( @@ -536,6 +547,7 @@ class ProjectMCPServer: inputSchema=json_schema_from_flow(flow), ) tools.append(tool) + existing_names.add(name) except Exception as e: # noqa: BLE001 msg = f"Error in listing project tools: {e!s} from flow: {name}" logger.warning(msg) diff --git a/src/backend/base/langflow/base/mcp/constants.py b/src/backend/base/langflow/base/mcp/constants.py new file mode 100644 index 000000000..1ae702a3f --- /dev/null +++ b/src/backend/base/langflow/base/mcp/constants.py @@ -0,0 +1,2 @@ +MAX_MCP_SERVER_NAME_LENGTH = 30 +MAX_MCP_TOOL_NAME_LENGTH = 30 diff --git a/src/backend/base/langflow/base/mcp/util.py b/src/backend/base/langflow/base/mcp/util.py index 0fcfcf800..9bd15ed25 100644 --- a/src/backend/base/langflow/base/mcp/util.py +++ b/src/backend/base/langflow/base/mcp/util.py @@ -80,6 +80,20 @@ def create_tool_func(tool_name: str, arg_schema: type[BaseModel], client) -> Cal return tool_func +def get_unique_name(base_name, max_length, existing_names): + name = base_name[:max_length] + if name not in existing_names: + return name + i = 1 + while True: + suffix = f"_{i}" + truncated_base = base_name[: max_length - len(suffix)] + candidate = f"{truncated_base}{suffix}" + if candidate not in existing_names: + return candidate + i += 1 + + async def get_flow_snake_case(flow_name: str, user_id: str, session, is_action: bool | None = None) -> Flow | None: uuid_user_id = UUID(user_id) if isinstance(user_id, str) else user_id stmt = select(Flow).where(Flow.user_id == uuid_user_id).where(Flow.is_component == False) # noqa: E712 diff --git a/src/backend/base/langflow/services/database/models/folder/constants.py b/src/backend/base/langflow/services/database/models/folder/constants.py index df3df3f3f..54a5cf46f 100644 --- a/src/backend/base/langflow/services/database/models/folder/constants.py +++ b/src/backend/base/langflow/services/database/models/folder/constants.py @@ -1,2 +1,3 @@ DEFAULT_FOLDER_DESCRIPTION = "Manage your own flows. Download and upload projects." DEFAULT_FOLDER_NAME = "My Projects" +NEW_FOLDER_NAME = "Starter Project" diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 5a00a648f..2ae093ea8 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -554,6 +554,8 @@ export const USER_PROJECTS_HEADER = "My Collection"; export const DEFAULT_FOLDER = "Starter Project"; export const DEFAULT_FOLDER_DEPRECATED = "My Projects"; +export const MAX_MCP_SERVER_NAME_LENGTH = 30; + /** * Header text for admin page * @constant diff --git a/src/frontend/src/modals/addMcpServerModal/index.tsx b/src/frontend/src/modals/addMcpServerModal/index.tsx index e4e0a3d11..14b5af2cb 100644 --- a/src/frontend/src/modals/addMcpServerModal/index.tsx +++ b/src/frontend/src/modals/addMcpServerModal/index.tsx @@ -134,7 +134,7 @@ export default function AddMcpServerModal({ "snake_case", "no_blank", "lowercase", - ]).slice(0, 20); + ]).slice(0, 30); try { await modifyMCPServer({ name, @@ -168,7 +168,7 @@ export default function AddMcpServerModal({ "snake_case", "no_blank", "lowercase", - ]).slice(0, 20); + ]).slice(0, 30); try { await modifyMCPServer({ name, @@ -202,7 +202,7 @@ export default function AddMcpServerModal({ "snake_case", "no_blank", "lowercase", - ]).slice(0, 20), + ]).slice(0, 30), })); } catch (e: any) { setError(e.message || "Invalid input"); diff --git a/src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx b/src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx index 793094aa6..40399dd3b 100644 --- a/src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx +++ b/src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx @@ -3,6 +3,11 @@ import ShadTooltip from "@/components/common/shadTooltipComponent"; import ToolsComponent from "@/components/core/parameterRenderComponent/components/ToolsComponent"; import { Button } from "@/components/ui/button"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button"; +import { + DEFAULT_FOLDER, + DEFAULT_FOLDER_DEPRECATED, + MAX_MCP_SERVER_NAME_LENGTH, +} from "@/constants/constants"; import { createApiKey } from "@/controllers/API"; import { useGetFlowsMCP, @@ -183,7 +188,7 @@ const McpServerTab = ({ folderName }: { folderName: string }) => { const MCP_SERVER_JSON = `{ "mcpServers": { - "lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, 11)}": { + "lf-${parseString(folderName === DEFAULT_FOLDER_DEPRECATED ? DEFAULT_FOLDER : (folderName ?? "project"), ["snake_case", "no_blank", "lowercase"]).slice(0, MAX_MCP_SERVER_NAME_LENGTH - 4)}": { "command": "${selectedPlatform === "windows" ? "cmd" : selectedPlatform === "wsl" ? "wsl" : "uvx"}", "args": [ ${ diff --git a/src/frontend/src/utils/mcpUtils.ts b/src/frontend/src/utils/mcpUtils.ts index c13d74db7..57953095a 100644 --- a/src/frontend/src/utils/mcpUtils.ts +++ b/src/frontend/src/utils/mcpUtils.ts @@ -153,7 +153,7 @@ export function extractMcpServersFromJson( throw new Error("No valid MCP server found in the input."); } return validServers.map(([name, server]) => ({ - name: name.slice(0, 20), + name: name.slice(0, 30), command: server.command, args: server.args, env: server.env && typeof server.env === "object" ? server.env : {},