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:
parent
4b48b63227
commit
1710c56e82
9 changed files with 64 additions and 14 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
2
src/backend/base/langflow/base/mcp/constants.py
Normal file
2
src/backend/base/langflow/base/mcp/constants.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
MAX_MCP_SERVER_NAME_LENGTH = 30
|
||||
MAX_MCP_TOOL_NAME_LENGTH = 30
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
${
|
||||
|
|
|
|||
|
|
@ -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 : {},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue