Skip to content

fix(mcp): stop enable/disable toggle from corrupting tool_sync_interval - #5049

Closed
Shaik-Sirajuddin wants to merge 3 commits into
maximhq:devfrom
Shaik-Sirajuddin:worktree-fix-mcp-tool-sync-interval-units
Closed

fix(mcp): stop enable/disable toggle from corrupting tool_sync_interval#5049
Shaik-Sirajuddin wants to merge 3 commits into
maximhq:devfrom
Shaik-Sirajuddin:worktree-fix-mcp-tool-sync-interval-units

Conversation

@Shaik-Sirajuddin

@Shaik-Sirajuddin Shaik-Sirajuddin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

The MCP client table's enable/disable Switch resent tool_sync_interval (nanoseconds, from GET) into PUT, which parses that field as minutes — overflowing int64 into a garbage negative duration for clients with a non-default sync interval.

Changes

  • Toggle now sends only { disabled }; the update handler already has PATCH semantics.
  • Fixed the optimistic RTK Query cache write (minutes → nanoseconds) to match.
  • Added DurationFromUnits, an overflow-guarded helper, and used it everywhere tool_sync_interval/tool_execution_timeout are derived from request or config data.
  • Fixed two more real unit bugs found while auditing: global tool_sync_interval reload used seconds instead of minutes in two places (config.go, lib/config.go).
  • Fixed a pointer-aliasing hazard in config.go's updateConfig (a rejected request could leave live state partially mutated) and a validate-after-DB-write ordering issue in the client update handler.

How to test

go test ./core/schemas/... ./transports/bifrost-http/handlers/... ./transports/bifrost-http/lib/...
cd ui && npm run typecheck

Added tests/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts (verified it reproduces the exact corrupted value from the bug report against pre-fix code, passes against the fix) and core/schemas/duration_test.go for the overflow-guard helper.

Related issues

Closes #5026

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 13d06026-baae-4a7c-8e14-2400ca7afc33

📥 Commits

Reviewing files that changed from the base of the PR and between ecb0007 and aa62697.

📒 Files selected for processing (1)
  • transports/bifrost-http/lib/config.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • transports/bifrost-http/lib/config.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Added stricter validation for MCP tool timing fields, preventing invalid or overflowing durations from being accepted.
    • Fixed an issue where toggling an MCP client could corrupt its sync interval.
    • Updated MCP client enable/disable updates to only change the enabled state, preserving other settings.
    • Ensured displayed and stored sync intervals/timeouts remain consistent across create/update and UI interactions.
  • Tests
    • Added an end-to-end regression test covering the MCP registry tool sync interval corruption scenario.

Walkthrough

Adds overflow-checked duration conversion and applies it to MCP timeout and sync-interval handling across core, HTTP, and global configuration paths. The MCP registry toggle now sends only disabled, and the UI cache converts tool_sync_interval from minutes to nanoseconds. Unit and e2e tests are added.

Changes

MCP duration conversion and toggle fix

Layer / File(s) Summary
Duration helper and tests
core/schemas/duration.go, core/schemas/duration_test.go
Adds DurationFromUnits with unit and overflow validation, plus table-driven coverage for normal, boundary, overflow, and invalid-unit inputs.
Core timeout conversion
core/bifrost.go, core/schemas/mcp.go
tool_execution_timeout now goes through DurationFromUnits in core config update and JSON unmarshaling paths, with conversion errors returned directly.
HTTP config prevalidation
transports/bifrost-http/handlers/config.go
updateConfig validates prospective MCP timing values before mutating state and writes back the prevalidated durations.
MCP client duration resolution
transports/bifrost-http/handlers/mcp.go
MCP client create and update paths now resolve tool_sync_interval and tool_execution_timeout with DurationFromUnits, including request and fallback error handling.
Global settings reconciliation
transports/bifrost-http/lib/config.go
Global MCP settings backfill now uses DurationFromUnits for timeout and reconciles tool_sync_interval on whole-minute boundaries.
MCP registry toggle fix
ui/app/workspace/mcp-registry/views/mcpClientsTable.tsx, ui/lib/store/apis/mcpApi.ts, tests/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts
The toggle sends only disabled, the optimistic cache converts minutes to nanoseconds, and the e2e test covers interval preservation during enable/disable changes.

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

Possibly related PRs

  • maximhq/bifrost#3811: Both PRs modify transports/bifrost-http/handlers/mcp.go around updateMCPClient resolution and persistence of tool_sync_interval.
  • maximhq/bifrost#4472: Both PRs change core/schemas/mcp.go timeout parsing and validation behavior.

Suggested reviewers: akshaydeo, danpiths, Pratham-Mishra04

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 describes the main fix: preventing the MCP toggle from corrupting tool_sync_interval.
Description check ✅ Passed The description includes summary, changes, testing, and related issue sections; optional template sections are omitted but the PR is mostly complete.
Linked Issues check ✅ Passed The changes fix #5026 by sending only disabled from the table toggle and preserving tool_sync_interval through the update path.
Out of Scope Changes check ✅ Passed The added duration helper, handler hardening, and config fixes all support the same MCP duration bug class and stated PR objectives.
✨ 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.

@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
transports/bifrost-http/handlers/mcp.go Validates resolved MCP client duration values before persisting updates.
ui/app/workspace/mcp-registry/views/mcpClientsTable.tsx Narrows the enable switch update payload to the changed disabled flag.
ui/lib/store/apis/mcpApi.ts Converts request minutes to cached nanoseconds for optimistic MCP client updates.
core/schemas/duration.go Adds a guarded helper for scaling integer duration units.
transports/bifrost-http/handlers/config.go Pre-validates MCP config duration values before live config mutation.
transports/bifrost-http/lib/config.go Applies global MCP sync interval settings using minute-based conversion.

Reviews (4): Last reviewed commit: "Merge branch 'dev' into worktree-fix-mcp..." | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/mcp.go
The MCP client table's enable/disable Switch resent tool_sync_interval
(nanoseconds from GET) into a PUT field the backend parses as minutes,
overflowing int64 into a garbage negative duration for clients with a
non-default sync interval. The toggle now sends only {disabled},
relying on the update handler's existing PATCH semantics.

Adds DurationFromUnits, an overflow-guarded minute/second-to-Duration
helper, and uses it across the MCP client and global-config handlers.
Also fixes two related unit bugs found while auditing those call
sites (global tool_sync_interval reload used seconds instead of
minutes in two places), a pointer-aliasing hazard in config.go's
updateConfig that could leave live state corrupted on a rejected
request, and a validate-after-DB-write ordering issue in the client
update handler's global-default fallback.

Fixes maximhq#5026.
@Shaik-Sirajuddin
Shaik-Sirajuddin force-pushed the worktree-fix-mcp-tool-sync-interval-units branch from c6c8c0f to 6f50870 Compare July 9, 2026 06:05

@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: 1

🧹 Nitpick comments (4)
transports/bifrost-http/handlers/config.go (1)

345-369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Three near-duplicate branches now decide the same two field values.

The prospective-value computation here mirrors the logic in the later mutation blocks (MCPToolSyncInterval/MCPToolExecutionTimeout handling further down in this function). Since this is precisely the kind of unit/logic drift that caused the original bug, consider deriving updatedConfig.MCPToolSyncInterval/MCPToolExecutionTimeout directly from prospectiveToolSyncInterval/prospectiveToolExecutionTimeout right after validation, and removing the now-redundant later assignments, so there's a single source of truth for "what value wins."

🤖 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 `@transports/bifrost-http/handlers/config.go` around lines 345 - 369, The
config update flow in configHandler duplicates the MCPToolSyncInterval and
MCPToolExecutionTimeout decision logic in two places, which risks drift and
inconsistent “winning” values. Keep the existing prospective value validation in
configHandler, then use prospectiveToolSyncInterval and
prospectiveToolExecutionTimeout as the single source of truth when assigning
updatedConfig, removing the later redundant branches for those two fields so the
mutation logic is centralized and consistent.
transports/bifrost-http/handlers/mcp.go (1)

677-695: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct overflow-guarded conversion, but four near-identical blocks now duplicate the same request/config resolution logic.

Each block resolves toolSyncInterval the same way (request value via DurationFromUnits → 400 on error; else config fallback → 500 on error), just with minor variations in how GetClientConfig errors are handled. Worth extracting into a small helper (e.g. resolveToolSyncInterval(ctx, req.ToolSyncInterval, h.store.ConfigStore)) to avoid drift across the four copies as this logic evolves again.

♻️ Sketch of extracted helper
func (h *MCPHandler) resolveToolSyncInterval(ctx *fasthttp.RequestCtx, reqVal int) (time.Duration, *schemas.BifrostError) {
	if reqVal != 0 {
		dur, err := schemas.DurationFromUnits(int64(reqVal), time.Minute, "tool_sync_interval")
		if err != nil {
			return 0, &schemas.BifrostError{StatusCode: fasthttp.StatusBadRequest, Message: err.Error()}
		}
		return dur, nil
	}
	config, err := h.store.ConfigStore.GetClientConfig(ctx)
	if err != nil || config == nil {
		return mcp.DefaultToolSyncInterval, nil
	}
	dur, durErr := schemas.DurationFromUnits(int64(config.MCPToolSyncInterval), time.Minute, "tool_sync_interval")
	if durErr != nil {
		return 0, &schemas.BifrostError{StatusCode: fasthttp.StatusInternalServerError, Message: durErr.Error()}
	}
	return dur, nil
}

Note: the existing blocks aren't fully identical today (some silently ignore GetClientConfig errors, others return 500) — that inconsistency predates this diff, so consolidating would also require picking one canonical behavior.

Also applies to: 788-806, 890-912, 965-987

🤖 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 `@transports/bifrost-http/handlers/mcp.go` around lines 677 - 695, The
toolSyncInterval resolution logic is duplicated across multiple handler blocks
and should be consolidated to prevent divergence. Extract the shared
request-vs-config fallback flow into a helper such as resolveToolSyncInterval on
MCPHandler, using schemas.DurationFromUnits and the store
ConfigStore/GetClientConfig path, then have each MCP handler call it and map its
result to the existing HTTP error responses. Make sure the helper centralizes
the canonical behavior for request value, config fallback, and error handling so
all copies stay consistent.
ui/lib/store/apis/mcpApi.ts (1)

176-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct unit conversion; extract the magic number given this exact class of bug.

data.tool_sync_interval * 60_000_000_000 correctly converts minutes (request contract) to nanoseconds (cached/GET contract) — matches the documented contract in core/schemas/mcp.go's UnmarshalJSON comment and transports/bifrost-http/lib/config.go's minutes-validation message. Given the bug this PR fixes was caused by exactly this kind of unit mismatch, naming the multiplier (e.g. NANOSECONDS_PER_MINUTE) would make future edits safer to eyeball-verify.

♻️ Suggested naming
+const NANOSECONDS_PER_MINUTE = 60_000_000_000;
+
 ...
-if (data.tool_sync_interval !== undefined)
-    draft.clients[index].config.tool_sync_interval = data.tool_sync_interval * 60_000_000_000;
+if (data.tool_sync_interval !== undefined)
+    draft.clients[index].config.tool_sync_interval = data.tool_sync_interval * NANOSECONDS_PER_MINUTE;
🤖 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 `@ui/lib/store/apis/mcpApi.ts` around lines 176 - 180, The optimistic cache
update in mcpApi’s client config is using a raw unit-conversion constant, which
is easy to misread and reintroduce as a minutes-vs-nanoseconds bug. Replace the
inline multiplier in the tool_sync_interval assignment with a clearly named
shared constant such as NANOSECONDS_PER_MINUTE, and use that in the relevant
update path inside the mcpApi store logic so the conversion is obvious and
consistent with the documented contract.
tests/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts (1)

60-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid regression coverage; consider also asserting the actual PUT request body.

The test verifies the outcome (value preserved across toggle) thoroughly, including a second round-trip. To directly pin down the described mechanism ("send only {disabled}"), you could additionally assert the request body captured via page.waitForResponse's .request().postData() equals exactly {"disabled":true} — this would catch a future regression where the payload grows again but happens to not corrupt the value (e.g. if the backend became more lenient).

🤖 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/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts`
around lines 60 - 102, The regression test in
mcp-registry-tool-sync-interval.spec.ts already checks the preserved outcome,
but it should also pin down the UI toggle payload itself. In the enabledSwitch
click flow, use the existing page.waitForResponse assertions around the PUT to
inspect the matching request body and verify it sends only the disabled field,
not the raw tool_sync_interval value. Keep the current round-trip assertions in
getClientConfig and the afterDisable/afterReEnable checks so the test covers
both the request shape and the preserved interval behavior.
🤖 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/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts`:
- Around line 73-75: The test is using a brittle role-based locator for the MCP
client enabled switch instead of the stable existing test id. Update the
selector in mcp-registry-tool-sync-interval.spec.ts to target the switch exposed
by mcpClientsTable.tsx via its data-testid pattern for the client id, and keep
the lookup anchored through mcpRegistryPage/getClientRow only as needed. Ensure
the test continues to use data-testid selectors consistently, in line with the
existing fixtures/import conventions from tests/e2e/core/fixtures/base.fixture.

---

Nitpick comments:
In `@tests/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts`:
- Around line 60-102: The regression test in
mcp-registry-tool-sync-interval.spec.ts already checks the preserved outcome,
but it should also pin down the UI toggle payload itself. In the enabledSwitch
click flow, use the existing page.waitForResponse assertions around the PUT to
inspect the matching request body and verify it sends only the disabled field,
not the raw tool_sync_interval value. Keep the current round-trip assertions in
getClientConfig and the afterDisable/afterReEnable checks so the test covers
both the request shape and the preserved interval behavior.

In `@transports/bifrost-http/handlers/config.go`:
- Around line 345-369: The config update flow in configHandler duplicates the
MCPToolSyncInterval and MCPToolExecutionTimeout decision logic in two places,
which risks drift and inconsistent “winning” values. Keep the existing
prospective value validation in configHandler, then use
prospectiveToolSyncInterval and prospectiveToolExecutionTimeout as the single
source of truth when assigning updatedConfig, removing the later redundant
branches for those two fields so the mutation logic is centralized and
consistent.

In `@transports/bifrost-http/handlers/mcp.go`:
- Around line 677-695: The toolSyncInterval resolution logic is duplicated
across multiple handler blocks and should be consolidated to prevent divergence.
Extract the shared request-vs-config fallback flow into a helper such as
resolveToolSyncInterval on MCPHandler, using schemas.DurationFromUnits and the
store ConfigStore/GetClientConfig path, then have each MCP handler call it and
map its result to the existing HTTP error responses. Make sure the helper
centralizes the canonical behavior for request value, config fallback, and error
handling so all copies stay consistent.

In `@ui/lib/store/apis/mcpApi.ts`:
- Around line 176-180: The optimistic cache update in mcpApi’s client config is
using a raw unit-conversion constant, which is easy to misread and reintroduce
as a minutes-vs-nanoseconds bug. Replace the inline multiplier in the
tool_sync_interval assignment with a clearly named shared constant such as
NANOSECONDS_PER_MINUTE, and use that in the relevant update path inside the
mcpApi store logic so the conversion is obvious and consistent with the
documented contract.
🪄 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: 0646ac3c-c1d0-4b54-abe2-c5767c17c19c

📥 Commits

Reviewing files that changed from the base of the PR and between efa59f5 and c6c8c0f.

📒 Files selected for processing (10)
  • core/bifrost.go
  • core/schemas/duration.go
  • core/schemas/duration_test.go
  • core/schemas/mcp.go
  • tests/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientsTable.tsx
  • ui/lib/store/apis/mcpApi.ts

Comment thread tests/e2e/features/mcp-registry/mcp-registry-tool-sync-interval.spec.ts Outdated
@coderabbitai
coderabbitai Bot requested a review from Pratham-Mishra04 July 9, 2026 06:13
- e2e test: use the stable data-testid selector instead of a role-based
  one, and assert the toggle's PUT payload is exactly {disabled: true}
  to pin down the actual fix mechanism, not just the outcome.
- mcpApi.ts: name the minutes->nanoseconds multiplier instead of a bare
  literal, since a misread multiplier here is exactly the bug class
  this PR fixes.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Shaik-Sirajuddin
❌ akshaydeo
You have signed the CLA already but the status is still pending? Let us recheck it.

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Hey @Shaik-Sirajuddin this is already fixed in this pr - #4954

@Shaik-Sirajuddin

Copy link
Copy Markdown
Contributor Author

Hey @Shaik-Sirajuddin this is already fixed in this pr - #4954

Missed to check that
Closing this as in wip here #4954

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.

[Bug]: Toggling an MCP client's enable/disable switch corrupts its tool_sync_interval (nanoseconds resent as minutes)

4 participants