Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions docs/mcp/server/mcp-server-tab.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ longer be added and have its tools discovered — a core MCP-server UI regressio
## Step by step *(required)*

1. Bootstrap; create a blank flow and drag an **API Request** component onto the
canvas (gives the flow a tool to expose), then exit the flow.
canvas (`data_sourceAPI Request`, via `dragComponentFromSidebar` — the drag is
repaired, see Notes), giving the flow a tool to expose; then exit the flow.
2. Open the **MCP Server tab** (`mcp-btn`); assert `mcp-server-title` and the
"Flows/Tools" header are visible.
3. Open **Edit Tools** (`button_open_actions`) → the "MCP Server Tools" modal.
Expand All @@ -68,9 +69,10 @@ longer be added and have its tools discovered — a core MCP-server UI regressio
assert its `args` SSE URL matches.
7. Assert the **setup guide** link points at the documented MCP-server anchor.
8. Bootstrap again; add an **MCP-starter-project** component to a new flow
(`add-component-button-lf-starter_project`); open the **Add MCP Server** modal
(`openAddMcpServerModal`); paste the Linux config with a unique server name
substituted; click `add-mcp-server-button`.
(`add-component-button-lf-starter_project`, via
`addComponentFromSidebarWithoutSearch` — the add is repaired, see Notes);
open the **Add MCP Server** modal (`openAddMcpServerModal`); paste the Linux
config with a unique server name substituted; click `add-mcp-server-button`.
9. Assert the `dropdown_str_tool` selector becomes enabled and, when opened,
exposes at least one tool option (`[data-testid*="-option"]`).

Expand Down Expand Up @@ -117,6 +119,9 @@ longer be added and have its tools discovered — a core MCP-server UI regressio
(`icon-copy`, API-key generation).
- Add-MCP-server modal (`add-mcp-server-simple-button` / `mcp-server-dropdown` →
`add-mcp-server-button`, `json-input`) via `helpers/mcp/open-add-mcp-server-modal.ts`.
- `helpers/flows/add-component-from-sidebar.ts` — both repairing adds this spec
needs: `dragComponentFromSidebar` (step 1, drag) and
`addComponentFromSidebarWithoutSearch` (step 8, click on a tab with no search).
- The `lf-starter_project` MCP starter (`add-component-button-lf-starter_project`).
- API Request component (`data_sourceAPI Request`) — the tool exposed on the flow.
- `helpers/other/await-bootstrap-test.ts`, `helpers/ui/adjust-screen-view.ts`.
Expand All @@ -139,6 +144,48 @@ longer be added and have its tools discovered — a core MCP-server UI regressio
limitation); step verification relies on `--retries=0` bursts + force-fail.
- The API-key generation is branch-guarded because the button only appears when
no key exists yet; both branches assert a valid end state.
- No flows are left behind by design — the flows created live in the default
project and the test does not persist named artifacts requiring id-scoped
cleanup; folder CRUD is exercised by the sibling starter-projects spec.
- **Cleanup is id-scoped, never a wipe** (#553), and it was added in #1335 —
the claim it replaces ("no flows are left behind by design") was false. The
test creates TWO flows per run and registered a fresh `test_server_<random>`
every time, deleting neither: measured on the local nightly while working
#1335, 14 orphan `test_server_*` registrations had accumulated (alongside 58
orphan "New Flow" flows, which this spec shares with every other blank-flow
spec). The servers are not merely litter — their count decides which branch
the widget under test renders (an empty list shows
`add-mcp-server-simple-button`, a populated one `mcp-server-dropdown`), so
leaving them behind quietly stopped the spec from ever taking the empty-list
path again. Flow ids come from `POST /api/v1/flows` 201 bodies, not from the
canvas URL, which still holds the stale bootstrap id (#681). Folder CRUD is
exercised by the sibling starter-projects spec.
- **The step-8 add is repaired, and that is what #1335 was** (recurrent flake on
the 2026-08-05 and 2026-08-06 dailies). The failure named
`mcp-server-dropdown` — `locator.click: Timeout 3000ms exceeded` — and the
issue read it as a widget too slow for a 3 s budget. It was not: the failing
attempt's `error-context.md` shows an empty `application "Flow canvas"` with
"Minimize all" disabled, i.e. Langflow had swallowed the sidebar click and
there was no MCP component at all. Both of the modal's entry points hang off
that node, so **no wait budget could have fixed it** — measured on nightly
1.12.0.dev17: **4 of 8** first clicks on the MCP tab produced no node within
12 s, all 4 repaired by an identical second click (the #1304 class, whose
Components-tab rate was 4/20), while a landed add rendered in 91–108 ms and
its entry point became visible 6–15 ms later, enabled, in 8 of 8.
- Consequently `openAddMcpServerModal` no longer decides its branch from a snap
read: `isVisible({ timeout: 1000 })` looks like a wait but Playwright ignores
that option, so the helper committed to the dropdown branch before the widget
had painted — and in the no-servers case that locator never appears at all. It
now waits for **either** entry point and, when neither arrives, fails naming
the canvas node count so an empty canvas is never reported as a slow dropdown.
- **The step-1 API Request add is repaired too, and it is a SECOND surface.**
Fixing the MCP-tab click left the spec at 4 of 5, and the one failure was not
the #1335 signature at all: the *drag* at the top of the test was swallowed,
and surfaced 30 s later as `waitForSelector: generic-node-title-arrangement`
timing out — naming the node that was never created rather than the gesture
that failed to create it, which is the same mis-attribution #1335 was filed
under, one surface over. Measured on nightly 1.12.0.dev18: **1 of 5** drags
swallowed while the repaired MCP-tab click was 5 of 5 clean. It now goes
through `dragComponentFromSidebar`, which re-issues the drag once. The gesture
is re-issued rather than swapped for a click: dragging out of the sidebar is an
interaction Langflow ships, and a spec that quietly stops exercising it stops
covering it. The comment this replaced ("use dragTo which is more reliable than
click on add-component-button") predates the #1304 repair — neither gesture is
reliable bare, and both are reliable repaired.
15 changes: 14 additions & 1 deletion docs/mcp/server/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ Unchanged by #1091 (no stdio surface). Derives the project's own
- MCPTools node (`dropdown_str_tool`, `mcp-server-dropdown`, `list_item_<name>`).
- `GET`/`DELETE /api/v2/mcp/servers[/{name}]`, `helpers/auth/get-auth-token.ts`,
`helpers/other/await-bootstrap-test.ts`, `helpers/ui/adjust-screen-view.ts`,
`helpers/ui/zoom-out.ts`, `helpers/flows/delete-flow.ts`.
`helpers/ui/zoom-out.ts`, `helpers/flows/delete-flow.ts`,
`helpers/flows/add-component-from-sidebar.ts`
(`addComponentFromSidebarWithoutSearch`).

---

Expand Down Expand Up @@ -323,3 +325,14 @@ Unchanged by #1091 (no stdio surface). Derives the project's own
force-fail.
- A commented-out seventh block (SSE against a public Cloudflare MCP endpoint)
remains at the bottom of the file, untouched by #1091.
- **The three MCP-starter adds are repaired, not bare clicks** (#1335). Langflow
swallows that sidebar click on the MCP tab roughly half the time on nightly
1.12.0.dev17 (measured 4/8, all 4 repaired by an identical second click), and
every entry point of the add-server modal hangs off the node it should have
created. Measured locally on dev17 before and after: this file failed 3 of its
6 runnable tests with the bare clicks — including the `@stable` tests 3
("STDIO … fields should persist") and 5 ("tools should be refreshed …") — and 1
of 6 with `addComponentFromSidebarWithoutSearch`. The remaining failure is test
6 ("Streamable HTTP … server-everything"), which registers through the sidebar
page rather than the modal, fails identically with and without this change, and
is not `@stable`.
77 changes: 77 additions & 0 deletions tests/helpers/flows/add-component-from-sidebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,83 @@ test("the swallowed-click message reports the observed sidebar state", () => {
assert.match(msg, /node count: 0 before, 0 after/);
});

test("a swallowed DRAG is reported as a drag, on its own measured evidence", () => {
// #1335, second half. Same product defect, different gesture — and the reader
// has to be sent to the right line. Naming the click would point at a "+"
// button the spec never touches.
const msg = swallowedAddMessage({
...DETAIL,
gesture: "drag",
addButtonTestId: "data_sourceAPI Request",
searchTerm: "api request",
});

assert.match(msg, /drag add was swallowed/);
assert.match(msg, /dragTo\(\) from getByTestId\("data_sourceAPI Request"\)/);
assert.match(msg, /onto the canvas/);
// The click path's 4/20 was measured on the click surface; quoting it for a
// drag would attribute a number to a surface it was never taken on.
assert.match(msg, /1\/5 on nightly 1\.12\.0\.dev18/);
assert.doesNotMatch(msg, /4\/20/);
assert.doesNotMatch(msg, /The click\(s\) were accepted/);
// A drag has no "+" button to report on.
assert.doesNotMatch(msg, /"\+" button still visible/);
assert.match(msg, /sidebar entry still visible/);
});

test("the gesture defaults to click, so the pre-#1335 message is unchanged", () => {
// The 34 existing call sites never pass a gesture. If the default drifted, all
// of them would start reporting a drag they never performed.
const withoutGesture = swallowedAddMessage(DETAIL);
const explicitClick = swallowedAddMessage({ ...DETAIL, gesture: "click" });

assert.equal(withoutGesture, explicitClick);
assert.match(withoutGesture, /click add was swallowed/);
assert.match(withoutGesture, /4\/20/);
});

test("a swallowed drag stays unclassifiable as infra, like the click one", () => {
// #1262's rule reaches the new gesture too: a real add regression must stay
// eligible for @stable auto-removal.
assert.equal(
classifyInfraError(swallowedAddMessage({ ...DETAIL, gesture: "drag" })),
null,
);
});

test("a tab with no search box reports no term and no input, not an empty one", () => {
// #1335: the MCP tab (`sidebar-nav-mcp`) adds entries straight from its list,
// so there is no term to name and no input to read back. `search input: ""` is
// a real observation on the Components tab (the input was reset) and must not
// read the same as "this tab has no input" — otherwise the reader is told the
// search was cleared by a surface that never had one.
const msg = swallowedAddMessage({
...DETAIL,
searchTerm: null,
searchValue: null,
addButtonTestId: "add-component-button-lf-starter_project",
});

assert.match(msg, /no search box/i);
assert.match(msg, /search input: <none on this tab>/);
assert.doesNotMatch(msg, /after filling the sidebar search/);
assert.doesNotMatch(msg, /search input: ""/);
// Still names the click and the budget — the two facts the message exists for.
assert.match(msg, /add-component-button-lf-starter_project/);
assert.match(msg, /2 attempt/);
});

test("the no-search message stays unclassifiable as infra, like the search one", () => {
// Same rule as below: a swallowed add on the MCP tab is a real add regression
// and must stay eligible for @stable auto-removal (#1262).
assert.equal(
classifyInfraError(
swallowedAddMessage({ ...DETAIL, searchTerm: null, searchValue: null }),
),
null,
);
});

test("the swallowed-click message is NOT classifiable as an infra failure", () => {
// Same rule as the page-entry barrier (#1262): claiming infra here would exempt
// the failure from @stable auto-removal and hide a genuine add regression.
Expand Down
Loading
Loading