Skip to content

Migrate NEAR AI MCP credentials to product auth - #4246

Merged
serrrfirat merged 10 commits into
reborn-integrationfrom
codex/nearai-mcp-product-auth
May 30, 2026
Merged

serrrfirat merged 10 commits into
reborn-integrationfrom
codex/nearai-mcp-product-auth

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

Port the NEAR AI MCP slice of #4176 by switching the bundled nearai-mcp manifest from a static SecretHandle to a ProductAuthAccount runtime credential source. Mirrors PR #4233's GitHub WASM migration for the MCP lane.

 runtime_credentials = [
-  { handle = "llm_nearai_api_key", audience = ..., target = ... },
+  { handle = "llm_nearai_api_key",
+    source = { type = "product_auth_account", provider = "nearai" },
+    audience = ...,
+    target = ... },
 ]

Effect on the auth flow

  • The authorization layer (introduced in Migrate GitHub WASM credentials to product auth #4233) already routes ProductAuthAccount-sourced credentials through Obligation::InjectCredentialAccountOnce when credential.required is true. Flipping the manifest source field is therefore sufficient end-to-end.
  • At dispatch, the obligation handler resolves the configured nearai account via RuntimeCredentialAccountResolver (ProductAuthRuntimeCredentialResolver impl), leases the access secret from the SecretStore, and stages it into RuntimeSecretInjectionStore under the llm_nearai_api_key slot.
  • NearAiMcpEgressPlanner continues referencing the slot via RuntimeCredentialSource::StagedObligation { capability_id } — no planner change required.
  • Reuses the unified CredentialStageError vocabulary from Wire Reborn auth consumers through staged credentials #4231 / Migrate GitHub WASM credentials to product auth #4233's alignment commit, so AuthRequired vs Backend semantics are uniform across first-party / WASM / MCP lanes.

The narrow nearai_mcp planner comment that pointed at #4176 as future work is updated to reflect that this PR closes the MCP slice.

Dependency

Stacked on #4233 (codex/github-wasm-product-auth). Merge order: #4233 must land first, since this PR depends on:

  • Obligation::InjectCredentialAccountOnce (host-api)
  • RuntimeCredentialAccountResolver trait + ProductAuthRuntimeCredentialResolver impl (host-runtime, composition)
  • RuntimeCredentialRequirementSource::ProductAuthAccount (host-api capability schema)
  • Manifest v2 parser support for source = { type = "product_auth_account", ... }

Base branch is intentionally codex/github-wasm-product-auth so the diff stays surgical. Once #4233 lands, retarget to reborn-integration and merge.

Tests

  • cargo test -p ironclaw_reborn_composition local_dev_nearai_mcp_installs_and_activates_model_visible_capability — asserts source = ProductAuthAccount(provider=nearai)
  • cargo test -p ironclaw_reborn_composition --lib nearai_mcp — 9/9
  • cargo test -p ironclaw_reborn_composition --lib available_extensions — 7/7
  • cargo test -p ironclaw_extensions --test manifest_v2_contract — 44/44 (covers parsing of the new source variant)
  • cargo test -p ironclaw_authorization --test runtime_credentials_contract — 6/6 (covers ProductAuthAccount → InjectCredentialAccountOnce emission)
  • cargo fmt --check clean
  • cargo check -p ironclaw_reborn_composition clean

Out of scope

  • Concrete NEAR AI manual-token provider client. Reborn's durable product-auth fallback (UnavailableAuthProviderClient) + manual-token submit flow already lets users create a nearai account and supply the access secret without a dedicated provider client. A first-class AuthProviderClient implementation can land as a follow-up.
  • NEARAI_API_KEY env var in ironclaw_llm (LLM chat session auth). That's a separate auth surface from the MCP extension credential and is not migrated here.

Related

…uired, error variants, thiserror, tests (#4233)

- authorization: wrap ProductAuthAccount obligation push in `if credential.required`
  so optional product-auth credentials do not hard-fail dispatch when unconfigured,
  mirroring the existing SecretHandle semantics
- product_auth_runtime_credentials: return `Failed` (not `AuthRequired`) when a
  Configured account has `access_secret = None` — data corruption is not a re-auth
  prompt; log discarded AuthProviderId parse error via tracing::debug before mapping
- obligations: add `#[derive(thiserror::Error)]` + `#[error(...)]` to
  RuntimeCredentialAccountError (CLAUDE.md: use thiserror); log discarded
  lease_once / consume / insert errors with tracing::debug instead of silent |_|
- tests (product_auth_runtime_credentials): add
    resolver_maps_unconfigured_account_status_to_auth_required,
    resolver_maps_configured_account_without_access_secret_to_failed,
    resolver_maps_multiple_accounts_to_auth_required
- tests (builtin_obligation_handler_contract): add
    inject_credential_account_once_fails_when_no_resolver_wired,
    inject_credential_account_once_fails_when_resolver_returns_auth_required
- tests (manifest_v2_contract): add rejects_unknown_runtime_credential_source_type
- docs: clarify required/optional semantics apply uniformly across credential sources
…ant use tracing

- extract stage_credential_material(store, injections, scope, cap_id, source, target)
  as a module-level helper that encapsulates the lease->consume->insert sequence
  with tracing on each error; inject_credential_accounts uses it after resolving
  the access_secret, removing ~15 lines of duplicated store manipulation
- remove redundant 'use tracing;' import from product_auth_runtime_credentials.rs;
  tracing::debug! resolves via full path without the explicit module import
… builds

build_backend_production (used by libsql + postgres production profiles) had
product_auth_ports available but never called with_runtime_credential_account_resolver,
meaning ProductAuthAccount runtime credentials would silently fail to inject in
production deployments (resolver=None path in inject_credential_accounts).

Wire the resolver the same way as build_local_dev: when product_auth_ports is Some,
create a ProductAuthRuntimeCredentialResolver from ports.credential_account_service()
and set it on the services builder before host_runtime_for_production is called.
…ormalize helper

CI failures: factory.rs called ports.credential_account_service() but
RebornProductAuthServicePorts had no such method (only RebornProductAuthServices did).
Add the missing public accessor.

Also address henrypark133 review findings:
- tests (High): add inject_credential_account_once_resolves_and_stages_secret (happy
  path: resolver Ok, material in store, staged injection present) and
  inject_credential_account_once_fails_when_resolved_secret_not_in_store (resolver Ok
  but resolved handle absent from store -> Failed)
- naming (Low): rename secret_injection_obligations -> secret_injection_handles to match
  staged_secret_injection_handles sibling (suffix _obligations implied Obligation return
  type, suffix _handles is accurate)
- refactor (Low): extract normalize_multi_inject + extract_inject_handle helpers in
  decision.rs; collapse duplicate InjectSecretOnce / InjectCredentialAccountOnce
  normalization loops (identical structure, only variant differed) into one call
…lide needless lifetime

- capability.rs: replace explicit impl Default with #[derive(Default)] + #[default] on
  SecretHandle; fixes clippy::derivable-impls (CI Clippy all-features failure)
- decision.rs: elide needless 'a lifetime on extract_inject_handle return-owned fn;
  fixes clippy::needless-lifetimes
Resolve conflicts in:
- crates/ironclaw_reborn_composition/src/lib.rs (mod list — keep nearai_mcp,
  product_auth_durable, product_auth_runtime_credentials)
- crates/ironclaw_reborn_composition/src/factory.rs (build_backend_production
  — adopt reborn's require_product_auth_runtime_ports + attach_nearai_mcp_runtime
  pre-step; drop obsolete Option<product_auth_ports> conditional; re-add resolver
  wiring unconditionally after compose_product_auth_services, before
  with_first_party_capabilities, sourcing credential_account_service from
  product_auth_services so the durable filesystem fallback from #4234 is
  honored)
- docs/reborn/contracts/host-runtime.md (keep MCP HTTP/SSE planner paragraph
  alongside PR4233's 'InjectSecretOnce or InjectCredentialAccountOnce' wording)
Reborn-integration introduced a canonical staged-credential vocabulary that
PR4233's auth flow now adopts:

A. Unify on ironclaw_host_api::CredentialStageError {AuthRequired, Backend}.
   - Delete RuntimeCredentialAccountError {AuthRequired, Failed} from
     ironclaw_host_runtime::obligations.
   - RuntimeCredentialAccountResolver::resolve_access_secret now returns
     Result<SecretHandle, CredentialStageError>, sharing its error type with
     ProductAuthCredentialStageError (host-runtime staging primitive) and
     GsuiteCredentialStageError (per-extension stager), so no per-layer
     conversion glue is needed.
   - ProductAuthRuntimeCredentialResolver in ironclaw_reborn_composition maps
     AuthProductError to CredentialStageError directly; documents the durable
     product-auth Configured <-> access_secret=Some invariant (#4234) under
     which the corrupt-state branch returns Backend, not AuthRequired.

B. Share the lease/consume/insert classifier between the WASM
   InjectCredentialAccountOnce path and the first-party stager path.
   - stage_credential_material in obligations.rs now returns
     CredentialStageError, classifying SecretStoreError via
     services::stage_secret_error — the same classifier
     ProductAuthProviderRuntimePorts::stage_secret_once uses. Result: unknown,
     expired, revoked, consumed, or unknown-lease access secrets now surface
     as AuthRequired on the WASM lane just as they do on the GSuite lane, so
     the runtime auth gate fires consistently regardless of which staging
     entry point discovered the gap.
   - credential_stage_error_to_obligation_error replaces
     runtime_credential_account_error; used by both inject_credential_accounts
     and stage_credential_material callers, eliminating duplicate mapping.
   - inject_credential_account_once_fails_when_resolved_secret_not_in_store
     renamed to inject_credential_account_once_maps_unknown_resolved_secret_to_auth_required
     to assert the new aligned semantics (was Failed; now AuthRequired).

C. Fix RuntimeCredentialRequirement struct literal in web-access network
   policy test (extension_surface.rs) — reborn-integration's #4219 test was
   written before PR4233 added the 'source' field; add explicit
   RuntimeCredentialRequirementSource::SecretHandle.

Tests: product_auth_runtime_credentials (5/5),
       builtin_obligation_handler_contract inject_credential_account_once (4/4),
       github_wasm_runtime_contract (5/5),
       authorization/runtime_credentials_contract (6/6),
       extension_v2_lifecycle_e2e github_v2_package_discovers_... (1/1).
Port the NEAR AI MCP slice of #4176 by switching the bundled
nearai-mcp manifest from a static SecretHandle to a ProductAuthAccount
runtime credential source.

Before:
  runtime_credentials = [
    { handle = "llm_nearai_api_key", audience = ..., target = ... },
  ]

After:
  runtime_credentials = [
    { handle = "llm_nearai_api_key",
      source = { type = "product_auth_account", provider = "nearai" },
      audience = ...,
      target = ... },
  ]

Effect:
- The authorization layer (ironclaw_authorization, PR #4233) already maps
  ProductAuthAccount sources to Obligation::InjectCredentialAccountOnce when
  credential.required is true, so flipping the source field is sufficient to
  route NEAR AI MCP credentials through the product-auth account path.
- At dispatch time, the obligation handler resolves the configured nearai
  account via RuntimeCredentialAccountResolver
  (ProductAuthRuntimeCredentialResolver), leases its access_secret from the
  SecretStore, and stages it in RuntimeSecretInjectionStore under the
  'llm_nearai_api_key' slot.
- NearAiMcpEgressPlanner continues referencing the slot via
  RuntimeCredentialSource::StagedObligation — no planner change required.
- Reuses the same staged-credential vocabulary (CredentialStageError) and
  classifier (services::stage_secret_error) the GSuite stager and the WASM
  InjectCredentialAccountOnce path use, so AuthRequired vs Backend semantics
  are uniform across first-party / WASM / MCP lanes.

Manifest digest auto-updates via include_str! at compile time. The narrow
nearai_mcp planner comment that pointed at #4176 as future work is updated
to reflect that this PR closes the MCP slice.

Stacked on PR #4233 (codex/github-wasm-product-auth) for the
InjectCredentialAccountOnce / RuntimeCredentialAccountResolver /
RuntimeCredentialRequirementSource::ProductAuthAccount machinery. Merge
order: #4233 must land first.

Tests:
- cargo test -p ironclaw_reborn_composition local_dev_nearai_mcp_installs_and_activates_model_visible_capability
  (asserts source = ProductAuthAccount(provider=nearai))
- cargo test -p ironclaw_reborn_composition --lib nearai_mcp (9/9)
- cargo test -p ironclaw_reborn_composition --lib available_extensions (7/7)
- cargo test -p ironclaw_extensions --test manifest_v2_contract (44/44)
- cargo test -p ironclaw_authorization --test runtime_credentials_contract (6/6)
@github-actions github-actions Bot added size: S 10-49 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels May 29, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the NEAR AI MCP server configuration to source its runtime credentials from a product-auth account provider (nearai) instead of relying on an out-of-band secret store handle. It updates the manifest file, adjusts the corresponding unit tests in factory.rs to assert this new source, and updates documentation comments in nearai_mcp.rs to clarify the upstream credential resolution flow. There are no review comments, so I have no feedback to provide.

Base automatically changed from codex/github-wasm-product-auth to reborn-integration May 29, 2026 23:34
@serrrfirat
serrrfirat merged commit b3b0d17 into reborn-integration May 30, 2026
22 checks passed
@serrrfirat
serrrfirat deleted the codex/nearai-mcp-product-auth branch May 30, 2026 09:37
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: S 10-49 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant