Skip to main content
Glama

opencode-mcp

An MCP server that discovers, monitors, and drives multiple OpenCode instances running across personal machines. Uses SSH reverse tunnels through a central relay for discovery and transport. Pluggable transport layer supports future backends (Tailscale, Cloudflare Tunnels, mDNS).

Quick Start (local testing)

# 1. Install
npm install && npm run build

# 2. Start opencode with HTTP side-car (in a separate terminal)
#    opencode-connected picks a random port and writes a registration file
ln -s ~/prg/opencode-mcp/scripts/opencode-connected ~/bin/opencode-connected
opencode-connected

# 3. Run with MCP inspector (in another terminal)
npx @modelcontextprotocol/inspector tsx src/index.ts

# Or run directly (stdio MCP server)
node dist/index.js

Both opencode-connected and the MCP server default to /tmp/opencode-relay for the registry directory — no configuration needed for local testing.

Related MCP server: agent-bus-mcp

Architecture

┌──────────────────────────────────────────────┐
│        Relay machine (GCE / VPS / etc.)      │
│                                              │
│  mcp-gateway ──── opencode-mcp (stdio)       │
│       │              │                       │
│       │         reads /tmp/opencode-relay/    │
│       │         or RELAY_REGISTRY_DIR         │
│       │              │                       │
│    OAuth         localhost:10001 ──┐         │
│    front         localhost:10002 ──┤ opencode│
│                  localhost:10003 ──┘ APIs    │
│                                              │
│  sshd: accepts reverse tunnels               │
└──────▲──────────▲───────────▲────────────────┘
       │          │           │
    ssh -R     ssh -R      ssh -R
       │          │           │
    laptop     desktop     laptop
    (oc:4823)  (oc:4567)   (oc:4901)

The MCP server runs on the same machine that accepts SSH reverse tunnels. It reads registration JSON files from a directory, health-checks each registered port on localhost, and creates OpenCode SDK clients for healthy instances. All OpenCode API calls go through localhost:{tunnel_port}.

OpenCode binds to 127.0.0.1 (default) — the SSH tunnel is the auth boundary. No passwords needed.

MCP Tools

Tool

Input

Description

instances

List all discovered instances with status (idle/busy) and recent session

send

instance, message, abort?

Send a message to the most recent session; set abort=true to stop a running task

read

instance, message_limit?

Read the last N messages from the most recent session

Instance names support fuzzy substring matching (e.g. "laptop" matches "laptop-myproject").

Environment Variables

MCP server

Variable

Default

Description

RELAY_REGISTRY_DIR

/tmp/opencode-relay

Directory containing registration JSON files

DISCOVERY_INTERVAL_MS

30000

How often to refresh instance list (ms)

HEALTH_CHECK_TIMEOUT_MS

3000

Timeout for health-checking each instance (ms)

TRANSPORT

local-relay

Transport backend (local-relay, future: tailscale)

SEND_TIMEOUT_MS

300000

Timeout for streaming send responses (ms)

opencode-connected script

Variable

Default

Description

RELAY_SSH_CMD

SSH command to reach relay. If unset, local only.

RELAY_REGISTRY_DIR

/tmp/opencode-relay

Registry directory (local or on relay)

INSTANCE_NAME

$(hostname)-$(basename $PWD)

Instance name for registration

Registration File Format

Each file in RELAY_REGISTRY_DIR is a JSON file named {instance-name}.json:

{
  "name": "laptop-myproject",
  "hostname": "laptop",
  "port": 10042,
  "localPort": 4823,
  "cwd": "/home/user/projects/myproject",
  "connectedAt": "2026-03-14T10:30:00Z"
}

Files are written by opencode-connected (locally or on the relay via SSH). The MCP server prunes files whose ports fail health checks.

Connecting an OpenCode Instance

Use opencode-connected instead of bare opencode to start the TUI with an HTTP side-car:

# Install (symlink)
ln -s ~/prg/opencode-mcp/scripts/opencode-connected ~/bin/opencode-connected

# Local only (no tunnel, writes registration to /tmp/opencode-relay/)
opencode-connected

# With relay (set RELAY_SSH_CMD in your shell profile)
export RELAY_SSH_CMD="gcloud compute ssh mcp-gateway --zone=us-central1-a --project=my-project --"
opencode-connected

# Or with direct SSH
export RELAY_SSH_CMD="ssh user@relay.example.com"
opencode-connected

# Pass extra args to opencode (after --)
opencode-connected -- -d

The script:

  1. Picks a random available local port (4096-5095)

  2. Starts opencode TUI with --port (enables HTTP side-car on 127.0.0.1)

  3. If RELAY_SSH_CMD is set: establishes SSH reverse tunnel with auto-retry

  4. Registers the instance (lazily creates the registry directory)

  5. Cleans up the registration file on exit

Note: opencode without --port does not start an HTTP server. The --port flag is what enables the HTTP side-car alongside the TUI.

Multiple instances in the same directory: The instance name defaults to $(hostname)-$(basename $PWD). If you run multiple opencode instances in the same directory, they'll compete for the same registration file — the last one wins and the others become invisible to the MCP server. To avoid this, set INSTANCE_NAME explicitly:

INSTANCE_NAME=my-tests opencode-connected
INSTANCE_NAME=my-refactor opencode-connected

For work machines with different MCP configs, set OPENCODE_CONFIG in your shell profile — the script does not handle config selection.

Integration with mcp-gateway (Docker)

To add opencode-mcp to an existing mcp-gateway Docker deployment:

1. Install from npm

npx -y opencode-mcp  # or add to gateway's SERVERS dict

2. Docker configuration

# docker run additions:
--network=host                    # reach SSH tunnel ports on host's localhost
-v /tmp/opencode-relay:/tmp/opencode-relay:ro  # read registration files
-e RELAY_REGISTRY_DIR=/tmp/opencode-relay

--network=host is required because SSH reverse tunnels bind on the host's localhost. The container needs to reach those ports directly.

3. MCP server config in mcp-gateway

Add to the gateway's server configuration:

{
  "mcpServers": {
    "opencode": {
      "command": "npx",
      "args": ["-y", "opencode-mcp"],
      "transport": "stdio",
      "env": {
        "RELAY_REGISTRY_DIR": "/tmp/opencode-relay"
      }
    }
  }
}

4. Verify

# On a client machine:
export RELAY_SSH_CMD="gcloud compute ssh mcp-gateway --zone=us-central1-a --project=my-project --"
opencode-connected

# From the chat interface, the LLM can now call:
# instances → sees the connected instance
# send → interacts with it
# read → sees what's been happening

Project Structure

opencode-mcp/
├── src/
│   ├── index.ts                 # MCP server entry + transport factory
│   ├── types.ts                 # RegistrationFile, OpenCodeInstance
│   ├── registry.ts              # Instance cache + OpenCode SDK client mgmt
│   ├── transport/
│   │   ├── interface.ts         # Abstract Transport interface
│   │   └── local-relay.ts       # File-based registry + localhost health checks
│   └── tools/
│       └── simplified.ts       # instances, send, read
├── scripts/
│   └── opencode-connected       # Client: random port + tunnel + exec opencode TUI
├── plans/
│   └── architecture.md          # Design doc + future work
├── package.json
├── tsconfig.json
└── .env.example

Development

npm install
npm run dev          # run with tsx (no build step)
npm run build        # compile TypeScript
npm start            # run compiled output

Security

  • SSH tunnels: the auth boundary — standard SSH key or gcloud auth

  • Tunnel ports: bound to host's localhost only, not externally accessible

  • OpenCode binding: 127.0.0.1 by default — not network-accessible

  • MCP transport: stdio (no network exposure); OAuth via mcp-gateway

  • Registration files: contain only name, hostname, port, cwd — no credentials

Available Tools

3 tools
instancesA

List all connected opencode instances with their current status (busy/idle). Call this to see what machines are available.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, but the description accurately reflects a read-only list operation. It discloses the output (status), and no side effects are expected. Fully transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no wasted words. The purpose is front-loaded, and the call to action is immediate.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given zero parameters and no output schema, the description covers the essential purpose and return content. It could optionally clarify output format, but the current text is largely sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

No parameters exist, and schema coverage is 100% trivially. The description adds no parameter detail, which is appropriate. Baseline for 0 parameters is 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'list' and the resource 'connected opencode instances', and specifies the information returned (current status busy/idle). No confusion with sibling tools (read, send).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description advises 'Call this to see what machines are available', providing a clear use case. It does not explicitly exclude alternatives, but siblings are unrelated, so guidance is adequate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

readA

Read the last few messages from the most recent opencode session on an instance. Use this to see what has been happening without sending a new message.

ParametersJSON Schema
NameRequiredDescriptionDefault
instanceYesInstance name (exact or fuzzy substring match)
message_limitNoMax number of messages to retrieve (default 10)

TDQS

A3.6/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full burden of behavioral disclosure. It only states the action and does not disclose traits such as idempotency, side effects, authentication requirements, rate limits, or error behaviors. This is a significant gap for a read operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, consisting of two sentences: the first states the core function, and the second provides usage context. There is no redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with two parameters and no output schema, the description covers the purpose and usage context adequately. However, it lacks details about edge cases (e.g., no session found, empty messages) and does not specify the format of the returned messages. Slightly incomplete but sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with both parameters ('instance' and 'message_limit') described adequately in the schema. The tool description adds context about the 'most recent opencode session,' providing minor added value beyond the schema but not enough to substantially raise the score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads 'the last few messages from the most recent opencode session on an instance,' specifying the action (read), resource (messages), and scope. This verb+resource structure effectively distinguishes it from sibling tools 'instances' and 'send'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear use case ('to see what has been happening without sending a new message') but does not explicitly state when not to use this tool or compare it to alternatives. It implies reading versus sending but lacks explicit exclusion criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sendB

Send a message to the most recent opencode session on an instance. Streams the response back in real-time. Set abort=true to stop a running task instead of sending a message.

ParametersJSON Schema
NameRequiredDescriptionDefault
messageNoThe message to send (not required when aborting)
instanceYesInstance name (exact or fuzzy substring match)
abortNoSet to true to abort the currently running task

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite no annotations, the description discloses streaming response and abort behavior. However, it omits potential side effects, permission requirements, and details on automatic session selection.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Concise two sentences; first states primary function, second adds edge case. No wasted words, though structure could be slightly improved.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers basic use and abort but omits error handling, return format, and session selection mechanism, which are relevant for correct usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema provides 100% coverage, so baseline 3 applies. The description adds a note on abort usage but no additional semantics for message or instance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action (send a message), the target (opencode session on an instance), and streaming behavior. It implicitly distinguishes from siblings by focusing on messaging vs. reading/listing, but could be more explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use this tool versus siblings 'instances' and 'read'. The description only mentions basic functionality and an abort case, but not selection criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A3.9/5.0
Disambiguation5/5

Each tool has a clearly distinct purpose: listing instances, reading session history, and sending messages. No overlap or ambiguity.

Naming Consistency4/5

All tool names are lowercase and concise, but 'instances' is a noun while 'read' and 'send' are verbs, creating a slight pattern inconsistency.

Tool Count5/5

Three tools cover the essential operations for interacting with opencode instances (list, read, send) without unnecessary bloat.

Completeness4/5

Core workflows are covered, but there is no tool to select a specific instance or start a new session, which are minor gaps.

Maintenance

ActivityInactive
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/klutometis/opencode-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server