Skip to content

Commit d89d66e

Browse files
authored
feat(opencode): add opencode MCP server (#4)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent d0221e1 commit d89d66e

9 files changed

Lines changed: 895 additions & 11 deletions

File tree

.github/workflows/image.yml

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,40 @@ jobs:
1717
runs-on: ubuntu-latest
1818
strategy:
1919
matrix:
20-
mcp: [duckduckgo, weather, wait, memory, homeassistant, scripts, localrecall, twitter, todo, mailbox]
20+
include:
21+
- mcp: duckduckgo
22+
dockerfile: ./Dockerfile
23+
context: ./
24+
- mcp: weather
25+
dockerfile: ./Dockerfile
26+
context: ./
27+
- mcp: wait
28+
dockerfile: ./Dockerfile
29+
context: ./
30+
- mcp: memory
31+
dockerfile: ./Dockerfile
32+
context: ./
33+
- mcp: homeassistant
34+
dockerfile: ./Dockerfile
35+
context: ./
36+
- mcp: scripts
37+
dockerfile: ./Dockerfile
38+
context: ./
39+
- mcp: localrecall
40+
dockerfile: ./Dockerfile
41+
context: ./
42+
- mcp: twitter
43+
dockerfile: ./Dockerfile
44+
context: ./
45+
- mcp: todo
46+
dockerfile: ./Dockerfile
47+
context: ./
48+
- mcp: mailbox
49+
dockerfile: ./Dockerfile
50+
context: ./
51+
- mcp: opencode
52+
dockerfile: ./opencode/Dockerfile
53+
context: ./
2154
permissions:
2255
packages: write
2356
contents: read
@@ -57,8 +90,8 @@ jobs:
5790
uses: docker/build-push-action@v6
5891
with:
5992
builder: ${{ steps.buildx.outputs.name }}
60-
context: ./
61-
file: ./Dockerfile
93+
context: ${{ matrix.context }}
94+
file: ${{ matrix.dockerfile }}
6295
platforms: linux/amd64,linux/arm64
6396
push: true
6497
tags: ${{ steps.meta.outputs.tags }}
@@ -72,8 +105,8 @@ jobs:
72105
uses: docker/build-push-action@v6
73106
with:
74107
builder: ${{ steps.buildx.outputs.name }}
75-
context: ./
76-
file: ./Dockerfile
108+
context: ${{ matrix.context }}
109+
file: ${{ matrix.dockerfile }}
77110
platforms: linux/amd64,linux/arm64
78111
tags: ${{ steps.meta.outputs.tags }}
79112
labels: ${{ steps.meta.outputs.labels }}

Makefile

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ GO_VERSION ?= 1.25.1
1111
# Docker image name
1212
IMAGE_NAME = $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY)/$(MCP_SERVER)
1313

14+
# Docker build configuration - can be overridden per server
15+
DOCKER_CONTEXT ?= ./
16+
DOCKER_FILE ?= ./Dockerfile
17+
18+
# Override for opencode (custom Dockerfile)
19+
ifeq ($(MCP_SERVER),opencode)
20+
DOCKER_FILE = ./opencode/Dockerfile
21+
endif
22+
1423
# Default target
1524
.PHONY: help
1625
help: ## Show this help message
@@ -20,23 +29,27 @@ help: ## Show this help message
2029
.PHONY: build
2130
build: ## Build the Docker image locally
2231
@echo "Building Docker image: $(IMAGE_NAME):$(DOCKER_TAG)"
32+
@echo "Using Dockerfile: $(DOCKER_FILE)"
33+
@echo "Build context: $(DOCKER_CONTEXT)"
2334
docker build \
2435
--build-arg MCP_SERVER=$(MCP_SERVER) \
2536
--build-arg GO_VERSION=$(GO_VERSION) \
2637
-t $(IMAGE_NAME):$(DOCKER_TAG) \
27-
-f Dockerfile \
28-
.
38+
-f $(DOCKER_FILE) \
39+
$(DOCKER_CONTEXT)
2940

3041
.PHONY: build-multiarch
3142
build-multiarch: ## Build multi-architecture Docker image (requires buildx)
3243
@echo "Building multi-architecture Docker image: $(IMAGE_NAME):$(DOCKER_TAG)"
44+
@echo "Using Dockerfile: $(DOCKER_FILE)"
45+
@echo "Build context: $(DOCKER_CONTEXT)"
3346
docker buildx build \
3447
--platform linux/amd64,linux/arm64 \
3548
--build-arg MCP_SERVER=$(MCP_SERVER) \
3649
--build-arg GO_VERSION=$(GO_VERSION) \
3750
-t $(IMAGE_NAME):$(DOCKER_TAG) \
38-
-f Dockerfile \
39-
.
51+
-f $(DOCKER_FILE) \
52+
$(DOCKER_CONTEXT)
4053

4154
.PHONY: run
4255
run: build ## Build and run the container locally
@@ -46,12 +59,14 @@ run: build ## Build and run the container locally
4659
.PHONY: test-build
4760
test-build: ## Test build without pushing (similar to PR build in CI)
4861
@echo "Testing build (PR mode): $(IMAGE_NAME):$(DOCKER_TAG)"
62+
@echo "Using Dockerfile: $(DOCKER_FILE)"
63+
@echo "Build context: $(DOCKER_CONTEXT)"
4964
docker build \
5065
--build-arg MCP_SERVER=$(MCP_SERVER) \
5166
--build-arg GO_VERSION=$(GO_VERSION) \
5267
-t $(IMAGE_NAME):$(DOCKER_TAG) \
53-
-f Dockerfile \
54-
.
68+
-f $(DOCKER_FILE) \
69+
$(DOCKER_CONTEXT)
5570

5671
.PHONY: push
5772
push: build ## Build and push to registry (requires authentication)
@@ -150,3 +165,5 @@ info: ## Show build information
150165
@echo "Go Version: $(GO_VERSION)"
151166
@echo "Docker Registry: $(DOCKER_REGISTRY)"
152167
@echo "Repository: $(DOCKER_REPOSITORY)"
168+
@echo "Dockerfile: $(DOCKER_FILE)"
169+
@echo "Context: $(DOCKER_CONTEXT)"

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,128 @@ mcp:
11461146

11471147
**Note:** Each agent instance must have a unique `MAILBOX_AGENT_NAME` to properly filter and manage its own messages. The mailbox file is shared across all agents, but each agent only sees messages where it is the recipient.
11481148

1149+
### 🚀 Opencode Server
1150+
1151+
An MCP server for controlling opencode AI sessions asynchronously. Start sessions, monitor progress, retrieve logs, and manage multiple concurrent opencode processes.
1152+
1153+
**Features:**
1154+
- Async session management with unique session IDs
1155+
- Start opencode sessions with full command-line option support
1156+
- Monitor session status (running, completed, failed, stopped)
1157+
- Retrieve stdout/stderr logs from sessions
1158+
- Stop running sessions gracefully
1159+
- List all sessions with filtering by status
1160+
- Configurable concurrent session limits
1161+
- Automatic log cleanup based on retention policy
1162+
- Ephemeral sessions (do not survive server restarts)
1163+
1164+
**Tools:**
1165+
- `start_session` - Start a new opencode session with a message and options
1166+
- `get_session_status` - Get the current status of a session by ID
1167+
- `get_session_logs` - Retrieve stdout and stderr logs from a session
1168+
- `stop_session` - Stop a running session
1169+
- `list_sessions` - List all sessions with optional status filtering
1170+
1171+
**Configuration:**
1172+
- `OPENCODE_SESSION_DIR` - Directory for session state and logs (default: `/tmp/opencode-sessions`)
1173+
- `OPENCODE_BINARY` - Path to opencode binary (default: `/usr/local/bin/opencode`)
1174+
- `OPENCODE_MAX_SESSIONS` - Maximum number of concurrent sessions (default: `10`)
1175+
- `OPENCODE_LOG_RETENTION_HOURS` - Hours to retain session logs before cleanup (default: `24`)
1176+
- `OPENCODE_CONFIG` - Path to opencode config file
1177+
- `OPENCODE_CONFIG_CONTENT` - Inline config as JSON string
1178+
- `OPENCODE_MODEL` - Model to use in provider/model format (e.g., `openai/gpt-4`)
1179+
- `OPENCODE_FORMAT` - Output format: `default` (formatted) or `json` (raw JSON events) (default: `json`)
1180+
- `OPENCODE_AGENT` - Agent to use for sessions
1181+
- `OPENCODE_SHARE` - Share sessions: `true` or `false` (default: `false`)
1182+
- `OPENCODE_VARIANT` - Model variant for provider-specific reasoning effort
1183+
1184+
**Start Session Example:**
1185+
```json
1186+
{
1187+
"message": "Explain quantum computing",
1188+
"title": "Quantum Computing Explanation"
1189+
}
1190+
```
1191+
1192+
**Start Session Output:**
1193+
```json
1194+
{
1195+
"session_id": "550e8400-e29b-41d4-a716-446655440000",
1196+
"status": "starting",
1197+
"message": "Session started successfully"
1198+
}
1199+
```
1200+
1201+
**Get Session Status Example:**
1202+
```json
1203+
{
1204+
"session_id": "550e8400-e29b-41d4-a716-446655440000"
1205+
}
1206+
```
1207+
1208+
**Get Session Status Output:**
1209+
```json
1210+
{
1211+
"session_id": "550e8400-e29b-41d4-a716-446655440000",
1212+
"status": "completed",
1213+
"pid": "12345",
1214+
"exit_code": "0",
1215+
"created_at": "2025-01-15T10:30:00Z",
1216+
"started_at": "2025-01-15T10:30:01Z",
1217+
"stopped_at": "2025-01-15T10:30:15Z",
1218+
"duration": "14s"
1219+
}
1220+
```
1221+
1222+
**Get Session Logs Example:**
1223+
```json
1224+
{
1225+
"session_id": "550e8400-e29b-41d4-a716-446655440000",
1226+
"lines": 50
1227+
}
1228+
```
1229+
1230+
**Get Session Logs Output:**
1231+
```json
1232+
{
1233+
"session_id": "550e8400-e29b-41d4-a716-446655440000",
1234+
"stdout": "Quantum computing is a form of computing that takes advantage...",
1235+
"stderr": "",
1236+
"line_count": 50
1237+
}
1238+
```
1239+
1240+
**Docker Image:**
1241+
```bash
1242+
docker run -e OPENCODE_MAX_SESSIONS=5 -e OPENCODE_LOG_RETENTION_HOURS=48 ghcr.io/mudler/mcps/opencode:latest
1243+
```
1244+
1245+
**With model configuration:**
1246+
```bash
1247+
docker run -e OPENCODE_MODEL=openai/gpt-4 -e OPENCODE_FORMAT=json ghcr.io/mudler/mcps/opencode:latest
1248+
```
1249+
1250+
**LocalAI configuration (to add to the model config):**
1251+
```yaml
1252+
mcp:
1253+
stdio: |
1254+
{
1255+
"mcpServers": {
1256+
"opencode": {
1257+
"command": "docker",
1258+
"env": {
1259+
"OPENCODE_MAX_SESSIONS": "5",
1260+
"OPENCODE_LOG_RETENTION_HOURS": "48"
1261+
},
1262+
"args": [
1263+
"run", "-i", "--rm",
1264+
"ghcr.io/mudler/mcps/opencode:master"
1265+
]
1266+
}
1267+
}
1268+
}
1269+
```
1270+
11491271
## Development
11501272

11511273
### Prerequisites
@@ -1258,6 +1380,9 @@ Docker images are automatically built and pushed to GitHub Container Registry:
12581380
- `ghcr.io/mudler/mcps/mailbox:latest` - Latest Mailbox server
12591381
- `ghcr.io/mudler/mcps/mailbox:v1.0.0` - Tagged versions
12601382
- `ghcr.io/mudler/mcps/mailbox:master` - Development versions
1383+
- `ghcr.io/mudler/mcps/opencode:latest` - Latest Opencode server
1384+
- `ghcr.io/mudler/mcps/opencode:v1.0.0` - Tagged versions
1385+
- `ghcr.io/mudler/mcps/opencode:master` - Development versions
12611386

12621387
## Contributing
12631388

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ require (
5050
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
5151
github.com/modern-go/reflect2 v1.0.2 // indirect
5252
github.com/mschoch/smat v0.2.0 // indirect
53+
github.com/mudler/go-processmanager v0.1.0 // indirect
5354
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
5455
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
5556
go.etcd.io/bbolt v1.4.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
128128
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
129129
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
130130
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
131+
github.com/mudler/go-processmanager v0.1.0 h1:fcSKgF9U/a1Z7KofAFeZnke5YseadCI5GqL9oT0LS3E=
132+
github.com/mudler/go-processmanager v0.1.0/go.mod h1:h6kmHUZeafr+k5hRYpGLMzJFH4hItHffgpRo2QIkP+o=
131133
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
132134
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
133135
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=

opencode/Dockerfile

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Build stage
2+
FROM golang:1.24-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
# Copy go mod files
7+
COPY go.mod go.sum ./
8+
RUN go mod download
9+
10+
# Copy source code
11+
COPY opencode/ ./opencode/
12+
13+
# Build the binary
14+
RUN CGO_ENABLED=0 GOOS=linux go build -o opencode-mcp-server ./opencode/
15+
16+
# Final stage - use Debian instead of Alpine for glibc compatibility
17+
FROM debian:bookworm-slim
18+
19+
# Install required runtime dependencies
20+
RUN apt-get update && apt-get install -y --no-install-recommends \
21+
ca-certificates \
22+
curl \
23+
bash \
24+
&& rm -rf /var/lib/apt/lists/*
25+
26+
# Install opencode and capture the installation location
27+
RUN curl -fsSL https://opencode.ai/install | bash && \
28+
# Find where opencode was installed and link it to /usr/local/bin
29+
if [ -f /root/.local/bin/opencode ]; then \
30+
ln -s /root/.local/bin/opencode /usr/local/bin/opencode; \
31+
elif [ -f /root/.opencode/bin/opencode ]; then \
32+
ln -s /root/.opencode/bin/opencode /usr/local/bin/opencode; \
33+
fi
34+
35+
# Copy the MCP server binary
36+
COPY --from=builder /app/opencode-mcp-server /usr/local/bin/opencode-mcp-server
37+
38+
# Verify opencode works in final container
39+
RUN which opencode && opencode --version || echo "Warning: opencode may not be properly installed"
40+
41+
# Create session directory
42+
RUN mkdir -p /tmp/opencode-sessions
43+
44+
# Set environment variables
45+
ENV OPENCODE_SESSION_DIR=/tmp/opencode-sessions
46+
ENV OPENCODE_BINARY=/usr/local/bin/opencode
47+
ENV OPENCODE_MAX_SESSIONS=10
48+
ENV OPENCODE_LOG_RETENTION_HOURS=24
49+
ENV OPENCODE_FORMAT=json
50+
ENV OPENCODE_SHARE=false
51+
52+
# Optional environment variables (uncomment to use):
53+
# ENV OPENCODE_MODEL=openai/gpt-4
54+
# ENV OPENCODE_AGENT=
55+
# ENV OPENCODE_VARIANT=
56+
# ENV OPENCODE_ATTACH=
57+
# ENV OPENCODE_PORT=0
58+
59+
# Run the MCP server
60+
ENTRYPOINT ["opencode-mcp-server"]

0 commit comments

Comments
 (0)