Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions src/backend/base/langflow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ def run(
if is_port_in_use(port, host):
port = get_free_port(port)

# Store the actual port being used in settings
get_settings_service().settings.current_port = port

Comment on lines +344 to +346
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix port propagation across processes (spawn) to avoid wrong SSE URLs.

When the configured port is occupied and a free one is chosen, only current_port is set in the parent. On macOS (spawn) and some setups, workers may not see this and fall back to stale settings.port, producing bad SSE URLs in MCP configs.

Apply both assignments (and export env) before starting the server:

-        # Store the actual port being used in settings
-        get_settings_service().settings.current_port = port
+        # Store the actual port being used in settings (propagate to workers/spawned processes)
+        settings_service = get_settings_service()
+        settings_service.settings.current_port = port
+        settings_service.settings.port = port
+        os.environ["LANGFLOW_PORT"] = str(port)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Store the actual port being used in settings
get_settings_service().settings.current_port = port
# Store the actual port being used in settings (propagate to workers/spawned processes)
settings_service = get_settings_service()
settings_service.settings.current_port = port
settings_service.settings.port = port
os.environ["LANGFLOW_PORT"] = str(port)
🤖 Prompt for AI Agents
In src/backend/base/langflow/__main__.py around lines 344 to 346, when the
chosen port differs from the configured one only current_port is set, causing
spawned worker processes to see stale settings.port and produce wrong SSE URLs;
set both get_settings_service().settings.port and
get_settings_service().settings.current_port to the chosen port and export it to
the environment (e.g., os.environ["PORT"] = str(port)) before starting the
server so child processes inherit the correct port.

protocol = "https" if ssl_cert_file_path and ssl_key_file_path else "http"

# Step 4: Loading Components (placeholder for components loading)
Expand Down
18 changes: 15 additions & 3 deletions src/backend/base/langflow/api/v1/mcp_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
from langflow.services.database.models.api_key.model import ApiKeyCreate
from langflow.services.database.models.user.model import User

# Constants
ALL_INTERFACES_HOST = "0.0.0.0" # noqa: S104

router = APIRouter(prefix="/mcp/project", tags=["mcp_projects"])


Expand Down Expand Up @@ -448,7 +451,7 @@ def is_local_ip(ip_str: str) -> bool:
return True

# Check if it's exactly "0.0.0.0" (which binds to all interfaces)
if ip_str == "0.0.0.0": # noqa: S104
if ip_str == ALL_INTERFACES_HOST:
return True

try:
Expand Down Expand Up @@ -888,8 +891,17 @@ async def get_project_sse_url(project_id: UUID) -> str:
"""Generate the SSE URL for a project, including WSL handling."""
# Get settings service to build the SSE URL
settings_service = get_settings_service()
host = getattr(settings_service.settings, "host", "localhost")
port = getattr(settings_service.settings, "port", 3000)
server_host = getattr(settings_service.settings, "host", "localhost")
# Use the actual running port (current_port) if available, otherwise fall back to configured port
server_port = getattr(settings_service.settings, "current_port", None) or getattr(
settings_service.settings, "port", 7860
)

# For MCP clients, always use localhost instead of 0.0.0.0
# 0.0.0.0 is a bind address, not a connect address
host = "localhost" if server_host == ALL_INTERFACES_HOST else server_host
port = server_port

base_url = f"http://{host}:{port}".rstrip("/")
project_sse_url = f"{base_url}/api/v1/mcp/project/{project_id}/sse"

Expand Down
49 changes: 49 additions & 0 deletions src/backend/base/langflow/api/v1/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,24 @@
from fastapi.responses import StreamingResponse
from fastapi_pagination import Params
from fastapi_pagination.ext.sqlmodel import apaginate
from lfx.base.mcp.constants import MAX_MCP_SERVER_NAME_LENGTH
from lfx.base.mcp.util import sanitize_mcp_name
from lfx.log.logger import logger
from sqlalchemy import or_, update
from sqlalchemy.orm import selectinload
from sqlmodel import select

from langflow.api.utils import CurrentActiveUser, DbSession, cascade_delete_flow, custom_params, remove_api_keys
from langflow.api.v1.flows import create_flows
from langflow.api.v1.mcp_projects import get_project_sse_url
from langflow.api.v1.schemas import FlowListCreate
from langflow.api.v2.mcp import update_server
from langflow.helpers.flow import generate_unique_flow_name
from langflow.helpers.folders import generate_unique_folder_name
from langflow.initial_setup.constants import STARTER_FOLDER_NAME
from langflow.services.auth.mcp_encryption import encrypt_auth_settings
from langflow.services.database.models.api_key.crud import create_api_key
from langflow.services.database.models.api_key.model import ApiKeyCreate
from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
from langflow.services.database.models.folder.model import (
Expand All @@ -32,6 +40,7 @@
FolderUpdate,
)
from langflow.services.database.models.folder.pagination_model import FolderWithPaginatedFlows
from langflow.services.deps import get_settings_service, get_storage_service

router = APIRouter(prefix="/projects", tags=["Projects"])

Expand Down Expand Up @@ -74,6 +83,46 @@ async def create_project(
await session.commit()
await session.refresh(new_project)

# Auto-register MCP server for this project with API key auth
if get_settings_service().settings.add_projects_to_mcp_servers:
try:
# Create API key
api_key_name = f"MCP Project {new_project.name} - default"
unmasked_api_key = await create_api_key(session, ApiKeyCreate(name=api_key_name), current_user.id)

# Set folder auth to API key mode (encrypted at rest)
new_project.auth_settings = encrypt_auth_settings({"auth_type": "apikey"})
session.add(new_project)
await session.commit()
await session.refresh(new_project)

# Build SSE URL
sse_url = await get_project_sse_url(new_project.id)

# Prepare server config
command = "uvx"
args = [
"mcp-proxy",
"--headers",
"x-api-key",
unmasked_api_key.api_key,
sse_url,
]
server_config = {"command": command, "args": args}

# Determine server name and register in user's _mcp_servers.json
server_name = f"lf-{sanitize_mcp_name(new_project.name)[: (MAX_MCP_SERVER_NAME_LENGTH - 4)]}"
await update_server(
server_name,
server_config,
current_user,
session,
get_storage_service(),
get_settings_service(),
)
except Exception as e: # noqa: BLE001
await logger.aexception(f"Failed to auto-register MCP server for project {new_project.id}: {e}")

if project.components_list:
update_statement_components = (
update(Flow).where(Flow.id.in_(project.components_list)).values(folder_id=new_project.id) # type: ignore[attr-defined]
Expand Down
7 changes: 7 additions & 0 deletions src/lfx/src/lfx/services/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ class Settings(BaseSettings):
"""The host on which Langflow will run."""
port: int = 7860
"""The port on which Langflow will run."""
current_port: int | None = None
"""The actual port on which Langflow is running (after checking for port conflicts)."""
workers: int = 1
"""The number of workers to run."""
log_level: str = "critical"
Expand Down Expand Up @@ -263,6 +265,11 @@ class Settings(BaseSettings):
mcp_server_enable_progress_notifications: bool = False
"""If set to False, Langflow will not send progress notifications in the MCP server."""

# Add projects to MCP servers automatically on creation
add_projects_to_mcp_servers: bool = True
"""If set to True, newly created projects will be added to the user's MCP servers config automatically.
Controlled by LANGFLOW_ADD_PROJECTS_TO_MCP_SERVERS env variable."""

# Public Flow Settings
public_flow_cleanup_interval: int = Field(default=3600, gt=600)
"""The interval in seconds at which public temporary flows will be cleaned up.
Expand Down
Loading