feat: add optional working_dir to MCP and LSP toolset configs#2460
Conversation
|
/review |
|
|
||
| // Resolve the working directory once; used for all subprocess-based branches. | ||
| // The remote branch never reaches here because working_dir is rejected by | ||
| // validation for toolsets with a remote.url. |
There was a problem hiding this comment.
MEDIUM: working_dir silently ignored when a ref: MCP catalog entry resolves to a remote server
The comment on this line is incorrect: the "remote branch" can be reached for ref:-based MCPs whose catalog entry resolves to serverSpec.Type == "remote" at runtime. In that case, createMCPTool returns early via mcp.NewRemoteToolset(...) (see line 323) without passing cwd:
if serverSpec.Type == "remote" {
return mcp.NewRemoteToolset(toolset.Name, serverSpec.Remote.URL, ..., nil, nil), nil
// ^ cwd is never passed — working_dir is silently discarded
}The validation in validate.go only blocks working_dir when Remote.URL is set directly in the config; it does not block ref:-based toolsets. A user who writes:
toolsets:
- type: mcp
ref: docker:some-remote-server
working_dir: ./workspacewill:
- Pass config validation ✓
- Have their directory existence-checked at startup (unnecessary work)
- Have
working_dirsilently discarded at runtime
Fix options:
- Update the comment to accurately state the invariant (the remote Ref path is NOT blocked by validation)
- Optionally add a warning or error when
working_diris set but the resolved gateway spec is remote
There was a problem hiding this comment.
Fixed in commit af057f34.
The misleading comment ("The remote branch never reaches here because working_dir is rejected by validation for toolsets with a remote.url") has been removed and replaced with an accurate one explaining the limitation: validation only blocks explicit remote.url toolsets; ref-based MCPs are not covered at parse time.
The runtime fix is in the serverSpec.Type == "remote" early-return inside the Ref branch:
if serverSpec.Type == "remote" {
if toolset.WorkingDir != "" {
return nil, fmt.Errorf("working_dir is not supported for MCP toolset %q: ref %q resolves to a remote server (no local subprocess)",
toolset.Name, toolset.Ref)
}
return mcp.NewRemoteToolset(...), nil
}The unnecessary checkDirExists call for ref-based toolsets is also deferred to after the server spec is resolved, so it only runs for subprocess-based refs.
| return errors.New("working_dir can only be used with type 'mcp' or 'lsp'") | ||
| } | ||
| // working_dir requires a local subprocess; it is meaningless for remote MCP toolsets. | ||
| if t.WorkingDir != "" && t.Type == "mcp" && t.Remote.URL != "" { |
There was a problem hiding this comment.
MEDIUM: Validation gap — working_dir not rejected for ref:-based MCPs that may resolve to remote at runtime
This check only blocks working_dir when Remote.URL is set explicitly:
if t.WorkingDir != "" && t.Type == "mcp" && t.Remote.URL != "" {
return errors.New("working_dir is not valid for remote MCP toolsets (no local subprocess)")
}ref:-based MCPs (e.g. ref: docker:context7) are not covered. Their serverSpec.Type is only known at runtime (via the MCP Catalog API), so if the catalog entry resolves to a remote server, working_dir is silently dropped with no user-visible error.
The companion misleading comment in registry.go ("The remote branch never reaches here because working_dir is rejected by validation for toolsets with a remote.url") reinforces this false assumption.
Suggested fix: At minimum, update the comment in registry.go to accurately describe that only explicit-remote.url toolsets are guarded at validation time, not Ref-based ones. Optionally emit a warning when a Ref-based MCP with working_dir resolves to remote at runtime.
There was a problem hiding this comment.
Fixed in commit af057f34.
- Updated the comment on this validation rule to clarify it only blocks explicit
remote.urltoolsets; ref-based MCPs are not covered at parse time because their transport type is only known at runtime. - Added a runtime check in the
serverSpec.Type == "remote"branch insidecreateMCPTool: iftoolset.WorkingDir != "", an explicit error is returned ("working_dir is not supported for MCP toolset ... ref ... resolves to a remote server") instead of silently discarding the field. - Also deferred the
checkDirExistscall for ref-based toolsets so it only runs after the server spec is resolved (avoids unnecessary work when the ref turns out to be remote). - Added
gateway.OverrideCatalogForTestinghelper +TestMaininpkg/teamloaderto enable unit tests without network, plusTestCreateMCPTool_RefRemote_WorkingDir_ReturnsErrorandTestCreateMCPTool_RefRemote_NoWorkingDir_Succeeds.
krissetto
left a comment
There was a problem hiding this comment.
overall lgtm, we should also updated the docs around this though
|
doc updated |
- Add WorkingDir field to Toolset struct (pkg/config/latest/types.go) - Validate that working_dir is only used with type 'mcp' or 'lsp' - Resolve working_dir relative to agent's working directory at process start - Propagate working_dir from top-level mcps: definitions to agent toolsets - Add resolveToolsetWorkingDir helper in registry.go - Add tests: validate_test.go, mcps_test.go, registry_test.go - Add example: examples/toolset-working-dir.yaml - Update agent-schema.json for Toolset and MCPToolset Closes docker#2459 Assisted-By: docker-agent
B1: pass resolved CWD to NewGatewayToolset; gateway-based MCPs now
honour working_dir. Remote MCP toolsets reject working_dir at
validation time (no local subprocess).
B2: call path.ExpandPath in resolveToolsetWorkingDir so ~ and
${VAR}/$VAR are expanded before path operations. Use filepath.Abs
when joining a relative path with the agent dir (fixes file://./backend
LSP root URI).
S1: add checkDirExists helper; createMCPTool and createLSPTool now
surface a clear error if working_dir does not exist at tool-creation
time.
S2: doc comment on resolveToolsetWorkingDir explains no-containment-
check posture (working_dir is treated like command/args).
S3: filepath.Abs on relative+non-empty-agentDir path ensures LSP
rootURI is always absolute.
S4: validation rejects working_dir on remote MCP toolsets.
S5: comment in applyMCPDefaults explains empty-string inheritance
semantics.
S6: doc comment covers the empty-agent-dir+relative edge case.
Nits: example model → gpt-5-mini; test name → 'bare relative dir';
schema descriptions aligned; import groups tidied; hard-coded
non-existent path in test → t.TempDir()+'/missing'.
N5: integration tests added:
- TestCreateMCPTool_WorkingDir_ReachesSubprocess
- TestCreateMCPTool_RelativeWorkingDir_ResolvedAgainstAgentDir
- TestCreateMCPTool_NonexistentWorkingDir_ReturnsError
- TestCreateLSPTool_WorkingDir_ReachesHandler
Also add WorkingDir() accessors on *mcp.Toolset and *builtin.LSPTool
to support the above integration tests.
Assisted-By: docker-agent
…epathJoin lint rule Assisted-By: docker-agent
…er with working_dir When a toolset uses ref: to point to a catalog entry that resolves to a remote server at runtime, working_dir cannot be validated at config-parse time (the transport type is only known after the catalog API call). The previous code silently discarded working_dir in this case. - Fix the misleading comment in createMCPTool that claimed the remote branch was unreachable due to validation; ref-based MCPs are not blocked at validation time. - Defer the checkDirExists call for ref-based toolsets until after the server spec is resolved (avoids unnecessary work for remote refs). - Return a clear error when serverSpec.Type == "remote" and working_dir is set, instead of silently discarding the field. - Update the Remote.URL branch comment to accurately describe what it does and does not cover. - Add gateway.OverrideCatalogForTesting helper so teamloader tests can seed a fake catalog without a live network call. - Add TestMain in pkg/teamloader that seeds remote and local catalog entries for use by tests. - Add TestCreateMCPTool_RefRemote_WorkingDir_ReturnsError and TestCreateMCPTool_RefRemote_NoWorkingDir_Succeeds. Closes docker#2461 Assisted-By: docker-agent
- docs/tools/lsp/index.md: add working_dir to Properties table; extend the Go/gopls example to show the monorepo subdirectory use-case - docs/configuration/tools/index.md: add working_dir to both the Local MCP (stdio) and Docker MCP (ref) property tables - CHANGELOG.md: add Unreleased section noting the new field Assisted-By: docker-agent
d8917eb to
c6bc325
Compare
simonferquel
left a comment
There was a problem hiding this comment.
Re-approving: force pushed was only a doc updated after a rebase
PR docker#2460 adds native working_dir support to LSP/MCP toolset configs, making the env -C ./backend gopls wrapper pattern unnecessary. Remove all code that was introduced to handle that pattern: - HealthChecker interface and StartableToolSet.Start() health-check block - LSPTool.Healthy(), watchExit goroutine, dead atomic.Bool, exited chan - stopAndWait reverted to stopLocked with direct cmd.Wait() - Toolset.Healthy() (MCP) — no longer needed without HealthChecker - _ tools.HealthChecker interface assertions - startable_test.go: healthyStub type + TestStartableToolSet_HealthChecker_* tests - lsp_test.go: TestLSPTool_Healthy_* + TestLSPTool_StartableToolSet_* tests What is retained: - MCP watcherAlive flag (B1 fix: prevents double watcher goroutine on reprobe) - reprobe() loop in runtime (fires after each tool-call batch) - MCPInitStarted/Finished gating in reprobe path (S3) - ShouldReportFailure / ConsumeRecovery deduplication - NoticeEvent + recovery notices - All existing reprobe and agent-level tests Assisted-By: docker-agent
Both files are fully reverted — no changes from this PR remain in pkg/tools/builtin/. The LSP-side modifications (WorkingDir() accessor, import/variable renames in tests) were introduced for the env-C wrapper pattern which is moot with PR docker#2460. Assisted-By: docker-agent
Summary
Adds an optional
working_dirfield to MCP and LSP toolset configurations, allowing these processes to be launched from a directory other than the agent's working directory.Closes #2459
Changes
pkg/config/latest/types.go— NewWorkingDirfield onToolsetstructpkg/config/latest/validate.go— Validation:working_dironly valid for typesmcpandlsppkg/config/mcps.go— Propagateworking_dirfrom top-levelmcps:definitions to referencing agent toolsets (existing toolset value takes precedence)pkg/teamloader/registry.go—resolveToolsetWorkingDirhelper;createMCPToolandcreateLSPToolresolve and pass the working directory to the processagent-schema.json—working_diradded toToolsetandMCPToolsetschema definitionsexamples/toolset-working-dir.yaml— New example demonstrating the featureBehaviour
working_diris optional; omitting it keeps existing behaviour (process starts from agent's working directory)mcps:section,working_diris inherited by referencing toolsets; a toolset-level value overrides the definition