Skip to content

Commit 11b7a5a

Browse files
opencode-agent[bot]Test
authored andcommitted
feat(core): split MCP timeout configuration (anomalyco#33977)
Co-authored-by: Test <test@opencode.test>
1 parent 17fd5cb commit 11b7a5a

4 files changed

Lines changed: 41 additions & 20 deletions

File tree

packages/core/src/config/mcp.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ export * as ConfigMCP from "./mcp"
33
import { Schema } from "effect"
44
import { PositiveInt } from "../schema"
55

6+
export class Timeout extends Schema.Class<Timeout>("ConfigV2.MCP.Timeout")({
7+
startup: PositiveInt.pipe(Schema.optional).annotate({
8+
description: "Maximum time in milliseconds to establish and initialize the MCP server.",
9+
}),
10+
request: PositiveInt.pipe(Schema.optional).annotate({
11+
description: "Maximum time in milliseconds to wait for each MCP request after initialization.",
12+
}),
13+
}) {}
14+
615
export class Local extends Schema.Class<Local>("ConfigV2.MCP.Local")({
716
type: Schema.Literal("local"),
817
command: Schema.String.pipe(Schema.Array),
@@ -11,7 +20,7 @@ export class Local extends Schema.Class<Local>("ConfigV2.MCP.Local")({
1120
}),
1221
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
1322
disabled: Schema.Boolean.pipe(Schema.optional),
14-
timeout: PositiveInt.pipe(Schema.optional),
23+
timeout: Timeout.pipe(Schema.optional),
1524
}) {}
1625

1726
export class OAuth extends Schema.Class<OAuth>("ConfigV2.MCP.OAuth")({
@@ -28,12 +37,12 @@ export class Remote extends Schema.Class<Remote>("ConfigV2.MCP.Remote")({
2837
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
2938
oauth: Schema.Union([OAuth, Schema.Literal(false)]).pipe(Schema.optional),
3039
disabled: Schema.Boolean.pipe(Schema.optional),
31-
timeout: PositiveInt.pipe(Schema.optional),
40+
timeout: Timeout.pipe(Schema.optional),
3241
}) {}
3342

3443
export const Server = Schema.Union([Local, Remote]).pipe(Schema.toTaggedUnion("type"))
3544

3645
export class Info extends Schema.Class<Info>("ConfigV2.MCP")({
37-
timeout: PositiveInt.pipe(Schema.optional),
46+
timeout: Timeout.pipe(Schema.optional),
3847
servers: Schema.Record(Schema.String, Server).pipe(Schema.optional),
3948
}) {}

packages/core/src/v1/config/migrate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function mcp(info: typeof ConfigV1.Info.Type) {
132132
)
133133
const timeout = info.experimental?.mcp_timeout
134134
if (!timeout && !Object.keys(servers).length) return undefined
135-
return { timeout, servers }
135+
return { timeout: timeout === undefined ? undefined : { request: timeout }, servers }
136136
}
137137

138138
function migrateMcp(info: ConfigMCPV1.Info) {
@@ -144,7 +144,7 @@ function migrateMcp(info: ConfigMCPV1.Info) {
144144
cwd: info.cwd,
145145
environment: info.environment,
146146
disabled,
147-
timeout: info.timeout,
147+
timeout: info.timeout === undefined ? undefined : { request: info.timeout },
148148
}
149149
return {
150150
type: info.type,
@@ -158,7 +158,7 @@ function migrateMcp(info: ConfigMCPV1.Info) {
158158
redirect_uri: info.oauth.redirectUri,
159159
},
160160
disabled,
161-
timeout: info.timeout,
161+
timeout: info.timeout === undefined ? undefined : { request: info.timeout },
162162
}
163163
}
164164

packages/core/test/config/config.test.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,21 +298,22 @@ describe("Config", () => {
298298
},
299299
tool_output: { max_lines: 1000, max_bytes: 32768 },
300300
mcp: {
301-
timeout: 5000,
301+
timeout: { startup: 5000, request: 60000 },
302302
servers: {
303303
local: {
304304
type: "local",
305305
command: ["node", "./mcp/server.js"],
306306
environment: { API_KEY: "secret" },
307307
disabled: false,
308-
timeout: 10000,
308+
timeout: { request: 10000 },
309309
},
310310
remote: {
311311
type: "remote",
312312
url: "https://mcp.example.com/mcp",
313313
headers: { Authorization: "Bearer token" },
314314
oauth: { client_id: "client", scope: "read write", callback_port: 19876 },
315315
disabled: true,
316+
timeout: { startup: 15000 },
316317
},
317318
},
318319
},
@@ -383,21 +384,22 @@ describe("Config", () => {
383384
})
384385
expect(documents[0]?.info.tool_output).toEqual({ max_lines: 1000, max_bytes: 32768 })
385386
expect(documents[0]?.info.mcp).toEqual({
386-
timeout: 5000,
387+
timeout: { startup: 5000, request: 60000 },
387388
servers: {
388389
local: {
389390
type: "local",
390391
command: ["node", "./mcp/server.js"],
391392
environment: { API_KEY: "secret" },
392393
disabled: false,
393-
timeout: 10000,
394+
timeout: { request: 10000 },
394395
},
395396
remote: {
396397
type: "remote",
397398
url: "https://mcp.example.com/mcp",
398399
headers: { Authorization: "Bearer token" },
399400
oauth: { client_id: "client", scope: "read write", callback_port: 19876 },
400401
disabled: true,
402+
timeout: { startup: 15000 },
401403
},
402404
},
403405
})
@@ -541,11 +543,12 @@ describe("Config", () => {
541543
compaction: { auto: true, tail_turns: 3, preserve_recent_tokens: 2000, reserved: 10000 },
542544
experimental: { mcp_timeout: 5000 },
543545
mcp: {
544-
local: { type: "local", command: ["node", "server.js"], enabled: false },
546+
local: { type: "local", command: ["node", "server.js"], enabled: false, timeout: 10000 },
545547
remote: {
546548
type: "remote",
547549
url: "https://mcp.example.com",
548550
oauth: { clientId: "client", callbackPort: 19876 },
551+
timeout: 20000,
549552
},
550553
},
551554
}),
@@ -623,13 +626,19 @@ describe("Config", () => {
623626
buffer: 10000,
624627
})
625628
expect(documents[0]?.info.mcp).toMatchObject({
626-
timeout: 5000,
629+
timeout: { request: 5000 },
627630
servers: {
628-
local: { type: "local", command: ["node", "server.js"], disabled: true },
631+
local: {
632+
type: "local",
633+
command: ["node", "server.js"],
634+
disabled: true,
635+
timeout: { request: 10000 },
636+
},
629637
remote: {
630638
type: "remote",
631639
url: "https://mcp.example.com",
632640
oauth: { client_id: "client", callback_port: 19876 },
641+
timeout: { request: 20000 },
633642
},
634643
},
635644
})

specs/v2/config.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -304,23 +304,25 @@ Rename legacy `permission` to `permissions` and expose the normalized ordered ru
304304

305305
External protocol and server integration configuration.
306306

307-
| Field | Current Purpose | Status | Notes |
308-
| ----- | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
309-
| `mcp` | MCP server definitions and enablement | redesign | Keep opencode's explicit local/remote server entry format, nested under `mcp.servers`; use `disabled` for inactive entries and move timeout here. |
307+
| Field | Current Purpose | Status | Notes |
308+
| ----- | ------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
309+
| `mcp` | MCP server definitions and enablement | redesign | Keep opencode's explicit local/remote server entry format, nested under `mcp.servers`; use `disabled` for inactive entries and move timeout defaults here. |
310310

311-
Keep the opencode MCP server entry format instead of adopting the common `mcpServers` copy/paste shape. Local servers remain explicit `type: "local"` entries with command arrays and `environment`; remote servers remain explicit `type: "remote"` entries with `url`, `headers`, and optional `oauth`. Nest the server map under `mcp.servers` so protocol-wide settings such as default timeout can live under the same subsystem.
311+
Keep the opencode MCP server entry format instead of adopting the common `mcpServers` copy/paste shape. Local servers remain explicit `type: "local"` entries with command arrays and `environment`; remote servers remain explicit `type: "remote"` entries with `url`, `headers`, and optional `oauth`. Nest the server map under `mcp.servers` so protocol-wide settings such as timeout defaults can live under the same subsystem.
312+
313+
MCP timeouts have separate startup and request budgets, expressed in milliseconds. `startup` covers establishing the transport and completing MCP initialization. `request` applies independently to each post-initialization MCP request. A server may override either default without repeating the other.
312314

313315
```jsonc
314316
{
315317
"mcp": {
316-
"timeout": 5000,
318+
"timeout": { "startup": 30000, "request": 300000 },
317319
"servers": {
318320
"github": {
319321
"type": "local",
320322
"command": ["npx", "-y", "@github/github-mcp-server"],
321323
"environment": { "GITHUB_TOKEN": "{env:GITHUB_TOKEN}" },
322324
"disabled": false,
323-
"timeout": 10000,
325+
"timeout": { "startup": 60000 },
324326
},
325327
"docs": {
326328
"type": "remote",
@@ -334,6 +336,7 @@ Keep the opencode MCP server entry format instead of adopting the common `mcpSer
334336
"redirect_uri": "http://127.0.0.1:19876/mcp/oauth/callback",
335337
},
336338
"disabled": false,
339+
"timeout": { "request": 600000 },
337340
},
338341
},
339342
},
@@ -375,7 +378,7 @@ Fields that should not be ported by inertia; each needs an explicit justificatio
375378
| `experimental.openTelemetry` | Enable AI SDK telemetry spans | remove | Do not port; observability is process-level and should use standard OpenTelemetry environment or declarative configuration. |
376379
| `experimental.primary_tools` | Restrict tools to primary agents | remove | Do not port obsolete gating; agent tool access is configured through permissions. |
377380
| `experimental.continue_loop_on_deny` | Continue loop after denied tool call | remove | Do not port legacy denied-tool loop behavior. |
378-
| `experimental.mcp_timeout` | MCP request timeout | redesign | Move to `mcp.timeout` for the default and `mcp.servers.<name>.timeout` for per-server overrides. |
381+
| `experimental.mcp_timeout` | MCP request timeout | redesign | Move to `mcp.timeout.request` for the default and `mcp.servers.<name>.timeout.request` for per-server overrides. |
379382

380383
## Review Order
381384

0 commit comments

Comments
 (0)