feat: add Control MCP server for introspecting process-compose itself#467
Open
jeanlucthumm wants to merge 17 commits intoF1bonacc1:mainfrom
Open
feat: add Control MCP server for introspecting process-compose itself#467jeanlucthumm wants to merge 17 commits intoF1bonacc1:mainfrom
jeanlucthumm wants to merge 17 commits intoF1bonacc1:mainfrom
Conversation
Introduces a new src/mcpctl package that exposes 8 MCP tools for agents to introspect and control the running process-compose instance itself: list_processes, get_process, get_logs, search_logs, start_process, stop_process, restart_process, get_dependency_graph. This is independent from src/mcp — that package wraps user-defined processes as MCP tools; this one targets process-compose's own runner API. The two packages share no types or interfaces. Includes a hand-rolled Okapi BM25 implementation for search_logs ranking (zero new go.mod deps). Not yet wired into cmd/root.go; next commit enables via the new mcpctl_server YAML block.
Load MCPCtlServer from the project YAML and start/stop it alongside the
existing MCP server in waitForProjectAndServer. Add validation so invalid
mcpctl_server blocks fail (or warn) on project load.
With this commit, a compose.yaml containing
mcpctl_server:
host: localhost
port: 11001
exposes the 8 Control MCP tools over SSE when the project is up.
- Remove unused ProcessRunner methods (GetProcessInfo, GetProcessLogLength) - Drop unused Manager.runner field - Replace hand-rolled depNodeJSON/depLink/toDepNodeJSON/toDepLink with direct serialization of types.DependencyGraph; its JSON tags already produce the required wire format - get_dependency_graph: batch-fetch state via GetProcessesState instead of N+1 GetProcessState calls - SSE: retain the SSEServer handle and call Shutdown on Stop so the listener goroutine unwinds cleanly - Introduce transport constants in both types/mcpctl.go and mcpctl/server.go in place of bare "sse"/"stdio" literals - Trim verbose doc comments that narrated the design rather than the code
Under `up`, stdio is already owned by the TUI or the sibling mcp_server stdio transport — there's no practical way to route mcpctl traffic there. Keeping the stdio code path created a claimed-but-broken surface (stdio was listed as a transport but SetStdio was never wired in cmd/root.go). - Remove startStdio / SetStdio / stdin / stdout / IsStdio - Validate() now rejects any non-SSE transport with a clear error - IsEnabled() documents the "empty block ⇒ disabled" semantic
Declared, YAML-parsed, but never read anywhere. Users would have thought they'd configured something. Drop the field and GetTimeout() with it.
sseServer.Start returns http.ErrServerClosed when Shutdown is called; that's the expected path, not an error worth logging.
Previously, if mcpctl failed to bind its port after mcp had already started, the mcp server leaked because waitForProjectAndServer returned the error immediately without stopping it.
The returned index is the position within the log chunk we fetched for search (tail of log_limit lines), not an absolute position in the process log buffer — lines may roll out between calls. Rename and document. Also drop the hardcoded "8" in the registerBuiltinTools comment and log message; the count would drift silently as tools are added or removed.
- NewServer now copies types.Processes into an internal map so get_dependency_graph iterations can't race any runtime mutation of the project's process set. Matches the sibling mcp.Server pattern. - get_dependency_graph now surfaces GetProcessesState errors instead of silently returning a graph with Pending/- placeholders. Matches the error policy of list_processes.
The SSE endpoint exposes full process control (start/stop/restart + logs) with no authentication. Binding to anything other than loopback without understanding that risk is probably a mistake — log a prominent warning at startup instead of silently listening.
… overlay - types/mcpctl_test.go: IsEnabled (nil/empty/populated) and Validate branches (stdio rejected, unknown transport rejected, missing host/port) - mcpctl/server_test.go: nil config ⇒ nil manager (with nil-safe Start/Stop), populated config ⇒ non-nil; toolGetDependencyGraph round-trip asserting that AllNodes ⇄ Nodes pointer aliasing overlays live status onto the JSON result
Use the descriptions from the TS prototype (repl-it-web PR #74337) so the agent-facing surface matches what was validated in prior use. Notable changes: - search_logs gains the "NOT by time — for recent/latest use get_logs" disambiguation and usage examples - list_processes, get_logs, get_process gain "Use this for X" guidance - get_dependency_graph mentions startup order / failure cascading - name parameters include e.g. hints
With stdio gone, Transport only accepts "sse" / "" — it's a field that exists but can't change behavior. Remove it along with IsSSE() and the transport constant. Host + Port is now the full config surface.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Adds an optional Control MCP server (
src/mcpctl/) that exposes 8 MCPtools letting agents introspect and control the running process-compose
instance itself — BM25-ranked log search, list/get processes, tail recent
logs, start/stop/restart processes, and read the dependency graph.
Closes #460.
Context
This is orthogonal to the existing
src/mcp/package (which wrapsuser-defined processes as MCP tools). Both packages use
mark3labs/mcp-gobut share no Go types, interfaces, or config — theyrun as two independent servers on separate ports and can be enabled
independently.
Usage
Enable via a new top-level block in the project YAML:
With the above, running
process-compose upexposes an SSE MCP endpointat
http://localhost:11001/sse. Off by default — an absent or emptyblock does nothing.
Tools
search_logslist_processesget_processget_logsstart_process/stop_process/restart_processget_dependency_graphDesign notes
mark3labs/mcp-gowas already pinned. BM25is a ~160-line hand roll (
src/mcpctl/bm25.go) — there's no lightweightmaintained BM25 package in the Go ecosystem, and pulling in a full
search engine like
blevefor a few thousand log lines felt like toomuch. Rationale is documented at the top of
bm25.go.src/mcp/;mcpctl.ProcessRunneris a fresh local interface duck-typed against*app.ProjectRunner.up, stdio is already owned by the TUI or thesibling
mcp_serverstdio transport, so mcpctl-over-stdio has no placeto send its traffic.
control (same posture as the existing
mcp_server). Startup logs aprominent warning when bound to a non-loopback address.
Files
src/mcpctl/{server,manager,tools,bm25}.go+ testssrc/types/mcpctl.go+ tests (MCPCtlServerConfig)src/cmd/root.go(start/stop alongsidemcpmanager,unwind cleanly on partial-startup errors),
src/loader/validators.go(validate block),src/types/project.go(add field),CLAUDE.md(package table)Test plan
go test -race ./src/mcpctl/... ./src/types/...— BM25 ranking,Validate,Managernil-safety, dep-graph overlay round-tripmake lintcleanmake buildgreenmcpctl_serverblock; MCPinitialize,tools/list, and eachtool call returns correctly shaped JSON-RPC responses over SSE
Out of scope (follow-ups)
mcp_serverdoesn't auth either;the loopback warning documents the expectation)
search_logs