fix: re-add name and description editing on tool mode except for Composio, fix MCP server code (#7901)

* Changed backend to contain readonly props for tools

* Show name editing for not readonly tools

* Fixed edit-tools test

* Updated command to use "uvx" instead of "npx" for stability

* Fixed mcp code for authentication on auto_login=false

* removed args from component desc

* [autofix.ci] apply automated fixes

* making tool mode inputs the priority.

* fix: Clean up comments and whitespace in component_tool.py

* Fix column name

* update the dispaly name in composio

* fix format

* ✨ (get-started-progress.tsx): add data-testid attribute to improve testability and accessibility
🔧 (user-progress-track.spec.ts): update test assertions to use the new data-testid attribute for get started progress title

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2025-05-05 18:20:19 -03:00 • committed by GitHub
commit 75deddd102
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 128 additions and 60 deletions

View file

@ -186,7 +186,7 @@ class ComposioBaseComponent(Component):
build_config["action"]["options"] = [ build_config["action"]["options"] = [
{ {
"name": self.sanitize_action_name(action), "name": self.sanitize_action_name(action),
"metaData": action, "metadata": action,
} }
for action in self._actions_data for action in self._actions_data
] ]
@ -282,10 +282,12 @@ class ComposioBaseComponent(Component):
configured_tools = [] configured_tools = []
for tool in tools: for tool in tools:
# Set the sanitized name # Set the sanitized name
display_name = self._sanitized_names.get(tool.name, self._name_sanitizer.sub("-", tool.name)) display_name = self._actions_data.get(tool.name, {}).get(
"display_name", self._sanitized_names.get(tool.name, self._name_sanitizer.sub("-", tool.name))
)
# Set the tags # Set the tags
tool.tags = [tool.name] tool.tags = [tool.name]
tool.metadata = {"display_name": display_name, "display_description": tool.description} tool.metadata = {"display_name": display_name, "display_description": tool.description, "readonly": True}
configured_tools.append(tool) configured_tools.append(tool)
return configured_tools return configured_tools

View file

@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Literal
import pandas as pd import pandas as pd
from langchain_core.tools import BaseTool, ToolException from langchain_core.tools import BaseTool, ToolException
from langchain_core.tools.structured import StructuredTool from langchain_core.tools.structured import StructuredTool
from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel
from langflow.base.tools.constants import TOOL_OUTPUT_NAME from langflow.base.tools.constants import TOOL_OUTPUT_NAME
@ -40,21 +39,8 @@ def _get_input_type(input_: InputTypes):
def build_description(component: Component, output: Output) -> str: def build_description(component: Component, output: Output) -> str:
if not output.required_inputs:
logger.warning(f"Output {output.name} does not have required inputs defined")
name = component.name or component.__class__.__name__ name = component.name or component.__class__.__name__
if output.required_inputs: return f"{name}. {output.method} - {component.description}"
args = ", ".join(
sorted(
[
f"{name}. {input_name}: {_get_input_type(component._inputs[input_name])}"
for input_name in output.required_inputs
]
)
)
else:
args = ""
return f"{name}. {output.method}({args}) - {component.description}"
async def send_message_noop( async def send_message_noop(
@ -211,6 +197,8 @@ class ComponentToolkit:
inputs=flow_mode_inputs, inputs=flow_mode_inputs,
param_key="flow_tweak_data", param_key="flow_tweak_data",
) )
elif tool_mode_inputs:
args_schema = create_input_schema(tool_mode_inputs)
elif output.required_inputs: elif output.required_inputs:
inputs = [ inputs = [
self.component._inputs[input_name] self.component._inputs[input_name]
@ -220,6 +208,7 @@ class ComponentToolkit:
# If any of the required inputs are not in tool mode, this means # If any of the required inputs are not in tool mode, this means
# that when the tool is called it will raise an error. # that when the tool is called it will raise an error.
# so we should raise an error here. # so we should raise an error here.
# TODO: This logic might need to be improved, example if the required is an api key.
if not all(getattr(_input, "tool_mode", False) for _input in inputs): if not all(getattr(_input, "tool_mode", False) for _input in inputs):
non_tool_mode_inputs = [ non_tool_mode_inputs = [
input_.name input_.name
@ -234,8 +223,7 @@ class ComponentToolkit:
) )
raise ValueError(msg) raise ValueError(msg)
args_schema = create_input_schema(inputs) args_schema = create_input_schema(inputs)
elif tool_mode_inputs:
args_schema = create_input_schema(tool_mode_inputs)
else: else:
args_schema = create_input_schema(self.component.inputs) args_schema = create_input_schema(self.component.inputs)

View file

@ -1166,6 +1166,7 @@ class Component(CustomComponent):
async def _build_tools_metadata_input(self): async def _build_tools_metadata_input(self):
tools = await self._get_tools() tools = await self._get_tools()
# Always use the latest tool data # Always use the latest tool data
tool_data = [ tool_data = [
{ {
"name": tool.name, "name": tool.name,
@ -1174,6 +1175,7 @@ class Component(CustomComponent):
"status": True, # Initialize all tools with status True "status": True, # Initialize all tools with status True
"display_name": tool.metadata.get("display_name", tool.name), "display_name": tool.metadata.get("display_name", tool.name),
"display_description": tool.metadata.get("display_description", tool.description), "display_description": tool.metadata.get("display_description", tool.description),
"readonly": tool.metadata.get("readonly", False),
"args": tool.args, "args": tool.args,
# "args_schema": tool.args_schema, # "args_schema": tool.args_schema,
} }

View file

@ -84,7 +84,10 @@ export const GetStartedProgress: FC<{
return ( return (
<div className="mt-3 h-[10.8rem] w-full"> <div className="mt-3 h-[10.8rem] w-full">
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<span className="text-sm font-semibold"> <span
className="text-sm font-semibold"
data-testid="get_started_progress_title"
>
{percentageGetStarted >= 100 ? ( {percentageGetStarted >= 100 ? (
<> <>
<span>All Set</span> <span className="pl-1"> 🎉 </span> <span>All Set</span> <span className="pl-1"> 🎉 </span>

View file

@ -147,27 +147,27 @@ export default function ToolsTable({
cellClass: "text-muted-foreground", cellClass: "text-muted-foreground",
}, },
{ {
field: isAction ? "name" : "tags", field: "name",
headerName: isAction ? "Action" : "Slug", headerName: isAction ? "Action" : "Slug",
flex: 1, flex: 1,
resizable: false, resizable: false,
valueGetter: (params) => valueGetter: (params) =>
isAction params.data.name !== ""
? params.data.name !== "" ? parseString(params.data.name, [
? parseString(params.data.name, [
"snake_case",
"no_blank",
"uppercase",
])
: parseString(params.data.display_name, [
"snake_case",
"no_blank",
"uppercase",
])
: parseString(params.data.tags.join(", "), [
"snake_case", "snake_case",
"no_blank",
"uppercase", "uppercase",
]), ])
: isAction
? parseString(params.data.display_name, [
"snake_case",
"no_blank",
"uppercase",
])
: parseString(params.data.tags.join(", "), [
"snake_case",
"uppercase",
]),
cellClass: "text-muted-foreground", cellClass: "text-muted-foreground",
}, },
{ {
@ -281,7 +281,7 @@ export default function ToolsTable({
> >
<SidebarHeader className="flex-none px-4 py-4"> <SidebarHeader className="flex-none px-4 py-4">
{focusedRow && {focusedRow &&
(isAction ? ( (isAction || !focusedRow.readonly ? (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label <label

View file

@ -69,18 +69,17 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
const MCP_SERVER_JSON = `{ const MCP_SERVER_JSON = `{
"mcpServers": { "mcpServers": {
"lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, 11)}": { "lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, 11)}": {
"command": "npx", "command": "uvx",
"args": [ "args": [
"-y", "mcp-proxy",${
"supergateway",
"--sse",
"${apiUrl}"${
isAutoLogin isAutoLogin
? "" ? ""
: `, : `
"--header", "--headers",
"x-api-key:${apiKey || "YOUR_API_KEY"}"` "x-api-key",
"${apiKey || "YOUR_API_KEY"}",`
} }
"${apiUrl}"
] ]
} }
} }

View file

@ -37,7 +37,9 @@ test(
await expect( await expect(
page.getByTestId("empty_page_drag_and_drop_text"), page.getByTestId("empty_page_drag_and_drop_text"),
).toBeVisible(); ).toBeVisible();
await expect(page.getByTestId("app-header")).not.toBeVisible(); await expect(
page.getByTestId("get_started_progress_title"),
).not.toBeVisible();
await page.getByTestId("empty_page_github_button").click(); await page.getByTestId("empty_page_github_button").click();
@ -69,7 +71,7 @@ test(
timeout: 100000, timeout: 100000,
}); });
await expect(page.getByTestId("app-header")).toBeVisible(); await expect(page.getByTestId("get_started_progress_title")).toBeVisible();
await expect( await expect(
page.getByTestId("github_starred_icon_get_started"), page.getByTestId("github_starred_icon_get_started"),
).toBeVisible(); ).toBeVisible();
@ -94,7 +96,7 @@ test(
await newPageDiscord.close(); await newPageDiscord.close();
await expect(page.getByTestId("app-header")).toBeVisible(); await expect(page.getByTestId("get_started_progress_title")).toBeVisible();
await expect( await expect(
page.getByTestId("discord_joined_icon_get_started"), page.getByTestId("discord_joined_icon_get_started"),
).toBeVisible(); ).toBeVisible();
@ -156,7 +158,9 @@ test(
await expect( await expect(
page.getByTestId("empty_page_drag_and_drop_text"), page.getByTestId("empty_page_drag_and_drop_text"),
).toBeVisible(); ).toBeVisible();
await expect(page.getByTestId("app-header")).not.toBeVisible(); await expect(
page.getByTestId("get_started_progress_title"),
).not.toBeVisible();
await page.getByTestId("empty_page_github_button").click(); await page.getByTestId("empty_page_github_button").click();
@ -188,7 +192,7 @@ test(
timeout: 100000, timeout: 100000,
}); });
await expect(page.getByTestId("app-header")).toBeVisible(); await expect(page.getByTestId("get_started_progress_title")).toBeVisible();
await expect( await expect(
page.getByTestId("github_starred_icon_get_started"), page.getByTestId("github_starred_icon_get_started"),
).toBeVisible(); ).toBeVisible();

View file

@ -74,8 +74,50 @@ test(
await page.locator('input[data-ref="eInput"]').nth(4).isChecked(), await page.locator('input[data-ref="eInput"]').nth(4).isChecked(),
).toBe(false); ).toBe(false);
await page.locator('input[data-ref="eInput"]').nth(0).click();
await page.waitForTimeout(500);
await page.getByRole("gridcell").nth(0).click();
await page.waitForTimeout(500);
expect(
await page.locator('[data-testid="sidebar_header_name"]').isHidden(),
).toBe(true);
expect(
await page
.locator('[data-testid="sidebar_header_description"]')
.isHidden(),
).toBe(true);
expect(
await page.locator('[data-testid="input_update_name"]').isVisible(),
).toBe(true);
expect(
await page
.locator('[data-testid="input_update_description"]')
.isVisible(),
).toBe(true);
await page.locator('[data-testid="input_update_name"]').fill("test name");
await page.waitForTimeout(500);
await page
.locator('[data-testid="input_update_description"]')
.fill("test description");
await page.waitForTimeout(500);
await page.getByText("Close").last().click(); await page.getByText("Close").last().click();
await page.waitForTimeout(500);
expect(await page.getByTestId("tool_test_name").isVisible()).toBe(true);
await page.waitForSelector( await page.waitForSelector(
'[data-testid="generic-node-title-arrangement"]', '[data-testid="generic-node-title-arrangement"]',
{ {
@ -93,38 +135,66 @@ test(
.isVisible(), .isVisible(),
).toBe(true); ).toBe(true);
await page.getByTestId("div-tools_tools_metadata").click(); await page.getByTestId("button_open_actions").click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
expect( expect(
await page.locator('input[data-ref="eInput"]').nth(3).isChecked(), await page.locator('input[data-ref="eInput"]').nth(3).isChecked(),
).toBe(false); ).toBe(true);
expect(
await page.locator('input[data-ref="eInput"]').nth(4).isChecked(),
).toBe(true);
await page.locator('input[data-ref="eInput"]').nth(4).click();
await page.waitForTimeout(500);
expect( expect(
await page.locator('input[data-ref="eInput"]').nth(4).isChecked(), await page.locator('input[data-ref="eInput"]').nth(4).isChecked(),
).toBe(false); ).toBe(false);
await page.locator('input[data-ref="eInput"]').nth(3).click(); await page.getByRole("gridcell").nth(0).click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
expect( expect(
await page.locator('input[data-ref="eInput"]').nth(3).isChecked(), await page.locator('[data-testid="sidebar_header_name"]').isHidden(),
).toBe(true);
await page.getByRole("gridcell").nth(0).click();
expect(
await page.locator('[data-testid="sidebar_header_name"]').isVisible(),
).toBe(true); ).toBe(true);
expect( expect(
await page await page
.locator('[data-testid="sidebar_header_description"]') .locator('[data-testid="sidebar_header_description"]')
.isHidden(),
).toBe(true);
expect(
await page.locator('[data-testid="input_update_name"]').isVisible(),
).toBe(true);
expect(
await page
.locator('[data-testid="input_update_description"]')
.isVisible(), .isVisible(),
).toBe(true); ).toBe(true);
expect(
await page.locator('[data-testid="input_update_name"]').inputValue(),
).toBe("test_name");
expect(
await page
.locator('[data-testid="input_update_description"]')
.inputValue(),
).toBe("test description");
await page.locator('[data-testid="input_update_name"]').fill("");
await page.waitForTimeout(500);
await page.locator('[data-testid="input_update_description"]').fill("");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByText("Close").last().click(); await page.getByText("Close").last().click();