Skip to content

[fix]: refresh stale per-user OAuth MCP tool catalogs - #5752

Open
zachgersh wants to merge 8 commits into
maximhq:devfrom
zachgersh:gersh/mcp-per-user-oauth-tool-refresh
Open

[fix]: refresh stale per-user OAuth MCP tool catalogs#5752
zachgersh wants to merge 8 commits into
maximhq:devfrom
zachgersh:gersh/mcp-per-user-oauth-tool-refresh

Conversation

@zachgersh

Copy link
Copy Markdown
Contributor

Summary

Refresh the global cached tool catalog for per-user OAuth MCP clients after their setup-time discovery becomes stale. Previously these clients had no persistent connection, so the periodic tool syncer skipped them and their catalogs remained unchanged until the client was recreated.

Changes

  • Start an asynchronous global tools/list refresh after resolving a valid per-user OAuth token.
  • Coalesce concurrent refreshes and throttle failed attempts using the configured tool sync interval.
  • Apply refresh results through immutable client config snapshots and reject results that raced with client updates or removal.
  • Persist only the discovered tools, name mapping, and last-sync timestamp through a conditional config-store update.
  • Add the discovered_tools_last_sync migration and restore the timestamp across restarts.
  • Document that the catalog is global while tool execution remains scoped to each caller OAuth token.
  • Add focused race and config-store regression tests.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

Also affects framework/configstore persistence and migrations.

How to test

cd core
go test -race -cover ./mcp

cd ../framework
go test -race -cover ./configstore

cd ../transports
go test -race -run '^TestMergeMCPConfig_HashReconciliationUpdatesAndCreates$' ./bifrost-http/lib
go test -race -run '^$' ./bifrost-http/server

Expected: all commands pass. The UI is not changed by this PR.

Screenshots/Recordings

Not applicable; there are no UI changes.

Breaking changes

  • Yes
  • No

Related issues

None.

Security considerations

The resolved bearer token is used only by the temporary upstream MCP connection and is not persisted or exposed to plugins. Refreshed tool definitions are global by design, while every tool execution continues to use the calling user OAuth token.

Checklist

  • I read the contribution guidelines and followed them
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified affected Go builds succeed
  • I verified the full CI pipeline passes locally if applicable

jeremym-tanium and others added 8 commits July 31, 2026 00:08
…iew maintenance (maximhq#5693)

* feat: support matview_refresh_interval "off" to disable logstore matview maintenance

The materialized views back only the dashboard UI. Deployments that run
Bifrost headless behind their own observability stack pay the REFRESH
MATERIALIZED VIEW CONCURRENTLY cost for views nothing reads, and the 5s
floor means the interval alone cannot turn maintenance off.

With "off" (or a non-positive duration) the logs store skips view
creation, the initial refresh, and the periodic refresher entirely.
matViewsReady stays false, so dashboard queries fall back to the raw
tables, and the runtime self-heal path cannot re-arm maintenance since
it only triggers from matview-path queries.

* fix: guard matview self-heal when maintenance is disabled

Review follow-up: carry the resolved disabled state onto the store so
triggerMatViewSelfHeal cannot recreate views the configuration says must
not exist, and make the schema/docs explicit that a zero duration also
disables (positive sub-5s values still clamp up).
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved token parameter compatibility handling to preserve alternative formats when the primary option is unsupported.

* **Chores**
  * Version updated to 2.0.0.
  * Enhanced load testing configuration for more reliable builds.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary

Adds a `THIRD_PARTY_NOTICES.md` file to formally document third-party components used in Bifrost that carry license terms requiring explicit attribution — specifically MPL-2.0 licensed dependencies and embedded source code derived from external projects.

## Changes

- Introduces `THIRD_PARTY_NOTICES.md` to attribute:
  - Embedded source code in `framework/migrator/migrator.go` derived from `go-gormigrate/gormigrate` (MIT)
  - Go binary dependencies carrying MPL-2.0 terms: `github.com/cyphar/filepath-securejoin` and `github.com/hashicorp/go-version`
  - npm build-time devDependencies carrying MPL-2.0 terms: `lightningcss` (never shipped to end users) and `dompurify` (Apache-2.0 option elected)
- All MPL-2.0 components are used unmodified and combined as a "Larger Work" per MPL-2.0 Section 3.3; no Bifrost source files are themselves MPL-licensed.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [x] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [x] Docs

## How to test

No functional changes — review the file contents to confirm accuracy of license attributions against the listed upstream repositories.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

This change has no security implications. It is a legal/compliance attribution document only.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
Rewarm each per-user OAuth MCP client global tool catalog asynchronously after a user token is resolved. Coalesce concurrent refreshes, throttle failed attempts by the configured sync interval, reject results produced from stale client configurations, and cancel work with the manager lifecycle.

Persist only the refreshed tools, name mapping, and last-sync timestamp through a narrow conditional config-store update so background refreshes cannot overwrite concurrent client edits. Restore the catalog across restarts and document its global semantics.

Affected packages:

- core/mcp and core/schemas

- framework/configstore

- transports/bifrost-http

- docs/mcp/auth
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved per-user OAuth MCP tool-catalog refreshes with background synchronization, throttling, retry handling, and stale-result protection.
    • Refreshed tool catalogs now persist across restarts without interrupting tool execution.
    • Client updates preserve discovered tools and safely handle renamed clients.
  • Documentation

    • Added documentation describing global tool-catalog refresh behavior for per-user OAuth connections.
  • Chores

    • Added storage support for tracking catalog synchronization times.

Walkthrough

The PR adds coordinated per-user OAuth MCP tool-catalog refreshes, stale-result protection, immutable client configuration updates, persistence of discovered tools and sync timestamps, database migration support, bootstrap wiring, tests, and documentation.

Changes

Per-user OAuth MCP tool catalog synchronization

Layer / File(s) Summary
Catalog persistence contract and storage
core/schemas/mcp.go, framework/configstore/..., transports/bifrost-http/...
The MCP configuration exposes a persistence callback. The config store persists discovered tools, name mappings, and DiscoveredToolsLastSync. Updates require the expected client name and preserve unrelated fields.
OAuth refresh coordination
core/mcp/..., docs/mcp/auth/per-user-oauth.mdx
Per-user OAuth calls capture bearer tokens and start coalesced, timeout-bounded refreshes. Successful results update and optionally persist the shared catalog. Stale or failed refreshes do not replace current tools.
Immutable client state updates
core/mcp/clientmanager.go, core/mcp/clientmanager_test.go
Disable, enable, and update operations copy configuration snapshots. Client updates preserve discovered-tool metadata and synchronize renamed tool keys.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: pratham-mishra04, akshaydeo, bearts

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant MCPManager
  participant MCPServer
  participant ConfigStore
  Caller->>MCPManager: Authorized MCP tool call
  MCPManager->>MCPManager: Coalesce and throttle refresh
  MCPManager->>MCPServer: Request tools/list with OAuth token
  MCPServer-->>MCPManager: Return discovered tools
  MCPManager->>ConfigStore: Persist tools, mappings, and sync time
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the primary fix: refreshing stale per-user OAuth MCP tool catalogs.
Description check ✅ Passed The description covers the purpose, changes, testing, affected areas, security, breaking changes, documentation, and checklist items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
core/mcp/clientmanager_test.go (1)

40-82: 🚀 Performance & Scalability | 🔵 Trivial

Consider adding coverage for the empty-tools refresh scenario.

maybeRefreshDiscoveredToolsAsync (the orchestration function that calls VerifyPerUserOAuthConnection then applyDiscoveredToolsRefresh) is not directly unit-tested here — only its constituent pieces (toolSyncFlight, applyDiscoveredToolsRefresh) are. Once the empty-tools guard proposed in core/mcp/clientmanager.go is added, add a test that exercises a successful-but-empty tools result and asserts the previous non-empty catalog is retained.

🤖 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 `@core/mcp/clientmanager_test.go` around lines 40 - 82, Add a unit test for
maybeRefreshDiscoveredToolsAsync covering a successful refresh that returns an
empty tools map, with VerifyPerUserOAuthConnection succeeding; assert
applyDiscoveredToolsRefresh is not allowed to replace the existing non-empty
catalog and the prior tools remain intact, while preserving the existing
stale-result and immutable-config coverage.
🤖 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 `@core/mcp/clientmanager.go`:
- Around line 287-306: Update the async refresh flow around
VerifyPerUserOAuthConnection and applyDiscoveredToolsRefresh to reject empty or
nil tools results when the client already has a non-empty shared catalog.
Preserve the existing catalog, set retryOnNextRequest so the next request
retries, and return without applying the empty tools or mapping; continue
applying empty results only when no prior catalog exists.
- Around line 259-275: Protect all reads of state.ExecutionConfig in
maybeRefreshDiscoveredToolsAsync, including the reads used by the dispatched
refresh and existing AcquireClientConn logic, with the same atomic pointer
snapshot mechanism used when DisableClient, EnableClient, UpdateClient, and
applyDiscoveredToolsRefresh replace it. Ensure the goroutine operates on the
captured configuration snapshot rather than an unsynchronized pointer.

---

Nitpick comments:
In `@core/mcp/clientmanager_test.go`:
- Around line 40-82: Add a unit test for maybeRefreshDiscoveredToolsAsync
covering a successful refresh that returns an empty tools map, with
VerifyPerUserOAuthConnection succeeding; assert applyDiscoveredToolsRefresh is
not allowed to replace the existing non-empty catalog and the prior tools remain
intact, while preserving the existing stale-result and immutable-config
coverage.
🪄 Autofix (Beta)

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: CHILL

Plan: Pro Plus

Run ID: 594893e1-bf64-4717-a775-5cf81768c554

📥 Commits

Reviewing files that changed from the base of the PR and between 29e49c9 and 3b87b6a.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/mcp/clientmanager.go
  • core/mcp/clientmanager_test.go
  • core/mcp/mcp.go
  • core/schemas/mcp.go
  • docs/mcp/auth/per-user-oauth.mdx
  • framework/changelog.md
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/rdb_test.go
  • framework/configstore/store.go
  • framework/configstore/tables/mcp.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md

Comment thread core/mcp/clientmanager.go
Comment thread core/mcp/clientmanager.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 31, 2026
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.

4 participants