Skip to content

feat: Support MCP Authentication - #194

Draft
dagardner-nv wants to merge 47 commits into
NVIDIA:mainfrom
dagardner-nv:david-mcp-auth
Draft

feat: Support MCP Authentication#194
dagardner-nv wants to merge 47 commits into
NVIDIA:mainfrom
dagardner-nv:david-mcp-auth

Conversation

@dagardner-nv

@dagardner-nv dagardner-nv commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Overview

Where should the reviewer start?

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • Closes FABRIC-170

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.

  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Summary by CodeRabbit

  • New Features

    • Added OAuth 2.0 and service-account authentication for MCP servers.
    • Added custom HTTP headers, browser sign-in, secure credential handling, token caching, and automatic refresh.
    • Added typed MCP transport options: stdio, SSE, and streamable HTTP.
    • Extended Python and Rust SDK configuration and schemas with authentication settings.
  • Documentation

    • Updated SDK and API references with MCP authentication and transport options.
  • Tests

    • Added coverage for authentication flows, validation, token refresh, headers, and adapter integration.

…d), run discover_mcp_tools in a loop

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
…mprovements

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
…-fabric into david-mcp-auth

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
…mprovements

Signed-off-by: David Gardner <dagardner@nvidia.com>
…-fabric into david-mcp-auth

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
@github-actions

Copy link
Copy Markdown

Signed-off-by: David Gardner <dagardner@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py`:
- Around line 399-406: Update _authenticated_mcp_servers and the MCP login flow
to reject stdio servers that specify authentication, preventing their raw env
credentials from reaching _login_mcp_server or add-json argv. Add a regression
test covering a stdio server with both authentication and env, asserting
credential values are absent from argv.

In `@adapters/codex/src/nemo_fabric_adapters/codex/adapter.py`:
- Around line 467-473: Update _authenticate_mcp_servers to accept the invocation
timeout, and in its _login_mcp_server call set timeout to the minimum of that
value and oauth.authorization_timeout_seconds. Pass the existing
timeout_seconds(payload) result from invoke into _authenticate_mcp_servers,
preserving the per-server OAuth timeout as the upper bound.
- Around line 211-221: Update the custom-header handling in the Codex adapter to
expand environment variables across header values before calling
mcp_auth.normalize_custom_headers. Validate the expanded mapping so CR/LF
introduced by environment variables is rejected, then build
result[name]["http_headers"] from the validated headers without performing a
second expansion.

In `@adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py`:
- Around line 513-590: Track each _LoopbackOAuthCallback or provider created by
create_mcp_oauth_provider at DeepAgentsRuntime scope, and update
DeepAgentsRuntime.stop to close every tracked callback by calling
close_reserved_socket() before awaiting close(). Ensure startup-failure cleanup
performs the same callback cleanup before propagating the failure, while
preserving normal OAuth authorization behavior.

In `@adapters/deepagents/src/nemo_fabric_adapters/deepagents/adapter.py`:
- Around line 306-314: Update the custom-header handling around
mcp_auth.normalize_custom_headers to expand environment variables in the header
mapping before normalization and validation. Then pass the expanded mapping to
normalize_custom_headers and use its validated result for connection["headers"],
preserving the existing McpAuthConfigError-to-AdapterConfigError conversion.

In `@adapters/hermes/src/nemo_fabric_adapters/hermes/adapter.py`:
- Around line 536-547: Update authenticate() to avoid replacing process-global
sys.stdin while the worker thread runs interactive OAuth. Isolate
discover_mcp_tools() input in a subprocess or otherwise coordinate lifecycle
reads so the main-thread lifecycle reader continues consuming real stdin and
stop messages cannot be lost during authentication.
- Around line 508-571: Update _authenticate_mcp_servers to execute every
synchronous get_mcp_status() and refresh_agent_mcp_tools() call through
asyncio.to_thread, including both status lookups and the final tool refresh,
while preserving their existing ordering and behavior.

In `@crates/fabric-core/src/config.rs`:
- Around line 1537-1566: Update the ServiceAccount validation branch in the
McpAuthenticationConfig match to require token_url to use HTTPS before accepting
the configuration. Preserve an explicit loopback exception only if the existing
configuration policy supports local development, and return invalid_config for
non-secure endpoints using the token_url field path.
- Around line 758-814: Update the OAuth2 and ServiceAccount variants of
McpAuthenticationConfig with serde unknown-field rejection, add regression tests
confirming extra authentication fields fail deserialization, and update both
authentication schema definitions to set additionalProperties: false so Rust,
the Python SDK, and CLI validation consistently reject unknown fields.

In
`@docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx`:
- Line 24: Update the MCP server URL documentation comment in MCPServerConfig
within crates/fabric-core/src/config.rs to format transport=stdio as inline
code, then run just docs to regenerate the reference page; do not edit the
generated MDX directly.

In `@python/src/nemo_fabric/models.py`:
- Around line 426-428: Widen the authentication type annotations to accept
mappings alongside McpAuthenticationConfig and None. Update the authentication
field near McpConfig, the McpConfig.add_server signature, and
FabricConfig.add_mcp_server to use the same dual-form pattern as relay, with
dict[str, Any] included.

In `@tests/adapters/test_adapters_common_mcp_auth.py`:
- Around line 83-95: Update
test_resolve_client_secret_uses_named_environment_variable and
test_create_mcp_oauth_provider_maps_client_configuration to accept the pytest
monkeypatch fixture and use monkeypatch.setenv for FABRIC_MCP_CLIENT_SECRET
instead of mutating os.environ. Remove the os import if no other code uses it.

In `@tests/adapters/test_claude_adapter.py`:
- Around line 364-385: Guard
test_claude_interactive_mcp_command_has_terminal_stdin with a platform
capability check and skip it when os.name is "nt" or os.openpty is unavailable.
Keep the existing pseudo-terminal assertions unchanged on supported POSIX
platforms.

In `@tests/adapters/test_codex_adapter.py`:
- Around line 275-277: Replace direct environment mutations with pytest
monkeypatching: in tests/adapters/test_codex_adapter.py lines 275-277, add the
monkeypatch fixture and use monkeypatch.setenv for all three variables; in
tests/adapters/test_hermes_adapter.py line 430, add the fixture and use
monkeypatch.setenv for FABRIC_MCP_CLIENT_SECRET; in
tests/adapters/test_claude_adapter.py lines 388-401, add the fixture and apply
monkeypatch.setenv for each browser_environment entry instead of
os.environ.update.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 03a29893-433f-404f-a2c5-67d5afdb236f

📥 Commits

Reviewing files that changed from the base of the PR and between 9f0881e and c4eefaa.

⛔ Files ignored due to path filters (6)
  • adapters/claude/uv.lock is excluded by !**/*.lock
  • adapters/codex/uv.lock is excluded by !**/*.lock
  • adapters/common/uv.lock is excluded by !**/*.lock
  • adapters/deepagents/uv.lock is excluded by !**/*.lock
  • adapters/hermes/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (73)
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
  • adapters/codex/src/nemo_fabric_adapters/codex/adapter.py
  • adapters/common/pyproject.toml
  • adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py
  • adapters/deepagents/pyproject.toml
  • adapters/deepagents/src/nemo_fabric_adapters/deepagents/adapter.py
  • adapters/hermes/src/nemo_fabric_adapters/hermes/adapter.py
  • crates/fabric-core/src/config.rs
  • crates/fabric-core/src/lib.rs
  • docs/reference/api/python-library-reference/index.md
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
  • docs/reference/api/rust-library-reference/nemo-fabric-core/adapter-contract/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/agent-config/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/agent-execution/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpauthenticationconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpexposure.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcptransport.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-oauthtokenendpointauthmethod.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofmode.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofstreamfieldnamepolicy.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofstreamtransport.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayotlptransport.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayunsupportedbehavior.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-resolutionstrategy.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-telemetryprovider.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-adapter-descriptor.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-config.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-metadataconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-modelconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvecontext.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvedadapterdescriptor.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runplan.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimecapabilities.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimeconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-skillconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryplan.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryproviderconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-tooldefinitionconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-workflowconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-workflowentrypointconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/enum-doctorstatus.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/fn-doctor-plan.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorcheck.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorreport.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/type-result.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/fn-version.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/schema/index.mdx
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/models.py
  • python/src/nemo_fabric/types.py
  • schemas/agent.schema.json
  • schemas/run-plan.schema.json
  • tests/adapters/test_adapter_package_metadata.py
  • tests/adapters/test_adapters_common_mcp_auth.py
  • tests/adapters/test_claude_adapter.py
  • tests/adapters/test_codex_adapter.py
  • tests/adapters/test_deepagents.py
  • tests/adapters/test_hermes_adapter.py
  • tests/python/test_native_sdk.py
  • tests/python/test_sdk_contract.py

Comment thread adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
Comment thread adapters/codex/src/nemo_fabric_adapters/codex/adapter.py Outdated
Comment thread adapters/codex/src/nemo_fabric_adapters/codex/adapter.py
Comment thread adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py
Comment thread adapters/deepagents/src/nemo_fabric_adapters/deepagents/adapter.py Outdated
### `url: String`

MCP server URL for network transports or executable for stdio.
MCP server URL or process command (when transport=stdio), depending on transport.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Should fix: Format the transport=stdio expression as code.

Line 24 renders the configuration expression as prose. Update the corresponding doc comment in crates/fabric-core/src/config.rs, wrap transport=stdio in backticks, and run just docs instead of editing this generated page directly.

As per coding guidelines, format expressions as inline code. As per path instructions, update the source and regenerate this reference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx`
at line 24, Update the MCP server URL documentation comment in MCPServerConfig
within crates/fabric-core/src/config.rs to format transport=stdio as inline
code, then run just docs to regenerate the reference page; do not edit the
generated MDX directly.

Sources: Coding guidelines, Path instructions

Comment on lines +426 to +428
authentication: McpAuthenticationConfig | None = Field(
default=None, exclude_if=lambda value: value is None
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Widen the authentication annotations to accept a mapping.

The annotation is McpAuthenticationConfig | None, but the documented usage passes a plain dict. tests/python/test_sdk_contract.py Line 340-350 and Line 576-584 both pass mappings, and pydantic coerces them at runtime.

A static type checker rejects that documented form in consumer code. The relay field at Line 830 already models the dual form as RelayConfig | dict[str, Any] | None. Apply the same pattern to the field at Line 426, to McpConfig.add_server at Line 491, and to FabricConfig.add_mcp_server at Line 855.

♻️ Proposed annotation change
-    authentication: McpAuthenticationConfig | None = Field(
+    authentication: McpAuthenticationConfig | Mapping[str, Any] | None = Field(
         default=None, exclude_if=lambda value: value is None
     )
-        authentication: McpAuthenticationConfig | None = None,
+        authentication: McpAuthenticationConfig | Mapping[str, Any] | None = None,

Also applies to: 491-491, 855-855

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/src/nemo_fabric/models.py` around lines 426 - 428, Widen the
authentication type annotations to accept mappings alongside
McpAuthenticationConfig and None. Update the authentication field near
McpConfig, the McpConfig.add_server signature, and FabricConfig.add_mcp_server
to use the same dual-form pattern as relay, with dict[str, Any] included.

Comment on lines +83 to +95
def test_resolve_client_secret_uses_named_environment_variable():
os.environ["FABRIC_MCP_CLIENT_SECRET"] = "oauth-secret"
config = mcp_auth.McpOAuth2Config(
client_id="fabric-client",
client_secret_env="FABRIC_MCP_CLIENT_SECRET",
scopes=(),
redirect_uri=None,
)

assert (
mcp_auth.resolve_client_secret("docs", config, require_client_id=True)
== "oauth-secret"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use monkeypatch.setenv instead of writing to os.environ.

Line 84 and Line 164 set FABRIC_MCP_CLIENT_SECRET in the real process environment and never remove it. The value then leaks into every later test in the session and makes results order-dependent. monkeypatch already appears elsewhere in this file.

🧪 Proposed fix for both sites
-def test_resolve_client_secret_uses_named_environment_variable():
-    os.environ["FABRIC_MCP_CLIENT_SECRET"] = "oauth-secret"
+def test_resolve_client_secret_uses_named_environment_variable(monkeypatch):
+    monkeypatch.setenv("FABRIC_MCP_CLIENT_SECRET", "oauth-secret")
     config = mcp_auth.McpOAuth2Config(

Apply the same change to test_create_mcp_oauth_provider_maps_client_configuration at Line 163-164, then drop the now-unused os import if nothing else needs it.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_resolve_client_secret_uses_named_environment_variable():
os.environ["FABRIC_MCP_CLIENT_SECRET"] = "oauth-secret"
config = mcp_auth.McpOAuth2Config(
client_id="fabric-client",
client_secret_env="FABRIC_MCP_CLIENT_SECRET",
scopes=(),
redirect_uri=None,
)
assert (
mcp_auth.resolve_client_secret("docs", config, require_client_id=True)
== "oauth-secret"
)
def test_resolve_client_secret_uses_named_environment_variable(monkeypatch):
monkeypatch.setenv("FABRIC_MCP_CLIENT_SECRET", "oauth-secret")
config = mcp_auth.McpOAuth2Config(
client_id="fabric-client",
client_secret_env="FABRIC_MCP_CLIENT_SECRET",
scopes=(),
redirect_uri=None,
)
assert (
mcp_auth.resolve_client_secret("docs", config, require_client_id=True)
== "oauth-secret"
)
🧰 Tools
🪛 Ruff (0.16.1)

[error] 84-84: Possible hardcoded password assigned to: "FABRIC_MCP_CLIENT_SECRET"

(S105)


[error] 87-87: Possible hardcoded password assigned to argument: "client_secret_env"

(S106)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/adapters/test_adapters_common_mcp_auth.py` around lines 83 - 95, Update
test_resolve_client_secret_uses_named_environment_variable and
test_create_mcp_oauth_provider_maps_client_configuration to accept the pytest
monkeypatch fixture and use monkeypatch.setenv for FABRIC_MCP_CLIENT_SECRET
instead of mutating os.environ. Remove the os import if no other code uses it.

Source: Coding guidelines

Comment thread tests/adapters/test_claude_adapter.py Outdated
Comment on lines +275 to +277
os.environ["DBUS_SESSION_BUS_ADDRESS"] = "unix:path=/run/user/1000/bus"
os.environ["FABRIC_UNRELATED_SECRET"] = "do-not-forward"
os.environ["XDG_RUNTIME_DIR"] = "/run/user/1000"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Three test files write os.environ directly, so values leak between tests. The shared root cause is direct process-environment mutation instead of monkeypatch.setenv. Each adapter under test reads these names from its INHERITED_ENV_NAMES set or through mcp_auth.resolve_client_secret, so a leaked value makes later tests order dependent and can mask a missing-variable failure.

  • tests/adapters/test_codex_adapter.py#L275-L277: replace the DBUS_SESSION_BUS_ADDRESS, FABRIC_UNRELATED_SECRET, and XDG_RUNTIME_DIR assignments with monkeypatch.setenv and add the monkeypatch fixture to the signature.
  • tests/adapters/test_hermes_adapter.py#L430-L430: replace the FABRIC_MCP_CLIENT_SECRET assignment with monkeypatch.setenv and add the monkeypatch fixture to the signature.
  • tests/adapters/test_claude_adapter.py#L388-L401: replace os.environ.update(browser_environment) with a monkeypatch.setenv loop over browser_environment and add the monkeypatch fixture to the signature.
🧰 Tools
🪛 Ruff (0.16.1)

[error] 276-276: Possible hardcoded password assigned to: "FABRIC_UNRELATED_SECRET"

(S105)

📍 Affects 3 files
  • tests/adapters/test_codex_adapter.py#L275-L277 (this comment)
  • tests/adapters/test_hermes_adapter.py#L430-L430
  • tests/adapters/test_claude_adapter.py#L388-L401
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/adapters/test_codex_adapter.py` around lines 275 - 277, Replace direct
environment mutations with pytest monkeypatching: in
tests/adapters/test_codex_adapter.py lines 275-277, add the monkeypatch fixture
and use monkeypatch.setenv for all three variables; in
tests/adapters/test_hermes_adapter.py line 430, add the fixture and use
monkeypatch.setenv for FABRIC_MCP_CLIENT_SECRET; in
tests/adapters/test_claude_adapter.py lines 388-401, add the fixture and apply
monkeypatch.setenv for each browser_environment entry instead of
os.environ.update.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/adapters/test_claude_adapter.py`:
- Around line 391-411: Extend the tests around
test_claude_interactive_mcp_command_requires_local_pseudo_terminal with a
focused case that monkeypatches os.openpty to raise OSError. Invoke
_run_claude_mcp_command with interactive=True and assert ClaudeAdapterError has
code claude_mcp_authentication_failed and a message matching requires a local
pseudo-terminal.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 3e6619f4-ea4d-4671-a8e2-352a006786a0

📥 Commits

Reviewing files that changed from the base of the PR and between c4eefaa and b241a2b.

📒 Files selected for processing (1)
  • tests/adapters/test_claude_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Test (Python 3.11, macos-arm64)
  • GitHub Check: Test (Python 3.14, macos-arm64)
  • GitHub Check: Test (Python 3.12, macos-arm64)
  • GitHub Check: Test (Python 3.13, macos-arm64)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{rs,py,pyi,json,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Determine and update every affected public surface, including the CLI, PyO3 bindings, Python SDK, type stubs, schemas, and adapter contract, so they remain in parity.

Files:

  • tests/adapters/test_claude_adapter.py
**/*

📄 CodeRabbit inference engine (.agents/skills/karpathy-guidelines/SKILL.md)

**/*: Before implementing, explicitly state assumptions, surface ambiguity and tradeoffs, present multiple interpretations when relevant, and ask for clarification rather than silently deciding or proceeding when requirements are unclear.
Prefer the minimum code needed to solve the requested problem: avoid speculative features, unnecessary abstractions, unrequested flexibility, and handling of impossible scenarios; simplify overcomplicated solutions.
When editing existing code, make surgical changes only: do not modify unrelated code, comments, formatting, or pre-existing dead code; match the existing style, and remove only unused imports, variables, or functions introduced by your changes.
Define verifiable success criteria for each task, such as writing regression tests for bugs and invalid-input tests for validation, then verify the implementation against those criteria. For multi-step work, state a brief plan with a verification check for each step.

**/*: Always spell NVIDIA in all caps; do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun, because the name begins with an “en” sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company; use trademark symbols with product names only when required by the document type or legal guidance.
Verify official capitalization, spacing, hyphenation, and spelling for NVIDIA and third-party product names; do not rewrite official product names for grammar or title-case rules.
Precede NVIDIA product names with NVIDIA on first mention when natural and accurate, and link the first mention when the destination helps the reader.
On first use, include the company name and full model qualifier when it helps identify the model; preserve official capitalization and punctuation, and use shorter family names only after establishing the full name.
For learning-oriented and developer content, do not force trademark symbols unless explicitly required; for press, ...

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For native binding changes, run cargo check -p fabric-python --locked.

Use snake_case for functions and variables; use PascalCase for Rust types and Python classes.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Python code or a Python-facing adapter changes, run just test-python.

In Python SDK, adapters, examples, and tests, follow the existing style, use type annotations for public APIs, and keep native binding declarations synchronized with their Rust implementations.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{rs,py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{rs,py,pyi}: If public configuration types change, confirm schema snapshot tests in just test-rust pass and review generated schema diffs.
For schema or public contract changes, run both language suites and review changes under schemas/ and generated API references.

Files:

  • tests/adapters/test_claude_adapter.py
tests/adapters/**/*.py

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

tests/adapters/**/*.py: If an adapter or integration changes, run its focused tests.
For adapter behavior changes, run focused adapter tests under tests/adapters, then run just test-python.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{py,pyi,rs}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

For Python SDK or PyO3 binding changes, use python-tests, run focused pytest tests first, then just test-python; rebuild with just build-python when native code or packaging changes.

Public contract changes must keep native Python binding declarations synchronized with their Rust implementations.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{rs,py,toml}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

When editing version helpers, verify every nemo-fabric-* workspace package through Cargo metadata and reject a static version in python/pyproject.toml.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{toml,rs,py}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Avoid blind repository-wide replacement of version-like strings; distinguish package-version references from examples and unrelated dependency versions.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{md,mdx,yml,py,rs,sh}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Keep documentation aligned with current NeMo Fabric behavior, repository layout, entry points, commands, package names, APIs, bindings, and support claims.

Files:

  • tests/adapters/test_claude_adapter.py
tests/**/*.{rs,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the corresponding Rust crate or the relevant area under tests/.

Files:

  • tests/adapters/test_claude_adapter.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run Python tests.
Do not add @pytest.mark.asyncio to tests; async tests are automatically detected by the async runner.
Do not add -> None return type annotations to test functions.
When mocking a class, use unittest.mock.MagicMock or AsyncMock, using the spec argument when necessary, rather than defining a new class.
Prefix mocked class names with mock, not fake.
Prefer pytest fixtures over helper methods.
If a fixture is needed in multiple test files, define it once in conftest.py rather than repeating it.
Define fixtures using @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and a <fixture_name>_fixture function; specify scope only when it is not function.
Prefer pytest.mark.parametrize over separate tests for different input types.
Use @pytest.mark.usefixtures when a fixture is needed but its returned value is unused or it returns no value.
Avoid defensive programming in tests; access expected values directly so missing data raises a clear failure, such as using results["data"] instead of results.get("data").
When adapter installation metadata changes, packaging metadata tests must directly assert that the root project depends unconditionally on the exact-version nemo-fabric-runtime distribution.
Packaging metadata tests must verify that each root harness extra delegates to the matching version of the leaf adapter's harness extra.
Packaging metadata tests must verify that bare leaf dependencies remain adapter-owned and that the root adapter-tests dependency group installs each leaf through its harness extra.
Packaging metadata tests must verify that every leaf provides full; only adapters importing NeMo Relay Python APIs provide relay, while adapters using an external Relay executable have full equal to harness.

Files:

  • tests/adapters/test_claude_adapter.py
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/adapters/test_claude_adapter.py
🔇 Additional comments (1)
tests/adapters/test_claude_adapter.py (1)

364-366: LGTM!

Comment thread tests/adapters/test_claude_adapter.py Outdated
Signed-off-by: David Gardner <dagardner@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
adapters/claude/src/nemo_fabric_adapters/claude/adapter.py (3)

409-424: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a regression test for CLI path resolution. Keep _resolve_path(payload, cli_path) as the only normalization. Do not call Path.expanduser() or use harness.settings.cwd. Assert that ~/bin/claude resolves under base_dir, while absolute paths remain unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 409
- 424, The CLI path resolution regression coverage is missing. Add a test for
_resolve_path(payload, cli_path) that verifies ~/bin/claude resolves relative to
base_dir and absolute paths remain unchanged; keep _resolve_path as the sole
normalization path without calling Path.expanduser() or using
harness.settings.cwd.

Source: Learnings


449-452: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make subprocess teardown bounded and exception-safe.

If create_subprocess_exec fails, close both PTY descriptors. The current cleanup leaks master.

During timeout or cancellation, bound process.wait(). If terminate() does not stop the process, call kill() and reap it. Drain the non-interactive PIPE streams during teardown.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 449
- 452, Update the subprocess cleanup around the adapter’s exception-handling
path to always close both PTY descriptors, including master, when process
creation fails. During timeout or cancellation, bound process.wait(), escalate
from terminate() to kill() if needed, then reap the process, and drain the
non-interactive PIPE streams during teardown; keep cleanup exception-safe.

1319-1386: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Run the repository’s configured Python checks before merge.

The python-tests command is undefined and exits with status 127. Run the focused test with uv run --no-sync pytest tests/adapters/test_claude_adapter.py, then run just test-python. Run the configured pre-commit checks before the final test pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 1319
- 1386, Run the focused Claude adapter test with uv run --no-sync pytest
tests/adapters/test_claude_adapter.py, then execute the configured pre-commit
checks and run just test-python. Do not use the undefined python-tests command;
resolve any failures before the final test pass.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py`:
- Around line 409-424: The CLI path resolution regression coverage is missing.
Add a test for _resolve_path(payload, cli_path) that verifies ~/bin/claude
resolves relative to base_dir and absolute paths remain unchanged; keep
_resolve_path as the sole normalization path without calling Path.expanduser()
or using harness.settings.cwd.
- Around line 449-452: Update the subprocess cleanup around the adapter’s
exception-handling path to always close both PTY descriptors, including master,
when process creation fails. During timeout or cancellation, bound
process.wait(), escalate from terminate() to kill() if needed, then reap the
process, and drain the non-interactive PIPE streams during teardown; keep
cleanup exception-safe.
- Around line 1319-1386: Run the focused Claude adapter test with uv run
--no-sync pytest tests/adapters/test_claude_adapter.py, then execute the
configured pre-commit checks and run just test-python. Do not use the undefined
python-tests command; resolve any failures before the final test pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b59cb374-e4d1-49b3-aa33-9c8f8d2de7b3

📥 Commits

Reviewing files that changed from the base of the PR and between b241a2b and c27c2fb.

📒 Files selected for processing (1)
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout. (17)
  • GitHub Check: Preview docs
  • GitHub Check: Test (Python 3.11, linux-arm64)
  • GitHub Check: Test (Python 3.13, windows-amd64)
  • GitHub Check: Test (Python 3.14, macos-arm64)
  • GitHub Check: Test (Python 3.12, linux-arm64)
  • GitHub Check: Test (Python 3.13, linux-arm64)
  • GitHub Check: Test (Python 3.14, windows-amd64)
  • GitHub Check: Test (Python 3.11, windows-amd64)
  • GitHub Check: Test (Python 3.11, macos-arm64)
  • GitHub Check: Test (Python 3.14, linux-arm64)
  • GitHub Check: Test (Python 3.13, macos-arm64)
  • GitHub Check: Test (Python 3.12, windows-amd64)
  • GitHub Check: Test (Python 3.12, macos-arm64)
  • GitHub Check: Test (Python 3.14, linux-amd64)
  • GitHub Check: Test (Python 3.11, linux-amd64)
  • GitHub Check: Test (Python 3.13, linux-amd64)
  • GitHub Check: Pre-commit
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{rs,py,pyi,json,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Determine and update every affected public surface, including the CLI, PyO3 bindings, Python SDK, type stubs, schemas, and adapter contract, so they remain in parity.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*

📄 CodeRabbit inference engine (.agents/skills/karpathy-guidelines/SKILL.md)

**/*: Before implementing, explicitly state assumptions, surface ambiguity and tradeoffs, present multiple interpretations when relevant, and ask for clarification rather than silently deciding or proceeding when requirements are unclear.
Prefer the minimum code needed to solve the requested problem: avoid speculative features, unnecessary abstractions, unrequested flexibility, and handling of impossible scenarios; simplify overcomplicated solutions.
When editing existing code, make surgical changes only: do not modify unrelated code, comments, formatting, or pre-existing dead code; match the existing style, and remove only unused imports, variables, or functions introduced by your changes.
Define verifiable success criteria for each task, such as writing regression tests for bugs and invalid-input tests for validation, then verify the implementation against those criteria. For multi-step work, state a brief plan with a verification check for each step.

**/*: Always spell NVIDIA in all caps; do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun, because the name begins with an “en” sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company; use trademark symbols with product names only when required by the document type or legal guidance.
Verify official capitalization, spacing, hyphenation, and spelling for NVIDIA and third-party product names; do not rewrite official product names for grammar or title-case rules.
Precede NVIDIA product names with NVIDIA on first mention when natural and accurate, and link the first mention when the destination helps the reader.
On first use, include the company name and full model qualifier when it helps identify the model; preserve official capitalization and punctuation, and use shorter family names only after establishing the full name.
For learning-oriented and developer content, do not force trademark symbols unless explicitly required; for press, ...

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For native binding changes, run cargo check -p fabric-python --locked.

Use snake_case for functions and variables; use PascalCase for Rust types and Python classes.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Python code or a Python-facing adapter changes, run just test-python.

In Python SDK, adapters, examples, and tests, follow the existing style, use type annotations for public APIs, and keep native binding declarations synchronized with their Rust implementations.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{rs,py,pyi}: If public configuration types change, confirm schema snapshot tests in just test-rust pass and review generated schema diffs.
For schema or public contract changes, run both language suites and review changes under schemas/ and generated API references.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{py,pyi,rs}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

For Python SDK or PyO3 binding changes, use python-tests, run focused pytest tests first, then just test-python; rebuild with just build-python when native code or packaging changes.

Public contract changes must keep native Python binding declarations synchronized with their Rust implementations.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py,toml}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

When editing version helpers, verify every nemo-fabric-* workspace package through Cargo metadata and reject a static version in python/pyproject.toml.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{toml,rs,py}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Avoid blind repository-wide replacement of version-like strings; distinguish package-version references from examples and unrelated dependency versions.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{md,mdx,yml,py,rs,sh}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Keep documentation aligned with current NeMo Fabric behavior, repository layout, entry points, commands, package names, APIs, bindings, and support claims.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
adapters/*/src/**/*.py

📄 CodeRabbit inference engine (.agents/skills/contribute-adapter/SKILL.md)

adapters/*/src/**/*.py: Implement adapters using the existing Fabric python or process runner and normalized request/result contracts; do not add a runner or one-off abstraction for a single adapter.
Treat normalized config, Fabric-resolved plans, and runtime_context as authoritative; reserve harness.settings for adapter-wide behavior and apply precedence in the order: normalized config, plans/context, harness settings, descriptor/default values.
Reject conflicting duplicate declarations and unsupported behavior with actionable errors naming the field and supported alternatives; never silently drop configuration.
Validate dependency versions, hooks, and credentials before harness invocation, and never expose credential values in outputs, errors, events, logs, or fixtures.
Forward only required system, selected credential, telemetry, and documented harness-specific environment variables; never forward or log unrelated environment values.
Maintain one local adapter host per Fabric runtime across ordered startinvoke*stop operations. Emit one JSON lifecycle response per request on stdout and diagnostics on stderr.
Return harness-level invocation failures as successful lifecycle responses containing response: null, failed: true, and structured error fields (code, message, retryable, and optional metadata).
Do not emit NeMo Relay stream records on adapter stdout; return exactly one terminal lifecycle response while streaming occurs through the SDK-owned out-of-band endpoint.
Scope workspace, generated configuration, state, sessions, and artifacts to the resolved runtime context, and isolate stateful adapter instances by Fabric runtime ID.
Use start to initialize adapter-owned harness state, retain it for continuation across repeated invoke calls on the same runtime, and release it in stop.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
{adapters/**,examples/**}

⚙️ CodeRabbit configuration file

{adapters/**,examples/**}: Review adapter and example changes for command correctness, config/schema consistency, artifact handling, and compatibility with the public NeMo Fabric contracts.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
🧠 Learnings (1)
📚 Learning: 2026-07-09T22:28:51.689Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 43
File: adapters/claude-sdk/src/nemo_fabric_adapters/claude_sdk/adapter.py:164-168
Timestamp: 2026-07-09T22:28:51.689Z
Learning: In the NeMo-Fabric adapters, treat path values used in Fabric adapter configuration (including logic like `_resolve_path` in adapter.py) as config-root-relative. Do not apply `Path.expanduser()` (or otherwise apply `~`/home or shell-style expansion), because it will make the resolved paths normalize inconsistently across adapters. Also, do not rely on or add any resolution behavior that uses `harness.settings.cwd` as an override point for these adapter paths—`harness.settings.cwd` is explicitly unsupported in this adapter context.

Applied to files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
🔇 Additional comments (5)
adapters/claude/src/nemo_fabric_adapters/claude/adapter.py (5)

482-485: LGTM!

Also applies to: 493-496


1058-1061: 🔒 Security & Privacy

Verify per-server isolation for OAuth client secrets.

build_options passes one shared environment to ClaudeAgentOptions.env at Line 924. These lines add each configured client_secret_env value to that process-wide environment.

If Claude CLI forwards that environment to stdio MCP processes, one server can read another server’s client secret. Keep secrets only in the authentication subprocess, or confirm that Claude CLI removes them before launching each MCP server. Add a two-server isolation test.

As per path instructions, adapters must forward only required system, selected credential, telemetry, and documented harness-specific environment variables.

Source: Path instructions


1263-1267: 🩺 Stability & Availability

Use one invocation deadline for authentication and the query.

invoke passes the full invocation_timeout to _authenticate_mcp_servers, then passes the same full value to _run_query.

If authentication applies the timeout to each server or retry, multiple servers can multiply the configured deadline before the query receives another full timeout. Compute one absolute deadline and pass the remaining budget to every authentication step and the query.

Also applies to: 1285-1290


1192-1193: 🩺 Stability & Availability

Revalidate MCP authentication after connection changes.

The new _mcp_authentication_checked state appears to make authentication one-time per runtime. The flag resets only during stop.

If an OAuth token expires or an MCP connection becomes disconnected after the first invocation, later invocations can skip login or reconnection. Confirm that Claude SDK refreshes and reconnects independently. Otherwise, recheck status per invocation or clear the flag after authentication and connection failures.

Also applies to: 1319-1386, 1452-1456


302-304: 🗄️ Data Integrity & Integration

No MCP contract change required. The adapter preserves the transport fields, maps OAuth fields, consumes client_secret_env and authorization_timeout_seconds, and rejects unsupported client_name and token_endpoint_auth_method fields with explicit errors.

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
adapters/claude/src/nemo_fabric_adapters/claude/adapter.py (1)

1340-1356: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Apply the invocation deadline to MCP status polling.

_mcp_server_status can wait five seconds before login and five seconds after reconnect. It does not receive invocation_timeout. An invocation with a shorter timeout can exceed its deadline before _authenticate_mcp_servers returns.

Wrap the full authentication sequence in asyncio.timeout(invocation_timeout). Add a regression test with a continuously pending server and a shorter invocation timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 1340
- 1356, The MCP authentication flow in `_authenticate_mcp_servers` must enforce
the overall invocation deadline, including both `_mcp_server_status` calls,
`_login_mcp_server`, and `client.reconnect_mcp_server`. Wrap the complete
per-invocation authentication sequence in `asyncio.timeout(invocation_timeout)`
and add a regression test using a continuously pending server with a shorter
invocation timeout.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py`:
- Around line 1340-1356: The MCP authentication flow in
`_authenticate_mcp_servers` must enforce the overall invocation deadline,
including both `_mcp_server_status` calls, `_login_mcp_server`, and
`client.reconnect_mcp_server`. Wrap the complete per-invocation authentication
sequence in `asyncio.timeout(invocation_timeout)` and add a regression test
using a continuously pending server with a shorter invocation timeout.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 6f111250-026e-4489-b31b-7b6401f87044

📥 Commits

Reviewing files that changed from the base of the PR and between c27c2fb and 556486a.

📒 Files selected for processing (2)
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
  • tests/adapters/test_claude_adapter.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{rs,py,pyi,json,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Determine and update every affected public surface, including the CLI, PyO3 bindings, Python SDK, type stubs, schemas, and adapter contract, so they remain in parity.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*

📄 CodeRabbit inference engine (.agents/skills/karpathy-guidelines/SKILL.md)

**/*: Before implementing, explicitly state assumptions, surface ambiguity and tradeoffs, present multiple interpretations when relevant, and ask for clarification rather than silently deciding or proceeding when requirements are unclear.
Prefer the minimum code needed to solve the requested problem: avoid speculative features, unnecessary abstractions, unrequested flexibility, and handling of impossible scenarios; simplify overcomplicated solutions.
When editing existing code, make surgical changes only: do not modify unrelated code, comments, formatting, or pre-existing dead code; match the existing style, and remove only unused imports, variables, or functions introduced by your changes.
Define verifiable success criteria for each task, such as writing regression tests for bugs and invalid-input tests for validation, then verify the implementation against those criteria. For multi-step work, state a brief plan with a verification check for each step.

**/*: Always spell NVIDIA in all caps; do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun, because the name begins with an “en” sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company; use trademark symbols with product names only when required by the document type or legal guidance.
Verify official capitalization, spacing, hyphenation, and spelling for NVIDIA and third-party product names; do not rewrite official product names for grammar or title-case rules.
Precede NVIDIA product names with NVIDIA on first mention when natural and accurate, and link the first mention when the destination helps the reader.
On first use, include the company name and full model qualifier when it helps identify the model; preserve official capitalization and punctuation, and use shorter family names only after establishing the full name.
For learning-oriented and developer content, do not force trademark symbols unless explicitly required; for press, ...

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For native binding changes, run cargo check -p fabric-python --locked.

Use snake_case for functions and variables; use PascalCase for Rust types and Python classes.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Python code or a Python-facing adapter changes, run just test-python.

In Python SDK, adapters, examples, and tests, follow the existing style, use type annotations for public APIs, and keep native binding declarations synchronized with their Rust implementations.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{rs,py,pyi}: If public configuration types change, confirm schema snapshot tests in just test-rust pass and review generated schema diffs.
For schema or public contract changes, run both language suites and review changes under schemas/ and generated API references.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
tests/adapters/**/*.py

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

tests/adapters/**/*.py: If an adapter or integration changes, run its focused tests.
For adapter behavior changes, run focused adapter tests under tests/adapters, then run just test-python.

Files:

  • tests/adapters/test_claude_adapter.py
**/*.{py,pyi,rs}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

For Python SDK or PyO3 binding changes, use python-tests, run focused pytest tests first, then just test-python; rebuild with just build-python when native code or packaging changes.

Public contract changes must keep native Python binding declarations synchronized with their Rust implementations.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py,toml}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

When editing version helpers, verify every nemo-fabric-* workspace package through Cargo metadata and reject a static version in python/pyproject.toml.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{toml,rs,py}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Avoid blind repository-wide replacement of version-like strings; distinguish package-version references from examples and unrelated dependency versions.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{md,mdx,yml,py,rs,sh}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Keep documentation aligned with current NeMo Fabric behavior, repository layout, entry points, commands, package names, APIs, bindings, and support claims.

Files:

  • tests/adapters/test_claude_adapter.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
tests/**/*.{rs,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the corresponding Rust crate or the relevant area under tests/.

Files:

  • tests/adapters/test_claude_adapter.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run Python tests.
Do not add @pytest.mark.asyncio to tests; async tests are automatically detected by the async runner.
Do not add -> None return type annotations to test functions.
When mocking a class, use unittest.mock.MagicMock or AsyncMock, using the spec argument when necessary, rather than defining a new class.
Prefix mocked class names with mock, not fake.
Prefer pytest fixtures over helper methods.
If a fixture is needed in multiple test files, define it once in conftest.py rather than repeating it.
Define fixtures using @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and a <fixture_name>_fixture function; specify scope only when it is not function.
Prefer pytest.mark.parametrize over separate tests for different input types.
Use @pytest.mark.usefixtures when a fixture is needed but its returned value is unused or it returns no value.
Avoid defensive programming in tests; access expected values directly so missing data raises a clear failure, such as using results["data"] instead of results.get("data").
When adapter installation metadata changes, packaging metadata tests must directly assert that the root project depends unconditionally on the exact-version nemo-fabric-runtime distribution.
Packaging metadata tests must verify that each root harness extra delegates to the matching version of the leaf adapter's harness extra.
Packaging metadata tests must verify that bare leaf dependencies remain adapter-owned and that the root adapter-tests dependency group installs each leaf through its harness extra.
Packaging metadata tests must verify that every leaf provides full; only adapters importing NeMo Relay Python APIs provide relay, while adapters using an external Relay executable have full equal to harness.

Files:

  • tests/adapters/test_claude_adapter.py
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/adapters/test_claude_adapter.py
adapters/*/src/**/*.py

📄 CodeRabbit inference engine (.agents/skills/contribute-adapter/SKILL.md)

adapters/*/src/**/*.py: Implement adapters using the existing Fabric python or process runner and normalized request/result contracts; do not add a runner or one-off abstraction for a single adapter.
Treat normalized config, Fabric-resolved plans, and runtime_context as authoritative; reserve harness.settings for adapter-wide behavior and apply precedence in the order: normalized config, plans/context, harness settings, descriptor/default values.
Reject conflicting duplicate declarations and unsupported behavior with actionable errors naming the field and supported alternatives; never silently drop configuration.
Validate dependency versions, hooks, and credentials before harness invocation, and never expose credential values in outputs, errors, events, logs, or fixtures.
Forward only required system, selected credential, telemetry, and documented harness-specific environment variables; never forward or log unrelated environment values.
Maintain one local adapter host per Fabric runtime across ordered startinvoke*stop operations. Emit one JSON lifecycle response per request on stdout and diagnostics on stderr.
Return harness-level invocation failures as successful lifecycle responses containing response: null, failed: true, and structured error fields (code, message, retryable, and optional metadata).
Do not emit NeMo Relay stream records on adapter stdout; return exactly one terminal lifecycle response while streaming occurs through the SDK-owned out-of-band endpoint.
Scope workspace, generated configuration, state, sessions, and artifacts to the resolved runtime context, and isolate stateful adapter instances by Fabric runtime ID.
Use start to initialize adapter-owned harness state, retain it for continuation across repeated invoke calls on the same runtime, and release it in stop.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
{adapters/**,examples/**}

⚙️ CodeRabbit configuration file

{adapters/**,examples/**}: Review adapter and example changes for command correctness, config/schema consistency, artifact handling, and compatibility with the public NeMo Fabric contracts.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
🧠 Learnings (1)
📚 Learning: 2026-07-09T22:28:51.689Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 43
File: adapters/claude-sdk/src/nemo_fabric_adapters/claude_sdk/adapter.py:164-168
Timestamp: 2026-07-09T22:28:51.689Z
Learning: In the NeMo-Fabric adapters, treat path values used in Fabric adapter configuration (including logic like `_resolve_path` in adapter.py) as config-root-relative. Do not apply `Path.expanduser()` (or otherwise apply `~`/home or shell-style expansion), because it will make the resolved paths normalize inconsistently across adapters. Also, do not rely on or add any resolution behavior that uses `harness.settings.cwd` as an override point for these adapter paths—`harness.settings.cwd` is explicitly unsupported in this adapter context.

Applied to files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
🔇 Additional comments (2)
adapters/claude/src/nemo_fabric_adapters/claude/adapter.py (1)

402-411: LGTM!

Also applies to: 464-511, 1218-1219, 1269-1282

tests/adapters/test_claude_adapter.py (1)

330-355: LGTM!

…th flow

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (3)
schemas/run-plan.schema.json (1)

1494-1500: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Fix the transport name in the custom_headers description.

The description says the header applies when transport is sse or streamable_http, but the McpTransport enum at Line 1601-1620 defines streamable-http with a hyphen. No configuration value named streamable_http exists, so the description misdirects config authors.

This file is generated output. Correct the Rust doc comment on McpServerConfig::custom_headers in crates/fabric-core/src/config.rs and regenerate the schema.

📝 Proposed description text
-          "description": "HTTP headers passed to an MCP server when transport is `sse` or `streamable_http`.",
+          "description": "HTTP headers passed to an MCP server when transport is `sse` or `streamable-http`.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas/run-plan.schema.json` around lines 1494 - 1500, Update the Rust doc
comment for McpServerConfig::custom_headers to reference the valid transport
name streamable-http instead of streamable_http, then regenerate
schemas/run-plan.schema.json so the generated description matches.
docs/reference/api/python-library-reference/nemo_fabric.models.md (1)

1035-1048: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Builder signatures still take an untyped transport.

McpServerConfig.transport is now Literal['stdio', 'sse', 'streamable-http'] (Line 920), but add_server and add_mcp_server (Line 2327) keep transport: str. Pydantic rejects a bad value at runtime, so this is not a correctness bug. A static type checker cannot catch transport="websocket" at the call site, which is the main benefit of the new literal.

Narrow both parameters to the same literal in python/src/nemo_fabric/models.py and regenerate this reference with just docs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/reference/api/python-library-reference/nemo_fabric.models.md` around
lines 1035 - 1048, Update the transport parameter types in both add_server and
add_mcp_server to use the same Literal['stdio', 'sse', 'streamable-http'] type
as McpServerConfig.transport, then regenerate the Python library reference
documentation with just docs.
python/src/nemo_fabric/models.py (1)

410-440: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add typed custom_headers support to the Python MCP API.

McpServerConfig, McpConfig.add_server, and FabricConfig.add_mcp_server omit the field, so direct construction and builders accept it only as an untyped extra. Add the typed field and forward it through both builders. Preserve existing extra_fields={"custom_headers": ...} usage without duplicate-key errors. Update the generated API reference and add typed validation coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/src/nemo_fabric/models.py` around lines 410 - 440, Add a typed
custom_headers field to McpServerConfig, then update McpConfig.add_server and
FabricConfig.add_mcp_server to accept and forward it without conflicting with
existing extra_fields custom_headers entries. Preserve current extra_fields
behavior, and update the generated API reference plus validation tests to cover
the new typed field.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py`:
- Around line 388-394: Remove the duration-based timeout parameter from
_self_authenticate_http_mcp_server and apply the operation timeout at its
caller, _authenticate_mcp_servers, using asyncio.timeout. Update the call and
preserve the existing timeout duration and authentication behavior while
satisfying Ruff ASYNC109.
- Around line 1219-1249: Replace the single _mcp_authentication_checked flag
with a set tracking completed MCP server names in the authentication flow. In
the loop around _self_authenticate_http_mcp_server, skip servers already in that
set and add each server only after its authentication, staged-header update,
reconnect, and connected-status validation succeed. Reset the completed-server
set in stop alongside the existing authentication state.
- Around line 463-482: Update the MCP configuration handling around config_path
and server_entry so JSON decoding and invalid mcpServers structure failures are
converted into ClaudeAdapterError, not only OSError. Ensure invoke receives the
structured failure and returns the existing harness-level response shape with
response: null, failed: true, and structured error fields.
- Around line 1229-1241: Update invoke to compute one absolute deadline at its
start, then derive each authentication timeout and the query timeout from the
remaining invocation budget rather than resetting to invocation_timeout. Pass
that remaining budget into _run_query, including accounting for per-server
status polling, so the full invoke flow remains bounded by
runtime.timeout_seconds.
- Around line 439-448: Centralize MCP OAuth token extraction by adding the
shared mcp_auth.access_token(provider) helper and replacing the local
context/current_tokens/access_token traversal in the Claude adapter’s OAuth
flow. Preserve the existing missing-token ClaudeAdapterError behavior and
metadata after using the helper.

---

Outside diff comments:
In `@docs/reference/api/python-library-reference/nemo_fabric.models.md`:
- Around line 1035-1048: Update the transport parameter types in both add_server
and add_mcp_server to use the same Literal['stdio', 'sse', 'streamable-http']
type as McpServerConfig.transport, then regenerate the Python library reference
documentation with just docs.

In `@python/src/nemo_fabric/models.py`:
- Around line 410-440: Add a typed custom_headers field to McpServerConfig, then
update McpConfig.add_server and FabricConfig.add_mcp_server to accept and
forward it without conflicting with existing extra_fields custom_headers
entries. Preserve current extra_fields behavior, and update the generated API
reference plus validation tests to cover the new typed field.

In `@schemas/run-plan.schema.json`:
- Around line 1494-1500: Update the Rust doc comment for
McpServerConfig::custom_headers to reference the valid transport name
streamable-http instead of streamable_http, then regenerate
schemas/run-plan.schema.json so the generated description matches.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2c57d02d-2a5d-4252-9039-294013b139cc

📥 Commits

Reviewing files that changed from the base of the PR and between 556486a and 32b6e14.

📒 Files selected for processing (9)
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • python/src/nemo_fabric/models.py
  • schemas/run-plan.schema.json
  • tests/python/test_native_sdk.py
  • tests/python/test_sdk_contract.py
💤 Files with no reviewable changes (1)
  • tests/python/test_native_sdk.py
📜 Review details
⏰ Context from checks skipped due to timeout. (18)
  • GitHub Check: Preview docs
  • GitHub Check: Test (Python 3.12, windows-amd64)
  • GitHub Check: Test (Python 3.13, linux-arm64)
  • GitHub Check: Test (Python 3.12, linux-arm64)
  • GitHub Check: Test (Python 3.11, linux-arm64)
  • GitHub Check: Test (Python 3.13, linux-amd64)
  • GitHub Check: Test (Python 3.12, macos-arm64)
  • GitHub Check: Test (Python 3.13, windows-amd64)
  • GitHub Check: Test (Python 3.11, linux-amd64)
  • GitHub Check: Test (Python 3.14, windows-amd64)
  • GitHub Check: Test (Python 3.11, windows-amd64)
  • GitHub Check: Test (Python 3.14, linux-arm64)
  • GitHub Check: Test (Python 3.13, macos-arm64)
  • GitHub Check: Test (Python 3.14, macos-arm64)
  • GitHub Check: Test (Python 3.12, linux-amd64)
  • GitHub Check: Test (Python 3.11, macos-arm64)
  • GitHub Check: Test (Python 3.14, linux-amd64)
  • GitHub Check: Pre-commit
🧰 Additional context used
📓 Path-based instructions (40)
{README.md,docs/**/*.{md,mdx,yml},examples/**/*.{md,mdx,yml}}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Keep package names, repository references, and build commands current in documentation and examples.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
{docs/**/*.{md,mdx,yml},examples/**/*.{md,mdx,yml}}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Update relevant getting-started, reference, adapter, and example documentation when the corresponding examples or adapters change.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

In MDX files, use JSX comment delimiters ({/* and */}) for top-of-file comments, including SPDX headers; do not use HTML comments.

**/*.mdx: For documentation site changes, run just docs to regenerate Python and Rust API references and validate Fern configuration.
MDX files must use the specified JSX-comment SPDX header.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
docs/**/*.{md,mdx,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Run just docs when the documentation site changes.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*

📄 CodeRabbit inference engine (.agents/skills/karpathy-guidelines/SKILL.md)

**/*: Before implementing, explicitly state assumptions, surface ambiguity and tradeoffs, present multiple interpretations when relevant, and ask for clarification rather than silently deciding or proceeding when requirements are unclear.
Prefer the minimum code needed to solve the requested problem: avoid speculative features, unnecessary abstractions, unrequested flexibility, and handling of impossible scenarios; simplify overcomplicated solutions.
When editing existing code, make surgical changes only: do not modify unrelated code, comments, formatting, or pre-existing dead code; match the existing style, and remove only unused imports, variables, or functions introduced by your changes.
Define verifiable success criteria for each task, such as writing regression tests for bugs and invalid-input tests for validation, then verify the implementation against those criteria. For multi-step work, state a brief plan with a verification check for each step.

**/*: Always spell NVIDIA in all caps; do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun, because the name begins with an “en” sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company; use trademark symbols with product names only when required by the document type or legal guidance.
Verify official capitalization, spacing, hyphenation, and spelling for NVIDIA and third-party product names; do not rewrite official product names for grammar or title-case rules.
Precede NVIDIA product names with NVIDIA on first mention when natural and accurate, and link the first mention when the destination helps the reader.
On first use, include the company name and full model qualifier when it helps identify the model; preserve official capitalization and punctuation, and use shorter family names only after establishing the full name.
For learning-oriented and developer content, do not force trademark symbols unless explicitly required; for press, ...

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • crates/fabric-core/src/agent_config.rs
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
  • schemas/run-plan.schema.json
**/*.{md,mdx,rst}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-guide.md)

**/*.{md,mdx,rst}: For NeMo Fabric documentation, verify technical claims against the current repository, public API, or documented command before reviewing style.
Always spell NVIDIA in all caps; do not use Nvidia, nvidia, or NV.
Format commands, code elements, expressions, package names, file names, and paths as inline code.
Use descriptive link text; avoid raw URLs and weak anchors such as here or read more.
Use title case consistently for technical documentation headings.
Introduce code blocks, lists, tables, and images with complete sentences.
Write procedures as imperative, parallel steps; split long procedures into smaller tasks.
Prefer active voice, present tense, short sentences, contractions, and plain English while preserving necessary technical precision.
Use can for possibility and reserve may for permission.
Use after for temporal relationships instead of once, and prefer refer to over see when directing readers to another resource.
Avoid culture-specific idioms, unnecessary Latinisms, jokes, and marketing exaggeration in technical documentation.
Spell out months in body text, avoid ordinal dates, and use clear time zones.
Spell out whole numbers from zero through nine unless they are technical values, parameters, versions, or UI values; use numerals for 10 or greater and commas in thousands.
Do not add trademark symbols to learning-oriented documentation unless the source, platform, or legal guidance explicitly requires them.
Do not replace precise technical terms with simpler words when doing so would lose precision.
Do not flag passive voice when the actor is unknown or the action is the important part.
Do not rewrite API names, package names, command flags, or code literals for style.

**/*.{md,mdx,rst}: Use consistent title case for technical-document headings and table headers; avoid quotation marks, ampersands, and exclamation marks in headings, while preserving official product, event, research, and whitepaper title ...

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
docs/reference/api/**/*

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Treat all files under docs/reference/api/ as generated output and do not modify them directly.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
docs/**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

docs/**/*.mdx: Use source-relative links with the target .mdx extension for links between files under docs/; do not use Fern site-root paths.
Use {/* ... */} delimiters for top-of-file MDX SPDX comments, not HTML comment delimiters.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
**/*.{md,mdx,yml,py,rs,sh}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Keep documentation aligned with current NeMo Fabric behavior, repository layout, entry points, commands, package names, APIs, bindings, and support claims.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • crates/fabric-core/src/agent_config.rs
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{md,mdx}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

**/*.{md,mdx}: Use the full product name NVIDIA NeMo Fabric on first use, typically in the title and H1; use NeMo Fabric thereafter. Use fabric alone only for the CLI tool and surround it with backticks.
Treat incorrect or stale commands, package names, paths, APIs, support claims, procedures, examples, terminology, or public behavior documentation as blocking issues.
Capitalize NVIDIA correctly and format code, commands, paths, and filenames as inline code where needed.
Use title case for technical-documentation headings.
Introduce code blocks, tables, and lists with complete lead-in sentences; ensure examples match current APIs and build commands.
Use descriptive anchor text, avoid raw URLs and generic labels such as here, and use repository-relative .mdx paths for links within docs/.
Prefer active voice, present tense, short sentences, plain English, consistent terminology, and imperative, parallel, scannable procedures.
Use after instead of once when expressing temporal sequence, and use can rather than may when describing possibility rather than permission.
Avoid ambiguous numeric dates and ordinal dates in body text.
For learning-oriented documentation, do not force trademark symbols unless the source document explicitly requires them.
When reporting documentation-review findings, lead with Must fix, Should fix, and Nice to have categories; include file path, line reference, current problem, rationale, and a concrete rewrite or direction.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Run just docs after changing the documentation site.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
{docs/**,README.md,AGENTS.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,AGENTS.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency with generated schemas.
For links between files under docs/, require paths relative to the source file with the target file's .mdx extension so they work in both Fern builds and repository browsers. Flag Fern site-root links such as NeMo Fabric overview; use the repository-relative equivalent, such as NeMo Fabric overview.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
{*.md,**/*.md,**/*.mdx,**/*.ipynb}

⚙️ CodeRabbit configuration file

{*.md,**/*.md,**/*.mdx,**/*.ipynb}: Enforce the product name in user-facing prose: use "NVIDIA NeMo Fabric" on first use and "NeMo Fabric" thereafter. Flag standalone capitalized "Fabric" when it refers to the product. Do not flag the lowercase fabric CLI command, package/import/crate names, code identifiers, API symbols, configuration keys, file paths, or unrelated generic uses of the word.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Implement new runtime or binding behavior in the shared Rust core first.

Files:

  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
**/*.{rs,py,pyi,json,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Determine and update every affected public surface, including the CLI, PyO3 bindings, Python SDK, type stubs, schemas, and adapter contract, so they remain in parity.

Files:

  • crates/fabric-core/src/agent_config.rs
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
  • schemas/run-plan.schema.json
**/*.{rs,toml}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For any Rust change, run just test-rust and cargo fmt --all -- --check.

For Rust core, CLI, or shared runtime semantic changes, run Rust formatting and tests, and add Python tests when behavior is exposed through the SDK.

Use Rust stable tooling; format Rust code with cargo fmt --all, verify formatting with cargo fmt --all -- --check, and compile with cargo check --workspace --locked.

Files:

  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For native binding changes, run cargo check -p fabric-python --locked.

Use snake_case for functions and variables; use PascalCase for Rust types and Python classes.

Files:

  • crates/fabric-core/src/agent_config.rs
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
crates/fabric-core/**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes under crates/fabric-core must run both the Rust and Python test suites.

Files:

  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
**/*.{rs,rmeta}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Rust code changes, run cargo fmt --all -- --check and just test-rust.

Files:

  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
crates/fabric-core/**/*.rs

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If crates/fabric-core changes in a way exposed through Python, run both the Rust and Python suites.

For Rust API reference changes, update Rust documentation comments under crates/fabric-core/ instead of generated API reference files.

Files:

  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
**/*.{rs,py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{rs,py,pyi}: If public configuration types change, confirm schema snapshot tests in just test-rust pass and review generated schema diffs.
For schema or public contract changes, run both language suites and review changes under schemas/ and generated API references.

Files:

  • crates/fabric-core/src/agent_config.rs
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{py,pyi,rs}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

For Python SDK or PyO3 binding changes, use python-tests, run focused pytest tests first, then just test-python; rebuild with just build-python when native code or packaging changes.

Public contract changes must keep native Python binding declarations synchronized with their Rust implementations.

Files:

  • crates/fabric-core/src/agent_config.rs
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{rs,py,toml}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

When editing version helpers, verify every nemo-fabric-* workspace package through Cargo metadata and reject a static version in python/pyproject.toml.

Files:

  • crates/fabric-core/src/agent_config.rs
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{toml,rs,py}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Avoid blind repository-wide replacement of version-like strings; distinguish package-version references from examples and unrelated dependency versions.

Files:

  • crates/fabric-core/src/agent_config.rs
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • crates/fabric-core/src/config.rs
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
crates/fabric-core/src/**/*.rs

⚙️ CodeRabbit configuration file

crates/fabric-core/src/**/*.rs: Review the Rust core for runtime lifecycle correctness, handle validation, capability routing accuracy, schema stability, and error semantics.
Public API changes should match committed schemas, tests, and documentation.

Files:

  • crates/fabric-core/src/agent_config.rs
  • crates/fabric-core/src/config.rs
**/*.{md,rst}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Update documentation and examples in the same branch as the public API change.

Files:

  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*.{md,rst,txt,adoc}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-language-mechanics.md)

**/*.{md,rst,txt,adoc}: For technical documentation, use professional, active, conversational, engaging, precise, and plain-English prose. Prefer active voice, present tense, short sentences, and scannable paragraphs. Avoid casual or imprecise language, swearing, threats, insults, jokes, puns, culture-specific idioms, marketing exaggeration, and unsupported third-party comparisons.
Use can for possibility and reserve may for permission; use after for temporal order; use refer to for cross-references; prefer short direct sentences and specific verbs; avoid unnecessary please in technical documentation.
Prefer active voice when the actor matters. Passive voice is acceptable when the actor is unknown or irrelevant, when the action or result is the focus, or in programmer documentation.
Use natural contractions in conversational technical prose, but do not force them in formal legal copy, API references, or generated text.
Prefer simpler English over Latinisms: use for example or such as instead of e.g., and so on instead of etc., that is instead of i.e., compared to instead of vs., and by, through, or using instead of via. Use industry-standard terms such as in silico, in vitro, and in vivo when appropriate, and italicize them in running text.
Use that without commas for essential clauses, and which with commas for nonessential clauses.
Format dates and times clearly: spell out months in body text; use forms such as June 12, 2025; avoid numeric or ordinal dates; capitalize days; use 12-hour time when appropriate; include a space before a.m. or p.m.; use ET and PT for needed time zones; avoid 24/7; and prefer from 12:30 to 1:00 p.m. for prose ranges.
Format numbers consistently: spell out zero through nine in body text, use numerals for 10 or greater and for technical values, use commas in thousands, do not begin a sentence with a numeral, spell out ordinals, and use numerals consistently within a category wh...

Files:

  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update relevant SDK, API reference, adapter, example, integration, and support documentation when the corresponding public surface changes.

Files:

  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*.{html,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

HTML and Markdown files must use the specified SPDX HTML-comment header.

Files:

  • docs/reference/api/python-library-reference/nemo_fabric.models.md
**/*.{py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Python code or a Python-facing adapter changes, run just test-python.

In Python SDK, adapters, examples, and tests, follow the existing style, use type annotations for public APIs, and keep native binding declarations synchronized with their Rust implementations.

Files:

  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/models.py
  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
tests/**/*.{rs,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the corresponding Rust crate or the relevant area under tests/.

Files:

  • tests/python/test_sdk_contract.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run Python tests.
Do not add @pytest.mark.asyncio to tests; async tests are automatically detected by the async runner.
Do not add -> None return type annotations to test functions.
When mocking a class, use unittest.mock.MagicMock or AsyncMock, using the spec argument when necessary, rather than defining a new class.
Prefix mocked class names with mock, not fake.
Prefer pytest fixtures over helper methods.
If a fixture is needed in multiple test files, define it once in conftest.py rather than repeating it.
Define fixtures using @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and a <fixture_name>_fixture function; specify scope only when it is not function.
Prefer pytest.mark.parametrize over separate tests for different input types.
Use @pytest.mark.usefixtures when a fixture is needed but its returned value is unused or it returns no value.
Avoid defensive programming in tests; access expected values directly so missing data raises a clear failure, such as using results["data"] instead of results.get("data").
When adapter installation metadata changes, packaging metadata tests must directly assert that the root project depends unconditionally on the exact-version nemo-fabric-runtime distribution.
Packaging metadata tests must verify that each root harness extra delegates to the matching version of the leaf adapter's harness extra.
Packaging metadata tests must verify that bare leaf dependencies remain adapter-owned and that the root adapter-tests dependency group installs each leaf through its harness extra.
Packaging metadata tests must verify that every leaf provides full; only adapters importing NeMo Relay Python APIs provide relay, while adapters using an external Relay executable have full equal to harness.

Files:

  • tests/python/test_sdk_contract.py
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/python/test_sdk_contract.py
python/src/nemo_fabric/**/*.py

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

For Python API reference changes, update source docstrings under python/src/nemo_fabric/ instead of generated API reference files.

Files:

  • python/src/nemo_fabric/models.py
python/src/nemo_fabric/**/*.{py,rs}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Ensure native extension naming and placement remain compatible with downstream consumers, including the editable maturin build producing nemo_fabric._native.

Files:

  • python/src/nemo_fabric/models.py
python/src/nemo_fabric/**/*

⚙️ CodeRabbit configuration file

python/src/nemo_fabric/**/*: Review Python SDK changes for typed API consistency, import-time dependency neutrality, async/session behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/src/nemo_fabric/models.py
adapters/*/src/**/*.py

📄 CodeRabbit inference engine (.agents/skills/contribute-adapter/SKILL.md)

adapters/*/src/**/*.py: Implement adapters using the existing Fabric python or process runner and normalized request/result contracts; do not add a runner or one-off abstraction for a single adapter.
Treat normalized config, Fabric-resolved plans, and runtime_context as authoritative; reserve harness.settings for adapter-wide behavior and apply precedence in the order: normalized config, plans/context, harness settings, descriptor/default values.
Reject conflicting duplicate declarations and unsupported behavior with actionable errors naming the field and supported alternatives; never silently drop configuration.
Validate dependency versions, hooks, and credentials before harness invocation, and never expose credential values in outputs, errors, events, logs, or fixtures.
Forward only required system, selected credential, telemetry, and documented harness-specific environment variables; never forward or log unrelated environment values.
Maintain one local adapter host per Fabric runtime across ordered startinvoke*stop operations. Emit one JSON lifecycle response per request on stdout and diagnostics on stderr.
Return harness-level invocation failures as successful lifecycle responses containing response: null, failed: true, and structured error fields (code, message, retryable, and optional metadata).
Do not emit NeMo Relay stream records on adapter stdout; return exactly one terminal lifecycle response while streaming occurs through the SDK-owned out-of-band endpoint.
Scope workspace, generated configuration, state, sessions, and artifacts to the resolved runtime context, and isolate stateful adapter instances by Fabric runtime ID.
Use start to initialize adapter-owned harness state, retain it for continuation across repeated invoke calls on the same runtime, and release it in stop.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
{adapters/**,examples/**}

⚙️ CodeRabbit configuration file

{adapters/**,examples/**}: Review adapter and example changes for command correctness, config/schema consistency, artifact handling, and compatibility with the public NeMo Fabric contracts.

Files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
**/*.{json,jsonschema}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Public contract changes must keep checked-in JSON Schema snapshots synchronized.

Files:

  • schemas/run-plan.schema.json
schemas/**/*

⚙️ CodeRabbit configuration file

schemas/**/*: Schemas are generated public contract snapshots. Check that schema diffs correspond to intentional Rust type changes and are covered by core tests.

Files:

  • schemas/run-plan.schema.json
🧠 Learnings (3)
📚 Learning: 2026-07-24T16:07:22.255Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 118
File: docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor.mdx:5-5
Timestamp: 2026-07-24T16:07:22.255Z
Learning: In this repo, files generated under `docs/reference/api/**` are NVIDIA NeMo Fabric API reference output. When reviewing changes to these generated pages, do not treat sidebar `position`/ordering updates as direct manual edits—these can be regenerated by running `just docs` after adding public types. Only flag substantive content changes that are not explained by generation.

Applied to files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx
📚 Learning: 2026-06-28T04:03:32.877Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 26
File: python/tests/smoke_typed_config.py:163-177
Timestamp: 2026-06-28T04:03:32.877Z
Learning: In NVIDIA NeMo Fabric Python SDK serialization of `RuntimeCapabilities` (to satisfy the “parity contract” with Rust core and the CLI), do not emit metadata keys when the corresponding metadata is absent. Instead, omit those fields entirely so the produced JSON matches the Rust/CLI output (e.g., avoid `null`, empty objects, or placeholder metadata). During review, verify the serializer/builders follow this omission rule and that Python outputs/parity tests reflect the same shape.

Applied to files:

  • python/src/nemo_fabric/models.py
📚 Learning: 2026-07-09T22:28:51.689Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 43
File: adapters/claude-sdk/src/nemo_fabric_adapters/claude_sdk/adapter.py:164-168
Timestamp: 2026-07-09T22:28:51.689Z
Learning: In the NeMo-Fabric adapters, treat path values used in Fabric adapter configuration (including logic like `_resolve_path` in adapter.py) as config-root-relative. Do not apply `Path.expanduser()` (or otherwise apply `~`/home or shell-style expansion), because it will make the resolved paths normalize inconsistently across adapters. Also, do not rely on or add any resolution behavior that uses `harness.settings.cwd` as an override point for these adapter paths—`harness.settings.cwd` is explicitly unsupported in this adapter context.

Applied to files:

  • adapters/claude/src/nemo_fabric_adapters/claude/adapter.py
🪛 Ruff (0.16.1)
adapters/claude/src/nemo_fabric_adapters/claude/adapter.py

[warning] 393-393: Async function definition with a timeout parameter

(ASYNC109)

🔇 Additional comments (12)
docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx (1)

12-18: LGTM!

Also applies to: 34-41

schemas/run-plan.schema.json (2)

1300-1424: Authentication variants still accept unknown fields.

Neither oneOf branch sets additionalProperties: false. A misplaced field such as token_url under type: "oauth2", or token_cache_buffer_seconds under type: "service_account", validates successfully and is then discarded. This is the same gap flagged on the Rust McpAuthenticationConfig variants; fix it in crates/fabric-core/src/config.rs and regenerate the snapshot.


1512-1519: LGTM!

Also applies to: 1549-1599, 1601-1620, 1688-1707

python/src/nemo_fabric/models.py (2)

426-428: The authentication annotation is narrower than the accepted input.

tests/python/test_sdk_contract.py Line 346-361 passes a plain mapping, and pydantic coerces it. A static type checker rejects that form. Widen the annotation the same way relay models its dual form.


413-413: LGTM!

crates/fabric-core/src/config.rs (2)

2465-2475: LGTM!

Also applies to: 2929-2956


3169-3169: LGTM!

Also applies to: 3183-3252, 3300-3413, 3803-3995

crates/fabric-core/src/agent_config.rs (1)

15-15: LGTM!

Also applies to: 321-326

tests/python/test_sdk_contract.py (1)

334-343: LGTM!

adapters/claude/src/nemo_fabric_adapters/claude/adapter.py (3)

338-344: LGTM!

Also applies to: 373-385


563-563: LGTM!

Also applies to: 1091-1091, 1109-1112, 1347-1347


1157-1170: 🩺 Stability & Availability

No change needed: MCP configuration errors are caught. AdapterConfigError subclasses ClaudeAdapterError, and _mcp_oauth_config converts McpAuthConfigError before it reaches this handler.

			> Likely an incorrect or invalid review comment.

Comment on lines +388 to +394
async def _self_authenticate_http_mcp_server(
server_name: str,
server_url: str,
config: mcp_auth.McpOAuth2Config,
*,
timeout: float,
) -> str:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Ruff flags the timeout parameter (ASYNC109).

The rule prefers the caller to bound the operation with asyncio.timeout instead of the coroutine accepting its own timeout argument. If the ASYNC ruleset runs in CI, this fails lint.

Either move the asyncio.timeout wrapper to _authenticate_mcp_servers at Line 1232-1237, or accept an absolute deadline instead of a duration.

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 393-393: Async function definition with a timeout parameter

(ASYNC109)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 388
- 394, Remove the duration-based timeout parameter from
_self_authenticate_http_mcp_server and apply the operation timeout at its
caller, _authenticate_mcp_servers, using asyncio.timeout. Update the call and
preserve the existing timeout duration and authentication behavior while
satisfying Ruff ASYNC109.

Source: Linters/SAST tools

Comment on lines +439 to +448
context = getattr(provider, "context", None)
tokens = getattr(context, "current_tokens", None) if context is not None else None
access_token: str | None = getattr(tokens, "access_token", None) if tokens is not None else None
if not access_token:
raise ClaudeAdapterError(
"claude_mcp_authentication_failed",
f"MCP server {server_name!r} OAuth flow did not return an access token",
metadata={"server": server_name},
)
return access_token

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether the token reach-through is duplicated across adapters.
set -uo pipefail

echo '--- current_tokens / access_token usage in adapters ---'
rg -n -C4 'current_tokens|access_token' adapters

echo '--- shared provider factory ---'
rg -n -C8 'def create_mcp_oauth_provider' adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py

Repository: NVIDIA/NeMo-Fabric

Length of output: 8803


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- OAuth provider consumers ---'
rg -n -C6 'create_mcp_oauth_provider|current_tokens|access_token' adapters --glob '*.py'

echo '--- shared authentication module structure ---'
sed -n '1,120p' adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py
sed -n '480,570p' adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py

echo '--- adapter package dependencies and tests ---'
rg -n -C3 'mcp|oauth|auth' adapters/{claude,codex,hermes,common} --glob 'pyproject.toml' --glob '*.py' --glob '*.pyi'

Repository: NVIDIA/NeMo-Fabric

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import ast

root = Path("adapters")
for path in sorted(root.rglob("*.py")):
    tree = ast.parse(path.read_text(encoding="utf-8"))
    calls = [
        node.lineno
        for node in ast.walk(tree)
        if isinstance(node, ast.Call)
        and isinstance(node.func, ast.Attribute)
        and node.func.attr == "create_mcp_oauth_provider"
    ]
    reaches = [
        node.lineno
        for node in ast.walk(tree)
        if isinstance(node, ast.Attribute)
        and node.attr in {"current_tokens", "access_token"}
    ]
    if calls or reaches:
        print(f"{path}: create_provider={calls}, token_attributes={reaches}")
PY

echo '--- factory return and provider construction ---'
sed -n '513,610p' adapters/common/src/nemo_fabric_adapters/common/mcp_auth.py

echo '--- exact consumers in Codex and Hermes ---'
for adapter in codex hermes; do
  echo "[$adapter]"
  rg -n 'create_mcp_oauth_provider|current_tokens|access_token|OAuthClientProvider' "adapters/$adapter" --glob '*.py' || true
done

echo '--- MCP dependency pins ---'
rg -n -C2 'mcp([<>=]|")|mcp-client' --glob 'pyproject.toml' --glob '*lock*' --glob '*.txt' .

Repository: NVIDIA/NeMo-Fabric

Length of output: 12983


🌐 Web query:

MCP Python SDK OAuthClientProvider current_tokens access_token mcp 1.26 1.28 source

💡 Result:

In the Model Context Protocol (MCP) Python SDK, the OAuthClientProvider is designed as an httpx.Auth implementation that handles OAuth2 authentication, including client registration and token management [1][2][3]. The current_tokens attribute and the access_token field are core components of the SDK's token management system [4][5][6]. Key Technical Details: 1. Token Structure: The OAuthToken class contains the access_token, refresh_token, expiry information, and scope [6]. 2. Provider Role: OAuthClientProvider is not an MCP-specific object but rather an httpx.Auth hook used to automatically manage Authorization headers [1][2]. It uses an internal OAuthContext to manage the token lifecycle [5][3]. 3. Token Lifecycle: When a request is made, the provider checks for a valid access_token [4][5]. If the token is expired or missing, it may attempt a refresh if a refresh_token is available [1]. 4. Version Context (1.26 - 1.28): - The SDK's v1.x branch (which includes versions 1.26 through 1.28) remains in maintenance mode [7]. - Version 1.28 introduced specific updates, such as adding subject and claims to the AccessToken [8][9]. - Known issues regarding token management, such as the behavior of token_expiry_time causing failures in proactive refreshes, have been identified and addressed in recent maintenance [10]. Specifically, if token_expiry_time is not correctly set (e.g., when loading from persistent storage), the SDK might treat tokens as perpetually valid or fail to trigger a refresh before a request receives a 401 response [10]. Ensuring that stored tokens are properly initialized with their expiry time is critical for stable authentication [10]. If you are encountering issues with token persistence or refreshes between these versions, ensure that your TokenStorage implementation is correctly loading and setting token expiry times upon initialization [10].

Citations:


Centralize MCP OAuth token extraction.

Claude is the only adapter that currently reads provider.context.current_tokens.access_token. Add mcp_auth.access_token(provider) and use it here so MCP SDK shape changes require one update in the shared module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 439
- 448, Centralize MCP OAuth token extraction by adding the shared
mcp_auth.access_token(provider) helper and replacing the local
context/current_tokens/access_token traversal in the Claude adapter’s OAuth
flow. Preserve the existing missing-token ClaudeAdapterError behavior and
metadata after using the helper.

Comment on lines +463 to +482
tmp_path = config_path.parent / (config_path.name + ".tmp")
try:
config = json.loads(config_path.read_text(encoding="utf-8"))
server_entry = config.setdefault("mcpServers", {}).setdefault(server_name, {})
server_entry.setdefault("headers", {})["Authorization"] = f"Bearer {token}"
descriptor = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
json.dump(config, stream, indent=2, sort_keys=True)
stream.write("\n")
except BaseException:
tmp_path.unlink(missing_ok=True)
raise
os.replace(tmp_path, config_path)
except OSError as error:
raise ClaudeAdapterError(
"claude_mcp_configuration_failed",
f"MCP server {server_name!r} authorization header could not be staged",
metadata={"server": server_name},
) from error

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Only OSError is converted to a structured failure.

json.loads at Line 465 raises json.JSONDecodeError, and config.setdefault("mcpServers", {}) at Line 466 raises AttributeError if mcpServers holds a non-mapping value. Neither is an OSError, so both escape this function unchanged.

invoke at Line 1162-1170 catches only ClaudeAdapterError, TimeoutError, and ClaudeSDKError. An escaping ValueError or AttributeError therefore bypasses the structured-failure path.

As per coding guidelines: "Return harness-level invocation failures as successful lifecycle responses containing response: null, failed: true, and structured error fields."

🛡️ Proposed exception handling
-    except OSError as error:
+    except (OSError, ValueError, TypeError, AttributeError) as error:
         raise ClaudeAdapterError(
             "claude_mcp_configuration_failed",
             f"MCP server {server_name!r} authorization header could not be staged",
             metadata={"server": server_name},
         ) from error
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tmp_path = config_path.parent / (config_path.name + ".tmp")
try:
config = json.loads(config_path.read_text(encoding="utf-8"))
server_entry = config.setdefault("mcpServers", {}).setdefault(server_name, {})
server_entry.setdefault("headers", {})["Authorization"] = f"Bearer {token}"
descriptor = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
json.dump(config, stream, indent=2, sort_keys=True)
stream.write("\n")
except BaseException:
tmp_path.unlink(missing_ok=True)
raise
os.replace(tmp_path, config_path)
except OSError as error:
raise ClaudeAdapterError(
"claude_mcp_configuration_failed",
f"MCP server {server_name!r} authorization header could not be staged",
metadata={"server": server_name},
) from error
tmp_path = config_path.parent / (config_path.name + ".tmp")
try:
config = json.loads(config_path.read_text(encoding="utf-8"))
server_entry = config.setdefault("mcpServers", {}).setdefault(server_name, {})
server_entry.setdefault("headers", {})["Authorization"] = f"Bearer {token}"
descriptor = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
json.dump(config, stream, indent=2, sort_keys=True)
stream.write("\n")
except BaseException:
tmp_path.unlink(missing_ok=True)
raise
os.replace(tmp_path, config_path)
except (OSError, ValueError, TypeError, AttributeError) as error:
raise ClaudeAdapterError(
"claude_mcp_configuration_failed",
f"MCP server {server_name!r} authorization header could not be staged",
metadata={"server": server_name},
) from error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 463
- 482, Update the MCP configuration handling around config_path and server_entry
so JSON decoding and invalid mcpServers structure failures are converted into
ClaudeAdapterError, not only OSError. Ensure invoke receives the structured
failure and returns the existing harness-level response shape with response:
null, failed: true, and structured error fields.

Source: Coding guidelines

Comment on lines +1219 to +1249
if self._mcp_authentication_checked:
return

servers = _authenticated_mcp_servers(payload)
if servers and self._mcp_config_path is None:
raise ClaudeAdapterError(
"claude_mcp_configuration_failed",
"MCP staged configuration is unavailable for OAuth authentication",
)

for name, server in servers.items():
authentication = _mcp_authentication(payload, name)
auth_timeout = min(invocation_timeout, authentication.authorization_timeout_seconds)
token = await _self_authenticate_http_mcp_server(
name,
server["url"],
authentication,
timeout=auth_timeout,
)
assert self._mcp_config_path is not None
_update_staged_mcp_server_header(self._mcp_config_path, name, token)
await client.reconnect_mcp_server(name)
status = await self._mcp_server_status(client, name)
if status != "connected":
raise ClaudeAdapterError(
"claude_mcp_unavailable",
f"Claude MCP server {name!r} is unavailable after OAuth authentication",
metadata={"server": name, "status": status},
)

self._mcp_authentication_checked = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

A partial failure repeats completed interactive authorizations.

self._mcp_authentication_checked is set only after the whole loop succeeds at Line 1249. If the second server fails, the first server has already completed its OAuth flow and had its bearer header staged. The next invoke restarts the loop from the first server.

For an authorization-code flow this re-prompts the user for a server that is already authenticated. Track completed servers instead of a single boolean, and skip them on retry. Reset that set in stop alongside Line 1347.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 1219
- 1249, Replace the single _mcp_authentication_checked flag with a set tracking
completed MCP server names in the authentication flow. In the loop around
_self_authenticate_http_mcp_server, skip servers already in that set and add
each server only after its authentication, staged-header update, reconnect, and
connected-status validation succeed. Reset the completed-server set in stop
alongside the existing authentication state.

Comment on lines +1229 to +1241
for name, server in servers.items():
authentication = _mcp_authentication(payload, name)
auth_timeout = min(invocation_timeout, authentication.authorization_timeout_seconds)
token = await _self_authenticate_http_mcp_server(
name,
server["url"],
authentication,
timeout=auth_timeout,
)
assert self._mcp_config_path is not None
_update_staged_mcp_server_header(self._mcp_config_path, name, token)
await client.reconnect_mcp_server(name)
status = await self._mcp_server_status(client, name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Authentication and the query each get a full invocation timeout.

Line 1231 bounds authentication by invocation_timeout. _run_query at Line 1291 then opens a fresh asyncio.timeout(invocation_timeout). Authentication runs inside invoke, so a caller that sets runtime.timeout_seconds to 60 can wait up to 120 seconds, plus the 5-second status poll per server at Line 1257. With several authenticated servers the overrun grows further.

schemas/run-plan.schema.json documents timeout_seconds as "Maximum duration of one invocation in seconds". The current behavior breaks that bound.

Compute one deadline at the start of invoke and derive both the authentication budget and the query budget from the remaining time.

⏱️ Proposed deadline handling
         try:
             prompt = request_prompt(payload)
             invocation_timeout = timeout_seconds(payload)
+            deadline = asyncio.get_running_loop().time() + invocation_timeout
             await self._authenticate_mcp_servers(
                 payload,
                 client,
-                invocation_timeout,
+                deadline,
             )
     async def _authenticate_mcp_servers(
         self,
         payload: dict[str, Any],
         client: ClaudeSDKClient,
-        invocation_timeout: float,
+        deadline: float,
     ) -> None:
         for name, server in servers.items():
             authentication = _mcp_authentication(payload, name)
-            auth_timeout = min(invocation_timeout, authentication.authorization_timeout_seconds)
+            remaining = deadline - asyncio.get_running_loop().time()
+            if remaining <= 0:
+                raise TimeoutError
+            auth_timeout = min(remaining, authentication.authorization_timeout_seconds)

Then pass the remaining budget to _run_query instead of invocation_timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude/src/nemo_fabric_adapters/claude/adapter.py` around lines 1229
- 1241, Update invoke to compute one absolute deadline at its start, then derive
each authentication timeout and the query timeout from the remaining invocation
budget rather than resetting to invocation_timeout. Pass that remaining budget
into _run_query, including accounting for per-server status polling, so the full
invoke flow remains bounded by runtime.timeout_seconds.

Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
Signed-off-by: David Gardner <dagardner@nvidia.com>
…g the loop

Signed-off-by: David Gardner <dagardner@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant