Skip to content

Commit 48aac68

Browse files
committed
Merge release-1.11.1 into main
2 parents 1d5f574 + 999c5ab commit 48aac68

19 files changed

Lines changed: 362 additions & 31 deletions

File tree

docs/docs/Support/troubleshooting.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ To pin FastAPI in an environment where Langflow is already installed:
113113
uv pip install "fastapi<0.140"
114114
```
115115

116-
Langflow version 1.11.1 constrains FastAPI to `<0.140.0`, so a new install of Langflow version `>=1.11.1` does not require this pin.
116+
Langflow version 1.11.1 resolves a compatible FastAPI 0.140.x and `fastapi-pagination` 0.15.16 pair on a new install, so it does not require this pin.
117+
If you constrain these dependencies yourself, use `fastapi-pagination>=0.15.16` with `fastapi>=0.140.5`.
117118

118119
## Langflow Desktop installation issues
119120

@@ -778,4 +779,4 @@ For more information, see [Workarounds for Intel Mac users](./macos-support-matr
778779
## See also
779780
780781
- [Langflow GitHub Issues and Discussions](/contributing-github-issues)
781-
- [Langflow telemetry](/contributing-telemetry)
782+
- [Langflow telemetry](/contributing-telemetry)

docs/versioned_docs/version-1.11.0/Support/troubleshooting.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ To pin FastAPI in an environment where Langflow is already installed:
113113
uv pip install "fastapi<0.140"
114114
```
115115

116-
Langflow version 1.11.1 constrains FastAPI to `<0.140.0`, so a new install of Langflow version `>=1.11.1` does not require this pin.
116+
Langflow version 1.11.1 resolves a compatible FastAPI 0.140.x and `fastapi-pagination` 0.15.16 pair on a new install, so it does not require this pin.
117+
If you constrain these dependencies yourself, use `fastapi-pagination>=0.15.16` with `fastapi>=0.140.5`.
117118

118119
## Langflow Desktop installation issues
119120

@@ -778,4 +779,4 @@ For more information, see [Workarounds for Intel Mac users](./macos-support-matr
778779
## See also
779780
780781
- [Langflow GitHub Issues and Discussions](/contributing-github-issues)
781-
- [Langflow telemetry](/contributing-telemetry)
782+
- [Langflow telemetry](/contributing-telemetry)

src/backend/base/langflow/api/utils/mcp/config_utils.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from fastapi import HTTPException
88
from lfx.base.mcp.constants import MAX_MCP_SERVER_NAME_LENGTH
99
from lfx.base.mcp.util import sanitize_mcp_name
10+
from lfx.base.mcp.uvx import mcp_sdk_constraint_args
1011
from lfx.log import logger
1112
from lfx.services.deps import get_settings_service
1213
from sqlmodel import select
@@ -23,6 +24,19 @@
2324
ALL_INTERFACES_HOST = "0.0.0.0" # noqa: S104
2425

2526

27+
def mcp_server_config_uses_current_uvx_constraint(server_config: dict, package: str) -> bool:
28+
"""Return whether a generated uvx config uses the current SDK constraint and package."""
29+
if server_config.get("command") != "uvx":
30+
return False
31+
32+
args = server_config.get("args")
33+
if not isinstance(args, list):
34+
return False
35+
36+
expected_prefix = [*mcp_sdk_constraint_args(), package]
37+
return args[: len(expected_prefix)] == expected_prefix
38+
39+
2640
class MCPServerValidationResult:
2741
"""Represents the result of an MCP server validation check.
2842
@@ -355,26 +369,27 @@ async def auto_configure_starter_projects_mcp(session):
355369
operation="create",
356370
)
357371

358-
# Skip if server already exists for this starter projects folder
372+
# Skip if the server already has the expected URL and uvx SDK constraint.
359373
if validation_result.should_skip:
360-
# Check if the URL needs updating (e.g., server port changed at restart)
361374
expected_url = await get_project_streamable_http_url(user_starter_folder.id)
362375
existing_config = validation_result.existing_config or {}
363376
existing_args = existing_config.get("args", [])
364377
existing_urls = await extract_urls_from_strings(existing_args)
365378

366-
if any(expected_url == url for url in existing_urls):
379+
if mcp_server_config_uses_current_uvx_constraint(existing_config, "mcp-proxy") and any(
380+
expected_url == url for url in existing_urls
381+
):
367382
await logger.adebug(
368383
f"MCP server '{validation_result.server_name}' already exists and is correctly "
369384
f"configured for user {user.username}'s starter projects (project ID: "
370385
f"{user_starter_folder.id}), skipping"
371386
)
372387
continue # Skip this user since server already exists for the same project
373388

374-
# URL has changed (e.g., server restarted on a different port), fall through to update
375389
await logger.adebug(
376390
f"MCP server '{validation_result.server_name}' exists for user {user.username}'s "
377-
f"starter projects but URL has changed (was: {existing_urls}, now: {expected_url}), updating"
391+
f"starter projects but its generated configuration is stale "
392+
f"(URLs: {existing_urls}, expected URL: {expected_url}), updating"
378393
)
379394

380395
server_name = validation_result.server_name
@@ -413,6 +428,7 @@ async def auto_configure_starter_projects_mcp(session):
413428
if default_auth.get("auth_type", "none") == "apikey":
414429
command = "uvx"
415430
args = [
431+
*mcp_sdk_constraint_args(),
416432
"mcp-proxy",
417433
"--transport",
418434
"streamablehttp",
@@ -429,6 +445,7 @@ async def auto_configure_starter_projects_mcp(session):
429445
# No authentication - direct connection
430446
command = "uvx"
431447
args = [
448+
*mcp_sdk_constraint_args(),
432449
"mcp-proxy",
433450
"--transport",
434451
"streamablehttp",

src/backend/base/langflow/api/v1/mcp_projects.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from fastapi.responses import HTMLResponse, JSONResponse
2020
from lfx.base.mcp.constants import MAX_MCP_SERVER_NAME_LENGTH
2121
from lfx.base.mcp.util import sanitize_mcp_name
22+
from lfx.base.mcp.uvx import mcp_sdk_constraint_args
2223
from lfx.log import logger
2324
from lfx.services.deps import get_settings_service, session_scope
2425
from lfx.services.mcp_composer.service import (
@@ -880,6 +881,7 @@ async def install_mcp_config(
880881
settings = get_settings_service().settings
881882
command = "uvx"
882883
args = [
884+
*mcp_sdk_constraint_args(),
883885
f"mcp-composer{settings.mcp_composer_version}",
884886
"--mode",
885887
"http",
@@ -896,7 +898,7 @@ async def install_mcp_config(
896898
streamable_http_url = await get_project_streamable_http_url(project_id)
897899
legacy_sse_url = await get_project_sse_url(project_id)
898900
command = "uvx"
899-
args = ["mcp-proxy"]
901+
args = [*mcp_sdk_constraint_args(), "mcp-proxy"]
900902
# Check if we need to add Langflow API key headers
901903
# Necessary only when Project API Key Authentication is enabled
902904

src/backend/base/langflow/api/v1/projects_mcp_helpers.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
from uuid import UUID
88

99
from fastapi import HTTPException
10+
from lfx.base.mcp.uvx import mcp_sdk_constraint_args
1011
from lfx.log.logger import logger
1112
from lfx.services.mcp_composer.service import MCPComposerService
1213

13-
from langflow.api.utils.mcp.config_utils import validate_mcp_server_for_project
14+
from langflow.api.utils.mcp.config_utils import (
15+
mcp_server_config_uses_current_uvx_constraint,
16+
validate_mcp_server_for_project,
17+
)
1418
from langflow.api.v1.mcp_projects import get_project_streamable_http_url
1519
from langflow.api.v2.mcp import update_server
1620
from langflow.services.database.models.api_key.crud import create_api_key
@@ -48,7 +52,11 @@ def _server_config_matches_project_auth(
4852
return False
4953

5054
args = existing_config.get("args")
51-
if not isinstance(args, list) or not _server_config_uses_streamable_http(args, streamable_http_url):
55+
if (
56+
not isinstance(args, list)
57+
or not mcp_server_config_uses_current_uvx_constraint(existing_config, "mcp-proxy")
58+
or not _server_config_uses_streamable_http(args, streamable_http_url)
59+
):
5260
return False
5361

5462
has_project_api_key = _server_config_has_project_api_key(args)
@@ -117,6 +125,7 @@ async def register_mcp_servers_for_project(
117125
unmasked_api_key = await create_api_key(session, ApiKeyCreate(name=api_key_name), current_user.id)
118126
command = "uvx"
119127
args = [
128+
*mcp_sdk_constraint_args(),
120129
"mcp-proxy",
121130
"--transport",
122131
"streamablehttp",
@@ -132,6 +141,7 @@ async def register_mcp_servers_for_project(
132141
else:
133142
command = "uvx"
134143
args = [
144+
*mcp_sdk_constraint_args(),
135145
"mcp-proxy",
136146
"--transport",
137147
"streamablehttp",

src/backend/base/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ maintainers = [
1818

1919
dependencies = [
2020
"lfx~=1.11.0",
21-
"fastapi>=0.139.0,<0.140.0",
21+
"fastapi>=0.139.0,<1.0.0",
2222
"slowapi>=0.1.9,<1.0.0",
2323
"httpx[http2]>=0.27,<1.0.0",
2424
"aiofile>=3.9.0,<4.0.0",

src/backend/tests/unit/api/utils/test_config_utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from langflow.api.utils.mcp.config_utils import (
77
MCPServerValidationResult,
88
auto_configure_starter_projects_mcp,
9+
mcp_server_config_uses_current_uvx_constraint,
910
validate_mcp_server_for_project,
1011
)
1112
from langflow.services.database.models.flow.model import Flow
@@ -27,6 +28,38 @@ def _build_server_config(base_url: str, project_id, transport: str):
2728
return url, {"command": "uvx", "args": args}
2829

2930

31+
@pytest.mark.parametrize(
32+
("constraint_args", "server_args", "expected"),
33+
[
34+
(["--with", "mcp~=1.28"], ["--with", "mcp~=1.28", "mcp-proxy"], True),
35+
(["--with", "mcp~=1.28"], ["mcp-proxy"], False),
36+
(["--with", "mcp~=1.30"], ["--with", "mcp~=1.28", "mcp-proxy"], False),
37+
([], ["mcp-proxy"], True),
38+
([], ["--with", "mcp~=1.28", "mcp-proxy"], False),
39+
],
40+
)
41+
def test_generated_uvx_config_matches_current_sdk_constraint(constraint_args, server_args, expected):
42+
config = {"command": "uvx", "args": [*server_args, "--transport", "streamablehttp"]}
43+
44+
with patch(
45+
"langflow.api.utils.mcp.config_utils.mcp_sdk_constraint_args",
46+
return_value=constraint_args,
47+
):
48+
assert mcp_server_config_uses_current_uvx_constraint(config, "mcp-proxy") is expected
49+
50+
51+
@pytest.mark.parametrize(
52+
"config",
53+
[
54+
{"command": "npx", "args": ["mcp-proxy"]},
55+
{"command": "uvx", "args": "mcp-proxy"},
56+
{"command": "uvx"},
57+
],
58+
)
59+
def test_generated_uvx_config_rejects_non_generated_shapes(config):
60+
assert mcp_server_config_uses_current_uvx_constraint(config, "mcp-proxy") is False
61+
62+
3063
class TestMCPServerValidationResult:
3164
"""Test the MCPServerValidationResult class and its properties."""
3265

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from unittest.mock import patch
2+
3+
from langflow.api.v1.projects_mcp_helpers import _server_config_matches_project_auth
4+
5+
6+
def _project_server_config(url: str, *, pinned: bool, api_key: bool = False) -> dict:
7+
args = ["--with", "mcp~=1.28"] if pinned else []
8+
args.extend(["mcp-proxy", "--transport", "streamablehttp"])
9+
if api_key:
10+
args.extend(["--headers", "x-api-key", "test-key"])
11+
args.append(url)
12+
return {"command": "uvx", "args": args}
13+
14+
15+
def test_existing_project_config_without_sdk_constraint_requires_reconciliation():
16+
url = "http://localhost:7860/api/v1/mcp/project/test/streamable"
17+
18+
with patch(
19+
"langflow.api.utils.mcp.config_utils.mcp_sdk_constraint_args",
20+
return_value=["--with", "mcp~=1.28"],
21+
):
22+
assert (
23+
_server_config_matches_project_auth(
24+
_project_server_config(url, pinned=False),
25+
"none",
26+
url,
27+
)
28+
is False
29+
)
30+
assert (
31+
_server_config_matches_project_auth(
32+
_project_server_config(url, pinned=True),
33+
"none",
34+
url,
35+
)
36+
is True
37+
)
38+
39+
40+
def test_existing_apikey_config_without_sdk_constraint_requires_reconciliation():
41+
url = "http://localhost:7860/api/v1/mcp/project/test/streamable"
42+
43+
with patch(
44+
"langflow.api.utils.mcp.config_utils.mcp_sdk_constraint_args",
45+
return_value=["--with", "mcp~=1.28"],
46+
):
47+
assert (
48+
_server_config_matches_project_auth(
49+
_project_server_config(url, pinned=False, api_key=True),
50+
"apikey",
51+
url,
52+
)
53+
is False
54+
)
55+
assert (
56+
_server_config_matches_project_auth(
57+
_project_server_config(url, pinned=True, api_key=True),
58+
"apikey",
59+
url,
60+
)
61+
is True
62+
)

src/frontend/tests/extended/features/mcp-server.spec.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { expect, test } from "../../fixtures";
22
import { adjustScreenView } from "../../utils/adjust-screen-view";
33
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
44
import { TEXTS } from "../../utils/constants/texts";
5+
import {
6+
FETCH_SERVER_ARGS,
7+
fillFetchServerCommand,
8+
} from "../../utils/fill-fetch-server-command";
59
import { openBlankFlow } from "../../utils/flow/open-blank-flow";
610
import { openFlowCard } from "../../utils/flow/open-flow-card";
711
import { openAddMcpServerModal } from "../../utils/open-add-mcp-server-modal";
@@ -56,8 +60,7 @@ test(
5660
const testName = `test_server_${randomSuffix}`;
5761
await page.getByTestId("stdio-name-input").fill(testName);
5862

59-
await page.getByTestId("stdio-command-input").fill("uvx");
60-
await page.getByTestId("stdio-args_0").fill("mcp-server-fetch");
63+
await fillFetchServerCommand(page);
6164

6265
await page.getByTestId("add-mcp-server-button").click();
6366

@@ -157,9 +160,11 @@ test(
157160
expect(await page.getByTestId("stdio-command-input").inputValue()).toBe(
158161
"uvx",
159162
);
160-
expect(await page.getByTestId("stdio-args_0").inputValue()).toBe(
161-
"mcp-server-fetch",
162-
);
163+
for (const [index, arg] of FETCH_SERVER_ARGS.entries()) {
164+
expect(await page.getByTestId(`stdio-args_${index}`).inputValue()).toBe(
165+
arg,
166+
);
167+
}
163168

164169
await page.waitForTimeout(500);
165170

@@ -234,8 +239,7 @@ test(
234239

235240
await page.waitForTimeout(500);
236241

237-
await page.getByTestId("stdio-command-input").fill("uvx");
238-
await page.getByTestId("stdio-args_0").fill("mcp-server-fetch");
242+
await fillFetchServerCommand(page);
239243

240244
await page.getByTestId("add-mcp-server-button").click();
241245

@@ -707,8 +711,7 @@ test(
707711
const testName = `test_server_${randomSuffix}`;
708712
await page.getByTestId("stdio-name-input").fill(testName);
709713

710-
await page.getByTestId("stdio-command-input").fill("uvx");
711-
await page.getByTestId("stdio-args_0").fill("mcp-server-fetch");
714+
await fillFetchServerCommand(page);
712715

713716
await page.getByTestId("add-mcp-server-button").click();
714717

@@ -821,11 +824,16 @@ test(
821824
expect(await page.getByTestId("stdio-command-input").inputValue()).toBe(
822825
"uvx",
823826
);
824-
expect(await page.getByTestId("stdio-args_0").inputValue()).toBe(
825-
"mcp-server-fetch",
826-
);
827+
for (const [index, arg] of FETCH_SERVER_ARGS.entries()) {
828+
expect(await page.getByTestId(`stdio-args_${index}`).inputValue()).toBe(
829+
arg,
830+
);
831+
}
827832

828-
await page.getByTestId("stdio-args_0").fill("mcp-server-time");
833+
// Swap only the package operand; the leading `--with mcp~=1.28` still applies.
834+
await page
835+
.getByTestId(`stdio-args_${FETCH_SERVER_ARGS.length - 1}`)
836+
.fill("mcp-server-time");
829837

830838
await page.getByTestId("add-mcp-server-button").click();
831839

@@ -937,8 +945,7 @@ test(
937945

938946
await page.getByTestId("stdio-name-input").fill(testName);
939947

940-
await page.getByTestId("stdio-command-input").fill("uvx");
941-
await page.getByTestId("stdio-args_0").fill("mcp-server-fetch");
948+
await fillFetchServerCommand(page);
942949

943950
await page.getByTestId("add-mcp-server-button").click();
944951

0 commit comments

Comments
 (0)