Summary
While integrating OpenCode into an AWF smoke test workflow, we hit several non-obvious configuration issues. This documents the problems and solutions as a reference for anyone configuring OpenCode in an agentic workflow.
Problem 1: OpenCode doesn't auto-discover MCP servers
Symptom: The agent runs and completes successfully but never calls any MCP tools. The `safe-output-items.jsonl` artifact is empty even though the MCP gateway is running.
Root cause: OpenCode has its own built-in tool registry (bash, read, glob, grep, write, etc.) and does not auto-discover MCP servers. Without explicit config, the agent has no MCP tools.
Fix: Add an explicit `mcp` section to `opencode.jsonc`:
```json
{
"mcp": {
"safeoutputs": {
"type": "remote",
"url": "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs",
"headers": { "Authorization": "${MCP_GATEWAY_API_KEY}" },
"timeout": 30000
}
}
}
```
Note: The key is `mcp` (not `mcpServers` — that's the VS Code / Cline convention).
Problem 2: MCP gateway URL must include the routed path
Symptom: MCP connection fails with `transport connection failed` / 404.
Root cause: The MCP gateway runs in routed mode and exposes servers at `/mcp/`, not at the root `/`. Pointing to `http://host.docker.internal:80\` returns 404.
Fix: Use the full routed path: `http://host.docker.internal:${PORT}/mcp/safeoutputs\`
The gateway startup log confirms this:
```
Routes: /mcp/ where is one of: [safeoutputs]
```
Problem 3: Agent permissions use `permission` (singular), not `permissions`
Symptom: In non-interactive `opencode run` mode, tool calls fail silently or are auto-rejected. MCP connects (tools appear in registry) but the model's file/directory operations are blocked.
Root cause: OpenCode's `ConfigAgent.Info` schema field is `permission` (singular). Using `permissions` (plural) causes the block to be silently moved to `options` by the `normalize()` function — no error is thrown and the config is ignored.
```json
// ❌ Wrong — silently ignored
"agent": {
"build": {
"permissions": { "external_directory": "allow" }
}
}
// ✅ Correct
"agent": {
"build": {
"permission": { "external_directory": "allow" }
}
}
```
Problem 4: `external_directory` must be explicitly allowed in non-interactive mode
Symptom: File read/write tool calls are auto-rejected even though MCP tools are available.
Root cause: OpenCode's default ruleset includes:
```json
{ "permission": "external_directory", "pattern": "*", "action": "ask" }
```
In interactive mode, this prompts the user. In non-interactive `opencode run` mode, `ask` becomes an automatic deny.
Fix: Override this in `agent.build.permission`:
```json
{
"agent": {
"build": {
"permission": {
"external_directory": "allow",
"bash": "allow",
"edit": "allow",
"read": "allow"
}
}
}
}
```
Problem 5: Copilot API has no `/v1` prefix
Symptom: API calls fail with 404 when using `api.githubcopilot.com` as the provider base URL.
Root cause: `api.githubcopilot.com` uses `/chat/completions` directly (no `/v1` prefix). The `@ai-sdk/openai-compatible` provider appends `/chat/completions` to the `baseURL` — if the baseURL already includes `/v1`, you get a doubled path.
Fix: Use `https://models.inference.ai.azure.com\` (Azure AI endpoint) instead of a Copilot-specific URL, or configure the provider baseURL to not include `/v1`.
Problem 6: `COPILOT_GITHUB_TOKEN` must be in the AWF Execute step's `env:`
Symptom: The api-proxy port 10004 (OpenCode) fails to start or returns auth errors.
Root cause: The AWF api-proxy's `resolveOpenCodeRoute()` reads `COPILOT_GITHUB_TOKEN` from the container environment. If the token is only set in an earlier shell step's `env:`, it's not forwarded into the AWF container.
Fix: Pass the token explicitly in the AWF execute step:
```yaml
- name: Execute
env:
COPILOT_GITHUB_TOKEN: ${{ steps.copilot-token.outputs.token }}
run: |
awf --enable-api-proxy ... opencode run ...
```
Reference: Working OpenCode config structure
```json
{
"provider": {
"copilot-proxy": {
"name": "Copilot Proxy",
"type": "openai-compatible",
"baseURL": "http://172.30.0.30:10004",
"models": ["gpt-4.1", "claude-sonnet-4-5"]
}
},
"model": "copilot-proxy/claude-sonnet-4-6",
"mcp": {
"safeoutputs": {
"type": "remote",
"url": "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs",
"headers": { "Authorization": "${MCP_GATEWAY_API_KEY}" },
"timeout": 30000
}
},
"agent": {
"build": {
"permission": {
"bash": "allow",
"edit": "allow",
"read": "allow",
"glob": "allow",
"grep": "allow",
"write": "allow",
"external_directory": "allow"
}
}
}
}
```
Summary
While integrating OpenCode into an AWF smoke test workflow, we hit several non-obvious configuration issues. This documents the problems and solutions as a reference for anyone configuring OpenCode in an agentic workflow.
Problem 1: OpenCode doesn't auto-discover MCP servers
Symptom: The agent runs and completes successfully but never calls any MCP tools. The `safe-output-items.jsonl` artifact is empty even though the MCP gateway is running.
Root cause: OpenCode has its own built-in tool registry (bash, read, glob, grep, write, etc.) and does not auto-discover MCP servers. Without explicit config, the agent has no MCP tools.
Fix: Add an explicit `mcp` section to `opencode.jsonc`:
```json
{
"mcp": {
"safeoutputs": {
"type": "remote",
"url": "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs",
"headers": { "Authorization": "${MCP_GATEWAY_API_KEY}" },
"timeout": 30000
}
}
}
```
Problem 2: MCP gateway URL must include the routed path
Symptom: MCP connection fails with `transport connection failed` / 404.
Root cause: The MCP gateway runs in routed mode and exposes servers at `/mcp/`, not at the root `/`. Pointing to `http://host.docker.internal:80\` returns 404.
Fix: Use the full routed path: `http://host.docker.internal:${PORT}/mcp/safeoutputs\`
The gateway startup log confirms this:
```
Routes: /mcp/ where is one of: [safeoutputs]
```
Problem 3: Agent permissions use `permission` (singular), not `permissions`
Symptom: In non-interactive `opencode run` mode, tool calls fail silently or are auto-rejected. MCP connects (tools appear in registry) but the model's file/directory operations are blocked.
Root cause: OpenCode's `ConfigAgent.Info` schema field is `permission` (singular). Using `permissions` (plural) causes the block to be silently moved to `options` by the `normalize()` function — no error is thrown and the config is ignored.
```json
// ❌ Wrong — silently ignored
"agent": {
"build": {
"permissions": { "external_directory": "allow" }
}
}
// ✅ Correct
"agent": {
"build": {
"permission": { "external_directory": "allow" }
}
}
```
Problem 4: `external_directory` must be explicitly allowed in non-interactive mode
Symptom: File read/write tool calls are auto-rejected even though MCP tools are available.
Root cause: OpenCode's default ruleset includes:
```json
{ "permission": "external_directory", "pattern": "*", "action": "ask" }
```
In interactive mode, this prompts the user. In non-interactive `opencode run` mode, `ask` becomes an automatic deny.
Fix: Override this in `agent.build.permission`:
```json
{
"agent": {
"build": {
"permission": {
"external_directory": "allow",
"bash": "allow",
"edit": "allow",
"read": "allow"
}
}
}
}
```
Problem 5: Copilot API has no `/v1` prefix
Symptom: API calls fail with 404 when using `api.githubcopilot.com` as the provider base URL.
Root cause: `api.githubcopilot.com` uses `/chat/completions` directly (no `/v1` prefix). The `@ai-sdk/openai-compatible` provider appends `/chat/completions` to the `baseURL` — if the baseURL already includes `/v1`, you get a doubled path.
Fix: Use `https://models.inference.ai.azure.com\` (Azure AI endpoint) instead of a Copilot-specific URL, or configure the provider baseURL to not include `/v1`.
Problem 6: `COPILOT_GITHUB_TOKEN` must be in the AWF Execute step's `env:`
Symptom: The api-proxy port 10004 (OpenCode) fails to start or returns auth errors.
Root cause: The AWF api-proxy's `resolveOpenCodeRoute()` reads `COPILOT_GITHUB_TOKEN` from the container environment. If the token is only set in an earlier shell step's `env:`, it's not forwarded into the AWF container.
Fix: Pass the token explicitly in the AWF execute step:
```yaml
env:
COPILOT_GITHUB_TOKEN: ${{ steps.copilot-token.outputs.token }}
run: |
awf --enable-api-proxy ... opencode run ...
```
Reference: Working OpenCode config structure
```json
{
"provider": {
"copilot-proxy": {
"name": "Copilot Proxy",
"type": "openai-compatible",
"baseURL": "http://172.30.0.30:10004",
"models": ["gpt-4.1", "claude-sonnet-4-5"]
}
},
"model": "copilot-proxy/claude-sonnet-4-6",
"mcp": {
"safeoutputs": {
"type": "remote",
"url": "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs",
"headers": { "Authorization": "${MCP_GATEWAY_API_KEY}" },
"timeout": 30000
}
},
"agent": {
"build": {
"permission": {
"bash": "allow",
"edit": "allow",
"read": "allow",
"glob": "allow",
"grep": "allow",
"write": "allow",
"external_directory": "allow"
}
}
}
}
```