nvidia-nat 1.3.0a20250910__py3-none-any.whl → 1.4.0a20251112__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/agent/base.py +13 -8
- nat/agent/prompt_optimizer/prompt.py +68 -0
- nat/agent/prompt_optimizer/register.py +149 -0
- nat/agent/react_agent/agent.py +6 -5
- nat/agent/react_agent/register.py +49 -39
- nat/agent/reasoning_agent/reasoning_agent.py +17 -15
- nat/agent/register.py +2 -0
- nat/agent/responses_api_agent/__init__.py +14 -0
- nat/agent/responses_api_agent/register.py +126 -0
- nat/agent/rewoo_agent/agent.py +304 -117
- nat/agent/rewoo_agent/prompt.py +19 -22
- nat/agent/rewoo_agent/register.py +51 -38
- nat/agent/tool_calling_agent/agent.py +75 -17
- nat/agent/tool_calling_agent/register.py +46 -23
- nat/authentication/api_key/api_key_auth_provider.py +6 -11
- nat/authentication/api_key/api_key_auth_provider_config.py +8 -5
- nat/authentication/credential_validator/__init__.py +14 -0
- nat/authentication/credential_validator/bearer_token_validator.py +557 -0
- nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
- nat/authentication/interfaces.py +5 -2
- nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +69 -36
- nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +2 -1
- nat/authentication/oauth2/oauth2_resource_server_config.py +125 -0
- nat/builder/builder.py +55 -23
- nat/builder/component_utils.py +9 -5
- nat/builder/context.py +54 -15
- nat/builder/eval_builder.py +14 -9
- nat/builder/framework_enum.py +1 -0
- nat/builder/front_end.py +1 -1
- nat/builder/function.py +370 -0
- nat/builder/function_info.py +1 -1
- nat/builder/intermediate_step_manager.py +38 -2
- nat/builder/workflow.py +5 -0
- nat/builder/workflow_builder.py +306 -54
- nat/cli/cli_utils/config_override.py +1 -1
- nat/cli/commands/info/info.py +16 -6
- nat/cli/commands/mcp/__init__.py +14 -0
- nat/cli/commands/mcp/mcp.py +986 -0
- nat/cli/commands/optimize.py +90 -0
- nat/cli/commands/start.py +1 -1
- nat/cli/commands/workflow/templates/config.yml.j2 +14 -13
- nat/cli/commands/workflow/templates/register.py.j2 +2 -2
- nat/cli/commands/workflow/templates/workflow.py.j2 +35 -21
- nat/cli/commands/workflow/workflow_commands.py +60 -18
- nat/cli/entrypoint.py +15 -11
- nat/cli/main.py +3 -0
- nat/cli/register_workflow.py +38 -4
- nat/cli/type_registry.py +72 -1
- nat/control_flow/__init__.py +0 -0
- nat/control_flow/register.py +20 -0
- nat/control_flow/router_agent/__init__.py +0 -0
- nat/control_flow/router_agent/agent.py +329 -0
- nat/control_flow/router_agent/prompt.py +48 -0
- nat/control_flow/router_agent/register.py +91 -0
- nat/control_flow/sequential_executor.py +166 -0
- nat/data_models/agent.py +34 -0
- nat/data_models/api_server.py +199 -69
- nat/data_models/authentication.py +23 -9
- nat/data_models/common.py +47 -0
- nat/data_models/component.py +2 -0
- nat/data_models/component_ref.py +11 -0
- nat/data_models/config.py +41 -17
- nat/data_models/dataset_handler.py +4 -3
- nat/data_models/function.py +34 -0
- nat/data_models/function_dependencies.py +8 -0
- nat/data_models/intermediate_step.py +9 -1
- nat/data_models/llm.py +15 -1
- nat/data_models/openai_mcp.py +46 -0
- nat/data_models/optimizable.py +208 -0
- nat/data_models/optimizer.py +161 -0
- nat/data_models/span.py +41 -3
- nat/data_models/thinking_mixin.py +2 -2
- nat/embedder/azure_openai_embedder.py +2 -1
- nat/embedder/nim_embedder.py +3 -2
- nat/embedder/openai_embedder.py +3 -2
- nat/eval/config.py +1 -1
- nat/eval/dataset_handler/dataset_downloader.py +3 -2
- nat/eval/dataset_handler/dataset_filter.py +34 -2
- nat/eval/evaluate.py +10 -3
- nat/eval/evaluator/base_evaluator.py +1 -1
- nat/eval/rag_evaluator/evaluate.py +7 -4
- nat/eval/register.py +4 -0
- nat/eval/runtime_evaluator/__init__.py +14 -0
- nat/eval/runtime_evaluator/evaluate.py +123 -0
- nat/eval/runtime_evaluator/register.py +100 -0
- nat/eval/swe_bench_evaluator/evaluate.py +1 -1
- nat/eval/trajectory_evaluator/register.py +1 -1
- nat/eval/tunable_rag_evaluator/evaluate.py +1 -1
- nat/eval/usage_stats.py +2 -0
- nat/eval/utils/output_uploader.py +3 -2
- nat/eval/utils/weave_eval.py +17 -3
- nat/experimental/decorators/experimental_warning_decorator.py +27 -7
- nat/experimental/test_time_compute/functions/execute_score_select_function.py +1 -1
- nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
- nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +1 -1
- nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +3 -3
- nat/experimental/test_time_compute/models/strategy_base.py +2 -2
- nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -1
- nat/front_ends/console/authentication_flow_handler.py +82 -30
- nat/front_ends/console/console_front_end_plugin.py +19 -7
- nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +1 -1
- nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
- nat/front_ends/fastapi/dask_client_mixin.py +65 -0
- nat/front_ends/fastapi/fastapi_front_end_config.py +25 -3
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +140 -3
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +445 -265
- nat/front_ends/fastapi/job_store.py +518 -99
- nat/front_ends/fastapi/main.py +11 -19
- nat/front_ends/fastapi/message_handler.py +69 -44
- nat/front_ends/fastapi/message_validator.py +8 -7
- nat/front_ends/fastapi/utils.py +57 -0
- nat/front_ends/mcp/introspection_token_verifier.py +73 -0
- nat/front_ends/mcp/mcp_front_end_config.py +71 -3
- nat/front_ends/mcp/mcp_front_end_plugin.py +85 -21
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +248 -29
- nat/front_ends/mcp/memory_profiler.py +320 -0
- nat/front_ends/mcp/tool_converter.py +78 -25
- nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
- nat/llm/aws_bedrock_llm.py +21 -8
- nat/llm/azure_openai_llm.py +14 -5
- nat/llm/litellm_llm.py +80 -0
- nat/llm/nim_llm.py +23 -9
- nat/llm/openai_llm.py +19 -7
- nat/llm/register.py +4 -0
- nat/llm/utils/thinking.py +1 -1
- nat/observability/exporter/base_exporter.py +1 -1
- nat/observability/exporter/processing_exporter.py +29 -55
- nat/observability/exporter/span_exporter.py +43 -15
- nat/observability/exporter_manager.py +2 -2
- nat/observability/mixin/redaction_config_mixin.py +5 -4
- nat/observability/mixin/tagging_config_mixin.py +26 -14
- nat/observability/mixin/type_introspection_mixin.py +420 -107
- nat/observability/processor/batching_processor.py +1 -1
- nat/observability/processor/processor.py +3 -0
- nat/observability/processor/redaction/__init__.py +24 -0
- nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
- nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
- nat/observability/processor/redaction/redaction_processor.py +177 -0
- nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
- nat/observability/processor/span_tagging_processor.py +21 -14
- nat/observability/register.py +16 -0
- nat/profiler/callbacks/langchain_callback_handler.py +32 -7
- nat/profiler/callbacks/llama_index_callback_handler.py +36 -2
- nat/profiler/callbacks/token_usage_base_model.py +2 -0
- nat/profiler/decorators/framework_wrapper.py +61 -9
- nat/profiler/decorators/function_tracking.py +35 -3
- nat/profiler/forecasting/models/linear_model.py +1 -1
- nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
- nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
- nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +1 -1
- nat/profiler/parameter_optimization/__init__.py +0 -0
- nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
- nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
- nat/profiler/parameter_optimization/parameter_optimizer.py +189 -0
- nat/profiler/parameter_optimization/parameter_selection.py +107 -0
- nat/profiler/parameter_optimization/pareto_visualizer.py +460 -0
- nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
- nat/profiler/parameter_optimization/update_helpers.py +66 -0
- nat/profiler/utils.py +3 -1
- nat/registry_handlers/pypi/register_pypi.py +5 -3
- nat/registry_handlers/rest/register_rest.py +5 -3
- nat/retriever/milvus/retriever.py +1 -1
- nat/retriever/nemo_retriever/register.py +2 -1
- nat/runtime/loader.py +1 -1
- nat/runtime/runner.py +111 -6
- nat/runtime/session.py +49 -3
- nat/settings/global_settings.py +2 -2
- nat/tool/chat_completion.py +4 -1
- nat/tool/code_execution/code_sandbox.py +3 -6
- nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +19 -32
- nat/tool/code_execution/local_sandbox/local_sandbox_server.py +6 -1
- nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +2 -0
- nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +10 -4
- nat/tool/datetime_tools.py +1 -1
- nat/tool/github_tools.py +450 -0
- nat/tool/memory_tools/add_memory_tool.py +3 -3
- nat/tool/memory_tools/delete_memory_tool.py +3 -4
- nat/tool/memory_tools/get_memory_tool.py +4 -4
- nat/tool/register.py +2 -7
- nat/tool/server_tools.py +15 -2
- nat/utils/__init__.py +76 -0
- nat/utils/callable_utils.py +70 -0
- nat/utils/data_models/schema_validator.py +1 -1
- nat/utils/decorators.py +210 -0
- nat/utils/exception_handlers/automatic_retries.py +278 -72
- nat/utils/io/yaml_tools.py +73 -3
- nat/utils/log_levels.py +25 -0
- nat/utils/responses_api.py +26 -0
- nat/utils/string_utils.py +16 -0
- nat/utils/type_converter.py +12 -3
- nat/utils/type_utils.py +6 -2
- nvidia_nat-1.4.0a20251112.dist-info/METADATA +197 -0
- {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/RECORD +199 -165
- {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/entry_points.txt +1 -0
- nat/cli/commands/info/list_mcp.py +0 -461
- nat/data_models/temperature_mixin.py +0 -43
- nat/data_models/top_p_mixin.py +0 -43
- nat/observability/processor/header_redaction_processor.py +0 -123
- nat/observability/processor/redaction_processor.py +0 -77
- nat/tool/code_execution/test_code_execution_sandbox.py +0 -414
- nat/tool/github_tools/create_github_commit.py +0 -133
- nat/tool/github_tools/create_github_issue.py +0 -87
- nat/tool/github_tools/create_github_pr.py +0 -106
- nat/tool/github_tools/get_github_file.py +0 -106
- nat/tool/github_tools/get_github_issue.py +0 -166
- nat/tool/github_tools/get_github_pr.py +0 -256
- nat/tool/github_tools/update_github_issue.py +0 -100
- nvidia_nat-1.3.0a20250910.dist-info/METADATA +0 -373
- /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
- {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/WHEEL +0 -0
- {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE.md +0 -0
- {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/top_level.txt +0 -0
|
@@ -21,6 +21,9 @@ from nat.builder.workflow_builder import WorkflowBuilder
|
|
|
21
21
|
from nat.front_ends.mcp.mcp_front_end_config import MCPFrontEndConfig
|
|
22
22
|
from nat.front_ends.mcp.mcp_front_end_plugin_worker import MCPFrontEndPluginWorkerBase
|
|
23
23
|
|
|
24
|
+
if typing.TYPE_CHECKING:
|
|
25
|
+
from mcp.server.fastmcp import FastMCP
|
|
26
|
+
|
|
24
27
|
logger = logging.getLogger(__name__)
|
|
25
28
|
|
|
26
29
|
|
|
@@ -42,7 +45,7 @@ class MCPFrontEndPlugin(FrontEndBase[MCPFrontEndConfig]):
|
|
|
42
45
|
worker_class = self.get_worker_class()
|
|
43
46
|
return f"{worker_class.__module__}.{worker_class.__qualname__}"
|
|
44
47
|
|
|
45
|
-
def _get_worker_instance(self)
|
|
48
|
+
def _get_worker_instance(self):
|
|
46
49
|
"""Get an instance of the worker class."""
|
|
47
50
|
# Import the worker class dynamically if specified in config
|
|
48
51
|
if self.front_end_config.runner_class:
|
|
@@ -57,31 +60,92 @@ class MCPFrontEndPlugin(FrontEndBase[MCPFrontEndConfig]):
|
|
|
57
60
|
|
|
58
61
|
async def run(self) -> None:
|
|
59
62
|
"""Run the MCP server."""
|
|
60
|
-
#
|
|
61
|
-
|
|
63
|
+
# Build the workflow and add routes using the worker
|
|
64
|
+
async with WorkflowBuilder.from_config(config=self.full_config) as builder:
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self.front_end_config.name,
|
|
66
|
-
host=self.front_end_config.host,
|
|
67
|
-
port=self.front_end_config.port,
|
|
68
|
-
debug=self.front_end_config.debug,
|
|
69
|
-
log_level=self.front_end_config.log_level,
|
|
70
|
-
)
|
|
66
|
+
# Get the worker instance
|
|
67
|
+
worker = self._get_worker_instance()
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
# Let the worker create the MCP server (allows plugins to customize)
|
|
70
|
+
mcp = await worker.create_mcp_server()
|
|
74
71
|
|
|
75
|
-
# Build the workflow and add routes using the worker
|
|
76
|
-
async with WorkflowBuilder.from_config(config=self.full_config) as builder:
|
|
77
72
|
# Add routes through the worker (includes health endpoint and function registration)
|
|
78
73
|
await worker.add_routes(mcp, builder)
|
|
79
74
|
|
|
80
75
|
# Start the MCP server with configurable transport
|
|
81
76
|
# streamable-http is the default, but users can choose sse if preferred
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
try:
|
|
78
|
+
# If base_path is configured, mount server at sub-path using FastAPI wrapper
|
|
79
|
+
if self.front_end_config.base_path:
|
|
80
|
+
if self.front_end_config.transport == "sse":
|
|
81
|
+
logger.warning(
|
|
82
|
+
"base_path is configured but SSE transport does not support mounting at sub-paths. "
|
|
83
|
+
"Use streamable-http transport for base_path support.")
|
|
84
|
+
logger.info("Starting MCP server with SSE endpoint at /sse")
|
|
85
|
+
await mcp.run_sse_async()
|
|
86
|
+
else:
|
|
87
|
+
full_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}{self.front_end_config.base_path}/mcp"
|
|
88
|
+
logger.info(
|
|
89
|
+
"Mounting MCP server at %s/mcp on %s:%s",
|
|
90
|
+
self.front_end_config.base_path,
|
|
91
|
+
self.front_end_config.host,
|
|
92
|
+
self.front_end_config.port,
|
|
93
|
+
)
|
|
94
|
+
logger.info("MCP server URL: %s", full_url)
|
|
95
|
+
await self._run_with_mount(mcp)
|
|
96
|
+
# Standard behavior - run at root path
|
|
97
|
+
elif self.front_end_config.transport == "sse":
|
|
98
|
+
logger.info("Starting MCP server with SSE endpoint at /sse")
|
|
99
|
+
await mcp.run_sse_async()
|
|
100
|
+
else: # streamable-http
|
|
101
|
+
full_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}/mcp"
|
|
102
|
+
logger.info("MCP server URL: %s", full_url)
|
|
103
|
+
await mcp.run_streamable_http_async()
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
logger.info("MCP server shutdown requested (Ctrl+C). Shutting down gracefully.")
|
|
106
|
+
|
|
107
|
+
async def _run_with_mount(self, mcp: "FastMCP") -> None:
|
|
108
|
+
"""Run MCP server mounted at configured base_path using FastAPI wrapper.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
mcp: The FastMCP server instance to mount
|
|
112
|
+
"""
|
|
113
|
+
import contextlib
|
|
114
|
+
|
|
115
|
+
import uvicorn
|
|
116
|
+
from fastapi import FastAPI
|
|
117
|
+
|
|
118
|
+
@contextlib.asynccontextmanager
|
|
119
|
+
async def lifespan(_app: FastAPI):
|
|
120
|
+
"""Manage MCP server session lifecycle."""
|
|
121
|
+
logger.info("Starting MCP server session manager...")
|
|
122
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
123
|
+
try:
|
|
124
|
+
# Initialize the MCP server's session manager
|
|
125
|
+
await stack.enter_async_context(mcp.session_manager.run())
|
|
126
|
+
logger.info("MCP server session manager started successfully")
|
|
127
|
+
yield
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error("Failed to start MCP server session manager: %s", e)
|
|
130
|
+
raise
|
|
131
|
+
logger.info("MCP server session manager stopped")
|
|
132
|
+
|
|
133
|
+
# Create a FastAPI wrapper app with lifespan management
|
|
134
|
+
app = FastAPI(
|
|
135
|
+
title=self.front_end_config.name,
|
|
136
|
+
description="MCP server mounted at custom base path",
|
|
137
|
+
lifespan=lifespan,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Mount the MCP server's ASGI app at the configured base_path
|
|
141
|
+
app.mount(self.front_end_config.base_path, mcp.streamable_http_app())
|
|
142
|
+
|
|
143
|
+
# Configure and start uvicorn server
|
|
144
|
+
config = uvicorn.Config(
|
|
145
|
+
app,
|
|
146
|
+
host=self.front_end_config.host,
|
|
147
|
+
port=self.front_end_config.port,
|
|
148
|
+
log_level=self.front_end_config.log_level.lower(),
|
|
149
|
+
)
|
|
150
|
+
server = uvicorn.Server(config)
|
|
151
|
+
await server.serve()
|
|
@@ -16,21 +16,31 @@
|
|
|
16
16
|
import logging
|
|
17
17
|
from abc import ABC
|
|
18
18
|
from abc import abstractmethod
|
|
19
|
+
from collections.abc import Mapping
|
|
20
|
+
from typing import Any
|
|
19
21
|
|
|
20
22
|
from mcp.server.fastmcp import FastMCP
|
|
23
|
+
from starlette.exceptions import HTTPException
|
|
21
24
|
from starlette.requests import Request
|
|
22
25
|
|
|
23
26
|
from nat.builder.function import Function
|
|
27
|
+
from nat.builder.function_base import FunctionBase
|
|
24
28
|
from nat.builder.workflow import Workflow
|
|
25
29
|
from nat.builder.workflow_builder import WorkflowBuilder
|
|
26
30
|
from nat.data_models.config import Config
|
|
27
31
|
from nat.front_ends.mcp.mcp_front_end_config import MCPFrontEndConfig
|
|
32
|
+
from nat.front_ends.mcp.memory_profiler import MemoryProfiler
|
|
28
33
|
|
|
29
34
|
logger = logging.getLogger(__name__)
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
class MCPFrontEndPluginWorkerBase(ABC):
|
|
33
|
-
"""Base class for MCP front end plugin workers.
|
|
38
|
+
"""Base class for MCP front end plugin workers.
|
|
39
|
+
|
|
40
|
+
This abstract base class provides shared utilities and defines the contract
|
|
41
|
+
for MCP worker implementations. Most users should inherit from
|
|
42
|
+
MCPFrontEndPluginWorker instead of this class directly.
|
|
43
|
+
"""
|
|
34
44
|
|
|
35
45
|
def __init__(self, config: Config):
|
|
36
46
|
"""Initialize the MCP worker with configuration.
|
|
@@ -41,6 +51,12 @@ class MCPFrontEndPluginWorkerBase(ABC):
|
|
|
41
51
|
self.full_config = config
|
|
42
52
|
self.front_end_config: MCPFrontEndConfig = config.general.front_end
|
|
43
53
|
|
|
54
|
+
# Initialize memory profiler if enabled
|
|
55
|
+
self.memory_profiler = MemoryProfiler(enabled=self.front_end_config.enable_memory_profiling,
|
|
56
|
+
log_interval=self.front_end_config.memory_profile_interval,
|
|
57
|
+
top_n=self.front_end_config.memory_profile_top_n,
|
|
58
|
+
log_level=self.front_end_config.memory_profile_log_level)
|
|
59
|
+
|
|
44
60
|
def _setup_health_endpoint(self, mcp: FastMCP):
|
|
45
61
|
"""Set up the HTTP health endpoint that exercises MCP ping handler."""
|
|
46
62
|
|
|
@@ -73,44 +89,48 @@ class MCPFrontEndPluginWorkerBase(ABC):
|
|
|
73
89
|
status_code=503)
|
|
74
90
|
|
|
75
91
|
@abstractmethod
|
|
76
|
-
async def
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
mcp: The FastMCP server instance
|
|
81
|
-
builder (WorkflowBuilder): The workflow builder instance
|
|
82
|
-
"""
|
|
83
|
-
pass
|
|
92
|
+
async def create_mcp_server(self) -> FastMCP:
|
|
93
|
+
"""Create and configure the MCP server instance.
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
workflow: The NAT workflow.
|
|
95
|
+
This is the main extension point. Plugins can return FastMCP or any subclass
|
|
96
|
+
to customize server behavior (for example, add authentication, custom transports).
|
|
90
97
|
|
|
91
98
|
Returns:
|
|
92
|
-
|
|
99
|
+
FastMCP instance or a subclass with custom behavior
|
|
93
100
|
"""
|
|
94
|
-
|
|
101
|
+
...
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
@abstractmethod
|
|
104
|
+
async def add_routes(self, mcp: FastMCP, builder: WorkflowBuilder):
|
|
105
|
+
"""Add routes to the MCP server.
|
|
99
106
|
|
|
100
|
-
|
|
107
|
+
Plugins must implement this method. Most plugins can call
|
|
108
|
+
_default_add_routes() for standard behavior and then add
|
|
109
|
+
custom enhancements.
|
|
101
110
|
|
|
102
|
-
|
|
111
|
+
Args:
|
|
112
|
+
mcp: The FastMCP server instance
|
|
113
|
+
builder: The workflow builder instance
|
|
114
|
+
"""
|
|
115
|
+
...
|
|
103
116
|
|
|
117
|
+
async def _default_add_routes(self, mcp: FastMCP, builder: WorkflowBuilder):
|
|
118
|
+
"""Default route registration logic - reusable by subclasses.
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
This is a protected helper method that plugins can call to get
|
|
121
|
+
standard route registration behavior. Plugins typically call this
|
|
122
|
+
from their add_routes() implementation and then add custom features.
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
This method:
|
|
125
|
+
- Sets up the health endpoint
|
|
126
|
+
- Builds the workflow and extracts all functions
|
|
127
|
+
- Filters functions based on tool_names config
|
|
128
|
+
- Registers each function as an MCP tool
|
|
129
|
+
- Sets up debug endpoints for tool introspection
|
|
110
130
|
|
|
111
131
|
Args:
|
|
112
132
|
mcp: The FastMCP server instance
|
|
113
|
-
builder
|
|
133
|
+
builder: The workflow builder instance
|
|
114
134
|
"""
|
|
115
135
|
from nat.front_ends.mcp.tool_converter import register_function_with_mcp
|
|
116
136
|
|
|
@@ -118,10 +138,10 @@ class MCPFrontEndPluginWorker(MCPFrontEndPluginWorkerBase):
|
|
|
118
138
|
self._setup_health_endpoint(mcp)
|
|
119
139
|
|
|
120
140
|
# Build the workflow and register all functions with MCP
|
|
121
|
-
workflow = builder.build()
|
|
141
|
+
workflow = await builder.build()
|
|
122
142
|
|
|
123
143
|
# Get all functions from the workflow
|
|
124
|
-
functions = self._get_all_functions(workflow)
|
|
144
|
+
functions = await self._get_all_functions(workflow)
|
|
125
145
|
|
|
126
146
|
# Filter functions based on tool_names if provided
|
|
127
147
|
if self.front_end_config.tool_names:
|
|
@@ -129,6 +149,10 @@ class MCPFrontEndPluginWorker(MCPFrontEndPluginWorkerBase):
|
|
|
129
149
|
filtered_functions: dict[str, Function] = {}
|
|
130
150
|
for function_name, function in functions.items():
|
|
131
151
|
if function_name in self.front_end_config.tool_names:
|
|
152
|
+
# Treat current tool_names as function names, so check if the function name is in the list
|
|
153
|
+
filtered_functions[function_name] = function
|
|
154
|
+
elif any(function_name.startswith(f"{group_name}.") for group_name in self.front_end_config.tool_names):
|
|
155
|
+
# Treat tool_names as function group names, so check if the function name starts with the group name
|
|
132
156
|
filtered_functions[function_name] = function
|
|
133
157
|
else:
|
|
134
158
|
logger.debug("Skipping function %s as it's not in tool_names", function_name)
|
|
@@ -136,8 +160,203 @@ class MCPFrontEndPluginWorker(MCPFrontEndPluginWorkerBase):
|
|
|
136
160
|
|
|
137
161
|
# Register each function with MCP, passing workflow context for observability
|
|
138
162
|
for function_name, function in functions.items():
|
|
139
|
-
register_function_with_mcp(mcp, function_name, function, workflow)
|
|
163
|
+
register_function_with_mcp(mcp, function_name, function, workflow, self.memory_profiler)
|
|
140
164
|
|
|
141
165
|
# Add a simple fallback function if no functions were found
|
|
142
166
|
if not functions:
|
|
143
167
|
raise RuntimeError("No functions found in workflow. Please check your configuration.")
|
|
168
|
+
|
|
169
|
+
# After registration, expose debug endpoints for tool/schema inspection
|
|
170
|
+
self._setup_debug_endpoints(mcp, functions)
|
|
171
|
+
|
|
172
|
+
async def _get_all_functions(self, workflow: Workflow) -> dict[str, Function]:
|
|
173
|
+
"""Get all functions from the workflow.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
workflow: The NAT workflow.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dict mapping function names to Function objects.
|
|
180
|
+
"""
|
|
181
|
+
functions: dict[str, Function] = {}
|
|
182
|
+
|
|
183
|
+
# Extract all functions from the workflow
|
|
184
|
+
functions.update(workflow.functions)
|
|
185
|
+
for function_group in workflow.function_groups.values():
|
|
186
|
+
functions.update(await function_group.get_accessible_functions())
|
|
187
|
+
|
|
188
|
+
if workflow.config.workflow.workflow_alias:
|
|
189
|
+
functions[workflow.config.workflow.workflow_alias] = workflow
|
|
190
|
+
else:
|
|
191
|
+
functions[workflow.config.workflow.type] = workflow
|
|
192
|
+
|
|
193
|
+
return functions
|
|
194
|
+
|
|
195
|
+
def _setup_debug_endpoints(self, mcp: FastMCP, functions: Mapping[str, FunctionBase]) -> None:
|
|
196
|
+
"""Set up HTTP debug endpoints for introspecting tools and schemas.
|
|
197
|
+
|
|
198
|
+
Exposes:
|
|
199
|
+
- GET /debug/tools/list: List tools. Optional query param `name` (one or more, repeatable or comma separated)
|
|
200
|
+
selects a subset and returns details for those tools.
|
|
201
|
+
- GET /debug/memory/stats: Get current memory profiling statistics (read-only)
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
@mcp.custom_route("/debug/tools/list", methods=["GET"])
|
|
205
|
+
async def list_tools(request: Request):
|
|
206
|
+
"""HTTP list tools endpoint."""
|
|
207
|
+
|
|
208
|
+
from starlette.responses import JSONResponse
|
|
209
|
+
|
|
210
|
+
from nat.front_ends.mcp.tool_converter import get_function_description
|
|
211
|
+
|
|
212
|
+
# Query params
|
|
213
|
+
# Support repeated names and comma-separated lists
|
|
214
|
+
names_param_list = set(request.query_params.getlist("name"))
|
|
215
|
+
names: list[str] = []
|
|
216
|
+
for raw in names_param_list:
|
|
217
|
+
# if p.strip() is empty, it won't be included in the list!
|
|
218
|
+
parts = [p.strip() for p in raw.split(",") if p.strip()]
|
|
219
|
+
names.extend(parts)
|
|
220
|
+
detail_raw = request.query_params.get("detail")
|
|
221
|
+
|
|
222
|
+
def _parse_detail_param(detail_param: str | None, has_names: bool) -> bool:
|
|
223
|
+
if detail_param is None:
|
|
224
|
+
if has_names:
|
|
225
|
+
return True
|
|
226
|
+
return False
|
|
227
|
+
v = detail_param.strip().lower()
|
|
228
|
+
if v in ("0", "false", "no", "off"):
|
|
229
|
+
return False
|
|
230
|
+
if v in ("1", "true", "yes", "on"):
|
|
231
|
+
return True
|
|
232
|
+
# For invalid values, default based on whether names are present
|
|
233
|
+
return has_names
|
|
234
|
+
|
|
235
|
+
# Helper function to build the input schema info
|
|
236
|
+
def _build_schema_info(fn: FunctionBase) -> dict[str, Any] | None:
|
|
237
|
+
schema = getattr(fn, "input_schema", None)
|
|
238
|
+
if schema is None:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
# check if schema is a ChatRequest
|
|
242
|
+
schema_name = getattr(schema, "__name__", "")
|
|
243
|
+
schema_qualname = getattr(schema, "__qualname__", "")
|
|
244
|
+
if "ChatRequest" in schema_name or "ChatRequest" in schema_qualname:
|
|
245
|
+
# Simplified interface used by MCP wrapper for ChatRequest
|
|
246
|
+
return {
|
|
247
|
+
"type": "object",
|
|
248
|
+
"properties": {
|
|
249
|
+
"query": {
|
|
250
|
+
"type": "string", "description": "User query string"
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"required": ["query"],
|
|
254
|
+
"title": "ChatRequestQuery",
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Pydantic models provide model_json_schema
|
|
258
|
+
if schema is not None and hasattr(schema, "model_json_schema"):
|
|
259
|
+
return schema.model_json_schema()
|
|
260
|
+
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
def _build_final_json(functions_to_include: Mapping[str, FunctionBase],
|
|
264
|
+
include_schemas: bool = False) -> dict[str, Any]:
|
|
265
|
+
tools = []
|
|
266
|
+
for name, fn in functions_to_include.items():
|
|
267
|
+
list_entry: dict[str, Any] = {
|
|
268
|
+
"name": name, "description": get_function_description(fn), "is_workflow": hasattr(fn, "run")
|
|
269
|
+
}
|
|
270
|
+
if include_schemas:
|
|
271
|
+
list_entry["schema"] = _build_schema_info(fn)
|
|
272
|
+
tools.append(list_entry)
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
"count": len(tools),
|
|
276
|
+
"tools": tools,
|
|
277
|
+
"server_name": mcp.name,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if names:
|
|
281
|
+
# Return selected tools
|
|
282
|
+
try:
|
|
283
|
+
functions_to_include = {n: functions[n] for n in names}
|
|
284
|
+
except KeyError as e:
|
|
285
|
+
raise HTTPException(status_code=404, detail=f"Tool \"{e.args[0]}\" not found.") from e
|
|
286
|
+
else:
|
|
287
|
+
functions_to_include = functions
|
|
288
|
+
|
|
289
|
+
# Default for listing all: detail defaults to False unless explicitly set true
|
|
290
|
+
return JSONResponse(
|
|
291
|
+
_build_final_json(functions_to_include, _parse_detail_param(detail_raw, has_names=bool(names))))
|
|
292
|
+
|
|
293
|
+
# Memory profiling endpoint (read-only)
|
|
294
|
+
@mcp.custom_route("/debug/memory/stats", methods=["GET"])
|
|
295
|
+
async def get_memory_stats(_request: Request):
|
|
296
|
+
"""Get current memory profiling statistics."""
|
|
297
|
+
from starlette.responses import JSONResponse
|
|
298
|
+
|
|
299
|
+
stats = self.memory_profiler.get_stats()
|
|
300
|
+
return JSONResponse(stats)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class MCPFrontEndPluginWorker(MCPFrontEndPluginWorkerBase):
|
|
304
|
+
"""Default MCP server worker implementation.
|
|
305
|
+
|
|
306
|
+
Inherit from this class to create custom MCP workers that extend or modify
|
|
307
|
+
server behavior. Override create_mcp_server() to use a different server type,
|
|
308
|
+
and override add_routes() to add custom functionality.
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
class CustomWorker(MCPFrontEndPluginWorker):
|
|
312
|
+
async def create_mcp_server(self):
|
|
313
|
+
# Return custom MCP server instance
|
|
314
|
+
return MyCustomFastMCP(...)
|
|
315
|
+
|
|
316
|
+
async def add_routes(self, mcp, builder):
|
|
317
|
+
# Get default routes
|
|
318
|
+
await super().add_routes(mcp, builder)
|
|
319
|
+
# Add custom features
|
|
320
|
+
self._add_my_custom_features(mcp)
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
async def create_mcp_server(self) -> FastMCP:
|
|
324
|
+
"""Create default MCP server with optional authentication.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
FastMCP instance configured with settings from NAT config
|
|
328
|
+
"""
|
|
329
|
+
# Handle auth if configured
|
|
330
|
+
auth_settings = None
|
|
331
|
+
token_verifier = None
|
|
332
|
+
|
|
333
|
+
if self.front_end_config.server_auth:
|
|
334
|
+
from mcp.server.auth.settings import AuthSettings
|
|
335
|
+
from pydantic import AnyHttpUrl
|
|
336
|
+
|
|
337
|
+
server_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}"
|
|
338
|
+
auth_settings = AuthSettings(issuer_url=AnyHttpUrl(self.front_end_config.server_auth.issuer_url),
|
|
339
|
+
required_scopes=self.front_end_config.server_auth.scopes,
|
|
340
|
+
resource_server_url=AnyHttpUrl(server_url))
|
|
341
|
+
|
|
342
|
+
# Create token verifier
|
|
343
|
+
from nat.front_ends.mcp.introspection_token_verifier import IntrospectionTokenVerifier
|
|
344
|
+
|
|
345
|
+
token_verifier = IntrospectionTokenVerifier(self.front_end_config.server_auth)
|
|
346
|
+
|
|
347
|
+
return FastMCP(name=self.front_end_config.name,
|
|
348
|
+
host=self.front_end_config.host,
|
|
349
|
+
port=self.front_end_config.port,
|
|
350
|
+
debug=self.front_end_config.debug,
|
|
351
|
+
auth=auth_settings,
|
|
352
|
+
token_verifier=token_verifier)
|
|
353
|
+
|
|
354
|
+
async def add_routes(self, mcp: FastMCP, builder: WorkflowBuilder):
|
|
355
|
+
"""Add default routes to the MCP server.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
mcp: The FastMCP server instance
|
|
359
|
+
builder: The workflow builder instance
|
|
360
|
+
"""
|
|
361
|
+
# Use the default implementation from base class to add the tools to the MCP server
|
|
362
|
+
await self._default_add_routes(mcp, builder)
|