claude-opencode-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +514 -0
  4. package/SECURITY.md +80 -0
  5. package/agents/deepseek-coder.md +32 -0
  6. package/agents/deepseek-researcher.md +36 -0
  7. package/agents/deepseek-reviewer.md +31 -0
  8. package/agents/deepseek-tester.md +31 -0
  9. package/dist/cli/init.d.ts +6 -0
  10. package/dist/cli/init.js +208 -0
  11. package/dist/cli/init.js.map +1 -0
  12. package/dist/config/config.d.ts +109 -0
  13. package/dist/config/config.js +163 -0
  14. package/dist/config/config.js.map +1 -0
  15. package/dist/errors.d.ts +29 -0
  16. package/dist/errors.js +68 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +116 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/mcp/execute.d.ts +30 -0
  22. package/dist/mcp/execute.js +128 -0
  23. package/dist/mcp/execute.js.map +1 -0
  24. package/dist/mcp/server.d.ts +19 -0
  25. package/dist/mcp/server.js +38 -0
  26. package/dist/mcp/server.js.map +1 -0
  27. package/dist/mcp/session-utils.d.ts +5 -0
  28. package/dist/mcp/session-utils.js +55 -0
  29. package/dist/mcp/session-utils.js.map +1 -0
  30. package/dist/mcp/tool-utils.d.ts +11 -0
  31. package/dist/mcp/tool-utils.js +44 -0
  32. package/dist/mcp/tool-utils.js.map +1 -0
  33. package/dist/mcp/tools/abort-session.d.ts +3 -0
  34. package/dist/mcp/tools/abort-session.js +48 -0
  35. package/dist/mcp/tools/abort-session.js.map +1 -0
  36. package/dist/mcp/tools/create-session.d.ts +3 -0
  37. package/dist/mcp/tools/create-session.js +53 -0
  38. package/dist/mcp/tools/create-session.js.map +1 -0
  39. package/dist/mcp/tools/delegate-task.d.ts +4 -0
  40. package/dist/mcp/tools/delegate-task.js +98 -0
  41. package/dist/mcp/tools/delegate-task.js.map +1 -0
  42. package/dist/mcp/tools/get-diff.d.ts +3 -0
  43. package/dist/mcp/tools/get-diff.js +97 -0
  44. package/dist/mcp/tools/get-diff.js.map +1 -0
  45. package/dist/mcp/tools/get-session.d.ts +3 -0
  46. package/dist/mcp/tools/get-session.js +79 -0
  47. package/dist/mcp/tools/get-session.js.map +1 -0
  48. package/dist/mcp/tools/list-agents.d.ts +3 -0
  49. package/dist/mcp/tools/list-agents.js +72 -0
  50. package/dist/mcp/tools/list-agents.js.map +1 -0
  51. package/dist/mcp/tools/send-message.d.ts +3 -0
  52. package/dist/mcp/tools/send-message.js +59 -0
  53. package/dist/mcp/tools/send-message.js.map +1 -0
  54. package/dist/opencode/agents.d.ts +46 -0
  55. package/dist/opencode/agents.js +239 -0
  56. package/dist/opencode/agents.js.map +1 -0
  57. package/dist/opencode/binary.d.ts +19 -0
  58. package/dist/opencode/binary.js +129 -0
  59. package/dist/opencode/binary.js.map +1 -0
  60. package/dist/opencode/client.d.ts +32 -0
  61. package/dist/opencode/client.js +55 -0
  62. package/dist/opencode/client.js.map +1 -0
  63. package/dist/opencode/manager.d.ts +66 -0
  64. package/dist/opencode/manager.js +492 -0
  65. package/dist/opencode/manager.js.map +1 -0
  66. package/dist/opencode/permission-watcher.d.ts +26 -0
  67. package/dist/opencode/permission-watcher.js +144 -0
  68. package/dist/opencode/permission-watcher.js.map +1 -0
  69. package/dist/opencode/run.d.ts +39 -0
  70. package/dist/opencode/run.js +318 -0
  71. package/dist/opencode/run.js.map +1 -0
  72. package/dist/opencode/sessions.d.ts +26 -0
  73. package/dist/opencode/sessions.js +105 -0
  74. package/dist/opencode/sessions.js.map +1 -0
  75. package/dist/opencode/setup.d.ts +38 -0
  76. package/dist/opencode/setup.js +109 -0
  77. package/dist/opencode/setup.js.map +1 -0
  78. package/dist/security/paths.d.ts +20 -0
  79. package/dist/security/paths.js +93 -0
  80. package/dist/security/paths.js.map +1 -0
  81. package/dist/security/policy.d.ts +17 -0
  82. package/dist/security/policy.js +86 -0
  83. package/dist/security/policy.js.map +1 -0
  84. package/dist/types/index.d.ts +131 -0
  85. package/dist/types/index.js +2 -0
  86. package/dist/types/index.js.map +1 -0
  87. package/dist/util/fs.d.ts +12 -0
  88. package/dist/util/fs.js +73 -0
  89. package/dist/util/fs.js.map +1 -0
  90. package/dist/util/git.d.ts +54 -0
  91. package/dist/util/git.js +202 -0
  92. package/dist/util/git.js.map +1 -0
  93. package/dist/util/logger.d.ts +21 -0
  94. package/dist/util/logger.js +129 -0
  95. package/dist/util/logger.js.map +1 -0
  96. package/dist/util/text.d.ts +11 -0
  97. package/dist/util/text.js +126 -0
  98. package/dist/util/text.js.map +1 -0
  99. package/dist/workspace/context.d.ts +12 -0
  100. package/dist/workspace/context.js +67 -0
  101. package/dist/workspace/context.js.map +1 -0
  102. package/dist/workspace/resolver.d.ts +35 -0
  103. package/dist/workspace/resolver.js +112 -0
  104. package/dist/workspace/resolver.js.map +1 -0
  105. package/dist/workspace/validator.d.ts +13 -0
  106. package/dist/workspace/validator.js +102 -0
  107. package/dist/workspace/validator.js.map +1 -0
  108. package/docs/agent-configuration.md +100 -0
  109. package/docs/architecture.md +115 -0
  110. package/docs/claude-code-setup.md +116 -0
  111. package/docs/configuration.md +184 -0
  112. package/docs/contributing.md +71 -0
  113. package/docs/deepseek-setup.md +84 -0
  114. package/docs/development.md +146 -0
  115. package/docs/installation.md +88 -0
  116. package/docs/opencode-setup.md +97 -0
  117. package/docs/security-audit.md +80 -0
  118. package/docs/security.md +128 -0
  119. package/docs/troubleshooting.md +126 -0
  120. package/docs/workspace-model.md +126 -0
  121. package/package.json +79 -0
@@ -0,0 +1,80 @@
1
+ # Security audit
2
+
3
+ - **Version audited:** 1.0.0
4
+ - **Date:** 2026-09-24
5
+ - **Scope:** the `claude-opencode-mcp` source tree, its configuration parsing,
6
+ process management, permission model, MCP tool surface, CI workflows, and
7
+ published package contents.
8
+
9
+ ## Method
10
+
11
+ 1. Manual review of every source module with attention to command execution,
12
+ path handling, credential flow, and permission construction.
13
+ 2. Pattern scan for unsafe primitives: `child_process.exec`, `execSync`,
14
+ `shell: true`, `eval`, `new Function`, non-local `http://` endpoints.
15
+ 3. Secret scan of the repository (API-key and private-key patterns) and a scan
16
+ for machine-specific paths in shipped files.
17
+ 4. Dependency audit: `npm audit` and `npm audit --omit=dev`.
18
+ 5. Review of the permission model against the documented threat model,
19
+ including how a model could bypass intended restrictions.
20
+ 6. Review of CI workflows for secret exposure and fork safety.
21
+ 7. Reproduction of security-relevant behavior with the automated test suite,
22
+ including real OpenCode/DeepSeek end-to-end tests.
23
+
24
+ ## Findings
25
+
26
+ | # | Finding | Risk | Status |
27
+ | --- | --- | --- | --- |
28
+ | 1 | `opencode.binary` configuration was ignored; the bridge used a PATH-resolved binary instead. | A user explicitly selecting a trusted binary could unknowingly run another. | Fixed: resolver receives the configured path; regression test asserts the configured binary is used. |
29
+ | 2 | Shutdown/startup race could orphan a spawned OpenCode process. | Local resource leak; a stale server keeps running after the bridge exits. | Fixed: children are tracked at spawn time, startup aborts during shutdown, and a synchronous `exit`-handler kills anything left. Tested with a fake binary. |
30
+ | 3 | Session registry and log file were created with default permissions. | Other local users could read agent output summaries and workspace paths. | Fixed: state directory `0700`, files `0600`; log directory created with `0700`. |
31
+ | 4 | Logger had no redaction. | A future code path logging credentials would leak them to disk. | Fixed: log field names matching password/secret/token/authorization/API-key/credential/private-key are redacted; unit-tested. |
32
+ | 5 | MCP tool inputs were unbounded. | Oversized payloads could bloat prompts/logs. | Fixed: `.max()` limits on task, message, cwd, model, agent, title, session id, path array (200 items), and timeout (1 hour). |
33
+ | 6 | CI E2E job could attempt to use secrets on fork pull requests. | Failed/unsafe runs on untrusted PRs. | Fixed: the E2E job runs only on `push` events, and checkouts disable credential persistence. |
34
+ | 7 | `edit: deny` alone is bypassable by shell commands. | Read-only intent could be violated. | Mitigated: read-only profiles also deny `bash`; shell-enabled tester is documented as able to write via shell. Verified end-to-end that a read-only agent cannot create a file. |
35
+
36
+ No unsafe command construction was found: all external processes use
37
+ `execFile`/`spawn` with explicit argument arrays and no shell. Credentials for
38
+ external OpenCode servers come only from explicit configuration or the URL,
39
+ never from the ambient environment. Dependency audit reports zero known
40
+ vulnerabilities.
41
+
42
+ ## Accepted risks
43
+
44
+ These are intentional and documented in [security.md](security.md):
45
+
46
+ - Shell-enabled agents (`deepseek-coder`, `deepseek-tester`) can write files
47
+ through shell commands; the tester's edit tools are denied but shell is
48
+ inherently powerful.
49
+ - Sensitive-file protection applies to OpenCode's file tools, not to `bash`.
50
+ - A delegated agent reads repository content, so prompt injection from a
51
+ hostile repository is possible; review diffs with `get_diff` and keep
52
+ `workspace.allowedRoots` narrow.
53
+ - Explicitly configured/`OPENCODE_BIN` executables are trusted by definition.
54
+ - Changing `opencode.hostname` from `127.0.0.1` exposes the OpenCode HTTP API
55
+ to the network; use a server password and firewall the port.
56
+
57
+ ## Security test coverage
58
+
59
+ The suite includes assertions for: workspace canonicalization and traversal
60
+ rejection, allowed-root enforcement, path-hint escape rejection, read-only
61
+ agent file-creation denial, `.env` secret non-disclosure, `git commit` denial,
62
+ unknown-session handling, timeout and abort without hanging, crashed-server
63
+ handling, workspace routing mismatch detection, child-process cleanup, log
64
+ redaction, and dependency freshness via CI.
65
+
66
+ ## Reproducing
67
+
68
+ ```bash
69
+ npm audit --omit=dev
70
+ npm audit
71
+ grep -rEn "exec\(|execSync|shell: true|eval\(|new Function\(" src/
72
+ grep -rEn "sk-[a-zA-Z0-9]{20,}|BEGIN (RSA|OPENSSH|EC) PRIVATE" .
73
+ npm test
74
+ npm run test:e2e # real OpenCode + provider required
75
+ ```
76
+
77
+ ## Reporting
78
+
79
+ Found something this audit missed? Please use the private process in
80
+ [SECURITY.md](../SECURITY.md).
@@ -0,0 +1,128 @@
1
+ # Security
2
+
3
+ Security in this bridge comes from four places: workspace validation in the
4
+ bridge, permission rules enforced by OpenCode, the absence of interactive
5
+ `ask` rules (headless safety), and honest documentation of the remaining
6
+ limits.
7
+
8
+ ## Workspace boundaries
9
+
10
+ - The workspace must exist, be a directory, and be readable.
11
+ - Paths are canonicalized (`realpath`); `..` and symlinks cannot escape.
12
+ - `workspace.allowedRoots` (when non-empty) restricts every workspace to those
13
+ canonical roots.
14
+ - `paths` hints cannot escape the workspace (`INVALID_PATH`).
15
+ - `workspace.defaultCwd` is only a fallback; explicit `cwd` and
16
+ `CLAUDE_PROJECT_DIR` fail loudly when invalid.
17
+ - The bridge probes the OpenCode server's `directory` before the first run and
18
+ aborts on mismatch (`WORKSPACE_MISMATCH`).
19
+
20
+ ## File protection
21
+
22
+ When `security.protectEnvFiles` is `true` (default), the read and edit
23
+ permission rules deny:
24
+
25
+ ```text
26
+ *.env, *.env.*, *.pem, *.key, *.p12, *.pfx,
27
+ id_rsa*, id_ed25519*, id_ecdsa*, credentials, credentials.*, .ssh/*, *.npmrc
28
+ ```
29
+
30
+ while keeping `*.env.example`, `*.env.sample` and `*.env.template` readable.
31
+ Add your own patterns with `security.extraProtectedPatterns`.
32
+
33
+ ## Git safety
34
+
35
+ For agents that can run shell commands (`coder`, `tester`), bash rules deny:
36
+
37
+ ```text
38
+ git push* git -C * push* git -c * push*
39
+ git commit* git -C * commit* git -c * commit*
40
+ ```
41
+
42
+ All other commands are allowed for those profiles. Read-only profiles have
43
+ `bash: deny` outright, so they cannot mutate anything through the shell.
44
+
45
+ The bridge never commits, pushes, stages or reverts anything itself. There is
46
+ no automatic commit or push anywhere in the codebase.
47
+
48
+ ## Permission matrix
49
+
50
+ | Permission | read | review | code | test |
51
+ | --- | --- | --- | --- | --- |
52
+ | `read` | allow (+ sensitive denies) | same | same | same |
53
+ | `edit` | deny | deny | allow (+ sensitive denies) | deny |
54
+ | `bash` | deny | deny | allow (git commit/push denied) | allow (git commit/push denied) |
55
+ | `glob`, `grep`, `list`, `lsp` | allow | allow | allow | allow |
56
+ | `webfetch`, `websearch`, `skill`, `todowrite` | allow | allow | allow | allow |
57
+ | `task` (subagents) | deny | deny | deny | deny |
58
+ | `external_directory` | deny | deny | deny | deny |
59
+ | `question`, `doom_loop` | deny | deny | deny | deny |
60
+
61
+ `externalDirectory` can be set to `allow`, `ask` or `deny`. `ask` is
62
+ discouraged: nothing answers the prompt in headless mode, although the bridge
63
+ auto-rejects permission events it sees.
64
+
65
+ ## Headless safety
66
+
67
+ - No `ask` rules are emitted by the bridge.
68
+ - The bridge subscribes to OpenCode's event stream and rejects any permission
69
+ request for an active delegated session, logging a warning. A run cannot hang
70
+ waiting for a human.
71
+ - Every run has a timeout; on timeout the bridge aborts the OpenCode session
72
+ and returns `AGENT_TIMEOUT`.
73
+ - Cancellation via MCP (`extra.signal`) aborts the session and returns
74
+ `AGENT_ABORTED`.
75
+
76
+ ## Local data protection
77
+
78
+ - Session registry: `~/.local/state/claude-opencode-mcp/sessions.json`, written
79
+ with mode `0600` inside a `0700` directory. It contains workspace paths and
80
+ agent output summaries, so treat it as private.
81
+ - Log file: `~/.local/state/claude-opencode-mcp/bridge.log`, mode `0600`,
82
+ rotated at 5 MB. Log fields whose names look like credentials (password,
83
+ secret, token, authorization, API key, credential, private key) are redacted
84
+ before writing.
85
+ - Provider credentials are never stored by the bridge; they stay in OpenCode's
86
+ auth store or environment variables.
87
+
88
+ ## Network exposure
89
+
90
+ The started OpenCode server binds to `127.0.0.1` by default and uses a
91
+ configurable port (`0` lets OpenCode pick). If you change `opencode.hostname`
92
+ to a routable address, the OpenCode HTTP API becomes reachable from your
93
+ network: set `OPENCODE_SERVER_PASSWORD` (or `opencode.password`) and firewall
94
+ the port. The bridge only sends credentials to the server you configured.
95
+
96
+ ## External OpenCode servers
97
+
98
+ When `opencode.url` is set, the bridge cannot inject agent definitions or
99
+ permissions. It verifies the required agents exist and refuses to delegate
100
+ otherwise. The permissions of those agents are whatever the external server's
101
+ configuration says — review it yourself.
102
+
103
+ Provider credentials are never sent to external URLs from the environment;
104
+ only explicit `username`/`password` or credentials embedded in the URL are
105
+ used.
106
+
107
+ ## Known limits
108
+
109
+ State these plainly when deciding what to delegate:
110
+
111
+ 1. **Shell access implies write access.** A coder or tester can write files via
112
+ `bash` (`printf > file`, `sed -i`, …). The bridge denies edit tools for the
113
+ tester and forbids git history changes, but shell is powerful. Only delegate
114
+ to shell-enabled agents in repositories you trust.
115
+ 2. **`.env` protection covers file tools.** A shell-enabled agent could `cat`
116
+ a protected file through `bash`. This matches OpenCode's own permission
117
+ model, which gates the `read` tool, not `bash`.
118
+ 3. **Prompt injection.** Agents read repository content; a hostile repository
119
+ can attempt to influence them. Keep `allowedRoots` tight and review diffs
120
+ with `get_diff` before accepting changes.
121
+ 4. **An agent can read anything inside the workspace.** That is intentional:
122
+ hints are not boundaries. Use workspace separation, not hints, to isolate
123
+ code.
124
+
125
+ ## Reporting
126
+
127
+ If you find a security issue, please open an issue with a minimal reproduction
128
+ and avoid sharing live credentials.
@@ -0,0 +1,126 @@
1
+ # Troubleshooting
2
+
3
+ Start with the log file:
4
+
5
+ ```bash
6
+ tail -f ~/.local/state/claude-opencode-mcp/bridge.log
7
+ ```
8
+
9
+ and raise the level:
10
+
11
+ ```bash
12
+ CLAUDE_OPENCODE_LOG=debug claude
13
+ ```
14
+
15
+ ## Error codes
16
+
17
+ | Code | Meaning | Typical fix |
18
+ | --- | --- | --- |
19
+ | `WORKSPACE_NOT_FOUND` | No usable workspace could be resolved. | Pass `cwd` explicitly or start Claude Code inside the project. |
20
+ | `WORKSPACE_NOT_ALLOWED` | Canonical workspace is outside `workspace.allowedRoots`. | Add the root or clear the list. |
21
+ | `WORKSPACE_NOT_READABLE` | Permission problem accessing the workspace. | Fix directory permissions. |
22
+ | `WORKSPACE_MISMATCH` | OpenCode reported a different directory than requested. | Restart Claude Code; report a bug if it persists. |
23
+ | `INVALID_PATH` | A path hint escaped the workspace, a path had a null byte, or `cwd` was not a directory. | Fix the hint. |
24
+ | `OPENCODE_NOT_AVAILABLE` | Executable not found or not executable. | `npm install -g opencode-ai` or set `OPENCODE_BIN`/`opencode.binary`. |
25
+ | `OPENCODE_START_TIMEOUT` | Server did not print its listening URL in time. | Run `opencode serve` manually; raise `opencode.startupTimeout`; check auth env vars. |
26
+ | `OPENCODE_UNAVAILABLE` | Server died or refused a request. | Retry; check `bridge.log` and `opencode serve` output. |
27
+ | `OPENCODE_SESSION_NOT_FOUND` | Unknown or deleted session id. | Create a new session. |
28
+ | `OPENCODE_ERROR` | OpenCode returned an error (includes provider auth failures). | Read the message; for auth re-run `opencode auth login`. |
29
+ | `AGENT_NOT_FOUND` | Unknown agent, or the external server lacks required agents. | Check `list_agents`; install agents on the external server. |
30
+ | `AGENT_TIMEOUT` | Run exceeded the timeout and was aborted. | Raise `timeouts.execution`/`timeout`, or split the task. |
31
+ | `AGENT_ABORTED` | Run was cancelled. | Expected after `abort_session` or MCP cancellation. |
32
+ | `AGENT_EXECUTION_FAILED` | Provider/API error during the run. | Inspect `details.statusCode`/`responseBody`; retry on `429`. |
33
+ | `MODEL_NOT_AVAILABLE` | No model could be resolved. | `opencode auth login` or set `defaults.model`. |
34
+ | `DIFF_UNAVAILABLE` | No changes were reported and the workspace is not a git repo. | Initialize git for reliable diffs. |
35
+ | `NOT_GIT_REPOSITORY` | A git-only operation was requested outside a repo. | Initialize git. |
36
+ | `CONFIG_INVALID` | Configuration file has wrong types or bad JSON. | Fix the file named in `details.file`. |
37
+ | `INTERNAL_ERROR` | Unexpected bridge error. | Report with the `request_id` from the log. |
38
+
39
+ ## The tool call times out in Claude Code
40
+
41
+ - Set `"timeout": 600000` in the `.mcp.json` server entry (per-tool wall clock).
42
+ - `MCP_TOOL_TIMEOUT` raises the default for all servers.
43
+ - Claude Code backgrounds calls after two minutes; this is normal and the
44
+ result still arrives.
45
+ - The stdio idle timeout is 30 minutes with no response *and no progress*; the
46
+ bridge emits progress notifications while polling, so long runs are safe.
47
+
48
+ ## OpenCode server fails to start
49
+
50
+ Run the same command the bridge runs, in the workspace:
51
+
52
+ ```bash
53
+ cd /path/to/project
54
+ opencode serve --hostname=127.0.0.1 --port=0
55
+ ```
56
+
57
+ Common causes:
58
+
59
+ - Port/hostname conflicts: leave `opencode.port` at `0` (choose a free port).
60
+ - `OPENCODE_SERVER_PASSWORD` set in the environment: the server requires basic
61
+ auth. The bridge handles it; if you connect manually, pass credentials.
62
+ - Stale `OPENCODE_CONFIG_CONTENT` with invalid JSON: the bridge ignores
63
+ malformed inherited content and merges its own agents on top.
64
+ - A broken global OpenCode config: run `opencode debug config`.
65
+
66
+ ## DeepSeek is not detected by `init`
67
+
68
+ `claude-opencode init` runs `opencode models` and looks for `deepseek/*`. If the
69
+ list is empty:
70
+
71
+ ```bash
72
+ opencode auth login
73
+ opencode models | grep deepseek
74
+ ```
75
+
76
+ ## Agents are missing on an external server
77
+
78
+ `AGENT_NOT_FOUND` with `opencode.url` set means the server has no definitions
79
+ for `deepseek-*`. Either:
80
+
81
+ ```bash
82
+ claude-opencode init
83
+ mkdir -p .opencode/agents
84
+ cp .claude-opencode/agents/*.md .opencode/agents/
85
+ ```
86
+
87
+ then restart the external server, or remove `opencode.url` and let the bridge
88
+ manage the server.
89
+
90
+ ## A run seems stuck
91
+
92
+ - Check `/tasks` in Claude Code for a backgrounded call.
93
+ - `get_session` returns live `opencode.status` (`busy`, `idle`, `retry`).
94
+ - `abort_session` stops it.
95
+ - The bridge auto-rejects permission prompts it observes, and every run has a
96
+ timeout, so a run cannot wait forever.
97
+
98
+ ## Provider errors during runs
99
+
100
+ Results include `error.details` from OpenCode, for example:
101
+
102
+ ```json
103
+ {
104
+ "status": "failed",
105
+ "error": {
106
+ "code": "AGENT_EXECUTION_FAILED",
107
+ "message": "rate limited",
108
+ "details": { "statusCode": 429, "isRetryable": true }
109
+ }
110
+ }
111
+ ```
112
+
113
+ Retry by sending a follow-up (`send_message`) on the same session or delegating
114
+ again.
115
+
116
+ ## Diagnostics
117
+
118
+ ```bash
119
+ claude-opencode-mcp --version
120
+ claude-opencode init # validates binary, provider, workspace, MCP config
121
+ claude mcp list # shows connection health
122
+ node dist/index.js --version
123
+ ```
124
+
125
+ When reporting a bug, include the `request_id` from the error payload and the
126
+ matching lines from `bridge.log`.
@@ -0,0 +1,126 @@
1
+ # Workspace model
2
+
3
+ The core invariant: **a delegated agent operates against the same intended
4
+ workspace as Claude Code, and can inspect it directly through OpenCode's
5
+ tools.** No repository copying, no temporary clones for normal operation.
6
+
7
+ ## Resolution
8
+
9
+ For every tool call the workspace is resolved in this order:
10
+
11
+ 1. explicit `cwd` argument
12
+ 2. `CLAUDE_PROJECT_DIR` (set by Claude Code for stdio MCP servers)
13
+ 3. the MCP server process working directory
14
+ 4. `workspace.defaultCwd` from configuration
15
+
16
+ Candidates 1 and 2 are *required*: if one of them is present but invalid
17
+ (`WORKSPACE_NOT_FOUND`, `WORKSPACE_NOT_READABLE`, `WORKSPACE_NOT_ALLOWED`,
18
+ `INVALID_PATH`), the call fails immediately. The bridge never silently falls
19
+ back to a different directory for an explicitly requested workspace. Only the
20
+ process directory and `defaultCwd` are best-effort fallbacks.
21
+
22
+ If no candidate can be validated, the error is:
23
+
24
+ ```
25
+ WORKSPACE_NOT_FOUND: Unable to determine project workspace. Provide cwd explicitly.
26
+ ```
27
+
28
+ ## Validation
29
+
30
+ Before a session is created:
31
+
32
+ - the path must exist, be a directory, and be readable/executable;
33
+ - it is canonicalized with `realpath` (symlinks and `..` are resolved);
34
+ - when `workspace.allowedRoots` is configured, the canonical path must live
35
+ inside one of the canonical roots — anything else is rejected with
36
+ `WORKSPACE_NOT_ALLOWED`;
37
+ - the git root is detected (`git rev-parse --show-toplevel`) for diff
38
+ baselines and reported as `gitRoot`.
39
+
40
+ Path traversal (for example `cwd: "../../etc"` or a `paths` hint escaping the
41
+ workspace) is rejected with `INVALID_PATH`.
42
+
43
+ ## Path hints
44
+
45
+ `delegate_task` accepts `paths`:
46
+
47
+ ```json
48
+ {
49
+ "task": "Review authentication",
50
+ "paths": ["src/auth", "src/middleware/auth.ts", "tests/auth"]
51
+ }
52
+ ```
53
+
54
+ Hints are turned into absolute paths, must stay inside the workspace, and are
55
+ inserted into the task message as starting points. They are **not** a
56
+ filesystem boundary: the agent is explicitly told to inspect additional files
57
+ and to not assume everything relevant lives under the hints.
58
+
59
+ ## One server per workspace
60
+
61
+ The bridge starts one OpenCode server per workspace directory and runs it with
62
+ that directory as its working directory. Sessions for that workspace reuse it.
63
+ `opencode.maxServers` (default 4) bounds how many idle servers are kept; the
64
+ least recently used idle server is stopped when the limit is exceeded.
65
+
66
+ During the first call for a workspace the bridge asks the server
67
+ (`GET /path`) which directory it is serving and compares it with the requested
68
+ canonical path. A mismatch aborts with `WORKSPACE_MISMATCH` rather than
69
+ delegating into the wrong repository.
70
+
71
+ ## The workspace contract
72
+
73
+ Internally every run carries:
74
+
75
+ ```ts
76
+ interface AgentWorkspace {
77
+ cwd: string; // canonical absolute directory
78
+ requestedPath: string;
79
+ allowedPaths?: string[]; // reserved; hints are not restrictions
80
+ deniedPaths?: string[]; // reserved; sensitive patterns are enforced via permissions
81
+ gitRoot?: string;
82
+ readOnly: boolean; // derived from the agent profile
83
+ }
84
+ ```
85
+
86
+ and the agent message always contains:
87
+
88
+ ```text
89
+ Workspace:
90
+ /abs/path
91
+
92
+ Task:
93
+ ...
94
+
95
+ Relevant paths (starting points only, not a boundary):
96
+ - /abs/path/src/auth
97
+
98
+ Permissions:
99
+ - file edits: denied
100
+ - shell commands: denied
101
+ - read-only agent: yes
102
+
103
+ Instructions:
104
+ - Work inside the specified workspace; do not read or write outside it.
105
+ - Inspect additional files and directories whenever that helps the task.
106
+ - Do not assume relevant code exists only under the supplied path hints.
107
+ ...
108
+ ```
109
+
110
+ ## Demo: two repositories
111
+
112
+ ```text
113
+ repo-a/
114
+ ONLY_A.txt
115
+ repo-b/
116
+ ONLY_B.txt
117
+ ```
118
+
119
+ Delegating to each with the same task ("list the root .txt file and report its
120
+ contents") returns `alpha-42` for repo-a and `beta-99` for repo-b. This scenario
121
+ is covered by the end-to-end test suite (`tests/e2e/real-opencode.test.ts`).
122
+
123
+ ## Choosing a workspace without Claude Code
124
+
125
+ Any MCP client can pass `cwd` explicitly. Claude Code additionally provides
126
+ `CLAUDE_PROJECT_DIR`, which makes the common case zero-configuration.
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "claude-opencode-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP bridge that lets Claude Code delegate software-engineering tasks to OpenCode agents (DeepSeek by default) inside the same workspace.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "claude-opencode-mcp contributors",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/putuandy/claude-opencode-mcp.git"
11
+ },
12
+ "homepage": "https://github.com/putuandy/claude-opencode-mcp#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/putuandy/claude-opencode-mcp/issues"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "claude-code",
20
+ "opencode",
21
+ "deepseek",
22
+ "coding-agent"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "bin": {
28
+ "claude-opencode-mcp": "./dist/index.js",
29
+ "claude-opencode": "./dist/index.js"
30
+ },
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "agents",
42
+ "docs",
43
+ "README.md",
44
+ "LICENSE",
45
+ "CHANGELOG.md",
46
+ "SECURITY.md"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.build.json",
50
+ "prepare": "npm run build",
51
+ "prepack": "npm run build",
52
+ "typecheck": "tsc -p tsconfig.json --noEmit",
53
+ "lint": "biome check .",
54
+ "lint:fix": "biome check --write .",
55
+ "format": "biome format --write .",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "test:e2e": "npm run build && cross-env CLAUDE_OPENCODE_E2E=1 vitest run --config vitest.e2e.config.ts",
59
+ "dev": "tsx src/index.ts"
60
+ },
61
+ "dependencies": {
62
+ "@modelcontextprotocol/sdk": "^1.30.1",
63
+ "@opencode-ai/sdk": "^1.18.32",
64
+ "commander": "^14.0.3",
65
+ "yaml": "^2.9.1",
66
+ "zod": "^4.6.5"
67
+ },
68
+ "optionalDependencies": {
69
+ "opencode-ai": "^1.18.32"
70
+ },
71
+ "devDependencies": {
72
+ "@biomejs/biome": "^2.5.14",
73
+ "@types/node": "^26.6.2",
74
+ "cross-env": "^10.1.0",
75
+ "tsx": "^4.23.15",
76
+ "typescript": "^5.9.3",
77
+ "vitest": "^4.1.11"
78
+ }
79
+ }