feat: Adds MCP_SSE to be filled from frontend (#7594)

* add mcp sse to copy field

* add MCP_SSE from frontend

* [autofix.ci] apply automated fixes

* refactor: reorder imports in mcp_component.py for consistency

* ✅ (mcp-server.spec.ts): refactor code to use consistent selector for sse_url textarea element in tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Edwin Jose 2025-04-17 14:02:37 -04:00 • committed by GitHub
commit 0ba8f6559c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 31 additions and 30 deletions

View file

@ -15,7 +15,7 @@ from langflow.base.mcp.util import (
from langflow.custom import Component from langflow.custom import Component
from langflow.inputs import DropdownInput from langflow.inputs import DropdownInput
from langflow.inputs.inputs import InputTypes from langflow.inputs.inputs import InputTypes
from langflow.io import MessageTextInput, Output, TabInput from langflow.io import MessageTextInput, MultilineInput, Output, TabInput
from langflow.io.schema import schema_to_langflow_inputs from langflow.io.schema import schema_to_langflow_inputs
from langflow.logging import logger from langflow.logging import logger
from langflow.schema import Message from langflow.schema import Message
@ -52,7 +52,7 @@ class MCPToolsComponent(Component):
show=True, show=True,
refresh_button=True, refresh_button=True,
), ),
MessageTextInput( MultilineInput(
name="sse_url", name="sse_url",
display_name="MCP SSE URL", display_name="MCP SSE URL",
info="URL for MCP SSE connection", info="URL for MCP SSE connection",
@ -147,9 +147,7 @@ class MCPToolsComponent(Component):
elif field_value == "SSE": elif field_value == "SSE":
build_config["command"]["show"] = False build_config["command"]["show"] = False
build_config["sse_url"]["show"] = True build_config["sse_url"]["show"] = True
_, port, _ = await self.find_langflow_instance() build_config["sse_url"]["value"] = "MCP_SSE"
if port:
build_config["sse_url"]["value"] = f"http://localhost:{port}/api/v1/mcp/sse"
return build_config return build_config
if field_name in ("command", "sse_url", "mode"): if field_name in ("command", "sse_url", "mode"):
try: try:

View file

@ -8,7 +8,9 @@ import { Input } from "../../../../ui/input";
import { InputProps, TextAreaComponentType } from "../../types"; import { InputProps, TextAreaComponentType } from "../../types";
const BACKEND_URL = "BACKEND_URL"; const BACKEND_URL = "BACKEND_URL";
const MCP_SSE_VALUE = "MCP_SSE";
const URL_WEBHOOK = `${window.location.protocol}//${window.location.host}/api/v1/webhook/`; const URL_WEBHOOK = `${window.location.protocol}//${window.location.host}/api/v1/webhook/`;
const URL_MCP_SSE = `${window.location.protocol}//${window.location.host}/api/v1/mcp/sse`;
const inputClasses = { const inputClasses = {
base: ({ isFocused }: { isFocused: boolean }) => base: ({ isFocused }: { isFocused: boolean }) =>
@ -60,15 +62,16 @@ export default function CopyFieldAreaComponent({
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const [isCopied, setIsCopied] = useState(false); const [isCopied, setIsCopied] = useState(false);
const isValueToReplace = value === BACKEND_URL; const isValueToReplace = value === BACKEND_URL || value === MCP_SSE_VALUE;
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const currentFlow = useFlowStore((state) => state.currentFlow); const currentFlow = useFlowStore((state) => state.currentFlow);
const endpointName = currentFlow?.endpoint_name ?? ""; const endpointName = currentFlow?.endpoint_name ?? "";
const valueToRender = useMemo(() => { const valueToRender = useMemo(() => {
if (isValueToReplace) { if (value === BACKEND_URL) {
const urlWebhook = `${URL_WEBHOOK}${endpointName}`; return `${URL_WEBHOOK}${endpointName}`;
return isValueToReplace ? urlWebhook : value; } else if (value === MCP_SSE_VALUE) {
return `${URL_MCP_SSE}`;
} }
return value; return value;
}, [value, endpointName]); }, [value, endpointName]);

View file

@ -20,6 +20,8 @@ const inputClasses = {
}; };
const WEBHOOK_VALUE = "CURL_WEBHOOK"; const WEBHOOK_VALUE = "CURL_WEBHOOK";
const MCP_SSE_VALUE = "MCP_SSE";
const URL_MCP_SSE = `${window.location.protocol}//${window.location.host}/api/v1/mcp/sse`;
const externalLinkIconClasses = { const externalLinkIconClasses = {
gradient: ({ gradient: ({
@ -75,6 +77,11 @@ export default function TextAreaComponent({
[nodeInformationMetadata?.nodeType], [nodeInformationMetadata?.nodeType],
); );
const isMCPSSE = useMemo(
() => nodeInformationMetadata?.nodeType === "mcp_sse",
[nodeInformationMetadata?.nodeType],
);
useEffect(() => { useEffect(() => {
if (isWebhook && value === WEBHOOK_VALUE) { if (isWebhook && value === WEBHOOK_VALUE) {
const curlWebhookCode = getCurlWebhookCode({ const curlWebhookCode = getCurlWebhookCode({
@ -84,8 +91,11 @@ export default function TextAreaComponent({
format: "singleline", format: "singleline",
}); });
handleOnNewValue({ value: curlWebhookCode }); handleOnNewValue({ value: curlWebhookCode });
} else if (value === MCP_SSE_VALUE) {
const mcpSSEUrl = `${URL_MCP_SSE}`;
handleOnNewValue({ value: mcpSSEUrl });
} }
}, [isWebhook]); }, [isWebhook, value, nodeInformationMetadata, handleOnNewValue]);
const getInputClassName = () => { const getInputClassName = () => {
return cn( return cn(

View file

@ -68,17 +68,12 @@ test(
await page.getByTestId("tab_1_sse").click(); await page.getByTestId("tab_1_sse").click();
await page.waitForSelector( await page.waitForSelector('[data-testid="textarea_str_sse_url"]', {
'[data-testid="anchor-popover-anchor-input-sse_url"]', state: "visible",
{ timeout: 30000,
state: "visible", });
timeout: 30000,
},
);
let sseURLCount = await page let sseURLCount = await page.getByTestId("textarea_str_sse_url").count();
.getByTestId("anchor-popover-anchor-input-sse_url")
.count();
expect(sseURLCount).toBeGreaterThan(0); expect(sseURLCount).toBeGreaterThan(0);
@ -122,17 +117,12 @@ test(
await page.getByTestId("tab_1_sse").click(); await page.getByTestId("tab_1_sse").click();
await page.waitForSelector( await page.waitForSelector('[data-testid="textarea_str_sse_url"]', {
'[data-testid="anchor-popover-anchor-input-sse_url"]', state: "visible",
{ timeout: 30000,
state: "visible", });
timeout: 30000,
},
);
sseURLCount = await page sseURLCount = await page.getByTestId("textarea_str_sse_url").count();
.getByTestId("anchor-popover-anchor-input-sse_url")
.count();
expect(sseURLCount).toBeGreaterThan(0); expect(sseURLCount).toBeGreaterThan(0);