Skip to content

feat(mcp): support static bearer credentials in auth.yaml - #2583

Merged
jbeda merged 4 commits into
mainfrom
impl/2577-mcp-auth-file
Oct 28, 2026
Merged

jbeda merged 4 commits into
mainfrom
impl/2577-mcp-auth-file

Conversation

@jbeda

@jbeda jbeda commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Store static MCP bearer tokens in the server's auth.yaml, referenced from non-secret settings with token_ref: github.

  • Resolve MECATL_MCP_TOKEN_GITHUB before mcp.github.token; only an absent environment variable permits file fallback. Empty/invalid values fail closed.
  • Share one selected credential source across providers, MCP and direct lifecycle commands. File selection is CLI > operator settings > XDG; file snapshots and strict-warning provenance are retained.
  • Preserve exact env-only token_env with a deprecation warning. CLI --mcp-server remains a separate, non-deprecated path. OAuth behavior is unchanged.
  • Wire all four local server roots; keep credentials server-side, static bearer global-only, and remote mecatui free of credential reads/uploads. Update docs and generated examples.

Closes #2577. Search migration (#2578) and broader settings ownership (#774) remain separate.

API compatibility

No wire/engine API or stored-session change; existing clients need no changes. Provider/OAuth formats remain unchanged. Older auth-file parsers may reject the new mcp namespace: upgrade the server before adopting it; remove that namespace before rollback. Restart the owning server/Pod after credential rotation.

Verification and review

For head 038688e48: local full test/race suites, lint, docs/site build and offline demo passed. Offline coverage includes precedence, invalid-env no-fallback, file snapshots, actual header delivery/secret containment, all four roots, OAuth isolation and remote-client isolation. An initial unrelated broker-listener test failure passed focused/package/full-suite rechecks. Current CI checks pass; conditional live jobs are skipped.

A fresh whole-PR panel covered Spec, Standards, Test adequacy, Security, Architecture, Reuse and Duplication. No blockers or important findings. Two optional advisories remain: consolidate CLI anonymous/no-fallback test assertions, and inline a one-use warning helper. No code was changed by the review.

Final design, contracts and review notes.

@jbeda

jbeda commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Final design and review

Focused current plan for PR #2583, replacing the revision chronology. Approved contracts and deferrals remain; historical local records are preserved. Full review found no blockers or important findings, with two optional advisories listed below. No code changed during this review.

Source: .scratch/2577-mcp-auth-file-plan.md (local provenance). Updated 2026-10-28T16:38:51.481737+00:00. Complete current plan SHA-256: 24510bc2721ccfed39906fb40b5d8aa3b351e64e157a86ae12144879b7602703.


MCP static bearer credentials in auth.yaml

Slug: 2577-mcp-auth-file
Implementation branch: impl/2577-mcp-auth-file
Base branch: main
Plan path: .scratch/2577-mcp-auth-file-plan.md
Review base: 38c5023
Delivered head: 038688e

Outcome and example

Operators can keep MCP API keys in server-owned auth.yaml, while URLs and non-secret configuration stay in settings.yaml. Implementation and verification are complete. This is the final design, not a revision log.

# settings.yaml
mcp:
  mode: global
  servers:
    - name: github
      url: https://mcp.example.com/github
      auth:
        mode: static_bearer
        static_bearer:
          token_ref: github
# auth.yaml
mcp:
  github:
    token: "<TOKEN>"

Contract

  • token_ref is a logical reference, not a path/environment name or implicit server-name lookup. References and MCP file keys match lowercase ASCII [a-z0-9_]+; reject rather than normalize invalid values. github selects exactly MECATL_MCP_TOKEN_GITHUB first, then mcp.github.token in the selected file.
  • Only an absent derived variable permits fallback. Present-empty, blank or invalid values fail without fallback; missing credentials never become anonymous. Invalid references and unknown secret kinds fail closed before source access. Preserve typed, value-free distinctions among invalid references, unavailable secrets and broken sources.
  • Require exactly one selector, with strict presence/unknown-field checks. Deprecated token_env remains exact MECATL_*, environment-only. Warn safely once per effective server/owning resolver lifetime, including subsequent credential failures. Override/filter/target selection precedes lookup and warning; discarded, invalid, OAuth and CLI entries do not warn. A fresh runtime may warn again.
  • Independent CLI --mcp-server name=URL is not deprecated: it replaces the whole same-name profile, uses only MCP_<UPPER_NAME>_TOKEN, and remains anonymous for missing/empty variables. Never use logical-secret/file fallback for these entries.
  • Preserve opaque token bytes. Reject forbidden controls and trailing ASCII space/tab before transport, rather than trim; validate authenticated URLs and retain valid internal whitespace. Keep values out of errors, diagnostics, prompts/tools, events, sessions and public/wire projections.
  • File precedence: --api-key-file > credential_store.api_key.file from the same operator settings resolver > conventional XDG auth.yaml. Preserve provider-only, MCP-only, mixed and existing empty-file behavior, full-document/schema/size/duplicate/permission checks, and provider mutation preservation of MCP entries. Do not delete a file with remaining MCP credentials.
  • An environment winner need not pass MCP's strict policy for an unused fallback; independent provider/explicit-file policy still applies. Retain acquisition-time strict warning metadata even after provider sanitization.

Ownership and scope

CredentialSource owns selected-path provenance and shared finalized file content/absence/failure snapshots for providers and MCP; ProviderFlags owns flags, not a second cache. SecretResolver.Lookup(kind, ref) supports MCP bearer only. Static preparation captures fallback state even when the environment wins. Direct lifecycle callers apply the same-settings path without relying on Build/provider-load ordering; OAuth-only effective/targeted operations must not acquire unrelated static files.

Environment enumeration uses injected ResolveEnv.Environ, with OSEnv.Environ = os.Environ, no separate slice/variadic plumbing, and no ambient fallback for synthetic environments. Simple capture remains allowed; no live reload or guarantee/test against in-process Setenv/Unsetenv is required. Restart the owning process/Pod after deployment credential rotation. File snapshot guarantees remain.

All four local roots share source/loader/warning ownership. Remote mecatui connect neither reads nor uploads local credentials. Static bearer is global-only; broker rejection precedes secret access/warning. OAuth custody, refresh, raw-env/client-secret paths and cleanup remain unchanged. Credentials stay on the server/Pod, not clients or executor Pods.

CLI mechanisms use CLIServers/cliServers/cliEntry/validateCLIHTTPRelaxations; explicitTokenEnvPattern names the separate deprecated selector. No aliases for superseded unmerged internals, new engine dependency, or generic secret-manager subsystem. Search migration (#2578), provider migration, settings ownership/downcasts (#774), OAuth redesign, broker static bearer, arbitrary headers/backends, editing CLI and authority renaming are out of scope.

Completion and verification

Completed work: parser/mutation support; shared source and root/lifecycle integration; isolated offline regression tests; minimal owning docs and generated references covering selection, rotation/restart, server custody and global-only deployment limits.

Local gates passed on the exact tree committed as 038688e48: full task test and task test:race, lint, docs, site build and offline demo (tool call, permission ask/approval, result). Complete suites used 1800-second outer timeouts; other gates used 600 seconds. Tests used GOFLAGS=-p=2 and Task concurrency 1 (test) / 2 (race).

Coverage includes precedence/invalid-present no-fallback/custom paths; file snapshots/failures/strict warnings; parser/provider mutation preservation; profile selection/warning isolation; actual destination headers/containment/reconnect/fresh startup; OAuth-only no-read with positive controls; four real local factories; remote no-read/no-upload; test-owned state and poisoned ambient inputs. No live provider calls required.

An initial run hit an untouched broker-listener test failure; ten focused repetitions, package recheck and complete aggregate rerun passed. Precise transient cause unproven. CI observed for the delivered head is passing; conditional live jobs are skipped. Local receipts and CI are separate evidence.

Full review and remaining advisories

Fresh whole-PR review against the base above: 49 files, +2,896/−261 lines, three commits. Spec, Standards, independent Test adequacy, Security, Architecture, Library reuse and Duplication completed. No blockers, important findings, or material churn residue. Two optional advisories remain; neither changes the contract and no code fixes were applied:

  1. Optionally combine existing CLI anonymous/no-fallback assertions into one absent/empty-token regression test (internal/cliconfig/mcp_warning_test.go:124, mcp_file_profile_test.go:159, mcpserver_test.go:30).
  2. Inline the one-use warning-default helper (internal/cliconfig/mcpprofile.go:139).

An initial duplicate-provider parsing concern was retracted after an offline proof confirmed rejection before map decoding. Historical review chronology and intermediate designs are omitted from this focused plan; local records are preserved.

API compatibility and authority

Host-local credential/configuration changes: conforming remote clients need no changes. No wire-major/feature identifier, public engine API change or stored-session migration. Provider identities/formats and OAuth formats stay unchanged; shipped token_env remains supported with a warning. Older auth-file parsers can reject the new mcp namespace: upgrade the owning server before adopting it and remove that namespace before rollback.

Commit/push and PR description/comment updates were explicitly authorized. Keep this plan ignored; update the existing plan comment in place. No merge, rebase, replacement PR, deployment or unrelated cleanup is authorized. No unresolved product decisions.

Resolve explicit token_ref profiles from the selected operator auth-file snapshot, preserving environment sources and legacy overrides. Wire all server roots, retain provider credential editing, and cover transport delivery, containment, custom paths, and remote-client isolation.

Co-authored-by: Mecatl <noreply@mecatl.dev>
…ntial source

Prefer derived MCP environment credentials over auth.yaml while preserving immutable startup snapshots. Deprecate exact token_env selectors with effective-profile warnings, remove flag-owned credential caches, and cover all composition roots and direct lifecycle paths.

Co-authored-by: Mecatl <noreply@mecatl.dev>
…ming

Use ResolveEnv.Environ for static credential enumeration, remove separate environment plumbing, and relax tests that guaranteed in-process environment mutation isolation. Preserve file snapshots and credential precedence. Keep CLI server entries distinct from deprecated token_env selectors and simplify the static bearer examples.

Co-authored-by: Mecatl <noreply@mecatl.dev>
Track the shared secret-management direction in #2585 without implying that the current startup resolver provides general secret storage.

Co-authored-by: Mecatl <noreply@mecatl.dev>
@jbeda
jbeda merged commit 90e9f30 into main Oct 28, 2026
44 checks passed
@jbeda
jbeda deleted the impl/2577-mcp-auth-file branch October 28, 2026 17:58
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.

feat(mcp): support static bearer keys in auth.yaml

1 participant