Skip to content

feat(mcp): add per-MCP-server tool execution timeout - #4472

Merged
Pratham-Mishra04 merged 11 commits into
maximhq:devfrom
Purvi09:feat/per-mcp-server-tool-timeout
Jul 2, 2026
Merged

feat(mcp): add per-MCP-server tool execution timeout#4472
Pratham-Mishra04 merged 11 commits into
maximhq:devfrom
Purvi09:feat/per-mcp-server-tool-timeout

Conversation

@Purvi09

@Purvi09 Purvi09 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a per-MCP-server tool_execution_timeout field to MCPClientConfig, allowing each MCP server to declare its own tool execution timeout instead of sharing the single global value from tool_manager_config. When set, it overrides the global for that server only. Omitting it (or setting 0) falls back to the global default.

Changes

  • Added ToolExecutionTimeout time.Duration to MCPClientConfig in core/schemas/mcp.go
  • Updated MCPClientConfig.UnmarshalJSON to parse the new field — accepts a Go duration string (e.g. "30s", "2m") or a bare integer treated as seconds, matching the behaviour of tool_manager_config.tool_execution_timeout
  • Updated executeToolInternal in core/mcp/toolmanager.go to check executionConfig.ToolExecutionTimeout before falling back to the global timeout
  • Added tool_execution_timeout to the mcp_client_config definition in transports/config.schema.json for IDE autocomplete/validation
  • Added DB column tool_execution_timeout to config_mcp_clients with migration add_mcp_client_tool_execution_timeout_column
  • Added UI field "Tool Execution Timeout (seconds)" in the MCP client sheet, following the same pattern as tool_sync_interval
  • Added tool_execution_timeout to the update request handler in transports/bifrost-http/handlers/mcp.go with negative value validation
  • Added 8 unit tests in core/schemas/mcp_json_test.go covering: duration string, bare integer, field not set, explicit zero, invalid string, negative integer, and negative string
  • Added 3 integration tests in core/internal/mcptests/per_server_timeout_test.go covering: per-server timeout overrides global, per-server timeout allows tool to complete, and zero timeout falls back to global

Design decision: Bare integers are treated as seconds (not nanoseconds) to match tool_manager_config.tool_execution_timeout behaviour and to be practical for timeout values. This intentionally differs from tool_sync_interval, which treats bare integers as nanoseconds following Go's time.Duration convention.

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

# Run the schema unit tests
cd core
go test ./schemas/... -run TestMCPClientConfigUnmarshalToolExecutionTimeout -v

# Run the integration tests
go test ./internal/mcptests/ -run TestPerServerTimeout -v

To test end-to-end via config file, add tool_execution_timeout to a server in your config.json:

{
  "client_configs": [
    {
      "name": "myslowserver",
      "connection_type": "http",
      "connection_string": "http://localhost:8080",
      "tool_execution_timeout": "5s"
    }
  ]
}

Tools on myslowserver will now time out after 5 seconds regardless of the global timeout. Other servers continue to use the global default.

You can also set the timeout via the UI — open the MCP client sheet and set "Tool Execution Timeout (seconds)". The value is stored in Postgres and survives restarts.

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

Related issues

Closes #4446

Security considerations

None — this is a timeout configuration field only. No auth, secrets, or PII involved.

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

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 292b4273-2036-459a-ab87-a7a05d675f08

📥 Commits

Reviewing files that changed from the base of the PR and between e60aa03 and 5773237.

📒 Files selected for processing (14)
  • core/internal/mcptests/per_server_timeout_test.go
  • core/mcp/clientmanager.go
  • core/mcp/toolmanager.go
  • core/schemas/mcp.go
  • core/schemas/mcp_json_test.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/mcp.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a per-client tool execution timeout setting, with support for overriding the global default.
    • Exposed the setting in the UI and API, with validation and clear fallback to the global value when set to 0 or left empty.
  • Bug Fixes

    • Tool execution now respects client-specific timeout values consistently across configuration, updates, and runtime execution.
  • Tests

    • Added coverage for timeout parsing, validation, and per-server timeout behavior.

Walkthrough

This PR adds per-client tool_execution_timeout support across JSON/schema handling, database persistence, HTTP wiring, runtime timeout selection, UI editing, and integration tests.

Changes

Per-server tool execution timeout

Layer / File(s) Summary
Config field, JSON parsing, and schema validation
core/schemas/mcp.go, core/schemas/mcp_json_test.go, transports/config.schema.json
Adds ToolExecutionTimeout to MCPClientConfig, custom JSON marshal/unmarshal support, matching schema validation, and tests for valid and invalid inputs.
Database column and RDB persistence
framework/configstore/tables/mcp.go, framework/configstore/migrations.go, framework/configstore/rdb.go
Adds the persisted column and migration, plus create/read/update conversion and validation between durations and stored seconds.
Handler updates and runtime precedence
transports/bifrost-http/handlers/mcp.go, transports/bifrost-http/lib/config.go, core/mcp/clientmanager.go, core/mcp/toolmanager.go
Threads the timeout through client listing and updates, copies it into in-memory configs, and makes tool execution prefer the per-client timeout over the global timeout.
Per-server timeout integration tests
core/internal/mcptests/per_server_timeout_test.go
Adds in-process MCP tests covering override, longer-than-global, and fallback-to-global behavior.
UI types and timeout editor
ui/lib/types/mcp.ts, ui/lib/types/schemas.ts, ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
Adds the UI-facing types, update schema, form parsing, submission, and numeric editor for the new timeout field.

Related issue: 4446

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UI as MCP Client Sheet
  participant Handler as MCP HTTP Handler
  participant ConfigStore as configstore RDB
  participant Manager as MCP Tool Manager

  UI->>Handler: submit tool_execution_timeout
  Handler->>Handler: resolve PATCH value
  Handler->>ConfigStore: persist timeout seconds
  Handler->>Manager: UpdateMCPClient(timeout)
  Manager->>Manager: copy timeout into execution config
  Manager->>Manager: executeToolInternal selects per-client timeout
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% 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 matches the main change: adding a per-MCP-server tool execution timeout.
Description check ✅ Passed The description follows the template well and includes summary, changes, testing, related issue, and checklist sections.
Linked Issues check ✅ Passed The PR implements #4446 by adding server-level timeout config with precedence over the global timeout and fallback on 0.
Out of Scope Changes check ✅ Passed The changes stay focused on MCP timeout configuration across core, schema, transport, DB, UI, and tests.
✨ 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: 1

🧹 Nitpick comments (1)
core/schemas/mcp_json_test.go (1)

86-95: ⚡ Quick win

Add an explicit tool_execution_timeout: 0 test case.

The contract for this PR includes “omitted or set to 0 uses global,” but this file currently validates only the omitted-path. Please add a dedicated {"tool_execution_timeout":0} unmarshal test to lock that behavior.

🤖 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/schemas/mcp_json_test.go` around lines 86 - 95, Add a new test function
to explicitly validate the behavior when tool_execution_timeout is set to 0 in
the JSON. Create a test similar to
TestMCPClientConfigUnmarshalToolExecutionTimeoutNotSet (which tests the omitted
case) but with JSON that includes "tool_execution_timeout":0, and verify that
cfg.ToolExecutionTimeout equals 0 after unmarshaling. This ensures the contract
that both omitted and explicitly zero-valued tool_execution_timeout fields use
the global timeout is tested at both code paths.
🤖 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/schemas/mcp.go`:
- Around line 353-373: The tool execution timeout parsing logic currently
accepts negative values for both the string duration format (after
time.ParseDuration) and the bare integer seconds format (after json.Unmarshal of
n as int64), which violates the schema contract requiring minimum: 0. Add
validation checks after parsing both formats to reject negative values by
returning an error if the parsed duration or integer is less than zero before
assigning to c.ToolExecutionTimeout. Apply this validation consistently wherever
tool_execution_timeout is parsed during unmarshal.

---

Nitpick comments:
In `@core/schemas/mcp_json_test.go`:
- Around line 86-95: Add a new test function to explicitly validate the behavior
when tool_execution_timeout is set to 0 in the JSON. Create a test similar to
TestMCPClientConfigUnmarshalToolExecutionTimeoutNotSet (which tests the omitted
case) but with JSON that includes "tool_execution_timeout":0, and verify that
cfg.ToolExecutionTimeout equals 0 after unmarshaling. This ensures the contract
that both omitted and explicitly zero-valued tool_execution_timeout fields use
the global timeout is tested at both code paths.
🪄 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: 55d39fe3-5ffe-48dd-b0dd-e6807790f865

📥 Commits

Reviewing files that changed from the base of the PR and between fa9d8f0 and 46993c5.

📒 Files selected for processing (4)
  • core/mcp/toolmanager.go
  • core/schemas/mcp.go
  • core/schemas/mcp_json_test.go
  • transports/config.schema.json

Comment thread core/schemas/mcp.go
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge — the core timeout-override logic, DB persistence, and marshaling round-trip are all correct; remaining comments are minor test-coverage gaps and a silent rounding behavior for sub-second values.

The implementation correctly addresses previously-flagged issues: negative duration rejection, integer overflow guard, symmetric MarshalJSON, and consistent seconds-based storage. The open items are a test that exercises context-deadline cancellation instead of the global-timeout code path, a missing marshal→unmarshal round-trip test, and sub-second timeouts silently rounded to 1 s on DB persist — none affect correct behavior for realistic timeout values.

core/internal/mcptests/per_server_timeout_test.go and core/schemas/mcp_json_test.go would benefit from the additional coverage described in the comments. toolExecutionTimeoutDurationToStoredSeconds in framework/configstore/rdb.go is worth revisiting if sub-second precision is ever needed.

Important Files Changed

Filename Overview
core/schemas/mcp.go Adds ToolExecutionTimeout time.Duration, robust parse helper with negative/overflow guards, and MarshalJSON that emits a duration string to fix the round-trip.
core/schemas/mcp_json_test.go Eight new unit tests covering all unmarshal cases; missing a marshal→unmarshal round-trip test.
core/internal/mcptests/per_server_timeout_test.go Override and allow-longer-than-global cases well covered; fallback-to-global test relies on context-deadline cancellation rather than the global toolExecutionTimeout value.
framework/configstore/rdb.go Create/read/update paths convert seconds symmetrically; math.Ceil in create silently rounds up sub-second values.
transports/bifrost-http/handlers/mcp.go API accepts integer seconds with non-negative validation, converts to time.Duration then back to int seconds for the DB struct — consistent with ToolSyncInterval.
ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx New toolExecutionTimeoutToSeconds parser, UI field with data-testid; two initialisation lines have a minor indentation inconsistency.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CF as config.json
    participant S as MCPClientConfig UnmarshalJSON
    participant M as MCPManager
    participant T as ToolsManager executeToolInternal
    participant DB as Postgres config_mcp_clients
    participant H as HTTP Handler updateMCPClient
    participant UI as UI mcpClientSheet

    CF->>S: tool_execution_timeout (string or int)
    S->>S: parseToolExecutionTimeoutField
    S->>M: AddClient MCPClientConfig
    M->>DB: CreateMCPClientConfig math.Ceil seconds
    UI->>H: PATCH tool_execution_timeout int seconds
    H->>H: validate non-negative
    H->>M: UpdateClient MCPClientConfig
    H->>DB: UpdateMCPClientConfig int seconds
    DB->>M: GetMCPConfig Duration seconds
    M->>T: executeToolInternal executionConfig
    T->>T: per-server gt 0 use per-server else global
    T->>T: context.WithTimeout ctx timeout
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CF as config.json
    participant S as MCPClientConfig UnmarshalJSON
    participant M as MCPManager
    participant T as ToolsManager executeToolInternal
    participant DB as Postgres config_mcp_clients
    participant H as HTTP Handler updateMCPClient
    participant UI as UI mcpClientSheet

    CF->>S: tool_execution_timeout (string or int)
    S->>S: parseToolExecutionTimeoutField
    S->>M: AddClient MCPClientConfig
    M->>DB: CreateMCPClientConfig math.Ceil seconds
    UI->>H: PATCH tool_execution_timeout int seconds
    H->>H: validate non-negative
    H->>M: UpdateClient MCPClientConfig
    H->>DB: UpdateMCPClientConfig int seconds
    DB->>M: GetMCPConfig Duration seconds
    M->>T: executeToolInternal executionConfig
    T->>T: per-server gt 0 use per-server else global
    T->>T: context.WithTimeout ctx timeout
Loading

Reviews (15): Last reviewed commit: "Merge branch 'dev' into feat/per-mcp-ser..." | Re-trigger Greptile

Comment thread core/schemas/mcp.go
Comment thread transports/config.schema.json
Comment thread core/schemas/mcp.go Outdated
@Purvi09
Purvi09 force-pushed the feat/per-mcp-server-tool-timeout branch from 46993c5 to 87bc6e1 Compare June 16, 2026 19:01
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

Comment thread core/schemas/mcp.go Outdated
Comment thread core/schemas/mcp.go
Comment thread core/schemas/mcp.go

@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

🤖 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 `@transports/bifrost-http/lib/config.go`:
- Around line 1792-1797: The tool_execution_timeout validation in the
bifrost-http config is incorrectly rejecting valid sub-second duration values
like "500ms" instead of accepting and rounding them for database persistence.
Rather than checking if clientConfig.ToolExecutionTimeout is not a whole number
of seconds and returning an error, remove the rejection logic and instead
convert the duration to whole seconds by rounding up using math.Ceil, storing
the result as an integer for the database (where 0 represents "use global
default"). This same fix needs to be applied in two locations within the file:
at the primary validation site (lines 1792-1797) and at the secondary validation
site (line 1817), ensuring both enforce the same rounding behavior rather than
rejection.
🪄 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: c7cd3b99-22a0-4580-94a0-f6889e2509c0

📥 Commits

Reviewing files that changed from the base of the PR and between 87bc6e1 and 62947b3.

📒 Files selected for processing (5)
  • core/mcp/clientmanager.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/mcp.go
  • transports/bifrost-http/lib/config.go

Comment thread transports/bifrost-http/lib/config.go Outdated
Comment thread transports/bifrost-http/lib/config.go Outdated
@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Hey @Purvi09 thanks for the PR! The direction looks good - just a few pointers before its merge ready

  • Resolving all greptile/code rabbit comments
  • adding UI support for the same
  • test cases in core/internal/mcptests

@Purvi09
Purvi09 force-pushed the feat/per-mcp-server-tool-timeout branch from 62947b3 to c3abcba Compare June 17, 2026 13:38

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
framework/configstore/rdb.go (1)

2144-2154: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce non-negative timeout on update path for DB invariant parity

Line 2153 writes tool_execution_timeout without validation, but create path (Line 1983-1986) rejects negatives. This can persist invalid values and break field-level contract consistency between create/update flows.

Suggested fix
 		// Update only editable fields using a map to avoid updating connection info
 		// Connection info (ConnectionType, ConnectionString, StdioConfig) is read-only and should not be modified via API
+		if clientConfigCopy.ToolExecutionTimeout < 0 {
+			return fmt.Errorf("tool_execution_timeout must be non-negative, got %d", clientConfigCopy.ToolExecutionTimeout)
+		}
+
 		updates := map[string]interface{}{
 			"name":                       clientConfigCopy.Name,
 			"is_code_mode_client":        clientConfigCopy.IsCodeModeClient,
 			"tools_to_execute_json":      string(toolsToExecuteJSON),
 			"tools_to_auto_execute_json": string(toolsToAutoExecuteJSON),
 			"headers_json":               headersJSONStr,
 			"allowed_extra_headers_json": string(allowedExtraHeadersJSON),
 			"tool_pricing_json":          string(toolPricingJSON),
 			"tool_sync_interval":         clientConfigCopy.ToolSyncInterval,
 			"tool_execution_timeout":     clientConfigCopy.ToolExecutionTimeout,
 			"allow_on_all_virtual_keys":  clientConfigCopy.AllowOnAllVirtualKeys,
 			"disabled":                   clientConfigCopy.Disabled,
 			"updated_at":                 time.Now(),
 		}
🤖 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 `@framework/configstore/rdb.go` around lines 2144 - 2154, The update path in
the method containing the updates map assignment does not validate that
tool_execution_timeout is non-negative before adding it to the updates map,
while the create path (around lines 1983-1986) enforces this validation. Add a
check to ensure clientConfigCopy.ToolExecutionTimeout is non-negative and reject
or handle negative values appropriately before including it in the updates map
to maintain consistency between the create and update flows and preserve the
field-level contract invariant.
transports/bifrost-http/lib/config.go (1)

1786-1812: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject negative ToolExecutionTimeout before ceil conversion.

math.Ceil can turn invalid negative sub-second durations into 0 (e.g., -500ms), which collides with the valid sentinel meaning “use global timeout.” Add an explicit < 0 check before conversion.

💡 Suggested fix
 func mcpClientConfigToTable(clientConfig *schemas.MCPClientConfig) (configstoreTables.TableMCPClient, error) {
 	if clientConfig == nil {
 		return configstoreTables.TableMCPClient{}, nil
 	}
 	if clientConfig.ToolSyncInterval%time.Second != 0 {
 		return configstoreTables.TableMCPClient{}, fmt.Errorf(
 			"tool_sync_interval must be a whole number of seconds, got %q",
 			clientConfig.ToolSyncInterval.String(),
 		)
 	}
+	if clientConfig.ToolExecutionTimeout < 0 {
+		return configstoreTables.TableMCPClient{}, fmt.Errorf(
+			"tool_execution_timeout must be >= 0, got %q",
+			clientConfig.ToolExecutionTimeout.String(),
+		)
+	}
 	authType := string(clientConfig.AuthType)

As per coding guidelines, tool_execution_timeout must preserve the schema contract (minimum: 0, with 0 reserved for global fallback).

🤖 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/lib/config.go` around lines 1786 - 1812, Add an
explicit validation check to reject negative ToolExecutionTimeout values before
the math.Ceil conversion, similar to the existing ToolSyncInterval validation
pattern. Insert a check that verifies clientConfig.ToolExecutionTimeout is not
less than zero and returns a descriptive error if it is, ensuring negative
durations cannot be silently converted to zero (which is a reserved sentinel
value for using global timeout). This validation should occur before the return
statement that constructs the configstoreTables.TableMCPClient object.

Source: Coding guidelines

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

354-385: 💤 Low value

Consider extracting ToolExecutionTimeout parsing into a helper.

The parsing logic for tool_execution_timeout (string vs integer, negative check, overflow check) is duplicated across both code paths. This could be consolidated into a helper like parseSecondsBasedDurationField(*json.RawMessage, string) (time.Duration, error).

Not blocking since functionality is correct, but would reduce maintenance surface and potential for divergence.

Also applies to: 407-436

🤖 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/schemas/mcp.go` around lines 354 - 385, The ToolExecutionTimeout parsing
logic contains duplicated code for handling both string duration format (like
"30s") and integer seconds format, along with shared validation checks for
negative values and overflow. Extract this duplicated logic into a helper
function that accepts a json.RawMessage and field name, then returns the parsed
time.Duration and any error. The helper should handle both the string duration
parsing path and the integer seconds conversion path with all their validation
rules, then replace both instances in the UnmarshalJSON method with calls to
this single helper function.
🤖 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.

Outside diff comments:
In `@framework/configstore/rdb.go`:
- Around line 2144-2154: The update path in the method containing the updates
map assignment does not validate that tool_execution_timeout is non-negative
before adding it to the updates map, while the create path (around lines
1983-1986) enforces this validation. Add a check to ensure
clientConfigCopy.ToolExecutionTimeout is non-negative and reject or handle
negative values appropriately before including it in the updates map to maintain
consistency between the create and update flows and preserve the field-level
contract invariant.

In `@transports/bifrost-http/lib/config.go`:
- Around line 1786-1812: Add an explicit validation check to reject negative
ToolExecutionTimeout values before the math.Ceil conversion, similar to the
existing ToolSyncInterval validation pattern. Insert a check that verifies
clientConfig.ToolExecutionTimeout is not less than zero and returns a
descriptive error if it is, ensuring negative durations cannot be silently
converted to zero (which is a reserved sentinel value for using global timeout).
This validation should occur before the return statement that constructs the
configstoreTables.TableMCPClient object.

---

Nitpick comments:
In `@core/schemas/mcp.go`:
- Around line 354-385: The ToolExecutionTimeout parsing logic contains
duplicated code for handling both string duration format (like "30s") and
integer seconds format, along with shared validation checks for negative values
and overflow. Extract this duplicated logic into a helper function that accepts
a json.RawMessage and field name, then returns the parsed time.Duration and any
error. The helper should handle both the string duration parsing path and the
integer seconds conversion path with all their validation rules, then replace
both instances in the UnmarshalJSON method with calls to this single helper
function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d4a8b9e-84f2-4202-a8a4-5ac1ee8a61e1

📥 Commits

Reviewing files that changed from the base of the PR and between 62947b3 and c3abcba.

📒 Files selected for processing (9)
  • core/mcp/clientmanager.go
  • core/mcp/toolmanager.go
  • core/schemas/mcp.go
  • core/schemas/mcp_json_test.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/mcp.go
  • transports/bifrost-http/lib/config.go
  • transports/config.schema.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • framework/configstore/tables/mcp.go
  • core/mcp/toolmanager.go
  • framework/configstore/migrations.go
  • core/schemas/mcp_json_test.go
  • core/mcp/clientmanager.go
  • transports/config.schema.json

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

🤖 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/internal/mcptests/per_server_timeout_test.go`:
- Around line 57-159: Migrate the three test functions
TestPerServerTimeout_OverridesGlobal,
TestPerServerTimeout_AllowsLongerThanGlobal, and
TestPerServerTimeout_FallsBackToGlobal from the custom setup pattern to the
standard test harness pattern required for core/internal/mcptests. Replace the
custom setupMCPManager and setupBifrost function calls with DynamicLLMMocker and
SetupAgentTest helpers to maintain consistency with the test suite. Preserve the
existing test logic and assertions while adapting the setup to use the
declarative pattern, then remove any custom setup functions that are no longer
needed once all tests have been migrated.
- Around line 79-86: The test is discarding the return values from
ExecuteChatMCPTool (line 81) and relying only on the elapsed time assertion,
which means the test could pass even if a different fast failure occurs instead
of a timeout. Capture the returned error/result from ExecuteChatMCPTool instead
of using blank identifiers, and add an explicit assertion that verifies the
timeout or cancellation error was actually returned (e.g., check for context
cancellation or timeout error). Apply the same fix to the second timeout-path
test at lines 151-158 where the same pattern occurs.

In `@transports/bifrost-http/handlers/mcp.go`:
- Around line 1021-1024: The updateMCPClient handler is not validating that
tool_execution_timeout values are non-negative before conversion and
persistence. Add a validation guard immediately after checking if
req.ToolExecutionTimeout is not nil to ensure the value is not negative, and
return a 400 error response if a negative value is provided. This validation
should occur before the time.Duration conversion to align with the schema
requirement that tool_execution_timeout has minimum: 0. Apply the same
validation logic at the other location mentioned (line 1195) where similar
timeout resolution occurs.

In `@ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx`:
- Around line 824-834: Add a data-testid attribute to the Input component with
type="number" in the mcpClientSheet.tsx file. The attribute should have a
descriptive value that identifies this as a timeout input field, following the
naming convention used elsewhere in the codebase for E2E testing. Place the
data-testid attribute alongside the existing className, placeholder, value,
onChange, min, and type attributes.
- Around line 829-832: The onChange handler for the timeout input field uses
parseInt which silently truncates decimal values instead of rejecting them.
Replace the parseInt approach with strict numeric parsing by first parsing the
input value to a number, then validating that it is actually an integer value.
If the parsed value is not an integer, either reject the input or handle it
appropriately rather than silently truncating to an integer. This ensures that
decimal inputs like "1.9" are properly validated rather than being silently
converted to "1".

In `@ui/lib/types/schemas.ts`:
- Line 1080: The tool_execution_timeout field validation currently accepts any
number including fractional values, but the API contract expects integer seconds
only. Update the schema validation for the tool_execution_timeout field by
adding the int() method to the z.number() chain to enforce that only integer
values are accepted, preventing fractional numbers from passing validation.
🪄 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: 2180f493-8c26-4d96-8949-09adda8c511b

📥 Commits

Reviewing files that changed from the base of the PR and between c3abcba and f88a8a4.

📒 Files selected for processing (8)
  • core/internal/mcptests/per_server_timeout_test.go
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (1)
  • ui/lib/types/mcp.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • transports/bifrost-http/lib/config.go
  • core/schemas/mcp.go
  • framework/configstore/rdb.go

Comment thread core/internal/mcptests/per_server_timeout_test.go
Comment thread core/internal/mcptests/per_server_timeout_test.go Outdated
Comment thread transports/bifrost-http/handlers/mcp.go
Comment thread ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
Comment thread ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
Comment thread ui/lib/types/schemas.ts Outdated
@Purvi09

Purvi09 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Hey @Purvi09 thanks for the PR! The direction looks good - just a few pointers before its merge ready

  • Resolving all greptile/code rabbit comments
  • adding UI support for the same
  • test cases in core/internal/mcptests

Hi @Pratham-Mishra04, I have addressed all the points, please take a look.

@akshaydeo
akshaydeo requested a review from a team as a code owner June 18, 2026 12:09
@Purvi09
Purvi09 force-pushed the feat/per-mcp-server-tool-timeout branch from f6d587c to 33291b8 Compare June 18, 2026 13:56

@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

♻️ Duplicate comments (1)
ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx (1)

829-836: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid silently truncating decimal timeout input values.

The current handler still coerces decimals via Math.trunc, so 1.9 becomes 1 without user awareness. Prefer accepting only integer input and rejecting invalid/non-integer entries explicitly.

Suggested fix
  onChange={(e) => {
    if (e.target.value === "") {
      field.onChange(undefined);
      return;
    }
    const n = Number(e.target.value);
-   field.onChange(Number.isInteger(n) ? n : Math.trunc(n));
+   field.onChange(Number.isInteger(n) && n >= 0 ? n : undefined);
  }}
🤖 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/app/workspace/mcp-registry/views/mcpClientSheet.tsx` around lines 829 -
836, The onChange handler for the timeout field is silently truncating decimal
values using Math.trunc, which provides no user feedback when entering values
like 1.9. Replace the current logic that coerces decimals via Math.trunc with
explicit integer validation: check if the parsed number is an integer using
Number.isInteger, and if not, either reject the input entirely or display an
explicit validation error/message to inform the user that only integers are
accepted rather than silently truncating the value.
🤖 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 `@ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx`:
- Around line 82-83: Replace Math.round(total) with Math.ceil(total) in the
return statement to ensure sub-second duration values are rounded up to at least
1 second instead of being collapsed to 0, which would incorrectly trigger the
global timeout fallback behavior on save and clear per-server timeout overrides.

---

Duplicate comments:
In `@ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx`:
- Around line 829-836: The onChange handler for the timeout field is silently
truncating decimal values using Math.trunc, which provides no user feedback when
entering values like 1.9. Replace the current logic that coerces decimals via
Math.trunc with explicit integer validation: check if the parsed number is an
integer using Number.isInteger, and if not, either reject the input entirely or
display an explicit validation error/message to inform the user that only
integers are accepted rather than silently truncating the value.
🪄 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: e3995af5-c711-45db-be75-086c9597ef1a

📥 Commits

Reviewing files that changed from the base of the PR and between f6d587c and 33291b8.

📒 Files selected for processing (14)
  • core/internal/mcptests/per_server_timeout_test.go
  • core/mcp/clientmanager.go
  • core/mcp/toolmanager.go
  • core/schemas/mcp.go
  • core/schemas/mcp_json_test.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/mcp.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • core/mcp/clientmanager.go
  • framework/configstore/tables/mcp.go
  • core/mcp/toolmanager.go
  • ui/lib/types/schemas.ts
  • transports/bifrost-http/lib/config.go
  • transports/config.schema.json
  • transports/bifrost-http/handlers/mcp.go
  • framework/configstore/migrations.go
  • ui/lib/types/mcp.ts
  • core/schemas/mcp_json_test.go
  • core/internal/mcptests/per_server_timeout_test.go
  • core/schemas/mcp.go
  • framework/configstore/rdb.go

Comment thread ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 18, 2026
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review June 19, 2026 07:23

The merge-base changed after approval.

@CLAassistant

CLAassistant commented Jun 19, 2026

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.

✅ Purvi09
❌ Pratham-Mishra04
You have signed the CLA already but the status is still pending? Let us recheck it.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 19, 2026
Comment thread framework/configstore/rdb.go
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review June 21, 2026 11:44

The merge-base changed after approval.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026

@Pratham-Mishra04 Pratham-Mishra04 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey @Purvi09 added 2 comments, rest looks good!

Comment thread docs/changelogs/ent-v1.5.0.mdx Outdated
Comment thread framework/configstore/rdb.go Outdated

@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 (2)
ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx (1)

795-846: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Timeout placeholder doesn't show the actual global default.

Line 827 hardcodes placeholder="0", while the analogous tool_sync_interval field above (line 782) shows the real global value via placeholder={String(globalToolSyncInterval)} sourced from bifrostConfig?.client_config?.mcp_tool_sync_interval. Since Bifrost exposes an equivalent global tool_execution_timeout setting under tool_manager_config (aliased as client_config.mcp_tool_execution_timeout at runtime), showing "0" as the placeholder is inconsistent with the sibling field and doesn't tell users what value will actually apply when "Using global setting" is shown.

♻️ Suggested fix
+	const globalToolExecutionTimeout = bifrostConfig?.client_config?.mcp_tool_execution_timeout ?? 30;
...
 													<Input
 														type="number"
 														className={`w-24 ${isUsingGlobal ? "text-muted-foreground" : ""}`}
-														placeholder="0"
+														placeholder={String(globalToolExecutionTimeout)}

As per path instructions, "Preserve workspace UI patterns: use existing shared components/constants... before introducing one-off UI conventions." Please confirm the exact field name/units exposed for the global tool execution timeout in bifrostConfig.client_config before applying this fix.

🤖 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/app/workspace/mcp-registry/views/mcpClientSheet.tsx` around lines 795 -
846, The Tool Execution Timeout field is hardcoding a placeholder of 0 instead
of showing the actual global default, which makes it inconsistent with the
neighboring tool_sync_interval input. Update the mcpClientSheet.tsx FormField
for tool_execution_timeout to source the placeholder from the same
bifrostConfig.client_config value used for the global setting, and verify the
exact runtime field name/units for the global tool execution timeout before
wiring it in. Keep the existing isUsingGlobal logic and shared UI pattern
intact, matching the sibling field’s behavior.
core/schemas/mcp.go (1)

427-437: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the schema package Marshal() wrapper here.

core/schemas custom marshalers should call the package-level wrapper instead of json.Marshal.

♻️ Proposed fix
-	return json.Marshal(s)
+	return Marshal(s)

Based on learnings, custom MarshalJSON methods in core/schemas must invoke the package-level Marshal() wrapper rather than encoding/json.Marshal directly.

🤖 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/schemas/mcp.go` around lines 427 - 437, The MCPClientConfig.MarshalJSON
method is bypassing the schema package’s custom marshal path by calling
encoding/json.Marshal directly. Update this method to use the package-level
Marshal() wrapper for the shadow struct instead, keeping the
ToolExecutionTimeout formatting logic intact and preserving the alias-based
serialization in MCPClientConfig.

Source: Learnings

🤖 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 `@framework/configstore/rdb.go`:
- Around line 129-130: `CreateMCPClientConfig` is allowing negative
`ToolExecutionTimeout` values to be persisted because it converts them via
`toolExecutionTimeoutDurationToStoredSeconds` without validation, and
`math.Ceil` can hide small negative durations as 0. Add the same non-negative
validation used on update before storing the config, and reject invalid timeouts
in the create path so direct configstore callers cannot save them. Keep the fix
centered around `CreateMCPClientConfig` and the timeout conversion helper in
`rdb.go`.

---

Nitpick comments:
In `@core/schemas/mcp.go`:
- Around line 427-437: The MCPClientConfig.MarshalJSON method is bypassing the
schema package’s custom marshal path by calling encoding/json.Marshal directly.
Update this method to use the package-level Marshal() wrapper for the shadow
struct instead, keeping the ToolExecutionTimeout formatting logic intact and
preserving the alias-based serialization in MCPClientConfig.

In `@ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx`:
- Around line 795-846: The Tool Execution Timeout field is hardcoding a
placeholder of 0 instead of showing the actual global default, which makes it
inconsistent with the neighboring tool_sync_interval input. Update the
mcpClientSheet.tsx FormField for tool_execution_timeout to source the
placeholder from the same bifrostConfig.client_config value used for the global
setting, and verify the exact runtime field name/units for the global tool
execution timeout before wiring it in. Keep the existing isUsingGlobal logic and
shared UI pattern intact, matching the sibling field’s behavior.
🪄 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: 423803c2-2b59-444a-bdf1-1e305c28426b

📥 Commits

Reviewing files that changed from the base of the PR and between 5691819 and eb77584.

📒 Files selected for processing (14)
  • core/internal/mcptests/per_server_timeout_test.go
  • core/mcp/clientmanager.go
  • core/mcp/toolmanager.go
  • core/schemas/mcp.go
  • core/schemas/mcp_json_test.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/mcp.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/mcp/clientmanager.go
  • ui/lib/types/mcp.ts
  • framework/configstore/tables/mcp.go
  • framework/configstore/migrations.go
  • transports/config.schema.json
  • transports/bifrost-http/lib/config.go
  • ui/lib/types/schemas.ts
  • core/schemas/mcp_json_test.go
  • core/mcp/toolmanager.go
  • core/internal/mcptests/per_server_timeout_test.go
  • transports/bifrost-http/handlers/mcp.go

Comment thread framework/configstore/rdb.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026

@Pratham-Mishra04 Pratham-Mishra04 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hey @Purvi09 one more comment

Comment thread framework/configstore/migrations.go Outdated
Migrate: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
migrator := tx.Migrator()
if !migrator.HasColumn(&tables.TableMCPClient{}, "tool_execution_timeout") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we have added new helper methods for this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @Pratham-Mishra04 addressed this as well.

@Purvi09
Purvi09 force-pushed the feat/per-mcp-server-tool-timeout branch from e60aa03 to d1ba436 Compare July 2, 2026 13:57
@Pratham-Mishra04
Pratham-Mishra04 merged commit d3d2f17 into maximhq:dev Jul 2, 2026
4 of 5 checks passed
yangtuooc added a commit to yangtuooc/bifrost that referenced this pull request Jul 2, 2026
* upstream/dev:
  feat(mcp): add per-MCP-server tool execution timeout (maximhq#4472)
  fix: billing on failed responses stream requests anthropic and bedrock (maximhq#4842)
  fix: gemini openai through signature compatibility (maximhq#4810)
  fix: cancelled state in logs (maximhq#4831)
  fix: perplexity responses api compatibility (maximhq#4813)
  docs: clarify two-layer token refresh behavior and disabled-client refresh token expiry (maximhq#4849)
  fix: skip background token refresh for disabled/unconfigured MCP clients and guarantee non-nil logger in sync workers (maximhq#4848)
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.

[Feature] Add per MCP server level tool timeout configuration

3 participants