Skip to content

Fix NAT FastAPI front end with Stdio-MCP server fails to initialize#788

Merged
zhongxuanwang-nv merged 5 commits intoNVIDIA:developfrom
zhongxuanwang-nv:fastapi_mcp
Sep 15, 2025
Merged

Fix NAT FastAPI front end with Stdio-MCP server fails to initialize#788
zhongxuanwang-nv merged 5 commits intoNVIDIA:developfrom
zhongxuanwang-nv:fastapi_mcp

Conversation

@zhongxuanwang-nv
Copy link
Member

@zhongxuanwang-nv zhongxuanwang-nv commented Sep 14, 2025

Description

Closes AIQ-1853

In the past, when you do

nat serve --config_file examples/MCP/simple_calculator_mcp/configs/config-mcp-date-stdio.yml

You will get something like

    watcher = events.get_child_watcher()
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/devcontainers/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/python3.12/asyncio/events.py", line 828, in get_child_watcher
    return get_event_loop_policy().get_child_watcher()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/devcontainers/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/python3.12/asyncio/events.py", line 645, in get_child_watcher
    raise NotImplementedError  

This is because when you start server with uvicorn.run("nat.front_ends.fastapi.main:get_app",..., Uvicorn by default uses loop="auto", which prefers uvloop if it’s installed.

However, on macOS, uvloop’s event loop policy doesn’t provide a child watcher, so calls that spawn subprocesses (used by MCP stdio via asyncio) call asyncio.events.get_child_watcher() and raise NotImplementedError.

Therefore, we can fix it by setting loop="asyncio", which forces Uvicorn to use the standard asyncio event loop (which includes child-watcher support) when the system is MacOS. That enables asyncio.create_subprocess_exec to work, so the stdio MCP client can launch its subprocess cleanly.

Follow up, why uvloop’s policy doesn’t provide a child watcher?

uvloop is a high‑performance event loop for Python’s asyncio, implemented on top of libuv (the C library used by Node.js). It’s a drop‑in replacement for the default asyncio loop and typically yields faster networking and scheduling.

uvloop’s loop policy design relies on libuv’s own process handling rather than Python’s legacy child‑watcher abstraction.

When code (e.g., anyio/asyncio paths) calls asyncio.get_event_loop_policy().get_child_watcher() under uvloop, the policy raises NotImplementedError because that API isn’t supported for MacOS.

By Submitting this PR I confirm:

  • I am familiar with the Contributing Guidelines.
  • We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license.
    • Any contribution which contains commits that are not Signed-Off will not be accepted.
  • When the PR is ready for review, new or existing tests cover these changes.
  • When the PR is ready for review, the documentation is up to date with these changes.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server reliability on macOS by selecting a more compatible event loop, resolving intermittent startup and hot-reload issues.
  • Chores

    • Made event loop selection platform-aware so macOS uses the standard loop while other platforms use the higher-performance loop when available.
    • No changes to public APIs.

Signed-off-by: Daniel Wang <daniewang@nvidia.com>
@zhongxuanwang-nv zhongxuanwang-nv requested a review from a team as a code owner September 14, 2025 18:49
@coderabbitai
Copy link

coderabbitai bot commented Sep 14, 2025

Walkthrough

Adds a platform-specific event loop selection in the non-Gunicorn FastAPI startup: imports sys, sets event_loop_policy = "asyncio" when sys.platform == "darwin" or sys.platform.startswith("linux"), otherwise sets it to "auto", and passes it as loop to uvicorn.run alongside existing reload_excludes.

Changes

Cohort / File(s) Summary
FastAPI front-end startup loop policy
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
Add import sys. In run() when not under Gunicorn, choose event_loop_policy = "asyncio" if sys.platform == "darwin" or sys.platform.startswith("linux"), otherwise "auto"; pass loop=event_loop_policy to uvicorn.run. No public signatures changed.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller as Caller
  participant Plugin as FastAPIFrontEndPlugin.run()
  participant Sys as sys.platform
  participant Uvicorn as uvicorn.run()

  Caller->>Plugin: start()
  alt under Gunicorn
    Plugin-->>Caller: defer to Gunicorn flow
  else direct startup
    Plugin->>Sys: read platform
    alt platform == "darwin" or startswith("linux")
      Plugin-->>Plugin: event_loop_policy = "asyncio"
    else
      Plugin-->>Plugin: event_loop_policy = "auto"
    end
    Plugin->>Uvicorn: run(app, loop=event_loop_policy, reload_excludes=...)
    Uvicorn-->>Caller: server running
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Fix NAT FastAPI front end with Stdio-MCP server fails to initialize" is concise, descriptive, written in the imperative mood ("Fix ..."), and accurately summarizes the main change (resolving the FastAPI front end initialization failure with the MCP stdio configuration); it is 67 characters, within the ~72-character guideline.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

@coderabbitai coderabbitai bot added bug Something isn't working non-breaking Non-breaking change labels Sep 14, 2025
Copy link

@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: 0

🧹 Nitpick comments (2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2)

157-160: Fix ruff RUF003: replace curly apostrophes and prefer “macOS”.

Curly quotes in comments trigger RUF003 and “MacOS” casing is inconsistent. Apply this comment-only patch:

-                # By default, Uvicorn uses "auto" event loop policy, which prefers `uvloop` if installed. However,
-                # uvloop’s event loop policy for MacOS with Python 3.12 doesn’t provide a child watcher (which is needed
-                # for MCP server), so setting loop="asyncio" forces Uvicorn to use the standard event loop, which
-                # includes child-watcher support.
+                # By default, Uvicorn uses "auto" event loop policy, which prefers `uvloop` if installed. However,
+                # uvloop's event loop policy on macOS with Python 3.12 does not provide a child watcher (which is needed
+                # for the MCP server), so setting loop="asyncio" forces Uvicorn to use the standard event loop, which
+                # includes child-watcher support.

161-167: Optional: gate the macOS override to Python 3.12+ to minimize behavior change.

If the failure is specific to 3.12 on macOS, this keeps uvloop performance for earlier versions.

-                if sys.platform == "darwin":
-                    # For MacOS
+                if sys.platform == "darwin" and sys.version_info >= (3, 12):
+                    # For macOS + Python 3.12+
                     event_loop_policy = "asyncio"
                 else:
-                    # For non-MacOS platforms
+                    # For non-macOS platforms or earlier Pythons
                     event_loop_policy = "auto"
📜 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 e1ae5fd and 7694547.

📒 Files selected for processing (1)
  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
🪛 Ruff (0.12.2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

158-158: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)


158-158: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)

🔇 Additional comments (2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2)

19-19: Import for platform check is appropriate.

sys is needed for the macOS platform guard; no issues.


174-175: Confirmed — no change required: uvicorn.run accepts loop= with the pinned uvicorn (~=0.32.0).

Manifest declares uvicorn[standard]~=0.32.0 and Uvicorn's programmatic API/Config accept a loop kwarg (same options as the CLI --loop); docs/examples also show loop used in worker CONFIG_KWARGS. (uvicorn.org)

@zhongxuanwang-nv zhongxuanwang-nv changed the title Fix NAT FastAPI front end with MCP Host using Stdio-MCP server fails to initialize Fix NAT FastAPI front end with Stdio-MCP server fails to initialize Sep 14, 2025
Signed-off-by: Daniel Wang <daniewang@nvidia.com>
@zhongxuanwang-nv zhongxuanwang-nv self-assigned this Sep 14, 2025
Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (1)

200-206: Also enforce asyncio loop on macOS in the Gunicorn path.

UvicornWorker will default to uvloop if present; the same failure can reappear on macOS. Set UVICORN_LOOP=asyncio for Darwin before starting Gunicorn.

Apply:

                 options = {
                     "bind": f"{self.front_end_config.host}:{self.front_end_config.port}",
                     "workers": self.front_end_config.workers,
                     "worker_class": "uvicorn.workers.UvicornWorker",
                 }
 
+                if sys.platform == "darwin":
+                    # Ensure UvicornWorker uses asyncio loop on macOS as well.
+                    os.environ.setdefault("UVICORN_LOOP", "asyncio")
+
                 StandaloneApplication(app, options=options).run()
🧹 Nitpick comments (1)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (1)

157-160: Fix RUF003: replace curly apostrophes in comments.

Ruff flags RIGHT SINGLE QUOTATION MARK. Use ASCII '.

Apply:

-                # uvloop’s event loop policy for macOS doesn’t provide a child watcher (which is needed for MCP server),
+                # uvloop's event loop policy for macOS doesn't provide a child watcher (which is needed for the MCP server),
📜 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 7694547 and 6ab5968.

📒 Files selected for processing (1)
  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
🪛 Ruff (0.12.2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

158-158: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)


158-158: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)

🔇 Additional comments (3)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (3)

19-19: Import for platform check — LGTM.

sys is required for the macOS gate below.


161-167: macOS loop override is correct and well-scoped.

Forcing loop="asyncio" on Darwin addresses the child-watcher gap; elsewhere retains auto.


174-175: Confirmed — passing loop into uvicorn.run is supported for the pinned version.

pyproject.toml pins uvicorn[standard]~=0.32.0 (pyproject.toml:47); uvicorn.run forwards kwargs to Config and Config exposes a "loop" option, so passing loop=event_loop_policy is supported. (github.com)

Copy link

@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: 1

♻️ Duplicate comments (1)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (1)

162-168: Extend fix beyond macOS; add safe override.

Ubuntu/Linux can hit the same NotImplementedError with stdio. Provide an env override and default to asyncio on darwin and linux.

Apply this diff:

-                if sys.platform == "darwin":
-                    # For macOS
-                    event_loop_policy = "asyncio"
-                else:
-                    # For non-macOS platforms
-                    event_loop_policy = "auto"
+                # Allow manual override for debugging or platform quirks.
+                loop_override = os.environ.get("UVICORN_LOOP") or os.environ.get("NAT_FORCE_ASYNCIO_LOOP")
+                if loop_override in {"asyncio", "uvloop", "auto"}:
+                    event_loop_policy = loop_override
+                elif sys.platform in {"darwin", "linux"}:
+                    # macOS and some Linux environments need asyncio for subprocess/stdio support.
+                    event_loop_policy = "asyncio"
+                else:
+                    event_loop_policy = "auto"

Please re-test:

  • macOS + stdio MCP: expect startup OK.
  • Ubuntu + stdio MCP: expect startup OK (no NotImplementedError).
  • Non-stdio configs: verify default performance remains acceptable.
🧹 Nitpick comments (2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2)

158-161: Fix RUF003: replace curly quotes in comments.

Ruff flags RIGHT SINGLE QUOTATION MARK (’). Use ASCII '.

Apply this diff:

-                # By default, Uvicorn uses "auto" event loop policy, which prefers `uvloop` if installed. However,
-                # uvloop’s event loop policy for macOS doesn’t provide a child watcher (which is needed for MCP server),
-                # so setting loop="asyncio" forces Uvicorn to use the standard event loop, which includes child-watcher
-                # support.
+                # By default, Uvicorn uses "auto" event loop policy, which prefers `uvloop` if installed. However,
+                # uvloop's event loop policy for macOS doesn't provide a child watcher (needed by MCP stdio),
+                # so setting loop="asyncio" forces Uvicorn to use the standard event loop, which includes child-watcher
+                # support.

162-168: Name nit: avoid “policy” confusion.

This is a loop mode, not an asyncio event loop policy.

Apply this diff:

-                event_loop_policy = ...
+                loop_mode = ...
@@
-                            reload_excludes=reload_excludes,
-                            loop=event_loop_policy)
+                            reload_excludes=reload_excludes,
+                            loop=loop_mode)

Also applies to: 175-176

📜 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 6ab5968 and 3a11daf.

📒 Files selected for processing (1)
  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
🪛 Ruff (0.12.2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

159-159: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)


159-159: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)

🔇 Additional comments (1)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (1)

19-19: Import looks good.

sys is needed for platform detection.

Signed-off-by: Daniel Wang <daniewang@nvidia.com>
Copy link

@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: 0

♻️ Duplicate comments (1)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (1)

201-205: Gunicorn path still defaults to uvloop — stdio will still fail on macOS/Linux.

When use_gunicorn=True, UvicornWorker will ignore the non‑Gunicorn loop selection and default to uvloop. Subclass the worker and inject the same loop.

Apply this diff:

-                    "worker_class": "uvicorn.workers.UvicornWorker",
+                    "worker_class": "nat.front_ends.fastapi.fastapi_front_end_plugin:UvicornWorkerWithLoop",

Add the subclass (outside this block):

from uvicorn.workers import UvicornWorker

class UvicornWorkerWithLoop(UvicornWorker):
    CONFIG_KWARGS = {"loop": _select_uvicorn_loop()}

Verify with stdio + use_gunicorn: true on macOS/Linux:

#!/bin/bash
# Find gunicorn usage and worker class settings
rg -nP 'use_gunicorn\s*:\s*true' -g '**/*.{yml,yaml}' || true
rg -n 'worker_class.*UvicornWorker' || true
🧹 Nitpick comments (6)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (6)

158-176: Unify loop selection; pass it via helper; fix comments; use it in uvicorn.run.

  • Current comments say “For macOS” but the condition includes Linux.
  • Prefer a single _select_uvicorn_loop() to centralize the rule and allow an env override.

Apply this diff within the shown range:

-                # By default, Uvicorn uses "auto" event loop policy, which prefers `uvloop` if installed. However,
-                # uvloop’s event loop policy for macOS doesn’t provide a child watcher (which is needed for MCP server),
-                # so setting loop="asyncio" forces Uvicorn to use the standard event loop, which includes child-watcher
-                # support.
-                if sys.platform == "darwin" or sys.platform.startswith("linux"):
-                    # For macOS
-                    event_loop_policy = "asyncio"
-                else:
-                    # For non-macOS platforms
-                    event_loop_policy = "auto"
+                # Select Uvicorn loop: force "asyncio" on macOS/Linux for MCP stdio (child watcher),
+                # otherwise fall back to "auto". Allow override via environment.
+                loop_mode = _select_uvicorn_loop()
@@
-                            reload_excludes=reload_excludes,
-                            loop=event_loop_policy)
+                            reload_excludes=reload_excludes,
+                            loop=loop_mode)

Add the following helper (outside this block, e.g., near other module-level defs):

def _select_uvicorn_loop() -> str:
    override = os.environ.get("UVICORN_LOOP") or os.environ.get("NAT_FORCE_UVICORN_LOOP")
    if override in {"asyncio", "uvloop", "auto"}:
        return override
    if sys.platform == "darwin" or sys.platform.startswith("linux"):
        return "asyncio"
    return "auto"

159-161: Fix RUF003: ambiguous right single quote in comments.

Replace curly ’ with ASCII ' to satisfy ruff.

Minimal change (if not adopting the larger refactor above):

-                # uvloop’s event loop policy for macOS doesn’t provide a child watcher (which is needed for MCP server),
+                # uvloop's event loop policy for macOS doesn't provide a child watcher (which is needed for MCP server),

93-94: Add return type and a brief docstring to run (public API).

Meets repo policy (type hints + Google-style docstrings).

-    async def run(self):
+    async def run(self) -> None:
+        """Start the NAT FastAPI front end.
+
+        On macOS/Linux, forces the asyncio loop for MCP stdio (child watcher support),
+        then starts Uvicorn or Gunicorn based on configuration and wires Dask cleanup.
+        """

37-41: Docstrings and return annotations for public methods.

Add -> None and concise docstrings to satisfy coding guidelines and avoid pyright noise.

 class FastApiFrontEndPlugin(DaskClientMixin, FrontEndBase[FastApiFrontEndConfig]):
+    """FastAPI front end plugin for NAT."""

-    def __init__(self, full_config: "Config"):
+    def __init__(self, full_config: "Config") -> None:
+        """Initialize the front end with the resolved `Config`."""
         super().__init__(full_config)

-    async def _periodic_cleanup(scheduler_address: str,
+    async def _periodic_cleanup(scheduler_address: str,
                                 db_url: str,
                                 sleep_time_sec: int = 300,
                                 log_level: int = logging.INFO):
+        """Periodically remove expired jobs from the job store."""
@@
-    async def _submit_cleanup_task(self, scheduler_address: str, db_url: str):
+    async def _submit_cleanup_task(self, scheduler_address: str, db_url: str) -> None:
         """Submit a cleanup task to the cluster to remove the job after expiry."""

Also applies to: 62-68, 82-92


133-138: Fix typo in comment.

-                # Set environment variabls such that the worker subprocesses will know how to connect to dask and to
-                # the database
+                # Set environment variables so worker subprocesses know how to connect to Dask and the database

77-80: Avoid bare except:; catch Exception explicitly.

Keeps behavior, removes E722 suppression.

-            except:  # noqa: E722
-                logger.exception("Error during job cleanup")
+            except Exception:
+                logger.exception("Error during job cleanup")
📜 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 3a11daf and d5ea7cc.

📒 Files selected for processing (1)
  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/front_ends/fastapi/fastapi_front_end_plugin.py
🪛 Ruff (0.12.2)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py

159-159: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)


159-159: Comment contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF003)

🔇 Additional comments (1)
src/nat/front_ends/fastapi/fastapi_front_end_plugin.py (1)

19-19: LGTM: needed import.

@zhongxuanwang-nv zhongxuanwang-nv merged commit dc29e1c into NVIDIA:develop Sep 15, 2025
17 checks passed
zhongxuanwang-nv added a commit that referenced this pull request Sep 16, 2025
## Description
<!-- Note: The pull request title will be included in the CHANGELOG. -->
<!-- Provide a standalone description of changes in this PR. -->
<!-- Reference any issues closed by this PR with "closes #1234". All PRs
should have an issue they close-->

This is a follow-up PR of
[PR-788](#788). This PR
will try to cleanly remove the `use_uvloop` field from the general
section of the config files and deprecate that field to not create any
breaking changes.

## By Submitting this PR I confirm:
- I am familiar with the [Contributing
Guidelines](https://github.com/NVIDIA/NeMo-Agent-Toolkit/blob/develop/docs/source/resources/contributing.md).
- We require that all contributors "sign-off" on their commits. This
certifies that the contribution is your original work, or you have
rights to submit it under the same license, or a compatible license.
- Any contribution which contains commits that are not Signed-Off will
not be accepted.
- When the PR is ready for review, new or existing tests cover these
changes.
- When the PR is ready for review, the documentation is up to date with
these changes.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- Refactor
- Deprecated the manual use_uvloop setting; event-loop selection is now
auto-determined per platform.
- Documentation
- Updated docs and examples to remove explicit use_uvloop examples and
add deprecation guidance.
- Chores
- Removed use_uvloop entries from sample configs and templates across
examples and guides.
- Tests
  - Cleansed test config data to remove explicit use_uvloop references.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Daniel Wang <daniewang@nvidia.com>
Signed-off-by: Zhongxuan (Daniel) Wang <zxwang2004@gmail.com>
Co-authored-by: Will Killian <2007799+willkill07@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants