nvidia-nat-mcp 1.4.0a20251014__py3-none-any.whl → 1.5.0a20260115__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.
Files changed (40) hide show
  1. nat/meta/pypi.md +1 -1
  2. nat/plugins/mcp/__init__.py +1 -1
  3. nat/plugins/mcp/auth/__init__.py +1 -1
  4. nat/plugins/mcp/auth/auth_flow_handler.py +65 -1
  5. nat/plugins/mcp/auth/auth_provider.py +3 -2
  6. nat/plugins/mcp/auth/auth_provider_config.py +5 -2
  7. nat/plugins/mcp/auth/register.py +9 -1
  8. nat/plugins/mcp/auth/service_account/__init__.py +14 -0
  9. nat/plugins/mcp/auth/service_account/provider.py +136 -0
  10. nat/plugins/mcp/auth/service_account/provider_config.py +137 -0
  11. nat/plugins/mcp/auth/service_account/token_client.py +156 -0
  12. nat/plugins/mcp/auth/token_storage.py +2 -2
  13. nat/plugins/mcp/cli/__init__.py +15 -0
  14. nat/plugins/mcp/cli/commands.py +1094 -0
  15. nat/plugins/mcp/client/__init__.py +15 -0
  16. nat/plugins/mcp/{client_base.py → client/client_base.py} +18 -10
  17. nat/plugins/mcp/{client_config.py → client/client_config.py} +24 -9
  18. nat/plugins/mcp/{client_impl.py → client/client_impl.py} +253 -62
  19. nat/plugins/mcp/exception_handler.py +1 -1
  20. nat/plugins/mcp/exceptions.py +1 -1
  21. nat/plugins/mcp/register.py +5 -4
  22. nat/plugins/mcp/server/__init__.py +15 -0
  23. nat/plugins/mcp/server/front_end_config.py +109 -0
  24. nat/plugins/mcp/server/front_end_plugin.py +155 -0
  25. nat/plugins/mcp/server/front_end_plugin_worker.py +415 -0
  26. nat/plugins/mcp/server/introspection_token_verifier.py +72 -0
  27. nat/plugins/mcp/server/memory_profiler.py +320 -0
  28. nat/plugins/mcp/server/register_frontend.py +27 -0
  29. nat/plugins/mcp/server/tool_converter.py +290 -0
  30. nat/plugins/mcp/utils.py +153 -36
  31. {nvidia_nat_mcp-1.4.0a20251014.dist-info → nvidia_nat_mcp-1.5.0a20260115.dist-info}/METADATA +5 -5
  32. nvidia_nat_mcp-1.5.0a20260115.dist-info/RECORD +37 -0
  33. nvidia_nat_mcp-1.5.0a20260115.dist-info/entry_points.txt +9 -0
  34. nat/plugins/mcp/tool.py +0 -138
  35. nvidia_nat_mcp-1.4.0a20251014.dist-info/RECORD +0 -23
  36. nvidia_nat_mcp-1.4.0a20251014.dist-info/entry_points.txt +0 -3
  37. {nvidia_nat_mcp-1.4.0a20251014.dist-info → nvidia_nat_mcp-1.5.0a20260115.dist-info}/WHEEL +0 -0
  38. {nvidia_nat_mcp-1.4.0a20251014.dist-info → nvidia_nat_mcp-1.5.0a20260115.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  39. {nvidia_nat_mcp-1.4.0a20251014.dist-info → nvidia_nat_mcp-1.5.0a20260115.dist-info}/licenses/LICENSE.md +0 -0
  40. {nvidia_nat_mcp-1.4.0a20251014.dist-info → nvidia_nat_mcp-1.5.0a20260115.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,290 @@
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 json
17
+ import logging
18
+ from inspect import Parameter
19
+ from inspect import Signature
20
+ from typing import TYPE_CHECKING
21
+ from typing import Any
22
+
23
+ from pydantic import BaseModel
24
+ from pydantic.fields import FieldInfo
25
+ from pydantic_core import PydanticUndefined
26
+
27
+ from mcp.server.fastmcp import FastMCP
28
+ from nat.builder.function import Function
29
+ from nat.builder.function_base import FunctionBase
30
+
31
+ if TYPE_CHECKING:
32
+ from nat.plugins.mcp.server.memory_profiler import MemoryProfiler
33
+ from nat.runtime.session import SessionManager
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # Sentinel: marks "optional; let Pydantic supply default/factory"
38
+ _USE_PYDANTIC_DEFAULT = object()
39
+
40
+
41
+ def is_field_optional(field: FieldInfo) -> tuple[bool, Any]:
42
+ """Determine if a Pydantic field is optional and extract its default value for MCP signatures.
43
+
44
+ For MCP tool signatures, we need to distinguish:
45
+ - Required fields: marked with Parameter.empty
46
+ - Optional with concrete default: use that default
47
+ - Optional with factory: use sentinel so Pydantic can apply the factory later
48
+
49
+ Args:
50
+ field: The Pydantic FieldInfo to check
51
+
52
+ Returns:
53
+ A tuple of (is_optional, default_value):
54
+ - (False, Parameter.empty) for required fields
55
+ - (True, actual_default) for optional fields with explicit defaults
56
+ - (True, _USE_PYDANTIC_DEFAULT) for optional fields with default_factory
57
+ """
58
+ if field.is_required():
59
+ return False, Parameter.empty
60
+
61
+ # Field is optional - has either default or factory
62
+ if field.default is not PydanticUndefined:
63
+ return True, field.default
64
+
65
+ # Factory case: mark optional in signature but don't fabricate a value
66
+ if field.default_factory is not None:
67
+ return True, _USE_PYDANTIC_DEFAULT
68
+
69
+ # Rare corner case: non-required yet no default surfaced
70
+ return True, _USE_PYDANTIC_DEFAULT
71
+
72
+
73
+ def create_function_wrapper(
74
+ function_name: str,
75
+ session_manager: 'SessionManager',
76
+ schema: type[BaseModel],
77
+ memory_profiler: 'MemoryProfiler | None' = None,
78
+ ):
79
+ """Create a wrapper function that exposes a NAT Function as an MCP tool using SessionManager.
80
+
81
+ Here SessionManager.run() which is used to create a Runner that
82
+ automatically handles observability (emits intermediate step events, starts exporters, etc).
83
+
84
+ Args:
85
+ function_name (str): The name of the function/tool
86
+ session_manager (SessionManager): SessionManager wrapping the function/workflow
87
+ schema (type[BaseModel]): The input schema of the function
88
+ memory_profiler: Optional memory profiler to track requests
89
+
90
+ Returns:
91
+ A wrapper function suitable for registration with MCP
92
+ """
93
+ # Check if we're dealing with ChatRequest - special case
94
+ is_chat_request = False
95
+
96
+ # Check if the schema name is ChatRequest
97
+ if schema.__name__ == "ChatRequest" or (hasattr(schema, "__qualname__") and "ChatRequest" in schema.__qualname__):
98
+ is_chat_request = True
99
+ logger.info("Function %s uses ChatRequest - creating simplified interface", function_name)
100
+
101
+ # For ChatRequest, we'll create a simple wrapper with just a query parameter
102
+ parameters = [Parameter(
103
+ name="query",
104
+ kind=Parameter.KEYWORD_ONLY,
105
+ default=Parameter.empty,
106
+ annotation=str,
107
+ )]
108
+ else:
109
+ # Regular case - extract parameter information from the input schema
110
+ # Extract parameter information from the input schema
111
+ param_fields = schema.model_fields
112
+
113
+ parameters = []
114
+ for name, field in param_fields.items():
115
+ # Get the field type and convert to appropriate Python type
116
+ field_type = field.annotation
117
+
118
+ # Check if field is optional and get its default value
119
+ _is_optional, param_default = is_field_optional(field)
120
+
121
+ # Add the parameter to our list
122
+ parameters.append(
123
+ Parameter(
124
+ name=name,
125
+ kind=Parameter.KEYWORD_ONLY,
126
+ default=param_default,
127
+ annotation=field_type,
128
+ ))
129
+
130
+ # Create the function signature WITHOUT the ctx parameter
131
+ # We'll handle this in the wrapper function internally
132
+ sig = Signature(parameters=parameters, return_annotation=str)
133
+
134
+ # Define the actual wrapper function that accepts ctx but doesn't expose it
135
+ def create_wrapper():
136
+
137
+ async def wrapper_with_ctx(**kwargs):
138
+ """Internal wrapper that will be called by MCP.
139
+
140
+ Uses SessionManager.run() which creates a Runner that automatically handles observability.
141
+ """
142
+ # MCP will add a ctx parameter, extract it
143
+ ctx = kwargs.get("ctx")
144
+
145
+ # Remove ctx if present
146
+ if "ctx" in kwargs:
147
+ del kwargs["ctx"]
148
+
149
+ # Process the function call
150
+ if ctx:
151
+ ctx.info("Calling function %s with args: %s", function_name, json.dumps(kwargs, default=str))
152
+ await ctx.report_progress(0, 100)
153
+
154
+ try:
155
+ # Prepare input payload
156
+ if is_chat_request:
157
+ from nat.data_models.api_server import ChatRequest
158
+ # Create a chat request from the query string
159
+ query = kwargs.get("query", "")
160
+ payload = ChatRequest.from_string(query)
161
+ else:
162
+ # Strip sentinel values so Pydantic can apply defaults/factories
163
+ cleaned_kwargs = {k: v for k, v in kwargs.items() if v is not _USE_PYDANTIC_DEFAULT}
164
+ # Always validate with the declared schema
165
+ payload = schema.model_validate(cleaned_kwargs)
166
+
167
+ # Use SessionManager.run() pattern - this automatically handles all observability
168
+ # The Runner created by session_manager.run() will:
169
+ # 1. Start the exporter manager
170
+ # 2. Emit WORKFLOW_START/FUNCTION_START events
171
+ # 3. Execute the function/workflow
172
+ # 4. Emit WORKFLOW_END/FUNCTION_END events
173
+ # 5. Stop the exporter manager
174
+ async with session_manager.run(payload) as runner:
175
+ result = await runner.result()
176
+
177
+ # Report completion
178
+ if ctx:
179
+ await ctx.report_progress(100, 100)
180
+
181
+ # Track request completion for memory profiling
182
+ if memory_profiler:
183
+ memory_profiler.on_request_complete()
184
+
185
+ # Handle different result types for proper formatting
186
+ if isinstance(result, str):
187
+ return result
188
+ if isinstance(result, dict | list):
189
+ return json.dumps(result, default=str)
190
+ return str(result)
191
+ except Exception as e:
192
+ if ctx:
193
+ ctx.error("Error calling function %s: %s", function_name, str(e))
194
+
195
+ # Track request completion even on error
196
+ if memory_profiler:
197
+ memory_profiler.on_request_complete()
198
+
199
+ raise
200
+
201
+ return wrapper_with_ctx
202
+
203
+ # Create the wrapper function
204
+ wrapper = create_wrapper()
205
+
206
+ # Set the signature on the wrapper function (WITHOUT ctx)
207
+ wrapper.__signature__ = sig # type: ignore
208
+ wrapper.__name__ = function_name
209
+
210
+ # Return the wrapper with proper signature
211
+ return wrapper
212
+
213
+
214
+ def get_function_description(function: FunctionBase) -> str:
215
+ """
216
+ Retrieve a human-readable description for a NAT function or workflow.
217
+
218
+ The description is determined using the following precedence:
219
+ 1. If the function is a Workflow and has a 'description' attribute, use it.
220
+ 2. If the Workflow's config has a 'description', use it.
221
+ 3. If the Workflow's config has a 'topic', use it.
222
+ 4. If the function is a regular Function, use its 'description' attribute.
223
+
224
+ Args:
225
+ function: The NAT FunctionBase instance (Function or Workflow).
226
+
227
+ Returns:
228
+ The best available description string for the function.
229
+ """
230
+ function_description = ""
231
+
232
+ # Import here to avoid circular imports
233
+ from nat.builder.workflow import Workflow
234
+
235
+ if isinstance(function, Workflow):
236
+ config = function.config
237
+
238
+ # Workflow doesn't have a description, but probably should
239
+ if hasattr(function, "description") and function.description:
240
+ function_description = function.description
241
+ # Try to get description from config
242
+ elif hasattr(config, "description") and config.description:
243
+ function_description = config.description
244
+ # Try to get anything that might be a description
245
+ elif hasattr(config, "topic") and config.topic:
246
+ function_description = config.topic
247
+ # Try to get description from the workflow config
248
+ elif hasattr(config, "workflow") and hasattr(config.workflow, "description") and config.workflow.description:
249
+ function_description = config.workflow.description
250
+
251
+ elif isinstance(function, Function):
252
+ function_description = function.description
253
+
254
+ return function_description
255
+
256
+
257
+ def register_function_with_mcp(mcp: FastMCP,
258
+ function_name: str,
259
+ session_manager: 'SessionManager',
260
+ memory_profiler: 'MemoryProfiler | None' = None,
261
+ function: FunctionBase | None = None) -> None:
262
+ """Register a NAT Function as an MCP tool using SessionManager.
263
+
264
+ Each function is wrapped in a SessionManager
265
+ so that all calls go through Runner that automatically handles observability.
266
+
267
+ Args:
268
+ mcp: The FastMCP instance
269
+ function_name: The name to register the function under
270
+ session_manager: SessionManager wrapping the function/workflow
271
+ memory_profiler: Optional memory profiler to track requests
272
+ """
273
+ logger.info("Registering function %s with MCP", function_name)
274
+
275
+ # Get the workflow from the session manager
276
+ workflow = session_manager.workflow
277
+
278
+ # Prefer the function's schema/description when available, fall back to workflow
279
+ target_function = function or workflow
280
+
281
+ # Get the input schema from the most specific object available
282
+ input_schema = getattr(target_function, "input_schema", workflow.input_schema)
283
+ logger.info("Function %s has input schema: %s", function_name, input_schema)
284
+
285
+ # Get function description
286
+ function_description = get_function_description(target_function)
287
+
288
+ # Create and register the wrapper function with MCP
289
+ wrapper_func = create_function_wrapper(function_name, session_manager, input_schema, memory_profiler)
290
+ mcp.tool(name=function_name, description=function_description)(wrapper_func)
nat/plugins/mcp/utils.py CHANGED
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -47,7 +47,7 @@ def model_from_mcp_schema(name: str, mcp_input_schema: dict) -> type[BaseModel]:
47
47
  "integer": int,
48
48
  "boolean": bool,
49
49
  "array": list,
50
- "null": None,
50
+ "null": type(None),
51
51
  "object": dict,
52
52
  }
53
53
 
@@ -58,51 +58,168 @@ def model_from_mcp_schema(name: str, mcp_input_schema: dict) -> type[BaseModel]:
58
58
  def _generate_valid_classname(class_name: str):
59
59
  return class_name.replace('_', ' ').replace('-', ' ').title().replace(' ', '')
60
60
 
61
- def _generate_field(field_name: str, field_properties: dict[str, Any]) -> tuple:
62
- json_type = field_properties.get("type", "string")
63
- enum_vals = field_properties.get("enum")
64
-
61
+ def _resolve_schema_type(schema: dict[str, Any], name: str) -> Any:
62
+ """
63
+ Recursively resolve a JSON schema to a Python type.
64
+ Handles nested anyOf/oneOf, arrays, objects, enums, and primitive types.
65
+ """
66
+ # Check for anyOf/oneOf first
67
+ any_of = schema.get("anyOf")
68
+ one_of = schema.get("oneOf")
69
+
70
+ if any_of or one_of:
71
+ union_schemas = any_of if any_of else one_of
72
+ resolved_type: Any = None
73
+
74
+ if union_schemas:
75
+ for sub_schema in union_schemas:
76
+ mapped = _resolve_schema_type(sub_schema, name)
77
+ if resolved_type is None:
78
+ resolved_type = mapped
79
+ elif mapped is not type(None):
80
+ # Don't add None here, handle separately
81
+ resolved_type = resolved_type | mapped
82
+ else:
83
+ # If we encounter null, combine with None at the end
84
+ resolved_type = resolved_type | None if resolved_type else type(None)
85
+
86
+ return resolved_type if resolved_type is not None else Any
87
+
88
+ # Handle enum values
89
+ enum_vals = schema.get("enum")
65
90
  if enum_vals:
66
- enum_name = f"{field_name.capitalize()}Enum"
67
- field_type = Enum(enum_name, {item: item for item in enum_vals})
68
-
69
- elif json_type == "object" and "properties" in field_properties:
70
- field_type = model_from_mcp_schema(name=field_name, mcp_input_schema=field_properties)
71
- elif json_type == "array" and "items" in field_properties:
72
- item_properties = field_properties.get("items", {})
73
- if item_properties.get("type") == "object":
74
- item_type = model_from_mcp_schema(name=field_name, mcp_input_schema=item_properties)
91
+ # Check if enum contains null
92
+ has_null = any(val is None or val == "null" for val in enum_vals)
93
+ # Filter out None/null values from enum
94
+ non_null_vals = [v for v in enum_vals if v is not None and v != "null"]
95
+
96
+ if non_null_vals:
97
+ enum_name = f"{name.capitalize()}Enum"
98
+ enum_type: Any = Enum(enum_name, {item: item for item in non_null_vals})
99
+ # If enum had null, make it a union with None
100
+ return enum_type | None if has_null else enum_type
101
+ elif has_null:
102
+ # Enum only contains null
103
+ return type(None)
75
104
  else:
76
- item_type = _type_map.get(item_properties.get("type", "string"), Any)
77
- field_type = list[item_type]
78
- elif isinstance(json_type, list):
79
- field_type = None
80
- for t in json_type:
81
- mapped = _type_map.get(t, Any)
82
- field_type = mapped if field_type is None else field_type | mapped
83
-
84
- return field_type, Field(
85
- default=field_properties.get("default", None if "null" in json_type else ...),
86
- description=field_properties.get("description", "")
87
- )
88
- else:
89
- field_type = _type_map.get(json_type, Any)
105
+ # Empty enum (shouldn't happen but handle gracefully)
106
+ return Any
107
+
108
+ schema_type = schema.get("type")
109
+
110
+ # Handle type as list (e.g., ["string", "integer", "null"])
111
+ if isinstance(schema_type, list):
112
+ list_type: Any = None
113
+ for t in schema_type:
114
+ if t == "array":
115
+ # Incorporate the mapped type of items
116
+ item_schema = schema.get("items", {})
117
+ if item_schema:
118
+ item_type = _resolve_schema_type(item_schema, name)
119
+ mapped = list[item_type]
120
+ else:
121
+ mapped = _type_map.get(t, Any)
122
+ elif t == "object":
123
+ # Incorporate the mapped type from properties
124
+ if "properties" in schema:
125
+ mapped = model_from_mcp_schema(name=name, mcp_input_schema=schema)
126
+ else:
127
+ mapped = _type_map.get(t, Any)
128
+ else:
129
+ mapped = _type_map.get(t, Any)
130
+
131
+ list_type = mapped if list_type is None else list_type | mapped
132
+ return list_type if list_type is not None else Any
133
+
134
+ # Handle null type
135
+ if schema_type == "null":
136
+ return type(None)
137
+
138
+ # Handle object type
139
+ if schema_type == "object" and "properties" in schema:
140
+ return model_from_mcp_schema(name=name, mcp_input_schema=schema)
141
+
142
+ # Handle array type
143
+ if schema_type == "array" and "items" in schema:
144
+ item_schema = schema.get("items", {})
145
+ # Recursively resolve item type (handles nested anyOf/oneOf)
146
+ item_type = _resolve_schema_type(item_schema, name)
147
+ return list[item_type]
148
+
149
+ # Handle primitive types
150
+ if schema_type is not None:
151
+ return _type_map.get(schema_type, Any)
152
+
153
+ return Any
154
+
155
+ def _has_null_in_type(field_properties: dict[str, Any]) -> bool:
156
+ """Check if a schema contains null as a valid type."""
157
+ # Check anyOf/oneOf for null
158
+ any_of = field_properties.get("anyOf")
159
+ one_of = field_properties.get("oneOf")
160
+ if any_of or one_of:
161
+ union_schemas = any_of if any_of else one_of
162
+ if union_schemas:
163
+ for schema in union_schemas:
164
+ if schema.get("type") == "null":
165
+ return True
166
+
167
+ # Check type list for null
168
+ json_type = field_properties.get("type")
169
+ if isinstance(json_type, list) and "null" in json_type:
170
+ return True
171
+
172
+ # Check enum for null (Python None or string "null")
173
+ enum_vals = field_properties.get("enum")
174
+ if enum_vals:
175
+ for val in enum_vals:
176
+ if val is None or val == "null":
177
+ return True
178
+
179
+ # Check const for null (Python None or string "null")
180
+ if "const" in field_properties:
181
+ const_val = field_properties.get("const")
182
+ if const_val is None or const_val == "null":
183
+ return True
184
+
185
+ return False
186
+
187
+ def _generate_field(field_name: str, field_properties: dict[str, Any]) -> tuple:
188
+ """
189
+ Generate a Pydantic field from JSON schema properties.
190
+ Uses _resolve_schema_type for type resolution and handles field-specific logic.
191
+ """
192
+ # Resolve the field type using the unified resolver
193
+ field_type = _resolve_schema_type(field_properties, field_name)
194
+
195
+ # Check if the type includes null
196
+ has_null = _has_null_in_type(field_properties)
90
197
 
91
198
  # Determine the default value based on whether the field is required
199
+ default_value = field_properties.get("default")
200
+
92
201
  if field_name in required_fields:
93
- # Field is required - use explicit default if provided, otherwise make it required
94
- default_value = field_properties.get("default", ...)
202
+ # Field is required - use explicit default if provided, otherwise use ... to enforce presence
203
+ if default_value is None and "default" not in field_properties:
204
+ # Required field without explicit default: always use ... even if nullable
205
+ default_value = ...
206
+ # Make the field type nullable if it allows null
207
+ if has_null:
208
+ field_type = field_type | None
95
209
  else:
96
210
  # Field is optional - use explicit default if provided, otherwise None
97
- default_value = field_properties.get("default", None)
98
- # Make the type optional if no default was provided
99
- if "default" not in field_properties:
211
+ if default_value is None:
212
+ default_value = None
213
+ # Make the type optional if no default was provided and not already nullable
214
+ if "default" not in field_properties and not has_null:
100
215
  field_type = field_type | None
101
216
 
217
+ # Handle nullable property (less common, but still supported)
102
218
  nullable = field_properties.get("nullable", False)
103
- description = field_properties.get("description", "")
219
+ if nullable and not has_null:
220
+ field_type = field_type | None
104
221
 
105
- field_type = field_type | None if nullable else field_type
222
+ description = field_properties.get("description", "")
106
223
 
107
224
  return field_type, Field(default=default_value, description=description)
108
225
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvidia-nat-mcp
3
- Version: 1.4.0a20251014
3
+ Version: 1.5.0a20260115
4
4
  Summary: Subpackage for MCP client integration in NeMo Agent toolkit
5
5
  Author: NVIDIA Corporation
6
6
  Maintainer: NVIDIA Corporation
@@ -16,13 +16,13 @@ Requires-Python: <3.14,>=3.11
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE-3rd-party.txt
18
18
  License-File: LICENSE.md
19
- Requires-Dist: nvidia-nat==v1.4.0a20251014
20
- Requires-Dist: aiorwlock~=1.5
21
- Requires-Dist: mcp~=1.14
19
+ Requires-Dist: nvidia-nat==v1.5.0a20260115
20
+ Requires-Dist: aiorwlock~=1.5.0
21
+ Requires-Dist: mcp~=1.25
22
22
  Dynamic: license-file
23
23
 
24
24
  <!--
25
- SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
25
+ SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
26
26
  SPDX-License-Identifier: Apache-2.0
27
27
 
28
28
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -0,0 +1,37 @@
1
+ nat/meta/pypi.md,sha256=-P0JFDuIEDuCRryhXejNke80c_b6AeAzMNKMGgS-NT4,1489
2
+ nat/plugins/mcp/__init__.py,sha256=hiEJ2ajB5zhZuZOUHAyHfTA9w9UNhygij7hp4yfA6QE,685
3
+ nat/plugins/mcp/exception_handler.py,sha256=iEVSZFgiKm6Mr2XB3NRYbYBgyK7GJ6LnnPYXs8MYwJI,7653
4
+ nat/plugins/mcp/exceptions.py,sha256=87fm20cBNijam8n0dYx7drwCZqH1zr9tKls2ayfkK0A,5985
5
+ nat/plugins/mcp/register.py,sha256=-_SHPsqmVuFNlo6fwnXpZA9whIlMKifk4vnBfNX33mA,858
6
+ nat/plugins/mcp/utils.py,sha256=5foCyTdCp0p72FvlP_VjkQe6wgWLc9__XCfGk_iAUbY,9116
7
+ nat/plugins/mcp/auth/__init__.py,sha256=hiEJ2ajB5zhZuZOUHAyHfTA9w9UNhygij7hp4yfA6QE,685
8
+ nat/plugins/mcp/auth/auth_flow_handler.py,sha256=BvrclzC1mmVBsUZ2lx6Cv5NNFtMmUDxsTwLAKRrjiWU,9130
9
+ nat/plugins/mcp/auth/auth_provider.py,sha256=c-Y_FIDVUjEUixvqzeh7Sl0r5aiCR9JDyfudnfC6um8,21326
10
+ nat/plugins/mcp/auth/auth_provider_config.py,sha256=l0jbQZ_lYEK0B1DlkjkoKUcWIvW5pZas_rbE_WWqvq8,4226
11
+ nat/plugins/mcp/auth/register.py,sha256=FEp4L_ff4HQgFI4QDH04NIEIKRvh8bacns957dQlk9g,1759
12
+ nat/plugins/mcp/auth/token_storage.py,sha256=Zuj0tAWqkv8pPt4baeVEL2eEmnCE4AjU4h6eLhyKyKI,9263
13
+ nat/plugins/mcp/auth/service_account/__init__.py,sha256=hiEJ2ajB5zhZuZOUHAyHfTA9w9UNhygij7hp4yfA6QE,685
14
+ nat/plugins/mcp/auth/service_account/provider.py,sha256=Gsb44cy-5Ap_1BcG3Ny9fc94VtfPg4lVrwM3LKeTBA8,5920
15
+ nat/plugins/mcp/auth/service_account/provider_config.py,sha256=rrzHAo1qmK1yI5CNaadNTVsmh5qtYF1UrYz2Q1nLF2E,5332
16
+ nat/plugins/mcp/auth/service_account/token_client.py,sha256=bGAjq_vG8oo8ZoQfRJjEgigGmF0hgF0umaUOWv0_ZOY,5986
17
+ nat/plugins/mcp/cli/__init__.py,sha256=GLpBQw8r1Nj38vIOluSHsTlLINI24tZ_JJbcWWksBJE,709
18
+ nat/plugins/mcp/cli/commands.py,sha256=CFqLqaG2ldKqdbOAfNfUc1wyYRxuq2psoGX0mE_-2Go,48869
19
+ nat/plugins/mcp/client/__init__.py,sha256=SFdgClH-7zCAAcujmdvtfiqSz3Xe1KVRX7fviW-45Zg,714
20
+ nat/plugins/mcp/client/client_base.py,sha256=jWNwb20qWIUuIJg7Ldp_WgiJlQmYin4YvVSJ9_ECP5c,26831
21
+ nat/plugins/mcp/client/client_config.py,sha256=qrBjXCKHpMoS2A5i9DoXkN7hdYnGy77FBcZDGZlyb4w,7179
22
+ nat/plugins/mcp/client/client_impl.py,sha256=dygcLnhnWnMh7u45aLvYbGEP1uv02uSb886YsOucxgs,36661
23
+ nat/plugins/mcp/server/__init__.py,sha256=Ioza5tZX7qlOicklSVbhcXO3r7EjRLENvV5AtBSusAQ,723
24
+ nat/plugins/mcp/server/front_end_config.py,sha256=yFmEMKcz_ANJnef4Zu4dwGtnfNZ0uzVdc_dYYCLmgCg,5749
25
+ nat/plugins/mcp/server/front_end_plugin.py,sha256=WAqgYFHAswUK1WKk_PIUo6Lyf0pvJLelT3esHhmIHFM,6896
26
+ nat/plugins/mcp/server/front_end_plugin_worker.py,sha256=2_PQ938IbYKFKDsmDw6rcgBWHZdbxZetUopU4Ik1FDQ,18187
27
+ nat/plugins/mcp/server/introspection_token_verifier.py,sha256=oyhZkCTo6u3w7m469DicF57BNmhTPLcMYtWuHvcCsRg,2840
28
+ nat/plugins/mcp/server/memory_profiler.py,sha256=KjJw4ARSmEFult2MorFySiucog0bD8ClI9ZbkJhIwns,12816
29
+ nat/plugins/mcp/server/register_frontend.py,sha256=gfHfQaQ4NhkZkDSrFur1bxbG93kbAGBtumm4eKYOzKE,1178
30
+ nat/plugins/mcp/server/tool_converter.py,sha256=v35IILT5S2jE7ZTYQzm8-4CMu5Xa21BACeRP1MS1vzA,11608
31
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/licenses/LICENSE-3rd-party.txt,sha256=fOk5jMmCX9YoKWyYzTtfgl-SUy477audFC5hNY4oP7Q,284609
32
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
33
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/METADATA,sha256=SVVtI6zoLVr33_W167VxtESeEabwXxzMsh9zaagfY_g,2326
34
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/entry_points.txt,sha256=t5HYRfXR-dyZf6BWRz1u1TqPprKpCWt-WLHKQXfiLLU,231
36
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
37
+ nvidia_nat_mcp-1.5.0a20260115.dist-info/RECORD,,
@@ -0,0 +1,9 @@
1
+ [nat.cli]
2
+ mcp = nat.plugins.mcp.cli.commands:mcp_command
3
+
4
+ [nat.components]
5
+ nat_mcp = nat.plugins.mcp.register
6
+ nat_mcp_auth = nat.plugins.mcp.auth.register
7
+
8
+ [nat.front_ends]
9
+ nat_mcp_server = nat.plugins.mcp.server.register_frontend