feat: QoL MCP (#7361)
* Update mcp_component.py
* [autofix.ci] apply automated fixes
* Update mcp_component.py
* Update mcp_component.py
* making tools empty when see url is empty
* ✅ (mcp-server.spec.ts): add additional tests to ensure dropdown_str_tool is disabled and has a timeout of 30 seconds for selector wait.
* fix mcp tests
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
parent
58b9551758
commit
b060183c8c
4 changed files with 69 additions and 25 deletions
|
|
@ -150,33 +150,54 @@ class MCPToolsComponent(Component):
|
||||||
_, port, _ = await self.find_langflow_instance()
|
_, port, _ = await self.find_langflow_instance()
|
||||||
if port:
|
if port:
|
||||||
build_config["sse_url"]["value"] = f"http://localhost:{port}/api/v1/mcp/sse"
|
build_config["sse_url"]["value"] = f"http://localhost:{port}/api/v1/mcp/sse"
|
||||||
self.sse_url = build_config["sse_url"]["value"]
|
|
||||||
return build_config
|
return build_config
|
||||||
if field_name in ("command", "sse_url", "mode"):
|
if field_name in ("command", "sse_url", "mode"):
|
||||||
try:
|
try:
|
||||||
# If SSE mode and localhost URL is not valid, try to find correct port
|
# If SSE mode and localhost URL is not valid, try to find correct port
|
||||||
if field_name == "sse_url":
|
if build_config["mode"]["value"] == "SSE" and (
|
||||||
self.sse_url = field_value
|
"localhost" in str(build_config["sse_url"]["value"])
|
||||||
elif self.mode == "SSE" and ("localhost" in str(self.sse_url) or "127.0.0.1" in str(self.sse_url)):
|
or "127.0.0.1" in str(build_config["sse_url"]["value"])
|
||||||
is_valid, _ = await self.sse_client.validate_url(self.sse_url)
|
):
|
||||||
|
is_valid, _ = await self.sse_client.validate_url(build_config["sse_url"]["value"])
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
found, port, message = await self.find_langflow_instance()
|
found, port, message = await self.find_langflow_instance()
|
||||||
if found:
|
if found:
|
||||||
new_url = f"http://localhost:{port}/api/v1/mcp/sse"
|
new_url = f"http://localhost:{port}/api/v1/mcp/sse"
|
||||||
logger.info(f"Original URL {self.sse_url} not valid. {message}")
|
logger.info(f"Original URL {build_config['sse_url']['value']} not valid. {message}")
|
||||||
build_config["sse_url"]["value"] = new_url
|
build_config["sse_url"]["value"] = new_url
|
||||||
self.sse_url = new_url
|
elif build_config["mode"]["value"] == "SSE":
|
||||||
|
if len(build_config["sse_url"]["value"]) > 0:
|
||||||
|
is_valid, _ = await self.sse_client.validate_url(build_config["sse_url"]["value"])
|
||||||
|
if not is_valid:
|
||||||
|
msg = (
|
||||||
|
f"Invalid SSE URL configuration: {build_config['sse_url']['value']}. "
|
||||||
|
"Please check the SSE URL and try again."
|
||||||
|
)
|
||||||
|
raise ValueError(msg)
|
||||||
|
else:
|
||||||
|
build_config["tool"]["options"] = []
|
||||||
|
return build_config
|
||||||
|
|
||||||
await self.update_tools()
|
await self.update_tools(
|
||||||
|
mode=build_config["mode"]["value"],
|
||||||
|
command=build_config["command"]["value"],
|
||||||
|
url=build_config["sse_url"]["value"],
|
||||||
|
)
|
||||||
if "tool" in build_config:
|
if "tool" in build_config:
|
||||||
build_config["tool"]["options"] = self.tool_names
|
build_config["tool"]["options"] = self.tool_names
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
build_config["tool"]["options"] = []
|
build_config["tool"]["options"] = []
|
||||||
msg = f"Failed to update tools: {e!s}"
|
msg = f"Failed to update tools: {e!s}"
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
|
else:
|
||||||
|
return build_config
|
||||||
elif field_name == "tool":
|
elif field_name == "tool":
|
||||||
if len(self.tools) == 0:
|
if len(self.tools) == 0:
|
||||||
await self.update_tools()
|
await self.update_tools(
|
||||||
|
mode=build_config["mode"]["value"],
|
||||||
|
command=build_config["command"]["value"],
|
||||||
|
url=build_config["sse_url"]["value"],
|
||||||
|
)
|
||||||
if self.tool is None:
|
if self.tool is None:
|
||||||
return build_config
|
return build_config
|
||||||
tool_obj = None
|
tool_obj = None
|
||||||
|
|
@ -239,7 +260,11 @@ class MCPToolsComponent(Component):
|
||||||
async def _update_tool_config(self, build_config: dict, tool_name: str) -> None:
|
async def _update_tool_config(self, build_config: dict, tool_name: str) -> None:
|
||||||
"""Update tool configuration with proper error handling."""
|
"""Update tool configuration with proper error handling."""
|
||||||
if not self.tools:
|
if not self.tools:
|
||||||
await self.update_tools()
|
await self.update_tools(
|
||||||
|
mode=build_config["mode"]["value"],
|
||||||
|
command=build_config["command"]["value"],
|
||||||
|
url=build_config["sse_url"]["value"],
|
||||||
|
)
|
||||||
|
|
||||||
if not tool_name:
|
if not tool_name:
|
||||||
return
|
return
|
||||||
|
|
@ -310,22 +335,30 @@ class MCPToolsComponent(Component):
|
||||||
logger.exception(msg)
|
logger.exception(msg)
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
|
|
||||||
async def update_tools(self) -> list[StructuredTool]:
|
async def update_tools(
|
||||||
|
self, mode: str | None = None, command: str | None = None, url: str | None = None
|
||||||
|
) -> list[StructuredTool]:
|
||||||
"""Connect to the MCP server and update available tools with improved error handling."""
|
"""Connect to the MCP server and update available tools with improved error handling."""
|
||||||
try:
|
try:
|
||||||
await self._validate_connection_params(self.mode, self.command, self.sse_url)
|
if mode is None:
|
||||||
|
mode = self.mode
|
||||||
|
if command is None:
|
||||||
|
command = self.command
|
||||||
|
if url is None:
|
||||||
|
url = self.sse_url
|
||||||
|
await self._validate_connection_params(mode, command, url)
|
||||||
|
|
||||||
if self.mode == "Stdio":
|
if mode == "Stdio":
|
||||||
if not self.stdio_client.session:
|
if not self.stdio_client.session:
|
||||||
self.tools = await self.stdio_client.connect_to_server(self.command)
|
self.tools = await self.stdio_client.connect_to_server(command)
|
||||||
elif self.mode == "SSE" and not self.sse_client.session:
|
elif mode == "SSE" and not self.sse_client.session:
|
||||||
try:
|
try:
|
||||||
is_valid, _ = await self.sse_client.validate_url(self.sse_url)
|
is_valid, _ = await self.sse_client.validate_url(url)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
msg = f"Invalid SSE URL configuration: {self.sse_url}. Please check the SSE URL and try again."
|
msg = f"Invalid SSE URL configuration: {url}. Please check the SSE URL and try again."
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
return []
|
return []
|
||||||
self.tools = await self.sse_client.connect_to_server(self.sse_url, {})
|
self.tools = await self.sse_client.connect_to_server(url, {})
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# URL validation error
|
# URL validation error
|
||||||
logger.error(f"SSE URL validation error: {e}")
|
logger.error(f"SSE URL validation error: {e}")
|
||||||
|
|
@ -397,6 +430,9 @@ class MCPToolsComponent(Component):
|
||||||
|
|
||||||
async def _get_tools(self):
|
async def _get_tools(self):
|
||||||
"""Get cached tools or update if necessary."""
|
"""Get cached tools or update if necessary."""
|
||||||
if not self.tools:
|
# if not self.tools:
|
||||||
return await self.update_tools()
|
if self.mode == "SSE" and self.sse_url is None:
|
||||||
return self.tools
|
msg = "SSE URL is not set"
|
||||||
|
raise ValueError(msg)
|
||||||
|
return await self.update_tools()
|
||||||
|
# return self.tools
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,10 @@ class TestMCPToolsComponent(ComponentTestBaseWithoutClient):
|
||||||
"""Test build config updates when mode changes."""
|
"""Test build config updates when mode changes."""
|
||||||
component = component_class(**default_kwargs)
|
component = component_class(**default_kwargs)
|
||||||
build_config = {
|
build_config = {
|
||||||
"command": {"show": False},
|
"command": {"show": False, "value": "uvx mcp-server-fetch"},
|
||||||
"sse_url": {"show": True},
|
"sse_url": {"show": True, "value": "http://localhost:7860/api/v1/mcp/sse"},
|
||||||
"tool": {"options": [], "show": True}, # Add tool field since component uses it
|
"tool": {"options": [], "show": True},
|
||||||
|
"mode": {"value": "Stdio"},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test switching to Stdio mode
|
# Test switching to Stdio mode
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { v4 as uuid } from "uuid";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { getInputsAndOutputs } from "../../utils/storeUtils";
|
import { getInputsAndOutputs } from "../../utils/storeUtils";
|
||||||
export default function PlaygroundPage() {
|
export default function PlaygroundPage() {
|
||||||
|
|
||||||
useGetConfig();
|
useGetConfig();
|
||||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||||
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,10 @@ test(
|
||||||
|
|
||||||
expect(sseURLCount).toBeGreaterThan(0);
|
expect(sseURLCount).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="dropdown_str_tool"]:disabled', {
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
|
|
||||||
await page.getByTestId("tab_0_stdio").click();
|
await page.getByTestId("tab_0_stdio").click();
|
||||||
|
|
||||||
await page.getByTestId("refresh-button-command").click();
|
await page.getByTestId("refresh-button-command").click();
|
||||||
|
|
@ -131,5 +135,9 @@ test(
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
expect(sseURLCount).toBeGreaterThan(0);
|
expect(sseURLCount).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="dropdown_str_tool"]:disabled', {
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue