Found while validating #1087 (canvas-controls migration). Not caused by that PR:
the failure reproduces identically on clean main.
Symptom
mcp/server/mcp-server.spec.ts:622 — "mcp server tools should be refreshed when
editing a server" — fails waiting for the add-server modal to close:
TimeoutError: page.waitForSelector: Timeout 30000ms exceeded.
- waiting for locator('[data-testid="add-mcp-server-button"]') to be hidden
at mcp-server.spec.ts:675
--workers=1 --retries=0, Nightly 1.12.0.dev7. Same run stashed to clean
main: same failure, same line. The button goes aria-busy and comes back
enabled — the save is REJECTED, and the modal correctly stays open. The reason is
in the error context:
Value error, MCP stdio command must be a single executable name or path;
put options and arguments in the 'args' field
Root cause — an intentional upstream security change, not a regression
src/lfx/src/lfx/base/mcp/security.py → validate_mcp_stdio_config() (line 440)
rejects any command carrying whitespace outside a path prefix:
# The structured command field is exactly one executable. Options belong in args so every
# policy layer sees the same argv. Parent-directory spaces remain valid for executable paths.
The intent is stated there: an option glued onto command means the policy
layers (allowlist, arg blocklist, interpreter hardening) do not all see the same
argv. It arrived with the multi-tenant hardening trail — #13530
(harden multi-tenant surfaces) → #14044 (close tenant-isolation gaps) →
f4d6ac4 fix(security): forward-port release-1.10.3 hardening (#14073,
2026-07-15).
Verdict: test defect. The feature is alive and npx / uvx are still
allowlisted (ALLOWED_MCP_COMMANDS, line 47) — only the input shape moved. This
is not a Langflow regression and not a dead surface.
Scope — every stdio registration in the file
All 6 fills of stdio-command-input pass <exe> <arg>:
| Line |
Value |
| 60 |
npx @modelcontextprotocol/server-everything |
| 239 |
npx @modelcontextprotocol/server-everything |
| 327 |
uvx mcp-server-test |
| 670 |
uvx mcp-server-fetch |
| 777 |
uvx mcp-server-time |
| 894 |
uvx mcp-server-fetch |
Line 327's test also fills stdio-args_0..2 already, so that one only needs the
package moved out of command.
Not affected: the @stable specs under mcp/client/ already register with
command: "npx" and the package in args — they satisfy the new contract as
written. This is confined to mcp/server/mcp-server.spec.ts, whose 6 tests carry
no @stable, which is why a break of this size went unnoticed: the file runs
in no automated lane.
The fix, and what it uncovers next
A local scout (command: "uvx" + stdio-args_0: "mcp-server-fetch", reverted)
got past the validation — registration succeeded and the modal closed. It then
failed one step later, at fetch-0-option:
Error loading server: Connection closed
Error updating tool list: Error updating tool list: unhandled errors in a TaskGroup (1 sub-exception)
with the tool dropdown showing "No options found" — the uvx stdio subprocess
does not come up in that environment. So splitting command/args is necessary but
not sufficient, and whoever picks this up should expect a second layer. Related
budget note: this test allows 30 s for the tool list and 10 s for the option,
while the sibling MCP stdio test in mcp-client-regression.spec.ts:391-396 had
to be raised to 120 s for cold npx startup (#463).
Coverage gap worth closing in the same pass
No spec asserts the new contract. A command with an embedded argument is now
rejected for a security reason, and nothing in the suite would notice if that
validation were dropped. A small @regression case ("stdio command with an
embedded argument is refused; command + args is accepted") is cheap and is real
security coverage.
Done when
Environment caveat
Everything above was reproduced on a local Nightly 1.12.0.dev7. The 2026-07-29
daily ran 1.12.0.dev9, so re-confirm on a refreshed image before assuming the
line numbers and messages still match.
Found while validating #1087 (canvas-controls migration). Not caused by that PR:
the failure reproduces identically on clean
main.Symptom
mcp/server/mcp-server.spec.ts:622— "mcp server tools should be refreshed whenediting a server" — fails waiting for the add-server modal to close:
--workers=1 --retries=0, Nightly1.12.0.dev7. Same run stashed to cleanmain: same failure, same line. The button goesaria-busyand comes backenabled — the save is REJECTED, and the modal correctly stays open. The reason is
in the error context:
Root cause — an intentional upstream security change, not a regression
src/lfx/src/lfx/base/mcp/security.py→validate_mcp_stdio_config()(line 440)rejects any
commandcarrying whitespace outside a path prefix:The intent is stated there: an option glued onto
commandmeans the policylayers (allowlist, arg blocklist, interpreter hardening) do not all see the same
argv. It arrived with the multi-tenant hardening trail —
#13530(harden multi-tenant surfaces) →
#14044(close tenant-isolation gaps) →f4d6ac4fix(security): forward-port release-1.10.3 hardening (#14073,2026-07-15).
Verdict: test defect. The feature is alive and
npx/uvxare stillallowlisted (
ALLOWED_MCP_COMMANDS, line 47) — only the input shape moved. Thisis not a Langflow regression and not a dead surface.
Scope — every stdio registration in the file
All 6 fills of
stdio-command-inputpass<exe> <arg>:npx @modelcontextprotocol/server-everythingnpx @modelcontextprotocol/server-everythinguvx mcp-server-testuvx mcp-server-fetchuvx mcp-server-timeuvx mcp-server-fetchLine 327's test also fills
stdio-args_0..2already, so that one only needs thepackage moved out of
command.Not affected: the
@stablespecs undermcp/client/already register withcommand: "npx"and the package inargs— they satisfy the new contract aswritten. This is confined to
mcp/server/mcp-server.spec.ts, whose 6 tests carryno
@stable, which is why a break of this size went unnoticed: the file runsin no automated lane.
The fix, and what it uncovers next
A local scout (
command: "uvx"+stdio-args_0: "mcp-server-fetch", reverted)got past the validation — registration succeeded and the modal closed. It then
failed one step later, at
fetch-0-option:with the tool dropdown showing "No options found" — the
uvxstdio subprocessdoes not come up in that environment. So splitting command/args is necessary but
not sufficient, and whoever picks this up should expect a second layer. Related
budget note: this test allows 30 s for the tool list and 10 s for the option,
while the sibling MCP stdio test in
mcp-client-regression.spec.ts:391-396hadto be raised to 120 s for cold
npxstartup (#463).Coverage gap worth closing in the same pass
No spec asserts the new contract. A command with an embedded argument is now
rejected for a security reason, and nothing in the suite would notice if that
validation were dropped. A small
@regressioncase ("stdio command with anembedded argument is refused; command + args is accepted") is cheap and is real
security coverage.
Done when
command= single executable with the package/options inargsuvx-does-not-start layer above is either resolved or filed separately with its own evidence--retries=0on the current nightly — which is the entry gate for ever validating and promoting these 6 testsEnvironment caveat
Everything above was reproduced on a local Nightly
1.12.0.dev7. The 2026-07-29daily ran
1.12.0.dev9, so re-confirm on a refreshed image before assuming theline numbers and messages still match.