Skip to content

feat: add per-server tool_timeout to override global gateway timeout#5206

Merged
lpcox merged 5 commits intomainfrom
copilot/configure-mcp-gateway-timeout
May 6, 2026
Merged

feat: add per-server tool_timeout to override global gateway timeout#5206
lpcox merged 5 commits intomainfrom
copilot/configure-mcp-gateway-timeout

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 6, 2026

Reusable shared workflows wrapping long-running HTTP MCP servers (e.g. Repo Mind Light) have no way to set the gateway's tool invocation timeout — consumers must know to configure MCP_GATEWAY_TOOL_TIMEOUT in sandbox.mcp.env, breaking the reusable component contract.

Changes

  • ServerConfig.ToolTimeout — new per-server field (TOML: tool_timeout, JSON stdin: tool_timeout); minimum 10s; zero/omitted means fall back to gateway.toolTimeout
  • callBackendTool — per-server ToolTimeout takes precedence over gateway.ToolTimeout when set
  • JSON schematool_timeout added to stdioServerConfig and httpServerConfig definitions so stdin validation accepts the field
  • ValidationvalidateStandardServerConfig enforces minimum of 10 for per-server tool_timeout
  • DocsCONFIGURATION.md documents the field and its relationship to the global timeout

Example

A reusable shared workflow can now declare its own timeout directly on the server:

{
  "mcpServers": {
    "repo-mind": {
      "type": "http",
      "url": "http://127.0.0.1:8000/mcp",
      "tool_timeout": 600
    }
  },
  "gateway": { "port": 3000, "domain": "localhost", "apiKey": "${API_KEY}" }
}

Consumers get a sane 600s timeout without needing to know about MCP_GATEWAY_TOOL_TIMEOUT. They can still override at the consumer level if needed.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build2847888803/b509/launcher.test /tmp/go-build2847888803/b509/launcher.test -test.testlogfile=/tmp/go-build2847888803/b509/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 997891/b463/_pkg_.a -trimpath ache/go/1.25.9/x64/pkg/tool/linux_amd64/vet -p ny/timefmt-go -lang=go1.25 ache/go/1.25.9/x-buildtags (dns block)
    • Triggering command: /tmp/go-build1669811436/b513/launcher.test /tmp/go-build1669811436/b513/launcher.test -test.testlogfile=/tmp/go-build1669811436/b513/testlog.txt -test.paniconexit0 -test.timeout=10m0s dock�� -stringintconv -tests 64/pkg/tool/linux_amd64/link _.a 997891/b150/ x_amd64/vet 64/pkg/tool/linux_amd64/link --no�� test.test x_amd64/vet x_amd64/vet 1.80.0/channelz/docker 997891/b150/ 64/pkg/tool/linu-nilfunc x_amd64/vet (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build871958364/b001/config.test /tmp/go-build871958364/b001/config.test -test.paniconexit0 -test.timeout=2m0s -test.count=1 rtcf�� 1024825-9d38bb4040a9/status/status.pb.go 64/src/internal/google.golang.org/grpc/internal/channelz x86_64/as (dns block)
    • Triggering command: /tmp/go-build2847888803/b491/config.test /tmp/go-build2847888803/b491/config.test -test.testlogfile=/tmp/go-build2847888803/b491/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true msSmPlS8N cfg x_amd64/vet . -imultiarch x86_64-linux-gnu--noprofile x_amd64/vet 9978�� om/spf13/cobra@v1.10.2/active_he-errorsas cfg x_amd64/vet --gdwarf-5 b/gh-aw-mcpg/intphp8.3 -o x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build1669811436/b495/config.test /tmp/go-build1669811436/b495/config.test -test.testlogfile=/tmp/go-build1669811436/b495/testlog.txt -test.paniconexit0 -test.timeout=10m0s -ato�� -bool -buildtags x_amd64/vet -errorsas -ifaceassert -nilfunc x_amd64/vet run test -tests ache/go/1.25.9/x64/pkg/tool/linux_amd64/compile _.a aw-mcpg/internal/tmp/go-build1282575160/b425/vet.cfg x_amd64/vet ache/go/1.25.9/x64/pkg/tool/linux_amd64/compile (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build2847888803/b509/launcher.test /tmp/go-build2847888803/b509/launcher.test -test.testlogfile=/tmp/go-build2847888803/b509/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 997891/b463/_pkg_.a -trimpath ache/go/1.25.9/x64/pkg/tool/linux_amd64/vet -p ny/timefmt-go -lang=go1.25 ache/go/1.25.9/x-buildtags (dns block)
    • Triggering command: /tmp/go-build1669811436/b513/launcher.test /tmp/go-build1669811436/b513/launcher.test -test.testlogfile=/tmp/go-build1669811436/b513/testlog.txt -test.paniconexit0 -test.timeout=10m0s dock�� -stringintconv -tests 64/pkg/tool/linux_amd64/link _.a 997891/b150/ x_amd64/vet 64/pkg/tool/linux_amd64/link --no�� test.test x_amd64/vet x_amd64/vet 1.80.0/channelz/docker 997891/b150/ 64/pkg/tool/linu-nilfunc x_amd64/vet (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build2847888803/b509/launcher.test /tmp/go-build2847888803/b509/launcher.test -test.testlogfile=/tmp/go-build2847888803/b509/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 997891/b463/_pkg_.a -trimpath ache/go/1.25.9/x64/pkg/tool/linux_amd64/vet -p ny/timefmt-go -lang=go1.25 ache/go/1.25.9/x-buildtags (dns block)
    • Triggering command: /tmp/go-build1669811436/b513/launcher.test /tmp/go-build1669811436/b513/launcher.test -test.testlogfile=/tmp/go-build1669811436/b513/testlog.txt -test.paniconexit0 -test.timeout=10m0s dock�� -stringintconv -tests 64/pkg/tool/linux_amd64/link _.a 997891/b150/ x_amd64/vet 64/pkg/tool/linux_amd64/link --no�� test.test x_amd64/vet x_amd64/vet 1.80.0/channelz/docker 997891/b150/ 64/pkg/tool/linu-nilfunc x_amd64/vet (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2847888803/b518/mcp.test /tmp/go-build2847888803/b518/mcp.test -test.testlogfile=/tmp/go-build2847888803/b518/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true se 958364/b060/vet.cfg ache/go/1.25.9/x64/pkg/tool/linux_amd64/vet . --gdwarf2 --64 ache/go/1.25.9/x-trimpath (dns block)
    • Triggering command: /tmp/go-build3024656909/b518/mcp.test /tmp/go-build3024656909/b518/mcp.test -test.testlogfile=/tmp/go-build3024656909/b518/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true /tmp/go-build689997891/b448/_pkg_.a -trimpath docker-buildx -p go.opentelemetryinspect -lang=go1.24 docker-buildx -uns�� -unreachable=false /tmp/go-build871958364/b091/vet.-ifaceassert .cfg -c=4 -nolocalimports -importcfg ache/go/1.25.9/x64/pkg/tool/linu{{.Config.OpenStdin}} (dns block)
    • Triggering command: /tmp/go-build3319390857/b518/mcp.test /tmp/go-build3319390857/b518/mcp.test -test.testlogfile=/tmp/go-build3319390857/b518/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true g_.a /tmp/go-build871958364/b023/vet.journal-or-kmsg x_amd64/vet -c=4 -nolocalimports -importcfg x_amd64/vet -uns�� hI9joKUGJ /tmp/go-build871958364/b152/vet.cfg ache/go/1.25.9/x64/pkg/tool/linux_amd64/vet en.go en.go ache/go/1.25.9/x-o ctHi6v3/br4fXcgGpaHZ4LUv69Ml (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Adds a new `tool_timeout` field to server configurations that allows
reusable shared workflows to set an appropriate tool invocation timeout
for their specific MCP server without requiring consumers to configure
the global `MCP_GATEWAY_TOOL_TIMEOUT` env var.

Changes:
- ServerConfig.ToolTimeout: new per-server tool timeout field (TOML + JSON)
- StdinServerConfig.ToolTimeout: JSON stdin support for HTTP/stdio servers
- JSON schema: tool_timeout added to httpServerConfig and stdioServerConfig
- callBackendTool: per-server ToolTimeout takes precedence over global
- validateStandardServerConfig: validates per-server tool_timeout >= 10
- docs/CONFIGURATION.md: documents the new tool_timeout field
- Tests: validation, config parsing, and server behavior tests

Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/60af4feb-95e7-48d7-b1e8-8729f32389a4

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Update MCP gateway tool timeout configuration for reusable workflows feat: add per-server tool_timeout to override global gateway timeout May 6, 2026
Copilot finished work on behalf of lpcox May 6, 2026 14:14
Copilot AI requested a review from lpcox May 6, 2026 14:14
@lpcox lpcox marked this pull request as ready for review May 6, 2026 21:19
Copilot AI review requested due to automatic review settings May 6, 2026 21:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a per-server tool_timeout configuration option so individual MCP backends can override the global gateway tool invocation timeout, improving reusable workflow ergonomics for long-running servers.

Changes:

  • Introduces ServerConfig.ToolTimeout / tool_timeout (TOML + stdin JSON) and wires it through stdin parsing + schema validation.
  • Updates UnifiedServer.callBackendTool to prefer per-server timeout over gateway.ToolTimeout when set.
  • Adds validation/tests and documentation for the new field.
Show a summary per file
File Description
internal/server/unified.go Applies per-server timeout precedence when creating the tool-call context deadline.
internal/server/call_backend_tool_test.go Adds tests for per-server timeout override and global fallback behavior.
internal/config/validation.go Validates per-server tool_timeout for stdin configs.
internal/config/validation_test.go Adds unit tests covering per-server tool_timeout validation cases.
internal/config/schema/mcp-gateway-config.schema.json Extends stdin JSON schema to accept tool_timeout for stdio/http servers.
internal/config/config_test.go Adds stdin loading tests verifying tool_timeout parsing and minimum enforcement.
internal/config/config_stdin.go Adds tool_timeout to stdin server config struct and conversion to internal ServerConfig.
internal/config/config_core.go Adds ToolTimeout field to core ServerConfig (TOML/JSON file + internal representation).
docs/CONFIGURATION.md Documents tool_timeout and its relationship to the global gateway tool timeout.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/9 changed files
  • Comments generated: 6

Comment thread internal/server/unified.go Outdated
Comment on lines +373 to +376
if serverCfg, ok := us.cfg.Servers[serverID]; ok && serverCfg.ToolTimeout > 0 {
toolTimeout = serverCfg.ToolTimeout
logUnified.Printf("callBackendTool: using per-server tool_timeout=%d for serverID=%s", toolTimeout, serverID)
} else if us.cfg.Gateway != nil && us.cfg.Gateway.ToolTimeout > 0 {
Comment thread internal/config/validation.go Outdated
Comment on lines +121 to +127
// Validate per-server tool_timeout if provided
if server.ToolTimeout != nil {
if err := rules.TimeoutMinimum(*server.ToolTimeout, ToolTimeoutMin, "tool_timeout", jsonPath+".tool_timeout"); err != nil {
logValidateServerFailed(name, fmt.Sprintf("tool_timeout %d is below minimum %d", *server.ToolTimeout, ToolTimeoutMin))
return err
}
}
Comment thread internal/config/config_stdin.go Outdated

// ToolTimeout is the per-server maximum time (seconds) to wait for a single tool invocation.
// When set, this overrides the global gateway.toolTimeout for calls to this server only.
// Minimum: 10. When 0 or omitted, the global gateway.toolTimeout is used.
Comment on lines +128 to 133
},
"tool_timeout": {
"type": "integer",
"description": "Per-server tool invocation timeout in seconds. When set, overrides the global gateway.toolTimeout for tool calls to this server only. Minimum: 10.",
"minimum": 10
}
Comment thread docs/CONFIGURATION.md Outdated

- **`connect_timeout`** (optional, HTTP servers only): Per-transport connection timeout in seconds for connecting to HTTP backends. The gateway tries streamable HTTP, then SSE, then plain JSON-RPC over HTTP POST in sequence; this timeout applies to each attempt. It does **not** set the end-to-end `tools/call` execution timeout. Default: `30`.

- **`tool_timeout`** (optional): Per-server tool invocation timeout in seconds. When set, overrides the global `gateway.toolTimeout` for `tools/call` requests to this specific server. This allows reusable shared workflow components wrapping long-running HTTP MCP servers to set an appropriate timeout once, without requiring every consumer to configure `MCP_GATEWAY_TOOL_TIMEOUT`. Minimum: `10`. When omitted, the global `gateway.toolTimeout` (or its `MCP_GATEWAY_TOOL_TIMEOUT` env fallback) is used.
Comment on lines +587 to +588
// Delay longer than per-server timeout (1s), but shorter than global (30s)
time.Sleep(3 * time.Second)
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 6, 2026

@copilot address the review feedback

- Add nil guard for serverCfg in callBackendTool (unified.go)
- Allow tool_timeout=0 to mean "unset/fallback to global"; update validation,
  schema (oneOf: 0 or >=10), comments, and test expectations accordingly
- Clarify global timeout field names per format in docs (toolTimeout JSON /
  tool_timeout TOML)
- Replace fixed 3s sleep in test handler with context-aware select to avoid
  slow test execution

Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/86ea6ba6-24d0-47b7-8ec2-79419e538001

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 6, 2026

@copilot address the review feedback

All review feedback addressed in 2ad7bac:

  • Nil guard – added serverCfg != nil check in callBackendTool before accessing ToolTimeout
  • tool_timeout: 0 semantics – validation now skips the minimum check when the value is 0, treating it as "unset / fall back to global"; schema updated to oneOf: [const: 0, minimum: 10]; comments aligned accordingly
  • Docs clarificationCONFIGURATION.md now explicitly names the global field for each format: toolTimeout in stdin JSON (gateway.toolTimeout) and tool_timeout in TOML ([gateway]tool_timeout)
  • Test handler sleep – replaced the fixed 3 s time.Sleep with a select { case <-r.Context().Done(): ... case <-time.After(10s): } so the handler returns as soon as the client cancels, cutting the test from ~3 s to ~1 s

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build298270591/b224/config.test /tmp/go-build298270591/b224/config.test -test.paniconexit0 -test.timeout=2m0s -test.count=1 /tmp/go-build2358822995/b371/vet.cfg rnal/encoding/defval/default.go 5Rv-kR7HA x_amd64/compile (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of lpcox May 6, 2026 21:48
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 6, 2026

@copilot fix the lint errors

Copilot AI and others added 2 commits May 6, 2026 21:55
…-gateway-timeout

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
… error

Merged latest main (which introduced get_server_status_test.go) and renamed
the second declaration of TestGetServerStatus_EmptyConfig to
TestGetServerStatus_NilServersField to reflect its distinct scenario
(nil Servers field vs empty Servers map).

Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/41c2d427-761b-43b9-9d47-9be47955824f

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 6, 2026

@copilot fix the lint errors

Fixed in 43d605c. The lint failure was caused by get_server_status_test.go in the base branch containing two declarations of TestGetServerStatus_EmptyConfig (one testing an empty Servers map, another testing a nil Servers field). Merged main and renamed the second to TestGetServerStatus_NilServersField to reflect its distinct scenario.

Copilot finished work on behalf of lpcox May 6, 2026 21:57
@lpcox lpcox merged commit 5d2e312 into main May 6, 2026
14 checks passed
@lpcox lpcox deleted the copilot/configure-mcp-gateway-timeout branch May 6, 2026 22:01
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.

MCP gateway tool timeout should be configurable by reusable workflow components

3 participants