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:
parent
67f744cdf2
commit
0ba8f6559c
4 changed files with 31 additions and 30 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue