Skip to content

Conversation

@Cristhianzl
Copy link
Member

@Cristhianzl Cristhianzl commented Nov 17, 2025

This pull request introduces several improvements and bug fixes to both the backend and frontend of the project, focusing on more robust handling of MCP Composer errors, improved synchronization of OAuth settings in the authentication modal, and better frontend cache management. The changes enhance user feedback during authentication issues, ensure correct UI state synchronization, and improve test coverage for OAuth field auto-sync behavior.

Backend improvements to MCP Composer error handling and commit logic:

  • MCP Composer service errors are now persisted per project, allowing the UI to display error messages when OAuth setup fails. Errors are cleared on successful composer start/stop, and only unexpected errors trigger a rollback of authentication settings. Commit operations are performed only after successful composer operations to prevent inconsistent states. [1] [2] [3] [4] [5]
  • The get_project_composer_url endpoint now returns the last error if OAuth setup previously failed, even if OAuth is currently disabled for the project. [1] [2]
  • Suppresses noisy ResourceWarning logs from SSE connections in the backend to reduce log clutter.

Frontend improvements to OAuth authentication modal and cache management:

  • The AuthModal now auto-synchronizes the Server URL and Callback Path fields whenever the OAuth Host or Port fields are changed, ensuring consistent and user-friendly configuration. Manual edits are preserved unless fields are changed again.
  • Added comprehensive tests for the OAuth modal to verify auto-sync behavior and correct field population, improving reliability and preventing regressions.

Frontend cache and query management:

  • The frontend now uses more granular query keys and invalidates queries per project, reducing unnecessary cache invalidations and improving performance when multiple projects are managed concurrently. [1] [2] [3]
  • The useMcpServer hook adds state for tracking slow composer responses and waiting status, laying the groundwork for better user feedback during long-running operations. [1] [2] [3]

Summary by CodeRabbit

  • New Features

    • OAuth settings now automatically synchronize server URL and callback path when host or port values change.
    • Added UI feedback for MCP Composer startup status, including slow startup warnings.
    • Enhanced per-project error tracking for MCP Composer operations.
  • Bug Fixes

    • Improved rollback safety for authentication settings during updates.
    • Enhanced startup reliability with automatic retries and improved error handling for MCP Composer.
    • Better port management and process cleanup.
  • Tests

    • Added comprehensive test coverage for OAuth workflows and Windows-specific MCP Composer operations.

@Cristhianzl Cristhianzl self-assigned this Nov 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces MCP Composer orchestration with rollback safety, port management, and error handling across backend and frontend. Backend adds composer start/stop orchestration with auth settings rollback on failure, per-project error tracking, and robust process management with retry logic. Frontend adds OAuth auto-sync, per-project cache scoping, and loading state tracking for composer startup.

Changes

Cohort / File(s) Summary
Backend MCP Orchestration
src/backend/base/langflow/api/v1/mcp_projects.py
Adds rollback safety by preserving original auth settings; delays session commits until after MCP Composer orchestration; implements error handling to store per-project startup errors; clears composer error state when OAuth is stopped; extends composer URL retrieval to return recent errors and clear them on success.
Main Initialization
src/backend/base/langflow/main.py
Suppresses ResourceWarning for MemoryObjectReceiveStream and MemoryObjectSendStream from anyio SSE connections via warnings.filterwarnings calls.
Frontend MCP Query Caching
src/frontend/src/controllers/API/queries/mcp/use-patch-flows-mcp.ts, use-patch-install-mcp.ts
Updates mutation keys to include project_id for per-project cache scoping; reverts onSettled to invalidate only per-project queries; removes explicit retry: 0 configuration.
Frontend OAuth Auto-Sync
src/frontend/src/modals/authModal/index.tsx
Adds auto-synchronization logic to compute and update oauthServerUrl and oauthCallbackPath when oauthHost or oauthPort changes within the field handler.
Frontend Auth Modal Tests
src/frontend/src/modals/authModal/__tests__/AuthModal.test.tsx
Adds comprehensive test suite covering OAuth auto-sync scenarios: server URL/callback path auto-sync on host/port changes, manual edit preservation, existing settings loading, and non-OAuth auth type handling.
Frontend MCP Server Hook
src/frontend/src/pages/MainPage/pages/homePage/hooks/useMcpServer.ts
Integrates React Query client for cache invalidation; adds isWaitingForComposer, showSlowWarning, and isLoadingComposerUrl state tracking; clears cached composer URL before auth save to fetch fresh errors; exposes loading states in hook return.
Backend MCP Composer Service
src/lfx/src/lfx/services/mcp_composer/service.py
Introduces comprehensive port management with cross-platform process killing (Windows netstat/taskkill, Unix lsof/kill); implements per-project error tracking and last-error accessors; adds concurrency control for startup tasks with cancellation/retry logic; enhances startup sequence with retry loop, zombie cleanup, obfuscated command logging, and detailed error extraction/reporting.
Backend Unit Tests
src/lfx/tests/unit/custom/custom_component/test_component.py
Removes fixture-based test variation from test_send_message_without_database; converts to standalone async test with internal AsyncMock for _store_message.
Backend MCP Composer Unit Tests
src/lfx/tests/unit/services/settings/test_mcp_composer.py
Adds unit test coverage for port availability checks, process termination, and auth config change detection; tests port change triggering restart logic and error handling.
Backend Windows-Specific Tests
src/lfx/tests/unit/services/settings/test_mcp_composer_windows.py
Adds Windows-focused test coverage for zombie process cleanup, temp file handling for stdout/stderr, startup timeout/retry logic, and stream reading behavior across platforms.

Sequence Diagram

sequenceDiagram
    participant Client as Client/API
    participant MCP_API as mcp_projects.py
    participant Auth_DB as Auth Settings (DB)
    participant Composer as MCP Composer Service
    participant Process as OS Process
    
    Client->>MCP_API: PATCH auth settings
    MCP_API->>Auth_DB: Load original_auth_settings
    Auth_DB-->>MCP_API: original_auth_settings
    
    MCP_API->>Composer: start_project_composer(max_retries=3)
    
    Note over Composer: Retry Loop (max_retries)
    Composer->>Composer: _ensure_port_available()
    alt Port Conflict
        Composer->>Process: Kill existing process
        Process-->>Composer: Killed
    else Port Available
        Composer->>Composer: Proceed
    end
    
    Composer->>Process: Start MCP process
    alt Process Starts Successfully
        Process-->>Composer: Process PID
        Composer->>Composer: Register port/PID mapping
        Composer-->>MCP_API: Success + URL
        MCP_API->>Auth_DB: Commit auth settings
        MCP_API-->>Client: 200 OK
    else Process Fails
        Process-->>Composer: Error
        Composer->>Composer: Extract error message
        Composer->>Composer: set_last_error(project_id, error_msg)
        Composer-->>MCP_API: Error object
        alt Max Retries Exceeded
            MCP_API->>Auth_DB: Rollback to original_auth_settings
            MCP_API-->>Client: 400 Error (with error details)
        else Retry Available
            Composer->>Process: Retry startup
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • src/lfx/src/lfx/services/mcp_composer/service.py: Extensive logic changes including cross-platform process management, retry orchestration, port tracking, and error extraction. Requires careful review of concurrency patterns, asyncio task handling, and platform-specific subprocess behavior.
  • src/backend/base/langflow/api/v1/mcp_projects.py: Critical orchestration logic with rollback safety, session commit ordering, and error state propagation. Requires verification of transaction safety and error handling paths.
  • src/frontend/src/pages/MainPage/pages/homePage/hooks/useMcpServer.ts: Complex state management with cache invalidation side effects and multiple loading states. Requires tracing of dependency chains and cache invalidation triggers.
  • src/lfx/tests/unit/services/settings/test_mcp_composer_windows.py: Large test file with extensive mocking. Review effort is moderate but cumulative; verify mock strategies accurately represent system behavior.

Possibly related PRs

Suggested labels

enhancement, size:XXL

Suggested reviewers

  • jordanrfrazier
  • ogabrielluiz
  • lucaseduoli

Pre-merge checks and finishing touches

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 3 warnings)
Check name Status Explanation Resolution
Test Coverage For New Implementations ❌ Error PR includes unresolved review issues: test_component.py uses incorrect msg.data["id"] instead of msg.id; useMcpServer.ts initializes showSlowWarning to false but never sets it true; missing test coverage for query mutation key changes; test imports fail. Fix msg.id assignment in test_component.py; add useEffect hook in useMcpServer.ts to set showSlowWarning true after timeout; add query mutation scoping tests; resolve test module import issues.
Test Quality And Coverage ⚠️ Warning Test suite contains critical runtime failures: asyncio.to_thread patches lack AsyncMock causing TypeError when awaited, msg.data['id'] assignment incorrect for Pydantic models, and showSlowWarning never set to true making timeout warnings impossible. Replace asyncio.to_thread patches with new_callable=AsyncMock, fix message ID assignment to msg.id, add timer effect setting showSlowWarning true after 10s delay, and add comprehensive error scenario tests for composer failures and OAuth validation errors.
Test File Naming And Structure ⚠️ Warning Multiple async tests patch asyncio.to_thread with plain MagicMock instead of AsyncMock, causing TypeError on await. Test ID assignments also violate Pydantic model patterns. Replace all patch('asyncio.to_thread') with patch('asyncio.to_thread', new_callable=AsyncMock) and correct Message ID assignment from msg.data['id'] to msg.id in test files.
Excessive Mock Usage Warning ⚠️ Warning Pull request introduces excessive mock usage (1.375 ratio in test_mcp_composer_windows.py) with plain asyncio.to_thread patches lacking AsyncMock, preventing test execution on await. Replace 5 plain asyncio.to_thread patches with AsyncMock, refactor to use real temporary files, extract Windows abstractions, fix Pydantic initialization, and enforce 0.5-0.7 mock density limits.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Improve OAuth error handling and frontend sync behavior' accurately reflects the main changes across backend and frontend. It captures the two primary objectives: OAuth error handling improvements and frontend synchronization behavior enhancements.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the enhancement New feature or request label Nov 17, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2025

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 15%
15.31% (4187/27344) 8.48% (1764/20798) 9.61% (579/6024)

Unit Test Results

Tests Skipped Failures Errors Time
1638 0 💤 0 ❌ 0 🔥 21.831s ⏱️

@codecov
Copy link

codecov bot commented Nov 17, 2025

Codecov Report

❌ Patch coverage is 56.15385% with 228 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.15%. Comparing base (79d02f2) to head (3c0220c).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/lfx/src/lfx/services/mcp_composer/service.py 56.27% 174 Missing and 28 partials ⚠️
src/backend/base/langflow/api/v1/mcp_projects.py 13.33% 13 Missing ⚠️
...ages/MainPage/pages/homePage/hooks/useMcpServer.ts 43.75% 6 Missing and 3 partials ⚠️
...controllers/API/queries/mcp/use-patch-flows-mcp.ts 0.00% 2 Missing ⚠️
...ntrollers/API/queries/mcp/use-patch-install-mcp.ts 0.00% 1 Missing ⚠️
src/frontend/src/modals/authModal/index.tsx 90.90% 0 Missing and 1 partial ⚠️

❌ Your project status has failed because the head coverage (39.97%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main   #10626      +/-   ##
==========================================
+ Coverage   31.51%   32.15%   +0.64%     
==========================================
  Files        1364     1364              
  Lines       62085    62474     +389     
  Branches     9180     9249      +69     
==========================================
+ Hits        19564    20090     +526     
+ Misses      41604    41372     -232     
- Partials      917     1012      +95     
Flag Coverage Δ
backend 50.73% <23.52%> (+0.05%) ⬆️
frontend 14.15% <56.66%> (+0.64%) ⬆️
lfx 39.97% <57.29%> (+0.99%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/backend/base/langflow/main.py 57.92% <100.00%> (+0.24%) ⬆️
src/lfx/src/lfx/graph/state/model.py 92.18% <100.00%> (+0.80%) ⬆️
src/lfx/src/lfx/services/settings/base.py 69.97% <100.00%> (ø)
...ntrollers/API/queries/mcp/use-patch-install-mcp.ts 0.00% <0.00%> (ø)
src/frontend/src/modals/authModal/index.tsx 81.05% <90.90%> (+81.05%) ⬆️
...controllers/API/queries/mcp/use-patch-flows-mcp.ts 0.00% <0.00%> (ø)
...ages/MainPage/pages/homePage/hooks/useMcpServer.ts 50.92% <43.75%> (-0.10%) ⬇️
src/backend/base/langflow/api/v1/mcp_projects.py 22.25% <13.33%> (-0.24%) ⬇️
src/lfx/src/lfx/services/mcp_composer/service.py 57.55% <56.27%> (+43.45%) ⬆️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lfx/src/lfx/services/mcp_composer/service.py (1)

74-98: Persist last_error for all MCPComposer startup failures, not just port/startup retries

The per-project last_error tracking is a good addition, but currently it’s only updated in a few paths:

  • When _ensure_port_available raises MCPComposerPortError, you store self._last_errors[project_id] = e.message.
  • When all retries in _do_start_project_composer fail, you store self._last_errors[project_id] = last_error.message.
  • On success you call self.clear_last_error(project_id).

However, several early failure paths in _do_start_project_composer raise MCPComposerConfigError (no auth settings, invalid/missing port, missing host, or _validate_oauth_settings failures) before any of those assignments, so get_last_error will remain unset or stale for those cases. That undermines the “persist MCP Composer service errors per project for UI display” goal.

To make error reporting consistent, consider centralizing this in start_project_composer:

  • Wrap the await self._do_start_project_composer(...) call in a try/except MCPComposerError block.
  • In the except, call self.set_last_error(project_id, e.message) and re-raise.
  • Inside _do_start_project_composer, use set_last_error rather than assigning self._last_errors[...] directly for the port/retry paths, and keep clear_last_error on success.

This way every MCPComposerError, including configuration issues, reliably updates last_error for the get_project_composer_url endpoint to surface.

Also applies to: 984-1015, 1050-1072, 1142-1157

src/frontend/src/controllers/API/queries/mcp/use-patch-flows-mcp.ts (1)

44-82: Fix options override so internal cache updates always run in usePatchFlowsMCP

The per-project mutation key and targeted invalidations look good, but the options wiring here is fragile:

const mutation = mutate(["usePatchFlowsMCP", params.project_id], patchFlowMCP, {
  onSuccess: (data, variables, context) => { /* internal updates */ },
  onSettled: () => { /* invalidate per-project flows */ },
  ...options,
});

Because ...options comes last, any caller that passes options.onSuccess or options.onSettled will completely replace the internal handlers, so:

  • auth_settings won't be updated in the useGetFlowsMCP cache.
  • project-composer-url and the per-project useGetFlowsMCP queries won't be invalidated.

usePatchInstallMCP uses the safer pattern (...options first, then an onSuccess that also calls options?.onSuccess), which is what we want here too.

A minimal fix:

-  const mutation: UseMutationResult<
-    PatchFlowMCPResponse,
-    any,
-    PatchFlowMCPRequest
-  > = mutate(["usePatchFlowsMCP", params.project_id], patchFlowMCP, {
-    onSuccess: (data, variables, context) => {
+  const mutation: UseMutationResult<
+    PatchFlowMCPResponse,
+    any,
+    PatchFlowMCPRequest
+  > = mutate(["usePatchFlowsMCP", params.project_id], patchFlowMCP, {
+    ...options,
+    onSuccess: (data, variables, context) => {
       const authSettings = (variables as PatchFlowMCPRequest).auth_settings;
       const currentMCPData = queryClient.getQueryData([
         "useGetFlowsMCP",
         params.project_id,
       ]);
       if (currentMCPData && authSettings !== undefined) {
         queryClient.setQueryData(["useGetFlowsMCP", params.project_id], {
           ...currentMCPData,
           auth_settings: authSettings,
         });
       }
       queryClient.invalidateQueries({
         queryKey: ["project-composer-url", params.project_id],
       });
-      if (options?.onSuccess) {
-        options.onSuccess(data, variables, context);
-      }
+      options?.onSuccess?.(data, variables, context);
     },
-    onSettled: () => {
-      queryClient.invalidateQueries({
-        queryKey: ["useGetFlowsMCP", params.project_id],
-      });
-    },
-    ...options,
+    onSettled: (data, error, variables, context) => {
+      queryClient.invalidateQueries({
+        queryKey: ["useGetFlowsMCP", params.project_id],
+      });
+      options?.onSettled?.(data, error, variables, context);
+    },
   });

This ensures the internal cache updates/invalidation always run while still honoring any callbacks provided via options.

🧹 Nitpick comments (10)
src/lfx/tests/unit/custom/custom_component/test_component.py (2)

117-117: Consider moving the import to module level.

While functionally correct, importing AsyncMock inside the function is unconventional. Consider moving it to the top of the file alongside the other unittest.mock imports for better code organization.

Apply this diff to move the import:

 from typing import Any
-from unittest.mock import MagicMock
+from unittest.mock import AsyncMock, MagicMock

 import pytest

And remove the import from inside the function:

 async def test_send_message_without_database():
-    from unittest.mock import AsyncMock
-
     component = Component()

132-140: Consider adding assertions to verify the mock behavior.

The test could be strengthened by:

  1. Asserting that result.id is set to verify the mock worked correctly
  2. Using assert_called_once_with(message) instead of just assert_called_once() to verify the correct argument was passed

Apply this diff to add the assertions:

     result = await component.send_message(message)
     assert isinstance(result, Message)
     assert result.text == "Hello"
     assert result.sender == "User"
     assert result.sender_name == "Test"
+    assert result.id == "test-message-id"
     # Verify the message was stored (mock was called)
-    component._store_message.assert_called_once()
+    component._store_message.assert_called_once_with(message)
     # The focus is on testing the message handling logic, not the database persistence layer
     assert event_manager.on_message.called
src/frontend/src/modals/authModal/index.tsx (1)

81-101: OAuth auto-sync logic looks correct; consider clearing URLs when port is emptied

The handleAuthFieldChange auto-sync for oauthHost/oauthPort correctly derives oauthServerUrl and oauthCallbackPath and aligns with the new tests and desired re-sync behavior when host/port change.

One minor UX edge case: when the user clears the Port field (empty string), the existing oauthServerUrl/oauthCallbackPath are left pointing at the old port. Consider clearing or updating those fields when port becomes empty so the form doesn’t display a now-invalid URL.

src/backend/base/langflow/api/v1/mcp_projects.py (2)

411-424: Auth rollback + composer orchestration mostly solid; consider clarifying behavior and rollback intent

The new flow around original_auth_settings, should_handle_mcp_composer, and the commit-at-end pattern makes sense and aligns with the PR goals:

  • Expected MCPComposerError cases persist auth settings and surface an error payload while not rolling back, which lets the UI show composer failures without losing OAuth config.
  • Unexpected exceptions roll back via project.auth_settings = original_auth_settings and a raised HTTPException, so the session context should abort without committing changes.

A couple of tweaks worth considering:

  1. Clarify commit semantics vs. comment

    The comment # Only commit if composer started successfully (or wasn't needed) is slightly misleading: in the MCPComposerError branch you intentionally do commit auth changes even when composer fails, to satisfy “persist settings, show error” behavior.

    Consider updating the comment to reflect the actual intent, e.g.:

    # Commit project/auth changes unless an unexpected error occurred
    # (composer startup failures are persisted so the UI can show errors)
  2. Make rollback intent explicit

    You currently snapshot original_auth_settings = project.auth_settings and then on unexpected errors do:

    project.auth_settings = original_auth_settings
    raise HTTPException(...)

    Given the surrounding session_scope, the transaction will be rolled back anyway, but this assignment may give a false sense of “guaranteed rollback” if handle_auth_settings_update mutates the dict in place.

    If you genuinely need an in-process rollback before the session context rolls back, consider copying the structure:

    import copy
    original_auth_settings = copy.deepcopy(project.auth_settings) if project.auth_settings else None

    Otherwise, you could drop the manual assignment and rely purely on the transaction rollback for clarity.

Also applies to: 447-524


777-790: Per-project composer last_error exposure works; confirm whether errors should be one-shot or persistent

The new behavior in get_project_composer_url to:

  • Return a stored last_error when should_use_mcp_composer(project) is False, and
  • Clear last_error only after a successful composer URL retrieval

is coherent with the idea of “remember the last OAuth failure even if composer is currently not usable”.

Two points to double-check:

  1. Lifetime of last_error when composer is permanently disabled

    If MCP Composer is turned off globally after a failure, should_use_mcp_composer will remain False and last_error will never be cleared by this endpoint. That’s probably fine, but consider whether the UI expects this to behave like a one-shot error (cleared after displaying once) versus a persistent banner until the next successful OAuth/composer run.

  2. Symmetry with other paths that clear last_error

    You clear last_error explicitly on:

    • successful start in the PATCH handler, and
    • explicit OAuth disable (stop-composer branch).

    Confirm that these three places (PATCH success, OAuth disable, composer-url success) cover all the scenarios you care about, and that you don’t need to clear last_error when auth type changes away from OAuth through other flows.

If the current behavior is intentional, this is good; if not, adding an explicit clear (or documenting persistence) would avoid surprises later.

Also applies to: 804-808

src/frontend/src/pages/MainPage/pages/homePage/hooks/useMcpServer.ts (1)

1-2: Confirm that useQueryClient shares the same QueryClient used inside UseRequestProcessor

You now call useQueryClient() directly and use it to removeQueries for ["project-composer-url", projectId], while usePatchFlowsMCP internally gets its own queryClient from UseRequestProcessor.

If UseRequestProcessor is also using useQueryClient under the hood, this is fine. If it creates its own QueryClient instance, you’d end up clearing queries on a different client than the one the mutation is invalidating, which would be confusing and could leave stale data around.

Please double-check that:

  • UseRequestProcessor and this hook are wired to the same React Query client (i.e., they both operate within the same QueryClientProvider and use the same underlying instance).
  • The query key you remove (["project-composer-url", projectId]) exactly matches the one used in useGetProjectComposerUrl.

If they are aligned, this pattern is good; if not, consider exposing queryClient from UseRequestProcessor and reusing that instead of calling useQueryClient here.

Also applies to: 48-48, 209-221

src/lfx/tests/unit/services/settings/test_mcp_composer.py (2)

21-38: Port-availability tests may be slightly flaky due to hard-coded ports

Using fixed high ports (59999, 59998) is usually fine but can still collide with other processes on CI, causing intermittent failures.

Consider making the “free port” test more robust by letting the OS allocate a port and then testing it after closing the socket, e.g.:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind(("localhost", 0))
    free_port = s.getsockname()[1]

assert mcp_service._is_port_available(free_port) is True

This reduces the chance of port collisions across environments.


127-176: Auth-config change detection tests align with _has_auth_config_changed behavior

These tests cover the main branches of _has_auth_config_changed (port changes, identical configs, auth_type change, both/one None) and match the documented implementation that compares OAuth-prefixed keys and relevant fields.

You might later add a case for subtle normalization differences (e.g., whitespace or int vs. str for ports) to exercise _normalize_config_value, but the current set already exercises the core logic well.

src/lfx/tests/unit/services/settings/test_mcp_composer_windows.py (2)

182-374: Windows temp-file and non-Windows pipe handling tests are thorough and aligned with the service design

The temp-file tests cover:

  • Windows path: NamedTemporaryFile usage and ensuring subprocess.Popen receives real file handles instead of PIPE.
  • Async reading and cleanup via _read_process_output_and_extract_error, verifying both contents and that temp files are removed.
  • Successful-start path cleaning up temp files after _start_project_composer_process returns.
  • Non-Windows path: asserting that stdout/stderr use subprocess.PIPE when platform.system() is not "Windows".

These tests exercise both success and failure/cleanup paths and match the Windows-specific design in the service snippets. The use of real temp files for the read/cleanup tests is appropriate and keeps behavior close to production.


473-523: Retry robustness test nicely covers zombie-cleanup failures

test_zombie_cleanup_failure_is_non_fatal_during_retry sets up:

  • A first _start_project_composer_process attempt that raises MCPComposerStartupError.
  • _kill_zombie_mcp_processes raising an exception.
  • A second attempt that succeeds.

Asserting that call_count == 2 and that project_id is present in project_composers confirms that:

  • Zombie cleanup errors do not abort retries.
  • Success on a later attempt still leads to proper tracking.

The async side-effect function for _start_project_composer_process is a good pattern here.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cbd4ec and 5bdf51b.

📒 Files selected for processing (11)
  • src/backend/base/langflow/api/v1/mcp_projects.py (7 hunks)
  • src/backend/base/langflow/main.py (1 hunks)
  • src/frontend/src/controllers/API/queries/mcp/use-patch-flows-mcp.ts (2 hunks)
  • src/frontend/src/controllers/API/queries/mcp/use-patch-install-mcp.ts (1 hunks)
  • src/frontend/src/modals/authModal/__tests__/AuthModal.test.tsx (1 hunks)
  • src/frontend/src/modals/authModal/index.tsx (1 hunks)
  • src/frontend/src/pages/MainPage/pages/homePage/hooks/useMcpServer.ts (6 hunks)
  • src/lfx/src/lfx/services/mcp_composer/service.py (13 hunks)
  • src/lfx/tests/unit/custom/custom_component/test_component.py (1 hunks)
  • src/lfx/tests/unit/services/settings/test_mcp_composer.py (1 hunks)
  • src/lfx/tests/unit/services/settings/test_mcp_composer_windows.py (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-05T22:51:27.961Z
Learnt from: edwinjosechittilappilly
Repo: langflow-ai/langflow PR: 0
File: :0-0
Timestamp: 2025-08-05T22:51:27.961Z
Learning: The TestComposioComponentAuth test in src/backend/tests/unit/components/bundles/composio/test_base_composio.py demonstrates proper integration testing patterns for external API components, including real API calls with mocking for OAuth completion, comprehensive resource cleanup, and proper environment variable handling with pytest.skip() fallbacks.

Applied to files:

  • src/lfx/tests/unit/services/settings/test_mcp_composer.py
  • src/frontend/src/modals/authModal/__tests__/AuthModal.test.tsx
  • src/lfx/tests/unit/custom/custom_component/test_component.py
📚 Learning: 2025-06-23T12:46:42.048Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-06-23T12:46:42.048Z
Learning: Custom React Flow node types should be implemented as memoized components, using Handle components for connection points and supporting optional icons and labels.

Applied to files:

  • src/frontend/src/pages/MainPage/pages/homePage/hooks/useMcpServer.ts
🧬 Code graph analysis (7)
src/lfx/tests/unit/services/settings/test_mcp_composer.py (1)
src/lfx/src/lfx/services/mcp_composer/service.py (5)
  • MCPComposerPortError (38-39)
  • _is_port_available (99-115)
  • _kill_process_on_port (117-238)
  • _has_auth_config_changed (793-824)
  • _do_start_project_composer (962-1156)
src/frontend/src/modals/authModal/__tests__/AuthModal.test.tsx (1)
src/frontend/src/components/common/genericIconComponent/index.tsx (1)
  • render (157-159)
src/frontend/src/pages/MainPage/pages/homePage/hooks/useMcpServer.ts (3)
src/frontend/src/controllers/API/queries/mcp/use-get-flows-mcp.ts (1)
  • useGetFlowsMCP (13-36)
src/frontend/src/controllers/API/queries/mcp/use-patch-flows-mcp.ts (1)
  • usePatchFlowsMCP (27-85)
src/frontend/src/customization/feature-flags.ts (1)
  • ENABLE_MCP_COMPOSER (20-21)
src/lfx/tests/unit/custom/custom_component/test_component.py (2)
src/lfx/src/lfx/custom/custom_component/component.py (2)
  • _store_message (1636-1646)
  • send_message (1578-1634)
src/lfx/src/lfx/schema/message.py (1)
  • Message (34-299)
src/lfx/tests/unit/services/settings/test_mcp_composer_windows.py (1)
src/lfx/src/lfx/services/mcp_composer/service.py (4)
  • _kill_zombie_mcp_processes (240-399)
  • _read_process_output_and_extract_error (506-579)
  • start_project_composer (907-960)
  • _read_stream_non_blocking (581-620)
src/lfx/src/lfx/services/mcp_composer/service.py (1)
src/backend/base/langflow/services/deps.py (1)
  • get_settings_service (124-137)
src/backend/base/langflow/api/v1/mcp_projects.py (3)
src/lfx/src/lfx/services/mcp_composer/service.py (5)
  • MCPComposerService (69-1392)
  • clear_last_error (95-97)
  • MCPComposerError (27-35)
  • set_last_error (91-93)
  • get_last_error (87-89)
src/backend/base/langflow/services/deps.py (1)
  • get_service (32-54)
src/backend/base/langflow/services/schema.py (1)
  • ServiceType (4-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Lint Backend / Run Mypy (3.10)
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
  • GitHub Check: Run Frontend Tests / Determine Test Suites and Shard Distribution
  • GitHub Check: Run Backend Tests / LFX Tests - Python 3.10
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
  • GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
  • GitHub Check: Run Frontend Unit Tests / Frontend Jest Unit Tests
  • GitHub Check: Test Starter Templates
  • GitHub Check: Update Component Index
  • GitHub Check: Optimize new Python code in this PR
🔇 Additional comments (6)
src/frontend/src/modals/authModal/__tests__/AuthModal.test.tsx (1)

1-275: Comprehensive and aligned test coverage for OAuth auto-sync

The test suite thoroughly covers the new OAuth auto-sync behavior (host/port, callback, manual overrides, save payload, existing settings, and non-OAuth types) and matches the component’s logic. This should give good confidence in the new flow.

src/lfx/src/lfx/services/mcp_composer/service.py (1)

99-239: Port/process ownership and startup monitoring look robust and conservative

The new helpers for port/process handling and startup monitoring (_is_port_available, _kill_process_on_port, _kill_zombie_mcp_processes, _is_port_used_by_another_project, _ensure_port_available, _read_process_output_and_extract_error, _read_stream_non_blocking, _start_project_composer_process) form a coherent lifecycle:

  • Only kill processes when they’re explicitly associated with the current project (_port_to_project / _pid_to_project) or, on Windows, clearly identified as MCP Composer zombies.
  • Avoid killing unknown external processes on occupied ports, instead surfacing clear MCPComposerPortError messages.
  • Use temp files for stdout/stderr on Windows to avoid pipe deadlocks and clean them up in all success/error paths.
  • Monitor startup with repeated port checks and non-blocking log reads, producing structured error logs with obfuscated secrets.

This is well-structured for cross-platform safety and observability; no changes needed here.

Also applies to: 240-415, 622-699, 1158-1377

src/frontend/src/controllers/API/queries/mcp/use-patch-install-mcp.ts (1)

46-58: Per-project keying and options composition look good in usePatchInstallMCP

The updated mutation key ["usePatchInstallMCP", params.project_id] and per-project invalidation of ["useGetInstalledMCP", params.project_id] are correct. Spreading ...options before the custom onSuccess while still calling options?.onSuccess keeps internal cache behavior intact even when callers pass their own callbacks—this is the pattern worth mirroring in usePatchFlowsMCP.

src/lfx/tests/unit/services/settings/test_mcp_composer.py (1)

178-321: Port-change handling and port-ownership tests look consistent with the service orchestration

The restart and port-ownership tests here are well-structured:

  • test_port_change_triggers_restart correctly seeds project_composers and asserts _do_stop_project_composer is invoked when auth config (port) changes.
  • test_port_in_use_by_own_project_triggers_kill validates the “own project owns port” path via _port_to_project and ensures _kill_process_on_port is called after an initial “in use” check.
  • test_port_in_use_by_unknown_process_raises_error matches the documented _ensure_port_available behavior of raising MCPComposerPortError for untracked ports with a user-facing message.

Mocks use AsyncMock for service coroutines, and the tests focus on control-flow effects, not implementation details, which is appropriate here.

src/lfx/tests/unit/services/settings/test_mcp_composer_windows.py (2)

376-439: Startup timeout and retry-parameter tests correctly pin the intended behavior

  • test_startup_timeout_is_80_seconds asserts the defaults on start_project_composer (max_startup_checks=40, startup_delay=2.0), matching the intended 80s timeout.
  • test_retry_with_increased_timeout patches _start_project_composer_process to always fail and then checks that each retry passes through the expected max_startup_checks and startup_delay values, accounting for both positional and keyword argument calls.

The combination gives good protection against accidental changes to startup timing semantics.


441-471: Stream-reading avoidance tests look accurate for Windows vs Unix behavior

The tests for _read_stream_non_blocking correctly reflect the implementation:

  • On Windows, it should immediately return "" and never touch the stream (peek()/readline()), which is what test_read_stream_non_blocking_returns_empty_on_windows asserts.
  • On Unix-like systems, patching select.select and verifying that readline() is used and the decoded content is returned matches the documented non-blocking behavior.

This gives good coverage of a subtle, platform-specific code path.

mcp_composer_service: MCPComposerService = cast(
MCPComposerService, get_service(ServiceType.MCP_COMPOSER_SERVICE)
)
mcp_composer_service.clear_last_error(str(project_id))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we also need to clear this if the auth settings changed to None or API Key?

Copy link
Member Author

Choose a reason for hiding this comment

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

the logic already covers all cases where OAuth is disabled.
I don't think it's necessary to change it..


if (port) {
newFields.oauthServerUrl = `http://${host}:${port}`;
newFields.oauthCallbackPath = `http://${host}:${port}/auth/idaas/callback`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't believe this is the correct behavior (though, it is cool).

Callback can be any user-defined string, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

agree! fixed!
now only auto-populates callback path if empty (initial setup convenience), otherwise preserves user's custom value

] = {} # Track active start tasks to cancel them when new request arrives
self._port_to_project: dict[int, str] = {} # Track which project is using which port
self._pid_to_project: dict[int, str] = {} # Track which PID belongs to which project
self._last_errors: dict[str, str] = {} # Track last error message per project for UI display
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm starting to think we should have a fourth auth settings state: Failed, which ensures no mcp server is started for this project, and can store the last error state.

Copy link
Member Author

Choose a reason for hiding this comment

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

hey @jordanrfrazier ,
We've already implemented something similar with the instant error detection improvements in this PR.
Now we can see error feedback almost in runtime.

# Also kill any process that might be using the old port
if existing_port:
try:
await asyncio.wait_for(self._kill_process_on_port(existing_port), timeout=5.0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The kill_process method is worrisome to me - I can imagine a situation where the composer started on a port then user manually stopped MCP Composer. Then, user started up another application (could be anything) on the same port.

Then, Langflow gets here and kills user's process.

Very serious issue, imo, so we should brainstorm if we have any other options here. (We also may have a similar situation with _do_stop_project_composer?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, but this scenario is already protected against. The code only kills processes that Langflow itself started and is actively tracking via self._port_to_project.

For the scenario you described:

  1. User manually stops MCP Composer
  2. User starts another application on the same port
  3. Langflow tries to restart MCP Composer

What actually happens:

  • At line 728-732 in _ensure_port_available(), we detect that the port is in use by an unknown process (not in our tracking)
  • We explicitly refuse to kill it for security reasons
  • We raise MCPComposerPortError asking the user to choose a different port

else:
# Port is in use by unknown process - don't kill it (security concern)
await logger.aerror(
f"Port {port} is in use by an unknown process (not owned by Langflow). "
f"Will not kill external application for security reasons."
)
raise MCPComposerPortError(...)

We only kill processes when:

  1. The port is owned by the same project trying to restart (line 710) - safe because it's our own stuck process
  2. The port is in existing_port (line 1083) - which comes from our internal tracking, meaning we started it

The tracking ensures we never kill external applications. If a process isn't in self._port_to_project or self.project_composers, we treat it as external and refuse to touch it.

@jordanrfrazier
Copy link
Collaborator

Merged #10644 in here

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 18, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 18, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 18, 2025
@github-actions github-actions bot added the enhancement New feature or request label Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 24, 2025
@Cristhianzl Cristhianzl added this pull request to the merge queue Nov 24, 2025
Merged via the queue into main with commit daa9fd5 Nov 24, 2025
81 of 84 checks passed
@Cristhianzl Cristhianzl deleted the cz/migrate-mcp-code branch November 24, 2025 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants