|
2 | 2 | import json |
3 | 3 | from io import BytesIO |
4 | 4 | from typing import Annotated |
5 | | -from urllib.parse import urlparse |
6 | | -from uuid import UUID |
7 | 5 |
|
8 | 6 | from fastapi import APIRouter, Depends, HTTPException, UploadFile |
9 | 7 | from lfx.base.agents.utils import safe_cache_get, safe_cache_set |
|
20 | 18 | upload_user_file, |
21 | 19 | ) |
22 | 20 | from langflow.logging import logger |
23 | | -from langflow.services.database.models import Folder |
24 | | -from langflow.services.database.models.api_key.crud import create_api_key |
25 | | -from langflow.services.database.models.api_key.model import ApiKeyCreate |
26 | 21 | from langflow.services.deps import get_settings_service, get_shared_component_cache_service, get_storage_service |
27 | 22 | from langflow.services.settings.service import SettingsService |
28 | 23 | from langflow.services.storage.service import StorageService |
@@ -112,35 +107,6 @@ async def get_server_list( |
112 | 107 | except json.JSONDecodeError: |
113 | 108 | raise HTTPException(status_code=500, detail="Invalid server configuration file format.") from None |
114 | 109 |
|
115 | | - servers_updated = False |
116 | | - created_api_key = False |
117 | | - mcp_servers = servers.get("mcpServers", {}) |
118 | | - |
119 | | - for server_name, server_config in list(mcp_servers.items()): |
120 | | - updated_config, config_changed, created_key = await _ensure_mcp_server_config( |
121 | | - server_name=server_name, |
122 | | - server_config=server_config, |
123 | | - current_user=current_user, |
124 | | - session=session, |
125 | | - settings_service=settings_service, |
126 | | - ) |
127 | | - if config_changed: |
128 | | - servers_updated = True |
129 | | - created_api_key = created_api_key or created_key |
130 | | - mcp_servers[server_name] = updated_config |
131 | | - |
132 | | - if servers_updated: |
133 | | - servers["mcpServers"] = mcp_servers |
134 | | - if created_api_key: |
135 | | - await session.commit() |
136 | | - await upload_server_config( |
137 | | - servers, |
138 | | - current_user, |
139 | | - session, |
140 | | - storage_service=storage_service, |
141 | | - settings_service=settings_service, |
142 | | - ) |
143 | | - |
144 | 110 | return servers |
145 | 111 |
|
146 | 112 |
|
@@ -309,157 +275,6 @@ async def update_server( |
309 | 275 | ) |
310 | 276 |
|
311 | 277 |
|
312 | | -def _extract_project_id_from_url(url: str) -> UUID | None: |
313 | | - """Return project UUID from a Langflow MCP URL if present.""" |
314 | | - try: |
315 | | - parsed = urlparse(url) |
316 | | - except ValueError: |
317 | | - return None |
318 | | - |
319 | | - path_parts = [part for part in parsed.path.split("/") if part] |
320 | | - for idx, part in enumerate(path_parts): |
321 | | - if part == "project" and idx + 1 < len(path_parts): |
322 | | - candidate = path_parts[idx + 1] |
323 | | - try: |
324 | | - return UUID(candidate) |
325 | | - except (ValueError, TypeError): |
326 | | - return None |
327 | | - return None |
328 | | - |
329 | | - |
330 | | -async def _ensure_mcp_server_config( |
331 | | - *, |
332 | | - server_name: str, |
333 | | - server_config: dict, |
334 | | - current_user: CurrentActiveUser, |
335 | | - session: DbSession, |
336 | | - settings_service: SettingsService, |
337 | | -) -> tuple[dict, bool, bool]: |
338 | | - """Normalize stored MCP server configs and ensure auth headers when required.""" |
339 | | - args = server_config.get("args") |
340 | | - if not isinstance(args, list) or not args: |
341 | | - return server_config, False, False |
342 | | - |
343 | | - command = server_config.get("command") |
344 | | - if command != "uvx": |
345 | | - return server_config, False, False |
346 | | - |
347 | | - if "mcp-proxy" not in args: |
348 | | - return server_config, False, False |
349 | | - |
350 | | - url_arg = next((arg for arg in reversed(args) if isinstance(arg, str) and arg.startswith("http")), None) |
351 | | - if not url_arg: |
352 | | - return server_config, False, False |
353 | | - |
354 | | - project_id = _extract_project_id_from_url(url_arg) |
355 | | - if project_id is None: |
356 | | - return server_config, False, False |
357 | | - |
358 | | - project: Folder | None = await session.get(Folder, project_id) |
359 | | - if project is None: |
360 | | - return server_config, False, False |
361 | | - |
362 | | - generated_api_key = False |
363 | | - should_generate_api_key = False |
364 | | - |
365 | | - if settings_service.settings.mcp_composer_enabled: |
366 | | - if project.auth_settings and project.auth_settings.get("auth_type") == "apikey": |
367 | | - should_generate_api_key = True |
368 | | - elif project.auth_settings: |
369 | | - if project.auth_settings.get("auth_type") == "apikey": |
370 | | - should_generate_api_key = True |
371 | | - elif not settings_service.auth_settings.AUTO_LOGIN: |
372 | | - should_generate_api_key = True |
373 | | - |
374 | | - if settings_service.auth_settings.AUTO_LOGIN and not settings_service.auth_settings.SUPERUSER: |
375 | | - should_generate_api_key = True |
376 | | - |
377 | | - existing_header_tokens: list[str] | None = None |
378 | | - preserved_args: list[str] = [] |
379 | | - uses_streamable = False |
380 | | - |
381 | | - start_index = 1 if args[0] == "mcp-proxy" else 0 |
382 | | - if start_index == 0: |
383 | | - preserved_args.append(args[0]) |
384 | | - |
385 | | - idx = start_index |
386 | | - while idx < len(args): |
387 | | - arg_item = args[idx] |
388 | | - if arg_item == "--transport": |
389 | | - uses_streamable = True |
390 | | - idx += 2 |
391 | | - continue |
392 | | - if arg_item == "--headers": |
393 | | - existing_header_tokens = args[idx : idx + 3] |
394 | | - idx += 3 |
395 | | - continue |
396 | | - if isinstance(arg_item, str) and arg_item.startswith("http"): |
397 | | - idx += 1 |
398 | | - continue |
399 | | - preserved_args.append(arg_item) |
400 | | - idx += 1 |
401 | | - |
402 | | - if isinstance(url_arg, str) and not url_arg.endswith("/sse"): |
403 | | - uses_streamable = True |
404 | | - |
405 | | - if not uses_streamable: |
406 | | - if existing_header_tokens is None and should_generate_api_key: |
407 | | - api_key_name = f"MCP Server {project.name}" |
408 | | - new_api_key = await create_api_key(session, ApiKeyCreate(name=api_key_name), current_user.id) |
409 | | - header_tokens = ["--headers", "x-api-key", new_api_key.api_key] |
410 | | - generated_api_key = True |
411 | | - |
412 | | - url_index = len(args) - 1 |
413 | | - for idx_arg in range(len(args) - 1, -1, -1): |
414 | | - if args[idx_arg] == url_arg: |
415 | | - url_index = idx_arg |
416 | | - break |
417 | | - |
418 | | - final_args = list(args) |
419 | | - final_args[url_index:url_index] = header_tokens |
420 | | - server_config["args"] = final_args |
421 | | - await logger.adebug( |
422 | | - "Added authentication headers for MCP server '%s' (project %s) while preserving SSE transport.", |
423 | | - server_name, |
424 | | - project_id, |
425 | | - ) |
426 | | - return server_config, True, generated_api_key |
427 | | - |
428 | | - return server_config, False, generated_api_key |
429 | | - |
430 | | - streamable_http_url = url_arg.removesuffix("/sse") |
431 | | - if not streamable_http_url.endswith("/streamable"): |
432 | | - streamable_http_url = streamable_http_url.rstrip("/") + "/streamable" |
433 | | - final_args: list[str] = ["mcp-proxy", "--transport", "streamablehttp"] |
434 | | - |
435 | | - if preserved_args: |
436 | | - final_args.extend(preserved_args) |
437 | | - |
438 | | - header_tokens = existing_header_tokens |
439 | | - if header_tokens is None and should_generate_api_key: |
440 | | - api_key_name = f"MCP Server {project.name}" |
441 | | - new_api_key = await create_api_key(session, ApiKeyCreate(name=api_key_name), current_user.id) |
442 | | - header_tokens = ["--headers", "x-api-key", new_api_key.api_key] |
443 | | - generated_api_key = True |
444 | | - |
445 | | - if header_tokens: |
446 | | - final_args.extend(header_tokens) |
447 | | - |
448 | | - final_args.append(streamable_http_url) |
449 | | - |
450 | | - config_updated = final_args != args |
451 | | - |
452 | | - if config_updated: |
453 | | - server_config["args"] = final_args |
454 | | - await logger.adebug( |
455 | | - "Normalized MCP server '%s' configuration for project %s (streamable HTTP + auth header).", |
456 | | - server_name, |
457 | | - project_id, |
458 | | - ) |
459 | | - |
460 | | - return server_config, config_updated, generated_api_key |
461 | | - |
462 | | - |
463 | 278 | @router.post("/servers/{server_name}") |
464 | 279 | async def add_server( |
465 | 280 | server_name: str, |
|
0 commit comments