Skip to content

Conversation

@AnuradhaKaruppiah
Copy link
Contributor

@AnuradhaKaruppiah AnuradhaKaruppiah commented Aug 26, 2025

Description

Closes #304 #314

This PR enhances MCP (Model Context Protocol) support by refactoring the client architecture and adding support for multiple transport types including stdio and streamable-http. SSE transport mechanism has been maintained for backwards compat but the default transport mechanism has been changed to streamable-http making this a BREAKING change.

MCP Server Changes:

  • Add support for transport config to the MCP frontend. This can be sse or streamable-http (default).

MCP Client Changes:

  • Refactored MCP client architecture with a base class and specialized implementations for different transports
  • Added support for streamable-http as the new default transport with SSE maintained for backwards compatibility
  • [Experimental] Introduced stdio transport for local process communication. This is an experimental change and the API is expected to change before the next release.
  • [Experimental] Add new configuration format, mcp_client to allow onboarding all tools dynamically from a MCP server (see Cursor Style MCP server config below). The earlier format mcp_tool_wrapper is still available and can be used as before for sse and streamable-http. However mcp_tool_wrapper is NOT recommended for stdio if there are multiple tools available from the same server.

[Experimental] Cursor Style MCP server config:

  • This PR adds support for configuring MCP servers (similar to cursor or VSC).
  • To do that a new mcp_client function has been added. This function connects to the server, discovers tools and adds them as dynamic functions (mcp_single_tool) to the NAT workflow builder.
  • This function has been marked experimental as it will be replaced by function groups before the next release.

Changes to examples:

  • All MCP server references have been migrated from the sse url to the mcp/streamable-http URL
  • New config file to demonstrate stdio usage - examples/MCP/simple_calculator_mcp/configs/config-mcp-date-stdio.yml

Dependency Changes

  • mcp=2.13 was needed to address streamable-http cleanup exceptions. This version of mcp had a dependency on pydantic=2.11
  • Updating pydantic from 2.10 to 2.11 in turn required moving arize-phoenix forward (by more than 8 months!) from 6.11 to 11.28
  • Semantic-kernel version has also been bumped to accomdate the new pydantic version

Additional Changes
These changes are not directly to MCP but were needed for handling new syntax:

  1. Literal support in the frontend plugin

Steps for testing streamable-http:

  1. Start MCP server with default transport:
nat mcp --config_file examples/getting_started/simple_calculator/configs/config.yml
  1. Run MCP client workflow:
nat run --config_file examples/MCP/simple_calculator_mcp/configs/config-mcp-math.yml --input "Is the product of 2 * 4 greater than the current hour of the day?"

Steps for testing stdio:

  1. Install mcp-server-time in your venv (stdio):
 uv pip install mcp-server-time
  1. Start MCP server for math tools (streamable-http):
nat mcp --config_file examples/getting_started/simple_calculator/configs/config.yml
  1. Run NAT workflow with the MCP date (stdio) and MCP math (streamable-http) services:
nat run --config_file examples/MCP/simple_calculator_mcp/configs/config-mcp-date-stdio.yml --input "Is the product of 2 * 4 greater than the current hour of the day?" 

Pending items:
stdio with fastapi is not working at the moment. To repro use steps 1&2 from above but instead of doing an nat run do a nat serve and curl the input:

  1. Serve:
nat serve --config_file examples/MCP/simple_calculator_mcp/configs/config-mcp-date-stdio.yml
  1. This will result in the following call stack:
    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

Fastapi->mcp_client->mcp_server(stdio) is showing that the child_catcher is disabled. We need to undertand why that is happening.

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

  • New Features

    • Multi-transport MCP support (streamable-http default, plus SSE and stdio), tool aliasing/overrides, and runtime client registration.
    • CLI: --transport, --command/--args/--env for list/ping with improved health/status output.
  • Documentation

    • Updated MCP client/server docs and examples to favor /mcp, add streamable-http and stdio usage, and expand the Simple Calculator README.
  • Chores

    • Dependency bumps: mcp, pydantic, arize-phoenix, semantic-kernel.
  • Tests

    • Added end-to-end and functional multi-transport MCP client test suites.

AnuradhaKaruppiah and others added 30 commits May 27, 2025 21:18
Sample Usage:
aiq info mcp --client-type stdio --command python --args "-m mcp_server_time --local-timezone=America/Los_Angeles"

Sample Output:
get_current_time
convert_time

Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Start of dynamic_functions

Signed-off-by: Anuradha Karuppiah <[email protected]>
This will need some iterating

Signed-off-by: Anuradha Karuppiah <[email protected]>
If no default is defined in the schema, and the field isn’t in
the "required" list, it should default to None (i.e., optional).

Earlier logic marked the field as required (using ...) unless
"default" is explicitly set i.e. it would mark optional fields
with no default as required.

Signed-off-by: Anuradha Karuppiah <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Anuradha Karuppiah <[email protected]>
@AnuradhaKaruppiah
Copy link
Contributor Author

@coderabbitai pause

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 (5)
src/nat/tool/mcp/mcp_client_base.py (5)

38-38: Confirm Python version compatibility for override import.

The import statement for override directly from typing is compatible with Python 3.12+, but the project supports Python 3.11+. Ensure that the fallback to typing_extensions is in place if needed.

#!/bin/bash
# Check if there's a compatibility layer for the override import
rg -n "from typing import override" --type=py

# Check if typing_extensions is a dependency
cat pyproject.toml | grep -i "typing.extensions"

# Check for any existing compatibility handling for override
rg -n "typing_extensions.*override" --type=py

119-121: Update docstring to include all supported transport types.

The docstring mentions 'sse', 'stdio', and 'streamable-http', which is accurate. However, to ensure consistency, verify that this list matches all supported transports mentioned elsewhere in the code.


68-71: Enum member names may fail with special characters or leading digits.

Raw enum values containing spaces, hyphens, or leading digits will cause ValueError when creating the Enum. Consider sanitizing these values.


203-204: Docstring incorrectly states it raises ValueError instead of MCPToolNotFoundError.

The docstring should correctly document the exception type that's actually raised.

Apply this diff to fix the docstring:

        Raises:
-            MCPToolNotFoundError: If no tool is available with that name.
+            MCPToolNotFoundError: If no tool is available with that name.

340-344: Update MCPToolClient docstring to match current parameters.

The docstring still references connect_fn which has been replaced with session. Update the documentation to reflect the current implementation.

Apply this diff to fix the docstring:

     Args:
-        connect_fn (callable): Function that returns an async context manager for connecting to the server
+        session (ClientSession): The active ClientSession to use for tool calls
         tool_name (str): The name of the tool to wrap
         tool_description (str): The description of the tool provided by the MCP server.
         tool_input_schema (dict): The input schema for the tool.
🧹 Nitpick comments (1)
src/nat/tool/mcp/mcp_client_base.py (1)

172-223: Consider adding validation for session state in call_tool.

While get_tool and call_tool both check for session initialization, only get_tool checks for _exit_stack. Consider consistent validation across both methods for robustness.

Apply this diff for consistency:

     @mcp_exception_handler
     async def call_tool(self, tool_name: str, tool_args: dict | None):
+        if not self._exit_stack:
+            raise RuntimeError("MCPBaseClient not initialized. Use async with to initialize.")
+
         if not self._session:
             raise RuntimeError("MCPBaseClient not initialized. Use async with to initialize.")
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1c48be1 and 51a6f6b.

📒 Files selected for processing (1)
  • src/nat/tool/mcp/mcp_client_base.py (5 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
{src/**/*.py,packages/*/src/**/*.py,examples/*/**/*.py}

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

Use abbreviations: 'nat' for API namespace and CLI tool, 'nvidia-nat' for package name, 'NAT' only for env var prefixes and informal code comments (never in documentation)

Files:

  • src/nat/tool/mcp/mcp_client_base.py
src/**/*.py

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

All importable Python code must live under src/ so namespace packages resolve correctly

Files:

  • src/nat/tool/mcp/mcp_client_base.py
**/*.py

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

**/*.py: Format Python code with yapf (PEP8 base, column_limit=120) run second in the toolchain
Run ruff (ruff check --fix) using configuration in pyproject.toml; fix warnings unless explicitly ignored; ruff is a linter only (not for formatting)
Respect naming: snake_case for functions and variables, PascalCase for classes, UPPER_CASE for constants
Prefer typing/collections.abc abstractions (e.g., Sequence over list)
Use typing.Annotated for units or extra metadata when useful
Treat pyright warnings (configured in pyproject.toml) as errors during development
Prefer httpx with SSL verification enabled by default for HTTP requests; follow OWASP Top-10
Use async/await for I/O-bound work (HTTP, DB, file reads)
Cache expensive computations with functools.lru_cache or an external cache when appropriate
Leverage NumPy vectorized operations when beneficial

Files:

  • src/nat/tool/mcp/mcp_client_base.py
**/*.{py,md,sh,yml,yaml,toml,json,ini,cfg,txt,rst,pyi}

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

Indent with 4 spaces (no tabs) and ensure every file ends with a single trailing newline

Files:

  • src/nat/tool/mcp/mcp_client_base.py
{src/**/*.py,packages/*/src/**/*.py}

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

{src/**/*.py,packages/*/src/**/*.py}: All public APIs require Python 3.11+ type hints on parameters and return values
Provide Google-style docstrings for every public module, class, function and CLI command
Docstring first line must be a concise description ending with a period

Files:

  • src/nat/tool/mcp/mcp_client_base.py
**/*.{py,sh,md,yml,yaml,toml,json,ini,txt,cfg,rst,pyi}

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

**/*.{py,sh,md,yml,yaml,toml,json,ini,txt,cfg,rst,pyi}: Every file must start with the standard SPDX Apache-2.0 header
All source files must include the SPDX Apache-2.0 header template

Files:

  • src/nat/tool/mcp/mcp_client_base.py
**/*.{py,md}

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

Version numbers are derived by setuptools-scm; never hard-code versions in code or documentation

Files:

  • src/nat/tool/mcp/mcp_client_base.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

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 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/tool/mcp/mcp_client_base.py
src/nat/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • src/nat/tool/mcp/mcp_client_base.py
🔇 Additional comments (5)
src/nat/tool/mcp/mcp_client_base.py (5)

117-163: LGTM! Well-designed base class with proper async lifecycle management.

The MCPBaseClient base class provides a clean abstraction for different transport types with proper async context manager support, session management, and exit stack handling. The implementation follows best practices for async resource management.


225-255: LGTM! SSE client implementation maintains backward compatibility.

The MCPSSEClient properly extends the base class and maintains the existing SSE transport functionality. Good use of the @override decorator and consistent server_name property implementation.


257-301: LGTM! Stdio client implementation addresses security concerns.

The MCPStdioClient implementation properly addresses the security concern about exposing sensitive environment variables in the server_name property by only including the command. The implementation correctly uses StdioServerParameters for subprocess management.


303-333: LGTM! Streamable-HTTP client implementation is clean and consistent.

The MCPStreamableHTTPClient follows the same pattern as other transport implementations and properly integrates with the MCP library's streamable-http transport.


383-405: LGTM! Proper error handling in MCPToolClient.acall method.

The method properly aggregates text content from results and raises RuntimeError when result.isError is true, providing clear error feedback.

@coderabbitai
Copy link

coderabbitai bot commented Aug 28, 2025

✅ Actions performed

Reviews paused.

Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
AnuradhaKaruppiah and others added 4 commits August 29, 2025 08:14
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Anuradha Karuppiah <[email protected]>
Signed-off-by: Will Killian <[email protected]>
@willkill07 willkill07 removed the external This issue was filed by someone outside of the NeMo Agent toolkit team label Aug 29, 2025
Copy link
Contributor

@mpenn mpenn left a comment

Choose a reason for hiding this comment

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

Overall, this looks good, just a few comments to address.

Signed-off-by: Anuradha Karuppiah <[email protected]>
@AnuradhaKaruppiah
Copy link
Contributor Author

/merge

1 similar comment
@AnuradhaKaruppiah
Copy link
Contributor Author

/merge

@rapids-bot rapids-bot bot merged commit 6e28f7b into NVIDIA:develop Aug 29, 2025
15 checks passed
@AnuradhaKaruppiah AnuradhaKaruppiah deleted the ak-mcp-tool-stdio-2 branch September 18, 2025 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaking change feature request New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA]: Add support for stdio MCP Servers

4 participants