Skip to content

feat(teams): CredentialStore interface + AES-256-GCM BBolt backend (074-T1)#587

Merged
Dumbris merged 1 commit into
mainfrom
mcp-1034-credential-store
Jun 4, 2026
Merged

feat(teams): CredentialStore interface + AES-256-GCM BBolt backend (074-T1)#587
Dumbris merged 1 commit into
mainfrom
mcp-1034-credential-store

Conversation

@Dumbris
Copy link
Copy Markdown
Member

@Dumbris Dumbris commented Jun 4, 2026

Summary

Foundation task MCP-1034 / 074-T1 for upstream token brokering (spec 074). No blockers. New server-edition package internal/teams/broker/ (//go:build server).

  • CredentialStore interface (Get/Put/Delete/List) — the abstraction seam so external secret managers (Vault/AWS) can be added later without changing callers (FR-023).
  • BBoltAESStore backend — bucket user_upstream_credentials:
    • upstream creds keyed <userID>:<serverKey> (serverKey = existing SHA256(name+url) scheme, see oauth.GenerateServerKey)
    • idp subject tokens keyed by bare <userID> (empty serverKey selects them)
  • UpstreamCredential value model (Type, AccessToken, RefreshToken, ExpiresAt, Scopes, TokenType, Audience, ObtainedVia, UpdatedAt), JSON-serialized then AES-256-GCM encrypted with a per-record random nonce (FR-020), isolated per user (FR-021).
  • Master key from env MCPPROXY_CRED_KEY or teams.credential_encryption_key (32-byte base64). Missing key → store disabled gracefully (rest of gateway unaffected, logged for startup surfacing); present-but-invalid → loud error (FR-022).
  • Decryption failure (e.g. rotated key) → record treated as absent, never fatal; List skips undecryptable records.
  • Adds teams.credential_encryption_key config field as the config-side key source.

Mirrors the obot-platform/mcp-oauth-proxy AES approach.

Tests (TDD — test-first, all green with -race)

  • encrypt/decrypt roundtrip (+ asserts plaintext token absent on disk)
  • per-user isolation (incl. userID-prefix bleed guard)
  • subject-token keying + List exclusion
  • nonce uniqueness (same plaintext → different ciphertext)
  • expiry helpers (IsExpired/IsValid/ExpiresWithin, zero = never)
  • missing-key disabled path (all ops → ErrStoreDisabled)
  • invalid/short key → constructor error
  • key-changed → ErrNotFound (not-connected)
  • ResolveMasterKey env-over-config precedence

Verification

go build ./cmd/mcpproxy                         # personal: OK
go build -tags server ./cmd/mcpproxy            # server:   OK
go test -tags server -race ./internal/teams/broker/ ./internal/config/   # ok
golangci-lint run --build-tags=server ./internal/teams/broker/...        # 0 issues

Notes

  • Docs: no docs/ file documents teams config keys (they live in CLAUDE.md, which is at the 40k CI size limit); the field is an inert foundation seam not yet wired/activated, so user-facing docs are deferred to the downstream startup-wiring task. Spec FRs already documented in specs/074-upstream-token-brokering/spec.md.
  • Out of scope (downstream tasks): startup wiring + "surface disabled at startup", token-exchange/refresh, callers.

Related spec: specs/074-upstream-token-brokering · FR-020..FR-023

Foundation for upstream token brokering (spec 074, MCP-1034). New
server-edition package internal/teams/broker provides:

- CredentialStore interface (Get/Put/Delete/List) as the abstraction seam
  for future external secret-manager backends (FR-023).
- BBoltAESStore: bucket "user_upstream_credentials", keyed
  "<userID>:<serverKey>" for upstream creds and bare "<userID>" for idp
  subject tokens. serverKey follows the existing SHA256(name+url) scheme.
- UpstreamCredential value model, JSON-serialized then AES-256-GCM
  encrypted with a per-record random nonce (FR-020) and isolated per user
  (FR-021).
- Master key resolved from MCPPROXY_CRED_KEY env or
  teams.credential_encryption_key (32-byte base64). Missing key ->
  store disabled gracefully; present-but-invalid -> loud error (FR-022).
- Decryption failure (e.g. rotated key) treated as record-absent, never
  fatal; List skips undecryptable records.

Adds teams.credential_encryption_key config field as the config-side key
source. TDD: roundtrip, per-user isolation, nonce uniqueness, expiry
helpers, missing-key disabled path, key-changed -> not-found.

Related spec: specs/074-upstream-token-brokering
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2526aa9
Status: ✅  Deploy successful!
Preview URL: https://69aeac4b.mcpproxy-docs.pages.dev
Branch Preview URL: https://mcp-1034-credential-store.mcpproxy-docs.pages.dev

View logs

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

📦 Build Artifacts

Workflow Run: View Run
Branch: mcp-1034-credential-store

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (24 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 26935941638 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris Dumbris merged commit a7412ff into main Jun 4, 2026
37 checks passed
Dumbris added a commit that referenced this pull request Jun 4, 2026
…(spec 074, MCP-1035) (#588)

* feat(config): per-upstream auth_broker block + teams credential keys (spec 074)

Add server-edition configuration surface for per-user upstream token
brokering (spec 074, T2 / MCP-1035):

- ServerConfig.AuthBroker *AuthBrokerConfig with mode
  (token_exchange|entra_obo|oauth_connect), token_endpoint, resource
  (RFC 8707 audience), scopes, client_id/secret, and configurable header
  + header_format (defaults "Authorization" / "Bearer {token}", FR-016).
- TeamsConfig.CredentialEncryptionKey (env fallback MCPPROXY_CRED_KEY,
  explicit config wins) and StoreIDPTokens bool, default false (FR-006).
- Validation rejects auth_broker on stdio/non-HTTP-family upstreams with
  an "unsupported in this phase" message (FR-002); HTTP-family upstreams
  pass and have header defaults applied. Opt-in per server; upstreams
  without a broker behave exactly as today (FR-003).

The AuthBrokerConfig type and validation are behind //go:build server with
a personal-edition stub (empty struct + no-op validator), so the personal
edition is unaffected. AuthBroker carries swaggerignore (mirrors Teams) —
swagger-verify confirms no OpenAPI drift.

Related #587

* test(storage): cover ServerConfig.AuthBroker in saveServerSync field canary

AuthBroker (spec 074) is server-edition per-upstream broker config carried
in the JSON config (like Shared), not persisted to the BBolt UpstreamRecord.
Mark it intentionally excluded in TestSaveServerSyncFieldCoverage so the
field-coverage canary passes.

Related #588
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.

2 participants