Skip to content

feat: add MCP server with stdio transport for LLM integration#4495

Open
amir20 wants to merge 6 commits intomasterfrom
claude/implement-mcp-go-server-jebjr
Open

feat: add MCP server with stdio transport for LLM integration#4495
amir20 wants to merge 6 commits intomasterfrom
claude/implement-mcp-go-server-jebjr

Conversation

@amir20
Copy link
Owner

@amir20 amir20 commented Mar 1, 2026

Implements a Model Context Protocol (MCP) server as a new dozzle mcp
subcommand using the mark3labs/mcp-go library. Exposes four tools:

  • list_containers: List all containers across hosts with optional state filter
  • get_container_logs: Fetch recent logs from a container (configurable time range)
  • container_action: Start/stop/restart containers
  • list_hosts: List all connected Docker hosts

Uses stdio transport for seamless integration with Claude and other MCP clients.

https://claude.ai/code/session_01YRr9yDHxRJBKxBbjARpRJM

Implements a Model Context Protocol (MCP) server as a new `dozzle mcp`
subcommand using the mark3labs/mcp-go library. Exposes four tools:
- list_containers: List all containers across hosts with optional state filter
- get_container_logs: Fetch recent logs from a container (configurable time range)
- container_action: Start/stop/restart containers
- list_hosts: List all connected Docker hosts

Uses stdio transport for seamless integration with Claude and other MCP clients.

https://claude.ai/code/session_01YRr9yDHxRJBKxBbjARpRJM
@claude
Copy link

claude bot commented Mar 1, 2026

Review

Bug: RawLogs called with zero 'to' time — internal/mcp/server.go:201

The call uses time.Time{} as the 'to' argument:

reader, err := containerSvc.RawLogs(ctx, since, time.Time{}, stdType)

time.Time{} formats as "0001-01-01T00:00:00Z" and is passed to Docker's Until option (see docker/client.go:292). Docker treats this as a timestamp in year 0001 — logs will always be empty. Should be time.Now(), matching the pattern in web/download.go:194.


Minor issues:

  • containerInfo exposes all container labels (Labels map[string]string) — labels can contain secrets/tokens. Consider omitting or filtering to dozzle-specific labels only.
  • Logs written to stdout in DEV mode (ConfigureLogger → ConsoleWriter) would corrupt the MCP stdio protocol if DEV=true is set. The MCP command should redirect logs to stderr or suppress them.
  • No tests for the new internal/mcp/server.go handlers.
  • go.mod: mcp-go is placed in the indirect require block without the // indirect marker — run go mod tidy to clean up.

Uses StreamableHTTPServer for remote access. Listens on :8081 by default,
configurable via --mcp-addr flag or DOZZLE_MCP_ADDR env var.

https://claude.ai/code/session_01YRr9yDHxRJBKxBbjARpRJM
@claude
Copy link

claude bot commented Mar 1, 2026

Review

Bug: RawLogs with zero to timeinternal/mcp/server.go:202

reader, err := containerSvc.RawLogs(ctx, since, time.Time{}, stdType)

time.Time{} formats as "0001-01-01T00:00:00Z" and is passed to Docker's Until option (docker/client.go:292). Docker interprets this as "show logs before year 1" — logs will always be empty. Fix: use time.Now() (matching the pattern in web/download.go:194).


PR description mismatch: Title and description say "stdio transport" but the implementation uses server.NewStreamableHTTPServer with an --mcp-addr flag. Needs clarification — if stdio is intended, the transport layer is wrong.


Minor issues:

  • containerInfo exposes all Labels map[string]string — labels can contain secrets/tokens. Consider filtering to dozzle-specific labels (dev.dozzle.*) only.
  • No tests for internal/mcp/server.go handlers.
  • go.mod: mcp-go is placed in the indirect require block without // indirect marker — run go mod tidy.

Exposes the last ~5 minutes of container stats history (up to 300 data
points) including CPU percentage, memory percentage, and memory usage.

https://claude.ai/code/session_01YRr9yDHxRJBKxBbjARpRJM
@claude
Copy link

claude bot commented Mar 1, 2026

Review: MCP Server Implementation

Bug - garbled log output:
handleGetContainerLogs uses RawLogs which returns the raw Docker multiplexed stream (8-byte frame headers + payload). io.ReadAll on this will include binary header bytes in the text response. Should use LogsBetweenDates (returns a parsed LogEvent channel) and join messages instead.

Mismatch - stdio vs HTTP:
PR description and CLI help text both say "stdio transport" but the code creates server.NewStreamableHTTPServer. Misleading for users.

Security - no auth on MCP endpoint:
The HTTP MCP server (default :8081) has no authentication, while container_action can start/stop/restart containers. Any process with network access can trigger destructive actions. Should inherit Dozzle auth config or be documented explicitly.

go.mod placement:
github.com/mark3labs/mcp-go v0.44.1 is listed in the indirect require block but is a direct dependency. Should be in the main require block.

Naming - ServeHTTP (internal/mcp/server.go:57):
Does not implement http.Handler (wrong signature). Rename to Start or ListenAndServe to avoid confusion.

No tests added for the new package.

Switch from RawLogs to LogsBetweenDates so the MCP get_container_logs
tool returns Dozzle's processed log events with detected log levels,
JSON parsing, and multi-line grouping. Output is JSONL with structured
entries containing timestamp, level, stream, type, and parsed message.

https://claude.ai/code/session_01YRr9yDHxRJBKxBbjARpRJM
@claude
Copy link

claude bot commented Mar 1, 2026

Review

Security - Critical

  • mcp_command.go: MCP HTTP server starts on :8081 with no authentication. The container_action tool allows start/stop/restart of containers by anyone with network access. At minimum, gate behind the existing auth middleware or require explicit opt-in.

Bug - Double JSON marshaling (server.go:253-271)
Entries are marshaled once to check size, then re-marshaled via json.NewEncoder for output. Use the line bytes directly in a [][]byte slice and write them to the strings.Builder instead.

Off-by-one in size limit (server.go:261-264)
totalSize > maxSize breaks after the current entry is added, so output can exceed 1MB by one entry. Check before appending.

Misleading method name (server.go:51)
ServeHTTP implies http.Handler implementation but it starts a server on an address. Use ListenAndServe or Start.

Description vs implementation mismatch
PR title/description says "stdio transport" but mcp_command.go and server.go use NewStreamableHTTPServer (HTTP transport). If stdio was dropped intentionally, update the description.

Stats may be empty (server.go:342-353)
FindContainer returns a ContainerService with a Container struct. c.Stats ring buffer is only populated during active streaming — it will likely be nil or empty when fetched on-demand. Needs a real-time stats fetch instead.

Minor

  • mcp_command.go:16: args.Mode != "server" blocks swarm users from MCP. Consider whether swarm mode should also be supported.

@claude
Copy link

claude bot commented Mar 3, 2026

Review

PR description mismatch (title says "stdio" but implementation is HTTP)
Title/description say "stdio transport" but mcp_command.go uses server.NewStreamableHTTPServer with --mcp-addr :8081. Clarify which transport is intended. If stdio is still wanted, the transport layer needs to change.


No authentication on MCP HTTP serverinternal/support/cli/mcp_command.go
The ServeHTTP server has no auth middleware. Any process that can reach :8081 can list containers, read logs, and start/stop/restart containers. The existing Dozzle web server has configurable auth (simple, forward-proxy); the MCP server bypasses all of it.


Stats may always be emptyinternal/mcp/server.go:346
c.Stats is populated only when Dozzle is actively streaming stats for that container. FindContainer doesn't start stat collection, so for containers not currently viewed in the UI, c.Stats.Data() will be empty. Callers will get "dataPoints": 0 with no indication of why.


Double JSON-encoding in handleGetContainerLogs — lines ~240–270
Each entry is marshaled once (to measure byte size) then re-encoded via json.NewEncoder. Use the already-marshaled line bytes directly in the string builder instead of re-encoding.


Minor

  • containerInfo still exposes all Labels map[string]string — labels can contain secrets/tokens. Consider filtering to dev.dozzle.* keys only.
  • No tests for internal/mcp/server.go handlers.

@claude
Copy link

claude bot commented Mar 9, 2026

Review

Transport mismatch — title/description say "stdio transport" but mcp_command.go uses server.NewStreamableHTTPServer with an --mcp-addr flag. The PR description is misleading; needs clarification on which transport is actually intended.

Misleading method nameinternal/mcp/server.go:54: ServeHTTP(addr string) error shadows the standard http.Handler interface signature. Rename to Start or ListenAndServe.

Double JSON serializationhandleGetContainerLogs marshals each entry to JSON to track totalSize, then re-encodes the same entries via json.NewEncoder. Compute size incrementally or marshal once and buffer.

Label secret exposurecontainerInfo.Labels map[string]string exposes all container labels; these can contain tokens/secrets. Consider filtering to only dev.dozzle.* labels.

go.mod needs go mod tidymcp-go is listed in the indirect require block without the // indirect comment marker.

No testsinternal/mcp/server.go has zero test coverage for any handler.

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.

2 participants