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 <edwin.jose@datastax.com>
This commit is contained in:
Lucas Oliveira 2025-06-12 17:33:24 -03:00 committed by GitHub
commit 1710c56e82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 64 additions and 14 deletions

View file

@ -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:

View file

@ -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)

View file

@ -0,0 +1,2 @@
MAX_MCP_SERVER_NAME_LENGTH = 30
MAX_MCP_TOOL_NAME_LENGTH = 30

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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");

View file

@ -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": [
${

View file

@ -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 : {},