Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
node: [22, 24]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -24,6 +24,13 @@ jobs:
cache: npm
- run: npm ci
- run: npm test
- name: Coverage gate
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
run: npm run test:coverage
- name: Dependency vulnerability gate
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
run: npm audit
- run: npm run docs:check
- run: npm pack --dry-run
- run: npm run test:package
- run: npm run test:compat
- run: git diff --check
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ pr_comments.txt

# Local test coverage reports
coverage/

# Locally prepared release candidate archives
artifacts/
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.1.0-rc.2] - 2026-10-18

Unpublished prerelease candidate with **experimental V2 support**. V1 behavior remains supported. The prerelease scope is 64 V2 tool mappings, 22 V1-only exceptions, and one disabled V2 operation; it does not complete the original 65-tool milestone.

- Keep `opencode_command_execute` disabled on V2 with `UNSUPPORTED_CAPABILITY` before submission until [upstream #49690](https://github.com/anomalyco/opencode/issues/49690) provides reliable command identity and completion correlation. V1 commands remain available.
- Integrate the applicable [#31](https://github.com/AlaeddineMessadi/opencode-mcp/issues/31) startup diagnostic fix: check the local executable before opt-in V1 startup, reject recognized V2 versions promptly, and preserve actionable connection instructions. Invalid V1 health responses cannot trigger replacement startup. Authentication failures and owned-child cleanup remain fail-closed.
- Include the October 18 client/CLI 2.0.32 compatibility, prefixed event-stream, macOS process-cleanup, and dependency updates described below.
- Live model workflows, recovery/cancellation, forms/permissions, OAuth completion, and interactive-client behavior remain unverified. Contract tests are not live validation. See [release checks](docs/releasing.md).

## [3.1.0-rc.1] - 2026-09-17

Evaluation candidate for the V1/V2 compatibility foundation. The original 65-operation V2 completion gate remains blocked by upstream command correlation; this is not a completed milestone declaration.

### October 18 follow-up

- Pin the V2 client and isolated CLI integration target to 2.0.32. Adapt project-list arguments and remove the URL-prefix workaround now handled by the client, including prefixed event-stream regression coverage.
- Make owned test-process cleanup tolerate groups exiting before a signal and transient Darwin EPERM while zombies are reaped, while preserving persistent permission failures. Classify cleanup errors without exposing raw server output.
- Refresh vulnerable lockfile dependencies within the existing declared ranges. No new dependency is introduced.
- Slash-command correlation remains blocked in 2.0.32. Parent session creation remains conservatively rejected pending validation of the newly added upstream option.

### Added

- Typed backend operations, normalized results, and one compatibility manifest for all 87 tools, 10 resources, 4 templates, and 6 prompts. V2 currently maps 64 tools, retains 22 explicit V1-only exceptions, and rejects `command_execute` before mutation because V2 2.0.6 supplies no execution receipt or caller-assigned command ID.
- Automatic or forced backend detection, read-only V2 service discovery, ownership reporting, and standalone `doctor`/`--check`, help, and version commands.
- V2 queue submission identity, durable log observation with saved correlation state, versioned job storage, and shared ordinary-tool/native-Task workflows.
- V2 model/agent preservation, scoped permissions, typed form values, exact OAuth attempt IDs, explicit diff ranges, staged reverts, pagination metadata, and project-filtered events.
- macOS CI, production archive installation tests, and isolated V1/V2 compatibility checks.

### Compatibility and validation

- V1 behavior and stored record format remain supported. Authentication errors and ambiguous mutations never trigger fallback submission or replay.
- The September candidate exercised `opencode-ai@1.18.31` and `@opencode/cli@2.0.6` on Node.js 22.23.2, macOS arm64. The current V2 client dependency and integration target are 2.0.32; see the [verification guide](docs/releasing.md) for current evidence.
- Live provider inference, live job execution, OAuth completion, and interactive permission/form flows were not verified. Those paths have fixture-backed contract and recovery tests; client UI compatibility is not claimed from those fixtures.
- See [migration guidance](MIGRATION.md) and the generated [compatibility matrix](docs/compatibility.md) for guarded options and the upstream blocker. No package has been published by this change.

## [3.0.0] - 2026-09-16

### Breaking changes
Expand Down
107 changes: 107 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# OpenCode V1/V2 compatibility migration

> **Experimental V2 support in 3.1.0-rc.2.** V2 slash-command execution is disabled before submission. V1 behavior remains supported. Live inference, recovery, forms, OAuth, and interactive clients are not yet verified; see [release evidence](docs/releasing.md).

The compatibility foundation preserves the existing V1 SDK and V1 job records. It adds the pinned `@opencode/client@2.0.32` adapter, explicit backend detection, and a read-only doctor. An API mapping is not proof of live end-to-end behavior: consult the [release verification guide](docs/releasing.md) and record actual backend check results separately.

## Select a backend

Keep the existing MCP stdio command and tool profile. `OPENCODE_BACKEND` defaults to `auto`; set `v1` or `v2` to require that backend's validated contract. `OPENCODE_BASE_URL`, when present, is authoritative. Detection never switches from a failed explicit URL to another server.

Without an explicit URL, the bridge probes `http://127.0.0.1:4096`, then checks an existing V2 service registration using the pinned client's read-only discovery contract. Present but invalid, unauthorized, or unresponsive registrations prevent alternate startup. HTTP 401/403 ends detection. Credentials stay in memory and are excluded from doctor output.

`OPENCODE_AUTO_SERVE=true` continues to allow an owned local V1 child when no existing backend or service registration is present. It never enables V2 `Service.ensure()` or stops a shared service. An owned child stops with MCP; externally managed and discovered servers can continue work while their processes remain alive.

For a manually started V2 server, set `OPENCODE_BACKEND=v2`, `OPENCODE_AUTO_SERVE=false`, `OPENCODE_BASE_URL` to its listening URL, and `OPENCODE_SERVER_PASSWORD` to the password it reports. The username defaults to `opencode`. The published 3.0.0 package does not support V2; use this compatibility candidate. The [#31](https://github.com/AlaeddineMessadi/opencode-mcp/issues/31) startup guard now checks `opencode --version` before owned-child startup and rejects a recognized V2 executable promptly. If a custom launcher cannot report a version, the bounded SDK startup path remains.

## Check readiness

For a source build:

```bash
npm run build
node dist/index.js doctor --json --directory /absolute/project/on/the/server
```

`doctor` and `--check` perform no inference, session creation, configuration writes, or startup. They share a 15-second deadline. A supplied directory is interpreted on the OpenCode host; omit it to skip the optional project check. External connections do not require an OpenCode executable on the MCP host. Exit codes are `0` for readiness, `1` for failed checks, and `2` for invalid arguments.

The versioned JSON reports `integrationTargetVersion` and `matchesIntegrationTarget`. These describe the intended version baseline, not a completed live test. Provider configuration is inspected without exercising provider credentials. Ordinary stdio can expose its catalog while detection is unresolved, but backend calls fail before mutation with `BACKEND_UNAVAILABLE`; repair the connection and restart MCP.

## Backend-specific behavior

The [generated inventory](docs/compatibility.md) records 87 tool names, with 64 V2 mappings, 22 deliberate V1-only exceptions, and one upstream-blocked command operation. Both profiles retain their names and schemas. Calls that cannot preserve their requested semantics return `UNSUPPORTED_CAPABILITY` before mutation rather than substituting weaker behavior.

| Operation | V2 behavior |
|---|---|
| Prompt output | `format` may be omitted or text. JSON-schema output, per-prompt `system`, and `noReply: true` are rejected. |
| Existing sessions | Model, variant, and agent must match the existing selections. Create a new session to choose different values. |
| Session creation | The bridge still rejects `parentID`; fork explicitly. Upstream 2.0.32 adds parent creation, but its semantics have not been validated by this bridge. A variant requires an explicit provider/model pair. |
| Fork | A message boundary is **before** that message. The result reports the boundary. |
| Diff/review | Supply explicit `from` and `to` message IDs; no range silently means “all session changes.” Cross-location ranges are rejected. |
| Revert | Whole-message revert is staged and remains reversible through `opencode_session_unrevert`; the bridge never automatically commits it. V2 2.0.32 implicitly commits a staged revert when prompting or compacting, so the bridge rejects those operations while a revert is staged. Clear it with `opencode_session_unrevert`, or explicitly commit it outside the bridge, before continuing. Individual `partID` reverts are rejected. |
| Permissions | `once` affects the specified request. `always` requires `scope: "project"`; `reject` requires `scope: "session"` and rejects all pending permissions in that session. Present that scope before forwarding the user's decision. |
| Questions/forms | Use `sessionId`, `requestId`, and typed, field-keyed `values` from the returned form fields. Legacy selected-label `answers` are not silently converted. |
| Authentication | `opencode_auth_set` accepts API keys through an integration with a published key method. Use the explicit OAuth workflow for OAuth. |
| Slash command execution | `opencode_command_execute` is blocked: V2 2.0.32 supplies no caller ID or submission receipt that allows exact completion correlation. No command is submitted. |

Configuration, providers, sessions, messages, and events may include explicitly labeled V2 raw data or pagination metadata. Consumers should use the bridge's normalized fields and inspect `isError`, structured state, and completeness instead of assuming V1 internals.

## V2 OAuth and explicit input

Discover the provider's authentication methods first. Use the returned integration/method identifiers, then retain the exact authorization attempt:

```javascript
opencode_provider_auth_methods({})
opencode_provider_oauth_authorize({
providerId: "<discovered-provider>",
integrationId: "<returned-integration-id>",
methodId: "<returned-oauth-method-id>"
// Include values only when the selected method's form requires them.
})
// Follow the returned authorization instructions. Retain structuredContent.data.attemptId.
opencode_provider_oauth_callback({
providerId: "<same-provider>",
callbackData: {
integrationId: "<same-integration-id>",
attemptId: "<exact-returned-attempt-id>"
// Include code only if the authorization method requests one.
}
})
```

For a form whose returned fields are `target` (string), `count` (integer), and `enabled` (boolean), send the user's selected values without coercing their types:

```javascript
opencode_job_input({
jobId: "<retained-job-id>",
responses: [{ id: "<form-id>", kind: "question", values: {
target: "web", count: 2, enabled: false
} }]
})
```

Use the fields of the actual pending form; this example does not define a universal schema. Conditional fields, patterns, or other forms that cannot be faithfully represented by MCP elicitation are returned for manual typed responses. A native task remains `working` with `pendingInputs` and manual instructions until that input is supplied. Representable forms use native `inputRequests`; both routes validate against the original form.

Permission decisions require the user's explicit approval of their scope:

```javascript
// Only after the user approves saved matching permissions across this project:
opencode_job_input({ jobId: "<job-id>", responses: [
{ id: "<permission-id>", kind: "permission", reply: "always", scope: "project" }
] })

// Only after the user chooses to reject ALL pending permissions in this session:
opencode_job_input({ jobId: "<job-id>", responses: [
{ id: "<permission-id>", kind: "permission", reply: "reject", scope: "session" }
] })
```

For one request, use `reply: "once"`. Native permission forms require a matching scope acknowledgment (`request`, `project`, or `session`) and have no preselected approval. Dismissing a native V2 permission form does not infer a session-wide rejection.

## Durable jobs

V1 records and behavior remain readable. V2 records identify their backend and preserve the correlation evidence needed to observe the submitted work. A lost response produces an unknown outcome; reconnect and inspect the existing job instead of resubmitting. Observation cancellation does not request a remote abort, and record expiration does not cancel remote work.

The same `JobService` serves ordinary `opencode_fire` / `opencode_job_get` / `opencode_job_input` calls and native `opencode_run` / `tasks/get` / `tasks/update`. A native `taskId` is the ordinary `jobId`; changing observation interface does not dispatch a second task. V1 records keep their original layout, while V2 records live in the server/caller scope's `v2/` subdirectory under `OPENCODE_TASK_STORE` (or its default root). Active jobs require their recorded backend; cached terminal results remain readable without re-executing the work.

Persistent records preserve observation handles and results. They do not checkpoint model execution or keep a stopped OpenCode process alive. Separate sessions share filesystem changes unless you explicitly use separate directories or Git worktrees.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@ opencode-mcp connects Claude, Cursor, VS Code, and other MCP clients to OpenCode

> Version 3.0.0 requires **Node.js 22 or newer**. Upgrading from 2.x? See the [migration notes](CHANGELOG.md#300---2026-09-16).

**3.1.0-rc.2 is a prerelease candidate; V2 support is experimental.** The stable npm package remains 3.0.0 and supports V1. Use the source-build instructions below to evaluate this unpublished candidate.

This source branch adds an OpenCode V1/V2 compatibility foundation. It retains the V1 API and adds the pinned V2 client contract. V2 currently maps **64 tools**, keeps **22 V1-only exceptions**, and disables **`opencode_command_execute` on V2** because its completion cannot be correlated safely. V1 command execution remains available. See the [generated compatibility inventory](docs/compatibility.md) and [V1/V2 migration guide](MIGRATION.md). This is not a claim that every live backend workflow has been verified.

## Quick Start

Install [OpenCode](https://opencode.ai/docs/) and start its server from your project:
For OpenCode V1, install [OpenCode](https://opencode.ai/docs/) and start its server from your project:

```bash
opencode serve --hostname 127.0.0.1 --port 4096
```

If you use the TUI, start it with `opencode --port 4096` and share that server. Set `OPENCODE_BASE_URL` for another endpoint.

For V2, start OpenCode separately and supply its URL, or let this bridge discover its existing local service registration. The bridge never starts or stops the shared V2 service. `OPENCODE_BACKEND=auto` recognizes the running backend; set `v1` or `v2` to require a specific contract. An explicit `OPENCODE_BASE_URL` is always authoritative.

For Claude Code:

```bash
Expand All @@ -43,6 +49,14 @@ Restart the client and call `opencode_setup`. Choose a provider from its configu

[Client-specific configuration](docs/configuration.md) includes VS Code, Windsurf, Continue, Zed, and Amazon Q. To test unreleased changes, [build from source](CONTRIBUTING.md) and configure your client to run `node` with the absolute path to `dist/index.js`.

Check a source build before connecting your MCP client:

```bash
node dist/index.js doctor --json --directory /absolute/path/on/opencode/server
```

Doctor performs only read operations, with a 15-second total deadline and no inference or server startup. It reports version recognition, provider configuration, project access, and lifecycle. Exit codes are `0` ready, `1` failed checks, and `2` invalid arguments. `--check` is an alias; no arguments still starts stdio.

## Choose a Workflow

| Need | Tools |
Expand All @@ -69,7 +83,7 @@ Async results distinguish `accepted`, `running`, `input_required`, `completed`,

Modern clients can use the MCP Tasks extension for `opencode_run`. Clients without that extension use ordinary tools, including `opencode_fire` and `opencode_check`. Task status is retrieved by polling; this package does not promise to wake an idle assistant with completion notifications.

Tools retain readable text and provide structured results for clients that consume them. Prompt tools accept optional model variants and OpenCode structured-output formats. See the [generated tools reference](docs/tools.md) and [examples](docs/examples.md).
Tools retain readable text and provide structured results for clients that consume them. V1 accepts OpenCode structured-output formats. V2 rejects JSON-schema output and preserves the selected model, variant, and agent when continuing an existing session. See [migration restrictions](MIGRATION.md), the [generated tools reference](docs/tools.md), and [examples](docs/examples.md).

## Multi-Project Use

Expand All @@ -85,9 +99,10 @@ All settings are optional; an OpenCode server must already be running by default

| Variable | Purpose |
|---|---|
| `OPENCODE_BASE_URL` | Server endpoint; defaults to `http://127.0.0.1:4096` |
| `OPENCODE_BACKEND` | `auto` (default), `v1`, or `v2` |
| `OPENCODE_BASE_URL` | Authoritative endpoint; otherwise probe loopback, then discover an existing local V2 service |
| `OPENCODE_SERVER_USERNAME`, `OPENCODE_SERVER_PASSWORD` | Optional server HTTP authentication |
| `OPENCODE_AUTO_SERVE` | Set to `true` to opt in to launching a local server |
| `OPENCODE_AUTO_SERVE` | Set to `true` to opt in to launching an owned local V1 child |
| `OPENCODE_DEFAULT_PROVIDER`, `OPENCODE_DEFAULT_MODEL` | Default prompt provider/model pair |
| `OPENCODE_TOOL_PROFILE` | `full` (default) or a smaller `essential` tool set |
| `OPENCODE_TASK_STORE` | Override the local directory for persisted job records |
Expand Down Expand Up @@ -115,6 +130,7 @@ Live smoke checks use a disposable project and owned session. Inference is opt-i

- [Getting started](docs/getting-started.md)
- [Configuration](docs/configuration.md)
- [V1/V2 migration](MIGRATION.md) and [capability inventory](docs/compatibility.md)
- [Tools reference](docs/tools.md)
- [Resources](docs/resources.md) and [prompts](docs/prompts.md)
- [Examples](docs/examples.md)
Expand Down
Loading
Loading