nvidia-nat-mcp 1.4.0a20260107__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nat/meta/pypi.md +32 -0
- nat/plugins/mcp/__init__.py +14 -0
- nat/plugins/mcp/auth/__init__.py +14 -0
- nat/plugins/mcp/auth/auth_flow_handler.py +208 -0
- nat/plugins/mcp/auth/auth_provider.py +431 -0
- nat/plugins/mcp/auth/auth_provider_config.py +86 -0
- nat/plugins/mcp/auth/register.py +33 -0
- nat/plugins/mcp/auth/service_account/__init__.py +14 -0
- nat/plugins/mcp/auth/service_account/provider.py +136 -0
- nat/plugins/mcp/auth/service_account/provider_config.py +137 -0
- nat/plugins/mcp/auth/service_account/token_client.py +156 -0
- nat/plugins/mcp/auth/token_storage.py +265 -0
- nat/plugins/mcp/cli/__init__.py +15 -0
- nat/plugins/mcp/cli/commands.py +1051 -0
- nat/plugins/mcp/client/__init__.py +15 -0
- nat/plugins/mcp/client/client_base.py +665 -0
- nat/plugins/mcp/client/client_config.py +146 -0
- nat/plugins/mcp/client/client_impl.py +782 -0
- nat/plugins/mcp/exception_handler.py +211 -0
- nat/plugins/mcp/exceptions.py +142 -0
- nat/plugins/mcp/register.py +23 -0
- nat/plugins/mcp/server/__init__.py +15 -0
- nat/plugins/mcp/server/front_end_config.py +109 -0
- nat/plugins/mcp/server/front_end_plugin.py +155 -0
- nat/plugins/mcp/server/front_end_plugin_worker.py +411 -0
- nat/plugins/mcp/server/introspection_token_verifier.py +72 -0
- nat/plugins/mcp/server/memory_profiler.py +320 -0
- nat/plugins/mcp/server/register_frontend.py +27 -0
- nat/plugins/mcp/server/tool_converter.py +286 -0
- nat/plugins/mcp/utils.py +228 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/METADATA +55 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/RECORD +37 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/WHEEL +5 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/entry_points.txt +9 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/licenses/LICENSE-3rd-party.txt +5478 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/licenses/LICENSE.md +201 -0
- nvidia_nat_mcp-1.4.0a20260107.dist-info/top_level.txt +1 -0
nat/meta/pypi.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# NVIDIA NeMo Agent Toolkit MCP Subpackage
|
|
22
|
+
Subpackage for MCP integration in NeMo Agent toolkit.
|
|
23
|
+
|
|
24
|
+
This package provides MCP (Model Context Protocol) functionality, allowing NeMo Agent toolkit workflows to connect to external MCP servers and use their tools as functions.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Connect to MCP servers via streamable-http, SSE, or stdio transports
|
|
29
|
+
- Wrap individual MCP tools as NeMo Agent toolkit functions
|
|
30
|
+
- Connect to MCP servers and dynamically discover available tools
|
|
31
|
+
|
|
32
|
+
For more information about the NVIDIA NeMo Agent toolkit, please visit the [NeMo Agent toolkit GitHub Repo](https://github.com/NVIDIA/NeMo-Agent-Toolkit).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import logging
|
|
18
|
+
import secrets
|
|
19
|
+
import webbrowser
|
|
20
|
+
|
|
21
|
+
import pkce
|
|
22
|
+
from authlib.integrations.httpx_client import AsyncOAuth2Client
|
|
23
|
+
from fastapi import FastAPI
|
|
24
|
+
|
|
25
|
+
from nat.authentication.oauth2.oauth2_auth_code_flow_provider_config import OAuth2AuthCodeFlowProviderConfig
|
|
26
|
+
from nat.data_models.authentication import AuthenticatedContext
|
|
27
|
+
from nat.data_models.authentication import AuthFlowType
|
|
28
|
+
from nat.data_models.authentication import AuthProviderBaseConfig
|
|
29
|
+
from nat.front_ends.console.authentication_flow_handler import ConsoleAuthenticationFlowHandler
|
|
30
|
+
from nat.front_ends.console.authentication_flow_handler import _FlowState
|
|
31
|
+
from nat.front_ends.fastapi.fastapi_front_end_controller import _FastApiFrontEndController
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MCPAuthenticationFlowHandler(ConsoleAuthenticationFlowHandler):
|
|
37
|
+
"""
|
|
38
|
+
Authentication helper for MCP environments.
|
|
39
|
+
|
|
40
|
+
This handler is specifically designed for MCP tool discovery scenarios where
|
|
41
|
+
authentication needs to happen before the default auth_callback is available
|
|
42
|
+
in the Context. It handles OAuth2 authorization code flow during MCP client
|
|
43
|
+
startup and tool discovery phases.
|
|
44
|
+
|
|
45
|
+
Key differences from console handler:
|
|
46
|
+
- Only supports OAuth2 Authorization Code flow (no HTTP Basic)
|
|
47
|
+
- Optimized for MCP tool discovery workflows
|
|
48
|
+
- Designed for single-use authentication during startup
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
super().__init__()
|
|
53
|
+
self._server_controller: _FastApiFrontEndController | None = None
|
|
54
|
+
self._redirect_app: FastAPI | None = None
|
|
55
|
+
self._server_lock = asyncio.Lock()
|
|
56
|
+
self._oauth_client: AsyncOAuth2Client | None = None
|
|
57
|
+
self._redirect_host: str = "localhost" # Default host, will be overridden from config
|
|
58
|
+
self._redirect_port: int = 8000 # Default port, will be overridden from config
|
|
59
|
+
self._server_task: asyncio.Task | None = None
|
|
60
|
+
|
|
61
|
+
async def authenticate(self, config: AuthProviderBaseConfig, method: AuthFlowType) -> AuthenticatedContext:
|
|
62
|
+
"""
|
|
63
|
+
Handle the OAuth2 authorization code flow for MCP environments.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
config: OAuth2 configuration for MCP server
|
|
67
|
+
method: Authentication method (only OAUTH2_AUTHORIZATION_CODE supported)
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
AuthenticatedContext with Bearer token for MCP server access
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If config is invalid for MCP use case
|
|
74
|
+
NotImplementedError: If method is not OAuth2 Authorization Code
|
|
75
|
+
"""
|
|
76
|
+
logger.info("Starting MCP authentication flow")
|
|
77
|
+
|
|
78
|
+
if method == AuthFlowType.OAUTH2_AUTHORIZATION_CODE:
|
|
79
|
+
if not isinstance(config, OAuth2AuthCodeFlowProviderConfig):
|
|
80
|
+
raise ValueError("Requested OAuth2 Authorization Code Flow but passed invalid config")
|
|
81
|
+
|
|
82
|
+
# MCP-specific validation
|
|
83
|
+
if not config.redirect_uri:
|
|
84
|
+
raise ValueError("MCP authentication requires redirect_uri to be configured")
|
|
85
|
+
|
|
86
|
+
logger.info("MCP authentication configured for server: %s", getattr(config, 'server_url', 'unknown'))
|
|
87
|
+
return await self._handle_oauth2_auth_code_flow(config)
|
|
88
|
+
|
|
89
|
+
raise NotImplementedError(f'Auth method "{method}" not supported for MCP environments')
|
|
90
|
+
|
|
91
|
+
async def _handle_oauth2_auth_code_flow(self, cfg: OAuth2AuthCodeFlowProviderConfig) -> AuthenticatedContext:
|
|
92
|
+
logger.info("Starting MCP OAuth2 authorization code flow")
|
|
93
|
+
|
|
94
|
+
# Extract and validate host and port from redirect_uri for callback server
|
|
95
|
+
from urllib.parse import urlparse
|
|
96
|
+
parsed_uri = urlparse(str(cfg.redirect_uri))
|
|
97
|
+
|
|
98
|
+
# Validate scheme/host and choose a safe non-privileged bind port
|
|
99
|
+
scheme = (parsed_uri.scheme or "http").lower()
|
|
100
|
+
if scheme not in ("http", "https"):
|
|
101
|
+
raise ValueError(f"redirect_uri must use http or https scheme, got '{scheme}'")
|
|
102
|
+
|
|
103
|
+
host = parsed_uri.hostname
|
|
104
|
+
if not host:
|
|
105
|
+
raise ValueError("redirect_uri must include a hostname, for example http://localhost:8000/auth/redirect")
|
|
106
|
+
|
|
107
|
+
# Never auto-bind to 80/443; default to 8000 when port is not specified
|
|
108
|
+
port = parsed_uri.port or 8000
|
|
109
|
+
if not (1 <= port <= 65535):
|
|
110
|
+
raise ValueError(f"Invalid redirect port: {port}. Expected 1-65535.")
|
|
111
|
+
|
|
112
|
+
if scheme == "https" and parsed_uri.port is None:
|
|
113
|
+
logger.warning(
|
|
114
|
+
"redirect_uri uses https without an explicit port; binding to %d (plain HTTP). "
|
|
115
|
+
"Terminate TLS at a reverse proxy and forward to this port.",
|
|
116
|
+
port)
|
|
117
|
+
|
|
118
|
+
self._redirect_host = host
|
|
119
|
+
self._redirect_port = port
|
|
120
|
+
logger.info("MCP redirect server will use %s:%d", self._redirect_host, self._redirect_port)
|
|
121
|
+
|
|
122
|
+
state = secrets.token_urlsafe(16)
|
|
123
|
+
flow_state = _FlowState()
|
|
124
|
+
client = self.construct_oauth_client(cfg)
|
|
125
|
+
|
|
126
|
+
flow_state.token_url = cfg.token_url
|
|
127
|
+
flow_state.use_pkce = cfg.use_pkce
|
|
128
|
+
|
|
129
|
+
# PKCE bits
|
|
130
|
+
if cfg.use_pkce:
|
|
131
|
+
verifier, challenge = pkce.generate_pkce_pair()
|
|
132
|
+
flow_state.verifier = verifier
|
|
133
|
+
flow_state.challenge = challenge
|
|
134
|
+
logger.debug("PKCE enabled for MCP authentication")
|
|
135
|
+
|
|
136
|
+
auth_url, _ = client.create_authorization_url(
|
|
137
|
+
cfg.authorization_url,
|
|
138
|
+
state=state,
|
|
139
|
+
code_verifier=flow_state.verifier if cfg.use_pkce else None,
|
|
140
|
+
code_challenge=flow_state.challenge if cfg.use_pkce else None,
|
|
141
|
+
**(cfg.authorization_kwargs or {})
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
async with self._server_lock:
|
|
145
|
+
if self._redirect_app is None:
|
|
146
|
+
self._redirect_app = await self._build_redirect_app()
|
|
147
|
+
|
|
148
|
+
await self._start_redirect_server()
|
|
149
|
+
self._flows[state] = flow_state
|
|
150
|
+
|
|
151
|
+
logger.info("MCP authentication: Your browser has been opened for authentication.")
|
|
152
|
+
logger.info("This will authenticate you with the MCP server for tool discovery.")
|
|
153
|
+
webbrowser.open(auth_url)
|
|
154
|
+
|
|
155
|
+
# Use default timeout for MCP tool discovery
|
|
156
|
+
timeout = 300
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
token = await asyncio.wait_for(flow_state.future, timeout=timeout)
|
|
160
|
+
logger.info("MCP authentication successful, token obtained")
|
|
161
|
+
except TimeoutError as exc:
|
|
162
|
+
logger.error("MCP authentication timed out")
|
|
163
|
+
raise RuntimeError(f"MCP authentication timed out ({timeout} seconds). Please try again.") from exc
|
|
164
|
+
finally:
|
|
165
|
+
async with self._server_lock:
|
|
166
|
+
self._flows.pop(state, None)
|
|
167
|
+
await self._stop_redirect_server()
|
|
168
|
+
|
|
169
|
+
return AuthenticatedContext(
|
|
170
|
+
headers={"Authorization": f"Bearer {token['access_token']}"},
|
|
171
|
+
metadata={
|
|
172
|
+
"expires_at": token.get("expires_at"),
|
|
173
|
+
"raw_token": token,
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
async def _start_redirect_server(self) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Override to use the host and port from redirect_uri config instead of hardcoded localhost:8000.
|
|
180
|
+
|
|
181
|
+
This allows MCP authentication to work with custom redirect hosts and ports
|
|
182
|
+
specified in the configuration.
|
|
183
|
+
"""
|
|
184
|
+
# If the server is already running, do nothing
|
|
185
|
+
if self._server_controller:
|
|
186
|
+
return
|
|
187
|
+
try:
|
|
188
|
+
if not self._redirect_app:
|
|
189
|
+
raise RuntimeError("Redirect app not built.")
|
|
190
|
+
|
|
191
|
+
self._server_controller = _FastApiFrontEndController(self._redirect_app)
|
|
192
|
+
|
|
193
|
+
self._server_task = asyncio.create_task(
|
|
194
|
+
self._server_controller.start_server(host=self._redirect_host, port=self._redirect_port))
|
|
195
|
+
logger.debug("MCP redirect server starting on %s:%d", self._redirect_host, self._redirect_port)
|
|
196
|
+
|
|
197
|
+
# Wait for the server to bind (max ~10s)
|
|
198
|
+
start = asyncio.get_running_loop().time()
|
|
199
|
+
while True:
|
|
200
|
+
server = getattr(self._server_controller, "_server", None)
|
|
201
|
+
if server and getattr(server, "started", False):
|
|
202
|
+
break
|
|
203
|
+
if asyncio.get_running_loop().time() - start > 10:
|
|
204
|
+
raise RuntimeError("Redirect server did not report ready within 10s")
|
|
205
|
+
await asyncio.sleep(0.1)
|
|
206
|
+
except Exception as exc:
|
|
207
|
+
raise RuntimeError(
|
|
208
|
+
f"Failed to start MCP redirect server on {self._redirect_host}:{self._redirect_port}: {exc}") from exc
|