feat: add servers persistence to MCP connection component, add MCP connections settings page (#8388)
* Added mcpinput to the backend * Fixed list selection component to contain descriptions * Added mcp component in the frontend with mock values * Added mcp rendering on Parameter Render Component * Changed input to be more concise and to have dynamic placeholder * Added header search placeholder * Fixed styling to match new input * Removed unused params * Adds AddMcpServerModal's first mock version * Adds Add button on mcp component and list selection component * First pass at mcp api * Add PATCH endpoint * Add DELETE endpoint * fix: Bump version numbers for langflow and langflow-base to 1.4.3 and 0.4.3 respectively * fix: Remove Igor Carvalho from maintainers list in pyproject.toml * fix(agent): reset model list when provider changes Switching the provider in the Agent component sometimes left models from the previous provider visible/selected. We now filter against the new , ensuring only models that belong to the active provider remain. * src/frontend/src/components/core/dropdownComponent/index.tsx – add guard when rebuilding * tests/extended/regression/general-bugs-dropdown-select-not-in-list.spec.ts – expand coverage for “model not in list” edge-cases Co-authored-by: Cristian Lousa <cristian.lousa@gmail.com> * fix: Update Pokédex Agent template (#8373) * Implement adding and getting MCP servers, implemented addMcpServerModal * Added sse and stdio ways of adding a server * Added no actions handling * added new mcp type to constants * Added headers to add mcp server modal * Changed mcp component to allow persistent mcp servers * fix input list component gradient * fix add server modal to patch when initial data is present, and to clean variables when switching tabs * changed message on add mcp server * Added required mutations for mcp page * Added mcp servers page * Changed design of page * Fixed delete problems and added delete confirmation * fixed wrong error parsing * changed padding * Made added server be used on mcp component * refactor: remove references to the langflow store (#8354) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: KimberlyFields <46325568+KimberlyFields@users.noreply.github.com> Co-authored-by: Ítalo Johnny <italojohnnydosanjos@gmail.com> Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com> fix: apikey lock issue and add option to disable tracking (#8361) * Fixed search on sidebar * fixed infinite use effect * Fixed error handling * Fixed tool mode disappearing * fixed key pair button submitting form * Fixed bugs * Added required * Changed message * Disabled other tabs when modifying * Removed tool dropdown if the mcp server is empty * parsed name * fixed data test id not applying * fixed mcp component * Fixed component not working when only stdio command is present * refactored tests * Updated mcp_component to remove old non default keys * Added data-testids * Modified tests to include settings page functionality * [autofix.ci] apply automated fixes * Refactor out the core part of the mcp * [autofix.ci] apply automated fixes * Added placeholders on frontend components for errors * Fixed bugs with mcp component * updated bug * fix: made empty project appear instead of empty flows list when mcp is enabled (#8336) * try to fix * Fix MCP persistence * Update mcp_component.py * Update mcp.py * [autofix.ci] apply automated fixes * fix: Bump version numbers for langflow and langflow-base to 1.4.3 and 0.4.3 respectively * fix: Remove Igor Carvalho from maintainers list in pyproject.toml * fix(agent): reset model list when provider changes Switching the provider in the Agent component sometimes left models from the previous provider visible/selected. We now filter against the new , ensuring only models that belong to the active provider remain. * src/frontend/src/components/core/dropdownComponent/index.tsx – add guard when rebuilding * tests/extended/regression/general-bugs-dropdown-select-not-in-list.spec.ts – expand coverage for “model not in list” edge-cases Co-authored-by: Cristian Lousa <cristian.lousa@gmail.com> * refactor: remove references to the langflow store (#8354) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: KimberlyFields <46325568+KimberlyFields@users.noreply.github.com> Co-authored-by: Ítalo Johnny <italojohnnydosanjos@gmail.com> Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com> fix: apikey lock issue and add option to disable tracking (#8361) * fix: made empty project appear instead of empty flows list when mcp is enabled (#8336) * fix mcp client async problems * fixed mcp sse access * [autofix.ci] apply automated fixes * Made values be maintained when refreshing page * Fixed bugs with tool mode and switching from tool mode to not tool mode * Update mcp_component.py * Update test_mcp_component.py * Don't expose file by name as external endpoint * Update files.py * Update files.py * Add checks for id * Refactor tests * Update test_mcp_component.py * Update test_mcp_component.py * Update test_mcp_component.py * updated tests * re-added placeholder on input for tests to not fail * updated session selector in order for tests to work --------- Co-authored-by: Eric Hare <ericrhare@gmail.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> Co-authored-by: Mike Fortman <michael.fortman@datastax.com> Co-authored-by: Cristian Lousa <cristian.lousa@gmail.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com> Co-authored-by: Deon Sanchez <69873175+deon-sanchez@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: KimberlyFields <46325568+KimberlyFields@users.noreply.github.com> Co-authored-by: Ítalo Johnny <italojohnnydosanjos@gmail.com> Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
This commit is contained in:
parent
b378eb81d0
commit
60ccdb500f
56 changed files with 2381 additions and 876 deletions
|
|
@ -199,7 +199,6 @@ test(
|
|||
|
||||
// Create a new flow with MCP component
|
||||
await page.getByTestId("blank-flow").click();
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("mcp connection");
|
||||
|
||||
|
|
@ -214,57 +213,54 @@ test(
|
|||
});
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await zoomOut(page, 3);
|
||||
|
||||
// Switch to SSE tab and paste the URL
|
||||
await page.getByTestId("tab_1_sse").click();
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
await page.waitForSelector('[data-testid="textarea_str_sse_url"]', {
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (error) {
|
||||
await page
|
||||
.getByTestId("mcp-server-dropdown")
|
||||
.click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("textarea_str_sse_url").fill("");
|
||||
await page.getByTestId("textarea_str_sse_url").fill(sseUrl);
|
||||
await page.waitForSelector('[data-testid="json-input"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
await page.getByTestId("json-input").fill(configJsonLinux || "");
|
||||
|
||||
// Wait for the tools to become available
|
||||
let attempts = 0;
|
||||
const maxAttempts = 3;
|
||||
let dropdownEnabled = false;
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
while (attempts < maxAttempts && !dropdownEnabled) {
|
||||
await page.getByTestId("refresh-button-sse_url").click();
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
try {
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
dropdownEnabled = true;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
console.log(`Retry attempt ${attempts} for refresh button`);
|
||||
}
|
||||
}
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
|
||||
if (!dropdownEnabled) {
|
||||
throw new Error(
|
||||
"Dropdown did not become enabled after multiple refresh attempts",
|
||||
);
|
||||
}
|
||||
|
||||
// Verify tools are available
|
||||
await page.waitForTimeout(3000);
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const fetchOptionCount = await page.getByText("mcp_test_name").count();
|
||||
|
||||
expect(fetchOptionCount).toBeGreaterThan(0);
|
||||
|
||||
// If we get here, the test passed
|
||||
|
|
|
|||
|
|
@ -29,36 +29,48 @@ test(
|
|||
|
||||
await zoomOut(page, 3);
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").isDisabled();
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = 3;
|
||||
let dropdownEnabled = false;
|
||||
|
||||
while (attempts < maxAttempts && !dropdownEnabled) {
|
||||
await page.getByTestId("refresh-button-command").click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
try {
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
dropdownEnabled = true;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
console.log(`Retry attempt ${attempts} for refresh button`);
|
||||
}
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
if (!dropdownEnabled) {
|
||||
throw new Error(
|
||||
"Dropdown did not become enabled after multiple refresh attempts",
|
||||
);
|
||||
}
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("stdio-tab").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="stdio-name-input"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("stdio-name-input").fill("test server");
|
||||
|
||||
await page.getByTestId("stdio-command-input").fill("uvx mcp-server-fetch");
|
||||
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
|
|
@ -89,124 +101,86 @@ test(
|
|||
|
||||
expect(urlOptionCount).toBeGreaterThan(0);
|
||||
|
||||
await page.getByTestId("tab_1_sse").click();
|
||||
await page.getByTestId("user_menu_button").click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="textarea_str_sse_url"]', {
|
||||
await page.getByTestId("menu_settings_button").click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="sidebar-nav-MCP Connections"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByTestId("sidebar-nav-MCP Connections")
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button-page"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await expect(page.getByText("test_server")).toBeVisible({
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByTestId(`mcp-server-menu-button-test_server`)
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page
|
||||
.getByText("Edit", { exact: true })
|
||||
.first()
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
let sseURLCount = await page.getByTestId("textarea_str_sse_url").count();
|
||||
|
||||
expect(sseURLCount).toBeGreaterThan(0);
|
||||
|
||||
await page.waitForSelector('[data-testid="dropdown_str_tool"]:disabled', {
|
||||
timeout: 30000,
|
||||
await expect(page.getByTestId("json-tab")).toBeDisabled({
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
attempts = 0;
|
||||
dropdownEnabled = false;
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
while (attempts < maxAttempts && !dropdownEnabled) {
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
try {
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 30000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
dropdownEnabled = true;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
console.log(`Retry attempt ${attempts} for second refresh button`);
|
||||
await page.getByTestId("refresh-button-sse_url").click();
|
||||
}
|
||||
}
|
||||
|
||||
if (!dropdownEnabled) {
|
||||
throw new Error(
|
||||
"Dropdown did not become enabled after multiple refresh attempts",
|
||||
);
|
||||
}
|
||||
|
||||
await page.getByTestId("tab_0_stdio").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
attempts = 0;
|
||||
dropdownEnabled = false;
|
||||
|
||||
while (attempts < maxAttempts && !dropdownEnabled) {
|
||||
await page.getByTestId("refresh-button-command").click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
try {
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
dropdownEnabled = true;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
console.log(`Retry attempt ${attempts} for second refresh button`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!dropdownEnabled) {
|
||||
throw new Error(
|
||||
"Dropdown did not become enabled after multiple refresh attempts",
|
||||
);
|
||||
}
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
fetchOptionCount = await page.getByTestId("fetch-0-option").count();
|
||||
|
||||
await page.getByTestId("fetch-0-option").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
expect(fetchOptionCount).toBeGreaterThan(0);
|
||||
|
||||
await page.waitForSelector('[data-testid="int_int_max_length"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
await expect(page.getByTestId("stdio-tab")).not.toBeDisabled({
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
maxLengthOptionCount = await page.getByTestId("int_int_max_length").count();
|
||||
|
||||
expect(maxLengthOptionCount).toBeGreaterThan(0);
|
||||
|
||||
urlOptionCount = await page
|
||||
.getByTestId("anchor-popover-anchor-input-url")
|
||||
.count();
|
||||
|
||||
expect(urlOptionCount).toBeGreaterThan(0);
|
||||
|
||||
await page.getByTestId("tab_1_sse").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="textarea_str_sse_url"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
await expect(page.getByTestId("sse-tab")).toBeDisabled({
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
sseURLCount = await page.getByTestId("textarea_str_sse_url").count();
|
||||
expect(await page.getByTestId("stdio-command-input").inputValue()).toBe(
|
||||
"uvx mcp-server-fetch",
|
||||
);
|
||||
|
||||
expect(sseURLCount).toBeGreaterThan(0);
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="dropdown_str_tool"]:disabled', {
|
||||
timeout: 30000,
|
||||
await page
|
||||
.getByTestId(`mcp-server-menu-button-test_server`)
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page
|
||||
.getByText("Delete", { exact: true })
|
||||
.first()
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="btn_delete_delete_confirmation_modal"]',
|
||||
{
|
||||
timeout: 3000,
|
||||
},
|
||||
);
|
||||
|
||||
await page
|
||||
.getByTestId("btn_delete_delete_confirmation_modal")
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button-page"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await expect(page.getByText("test_server")).not.toBeVisible({
|
||||
timeout: 3000,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue