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 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/workspace/context.ts"],"names":[],"mappings":"AAaA,SAAS,eAAe,CAAC,YAAiC;IACxD,OAAO;QACL,iBAAiB,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;QAC9D,qBAAqB,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;QACrE,sBAAsB,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;KAC7D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,gBAAgB,CAAC,YAAiC;IACzD,MAAM,KAAK,GAAG;QACZ,yEAAyE;QACzE,0EAA0E;QAC1E,0EAA0E;QAC1E,+FAA+F;KAChG,CAAC;IACF,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,qFAAqF,CACtF,CAAC;QACF,KAAK,CAAC,IAAI,CACR,uFAAuF,CACxF,CAAC;IACJ,CAAC;IACD,IAAI,YAAY,CAAC,UAAU,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QACrD,KAAK,CAAC,IAAI,CACR,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAClE,MAAM,SAAS,GACb,KAAK,CAAC,MAAM,GAAG,CAAC;QACd,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7C,CAAC,CAAC,uDAAuD,CAAC;IAE9D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO;YACL,cAAc,SAAS,CAAC,GAAG,EAAE;YAC7B,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;YAC3D,UAAU,SAAS,EAAE;YACrB,EAAE;YACF,OAAO;YACP,IAAI;YACJ,EAAE;YACF,cAAc;YACd,eAAe,CAAC,YAAY,CAAC;SAC9B;aACE,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;aAC/C,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,OAAO;QACL,YAAY;QACZ,SAAS,CAAC,GAAG;QACb,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,kCAAkC;QACzF,EAAE;QACF,UAAU,SAAS,EAAE;QACrB,EAAE;QACF,OAAO;QACP,IAAI;QACJ,EAAE;QACF,wDAAwD;QACxD,SAAS;QACT,EAAE;QACF,cAAc;QACd,eAAe,CAAC,YAAY,CAAC;QAC7B,EAAE;QACF,eAAe;QACf,gBAAgB,CAAC,YAAY,CAAC;KAC/B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type LoadedConfig, type ResolvedConfig } from "../config/config.js";
2
+ import type { AgentWorkspace } from "../types/index.js";
3
+ export interface ResolveWorkspaceInput {
4
+ /** Explicit cwd supplied to the MCP tool. */
5
+ cwd?: string | undefined;
6
+ env?: NodeJS.ProcessEnv;
7
+ /** Global config loaded before workspace resolution. */
8
+ globalConfig?: ResolvedConfig;
9
+ /** Override how per-workspace configuration is loaded (used for tests/embedding). */
10
+ loadProjectConfig?: (cwd: string) => Promise<LoadedConfig>;
11
+ }
12
+ export interface WorkspaceResolution {
13
+ workspace: AgentWorkspace;
14
+ config: ResolvedConfig;
15
+ configSources: string[];
16
+ warnings: string[];
17
+ }
18
+ export interface WorkspaceCandidate {
19
+ path: string;
20
+ required: boolean;
21
+ origin: "cwd" | "CLAUDE_PROJECT_DIR" | "process.cwd" | "workspace.defaultCwd";
22
+ }
23
+ /**
24
+ * Workspace resolution priority (PRD §4.1):
25
+ * 1. explicit `cwd`
26
+ * 2. `CLAUDE_PROJECT_DIR` (set by Claude Code for stdio MCP servers)
27
+ * 3. the MCP server process working directory
28
+ * 4. configured `workspace.defaultCwd`
29
+ *
30
+ * The first two are "required": when they are present and invalid the bridge
31
+ * fails loudly instead of silently delegating somewhere else.
32
+ */
33
+ export declare function candidateWorkspacePaths(input: ResolveWorkspaceInput): WorkspaceCandidate[];
34
+ export declare function resolveWorkspace(input?: ResolveWorkspaceInput): Promise<WorkspaceResolution>;
35
+ export declare function resolveWorkspaceHints(cwd: string, hints: string[] | undefined): string[];
@@ -0,0 +1,112 @@
1
+ import path from "node:path";
2
+ import { loadConfig } from "../config/config.js";
3
+ import { BridgeError } from "../errors.js";
4
+ import { resolvePathHint } from "../security/paths.js";
5
+ import { validateWorkspace } from "./validator.js";
6
+ /**
7
+ * Workspace resolution priority (PRD §4.1):
8
+ * 1. explicit `cwd`
9
+ * 2. `CLAUDE_PROJECT_DIR` (set by Claude Code for stdio MCP servers)
10
+ * 3. the MCP server process working directory
11
+ * 4. configured `workspace.defaultCwd`
12
+ *
13
+ * The first two are "required": when they are present and invalid the bridge
14
+ * fails loudly instead of silently delegating somewhere else.
15
+ */
16
+ export function candidateWorkspacePaths(input) {
17
+ const env = input.env ?? process.env;
18
+ const candidates = [];
19
+ if (input.cwd?.trim()) {
20
+ candidates.push({ path: input.cwd.trim(), required: true, origin: "cwd" });
21
+ }
22
+ const claudeProjectDir = env.CLAUDE_PROJECT_DIR;
23
+ if (claudeProjectDir?.trim()) {
24
+ candidates.push({
25
+ path: claudeProjectDir.trim(),
26
+ required: true,
27
+ origin: "CLAUDE_PROJECT_DIR",
28
+ });
29
+ }
30
+ candidates.push({ path: process.cwd(), required: false, origin: "process.cwd" });
31
+ const defaultCwd = input.globalConfig?.workspace.defaultCwd;
32
+ if (defaultCwd?.trim()) {
33
+ candidates.push({ path: defaultCwd.trim(), required: false, origin: "workspace.defaultCwd" });
34
+ }
35
+ const seen = new Set();
36
+ const unique = [];
37
+ for (const candidate of candidates) {
38
+ const key = path.resolve(candidate.path);
39
+ if (seen.has(key))
40
+ continue;
41
+ seen.add(key);
42
+ unique.push(candidate);
43
+ }
44
+ return unique;
45
+ }
46
+ export async function resolveWorkspace(input = {}) {
47
+ const env = input.env ?? process.env;
48
+ let baseConfig;
49
+ if (input.globalConfig) {
50
+ baseConfig = input.globalConfig;
51
+ }
52
+ else {
53
+ baseConfig = (await loadConfig({ env })).config;
54
+ }
55
+ const candidates = candidateWorkspacePaths({ ...input, env });
56
+ const failures = [];
57
+ for (const candidate of candidates) {
58
+ let validated;
59
+ try {
60
+ validated = await validateWorkspace({
61
+ requestedPath: candidate.path,
62
+ allowedRoots: baseConfig.workspace.allowedRoots,
63
+ });
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ if (candidate.required)
68
+ throw error;
69
+ failures.push(`${candidate.origin} (${candidate.path}): ${message}`);
70
+ continue;
71
+ }
72
+ let loaded;
73
+ try {
74
+ loaded = input.loadProjectConfig
75
+ ? await input.loadProjectConfig(validated.cwd)
76
+ : await loadConfig({ cwd: validated.cwd, env });
77
+ }
78
+ catch (error) {
79
+ if (error instanceof BridgeError && error.code === "CONFIG_INVALID")
80
+ throw error;
81
+ loaded = { config: baseConfig, sources: [] };
82
+ }
83
+ let finalValidated = validated;
84
+ const rootsChanged = loaded.config.workspace.allowedRoots.join("\u0000") !==
85
+ baseConfig.workspace.allowedRoots.join("\u0000");
86
+ if (rootsChanged) {
87
+ finalValidated = await validateWorkspace({
88
+ requestedPath: validated.cwd,
89
+ allowedRoots: loaded.config.workspace.allowedRoots,
90
+ });
91
+ }
92
+ const workspace = {
93
+ cwd: validated.cwd,
94
+ requestedPath: validated.requestedPath,
95
+ readOnly: false,
96
+ ...(finalValidated.gitRoot ? { gitRoot: finalValidated.gitRoot } : {}),
97
+ };
98
+ return {
99
+ workspace,
100
+ config: loaded.config,
101
+ configSources: loaded.sources,
102
+ warnings: [...finalValidated.warnings],
103
+ };
104
+ }
105
+ throw new BridgeError("WORKSPACE_NOT_FOUND", "Unable to determine project workspace. Provide cwd explicitly.", { details: { candidates: candidates.map((c) => `${c.origin}:${c.path}`), failures } });
106
+ }
107
+ export function resolveWorkspaceHints(cwd, hints) {
108
+ if (!hints || hints.length === 0)
109
+ return [];
110
+ return hints.map((hint) => resolvePathHint(cwd, hint));
111
+ }
112
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/workspace/resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface ValidateWorkspaceOptions {
2
+ /** Requested path (absolute or relative to process.cwd()). */
3
+ requestedPath: string;
4
+ /** When non-empty, the canonical workspace must live inside one of these roots. */
5
+ allowedRoots?: string[];
6
+ }
7
+ export interface ValidatedWorkspace {
8
+ cwd: string;
9
+ requestedPath: string;
10
+ gitRoot?: string;
11
+ warnings: string[];
12
+ }
13
+ export declare function validateWorkspace(options: ValidateWorkspaceOptions): Promise<ValidatedWorkspace>;
@@ -0,0 +1,102 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { BridgeError } from "../errors.js";
4
+ import { isPathInside } from "../security/paths.js";
5
+ import { findGitRoot } from "../util/git.js";
6
+ async function canonicalizeRoots(roots) {
7
+ const canonical = [];
8
+ const warnings = [];
9
+ for (const root of roots) {
10
+ const resolved = path.resolve(root);
11
+ try {
12
+ const real = await fs.promises.realpath(resolved);
13
+ const stat = await fs.promises.stat(real);
14
+ if (!stat.isDirectory()) {
15
+ warnings.push(`allowed root is not a directory and was ignored: ${root}`);
16
+ continue;
17
+ }
18
+ canonical.push(real);
19
+ }
20
+ catch {
21
+ warnings.push(`allowed root does not exist and was ignored: ${root}`);
22
+ }
23
+ }
24
+ return { roots: canonical, warnings };
25
+ }
26
+ export async function validateWorkspace(options) {
27
+ const warnings = [];
28
+ const requestedPath = options.requestedPath;
29
+ if (!requestedPath?.trim()) {
30
+ throw new BridgeError("WORKSPACE_NOT_FOUND", "Unable to determine project workspace. Provide cwd explicitly.");
31
+ }
32
+ const absolute = path.isAbsolute(requestedPath)
33
+ ? path.resolve(requestedPath)
34
+ : path.resolve(process.cwd(), requestedPath);
35
+ let stat;
36
+ try {
37
+ stat = await fs.promises.stat(absolute);
38
+ }
39
+ catch (error) {
40
+ const errno = error.code;
41
+ if (errno === "ENOENT") {
42
+ throw new BridgeError("WORKSPACE_NOT_FOUND", `Workspace does not exist: ${absolute}`, {
43
+ details: { cwd: absolute },
44
+ cause: error,
45
+ });
46
+ }
47
+ if (errno === "EACCES" || errno === "EPERM") {
48
+ throw new BridgeError("WORKSPACE_NOT_READABLE", `Workspace is not readable: ${absolute}`, {
49
+ details: { cwd: absolute },
50
+ cause: error,
51
+ });
52
+ }
53
+ throw new BridgeError("WORKSPACE_NOT_FOUND", `Workspace cannot be accessed: ${absolute}`, {
54
+ details: { cwd: absolute, reason: errno },
55
+ cause: error,
56
+ });
57
+ }
58
+ if (!stat.isDirectory()) {
59
+ throw new BridgeError("INVALID_PATH", `Workspace is not a directory: ${absolute}`, {
60
+ details: { cwd: absolute },
61
+ });
62
+ }
63
+ let canonical;
64
+ try {
65
+ canonical = await fs.promises.realpath(absolute);
66
+ }
67
+ catch (error) {
68
+ throw new BridgeError("WORKSPACE_NOT_READABLE", `Workspace cannot be resolved: ${absolute}`, {
69
+ details: { cwd: absolute },
70
+ cause: error,
71
+ });
72
+ }
73
+ try {
74
+ await fs.promises.access(canonical, fs.constants.R_OK | fs.constants.X_OK);
75
+ }
76
+ catch (error) {
77
+ throw new BridgeError("WORKSPACE_NOT_READABLE", `Workspace is not readable: ${canonical}`, {
78
+ details: { cwd: canonical },
79
+ cause: error,
80
+ });
81
+ }
82
+ const allowedRoots = options.allowedRoots ?? [];
83
+ if (allowedRoots.length > 0) {
84
+ const { roots, warnings: rootWarnings } = await canonicalizeRoots(allowedRoots);
85
+ warnings.push(...rootWarnings);
86
+ if (roots.length === 0) {
87
+ throw new BridgeError("WORKSPACE_NOT_ALLOWED", "No configured workspace.allowedRoots entry exists; refusing to delegate.", { details: { allowedRoots } });
88
+ }
89
+ const inside = roots.some((root) => isPathInside(root, canonical));
90
+ if (!inside) {
91
+ throw new BridgeError("WORKSPACE_NOT_ALLOWED", `Workspace is outside the configured allowed roots: ${canonical}`, { details: { cwd: canonical, allowedRoots: roots } });
92
+ }
93
+ }
94
+ const gitRoot = await findGitRoot(canonical);
95
+ return {
96
+ cwd: canonical,
97
+ requestedPath,
98
+ ...(gitRoot ? { gitRoot } : {}),
99
+ warnings,
100
+ };
101
+ }
102
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/workspace/validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ # Agent configuration
2
+
3
+ The bridge ships four agents. Their prompts live in [`agents/`](../agents/) as
4
+ markdown with YAML frontmatter, and they are injected into the OpenCode server
5
+ configuration at startup.
6
+
7
+ | Agent | Profile | read | edit | bash | Purpose |
8
+ | --- | --- | --- | --- | --- | --- |
9
+ | `deepseek-researcher` | `read` | allow | deny | deny | architecture, exploration, dependency analysis, recommendations |
10
+ | `deepseek-reviewer` | `review` | allow | deny | deny | code review, bugs, regressions, security, architecture |
11
+ | `deepseek-coder` | `code` | allow | allow | allow (no `git commit`/`git push`) | implementation, refactoring, tests |
12
+ | `deepseek-tester` | `test` | allow | deny | allow (no `git commit`/`git push`) | run tests, diagnose failures, suggest fixes |
13
+
14
+ Profiles also deny `external_directory`, `task`, `question` and `doom_loop`, and
15
+ never emit `ask` rules (a headless bridge cannot answer them).
16
+
17
+ ## Customizing a built-in agent
18
+
19
+ Create a project-local file with the same name:
20
+
21
+ ```markdown
22
+ ---
23
+ description: My strict reviewer
24
+ model: deepseek/deepseek-v4-pro
25
+ temperature: 0
26
+ ---
27
+
28
+ You are a security-focused reviewer. ...
29
+ ```
30
+
31
+ Place it in `<project>/.claude-opencode/agents/deepseek-reviewer.md`. The file
32
+ overrides the description, prompt, model and temperature. Permissions still come
33
+ from the `review` profile — the frontmatter cannot widen them.
34
+
35
+ ## Adding a new agent
36
+
37
+ ```markdown
38
+ ---
39
+ description: Database migration specialist
40
+ mode: all
41
+ profile: code
42
+ model: deepseek/deepseek-v4-pro
43
+ temperature: 0.1
44
+ ---
45
+
46
+ You are a database migration specialist. ...
47
+ ```
48
+
49
+ Save it as `<project>/.claude-opencode/agents/db-migrator.md`. It appears in
50
+ `list_agents` and can be used with `delegate_task({ agent: "db-migrator" })`.
51
+
52
+ Supported frontmatter keys:
53
+
54
+ | Key | Values | Notes |
55
+ | --- | --- | --- |
56
+ | `description` | string | Shown by `list_agents`; required for usefulness. |
57
+ | `mode` | `primary`, `subagent`, `all` | Defaults to `all`. |
58
+ | `profile` | `read`, `review`, `code`, `test` | Selects the permission policy. Defaults by name heuristics; unknown names get `read` (safest). |
59
+ | `model` | `provider/model` | Overrides the bridge default. |
60
+ | `temperature` | number | Passed to the provider. |
61
+
62
+ The prompt is the markdown body.
63
+
64
+ ## Overriding agents in config
65
+
66
+ The same values can be set in JSON, which is handy for global defaults:
67
+
68
+ ```json
69
+ {
70
+ "agents": {
71
+ "deepseek-coder": { "model": "deepseek/deepseek-v4-pro", "temperature": 0.1 },
72
+ "deepseek-tester": { "enabled": false }
73
+ }
74
+ }
75
+ ```
76
+
77
+ Precedence: config `agents` > project markdown > built-in markdown.
78
+
79
+ ## Writing a good prompt
80
+
81
+ The bridge appends the workspace, task, path hints, and permission summary to
82
+ every delegated message, and asks agents to finish with a structured response:
83
+
84
+ - `## Summary`
85
+ - `## Findings` for review agents, using
86
+ `- [severity: high] Title (path/to/file:42) — detail` so findings come back
87
+ as structured JSON
88
+ - `## Changes`, `## Verification`, `## Follow-ups` for coder agents
89
+
90
+ Keep custom prompts aligned with that contract: the orchestrator depends on the
91
+ structure.
92
+
93
+ ## What the bridge does not do
94
+
95
+ - It does not copy repository contents into prompts. Agents read files through
96
+ OpenCode's tools.
97
+ - It does not restrict agents to the `paths` hints. Those are starting points;
98
+ agents may inspect anything inside the workspace.
99
+ - It does not let a project agent override permissions from frontmatter. Edit
100
+ `security.*` or the bridge source if a policy change is needed.
@@ -0,0 +1,115 @@
1
+ # Architecture
2
+
3
+ The bridge is intentionally thin. It owns workspace resolution, security
4
+ policy, session bookkeeping, and the MCP surface. Everything else is OpenCode's
5
+ job.
6
+
7
+ ```text
8
+ ┌────────────────────────────────────────────────────────────────────┐
9
+ │ Claude Code │
10
+ │ · understands the request, decomposes work, picks agents │
11
+ │ · calls mcp__opencode__* tools │
12
+ └───────────────────────────────┬────────────────────────────────────┘
13
+ │ MCP over stdio
14
+ ┌───────────────────────────────▼────────────────────────────────────┐
15
+ │ claude-opencode-mcp │
16
+ │ │
17
+ │ mcp/server.ts ── tools/*.ts seven tools, zod schemas │
18
+ │ workspace/* resolution, validation, context │
19
+ │ security/* path policy, permission profiles │
20
+ │ config/config.ts layered configuration │
21
+ │ opencode/manager.ts one server per workspace, LRU │
22
+ │ opencode/agents.ts built-in + project agent definitions │
23
+ │ opencode/run.ts prompt, poll, timeout, abort │
24
+ │ opencode/sessions.ts registry persisted to disk │
25
+ │ opencode/permission-watcher auto-reject asks over SSE │
26
+ └───────────────┬────────────────────────────────────────────────────┘
27
+ │ @opencode-ai/sdk (v1 HTTP API) + OPENCODE_CONFIG_CONTENT
28
+ ┌───────────────▼────────────────────────────────────────────────────┐
29
+ │ OpenCode server (one per workspace directory) │
30
+ │ · tools: read, glob, grep, edit, write, apply_patch, bash, … │
31
+ │ · sessions, permissions, LSP, providers │
32
+ └───────────────┬────────────────────────────────────────────────────┘
33
+ │ provider API (DeepSeek by default)
34
+ ▼
35
+ DeepSeek / any configured provider
36
+ │
37
+ ▼
38
+ the same project/worktree
39
+ ```
40
+
41
+ ## Components
42
+
43
+ | Module | Responsibility |
44
+ | --- | --- |
45
+ | `src/index.ts` | CLI: default command runs the MCP stdio server; `init` subcommand. |
46
+ | `src/mcp/server.ts` | Creates `McpServer`, registers tools, passes app context. |
47
+ | `src/mcp/tools/*` | Thin validation + orchestration per tool. |
48
+ | `src/mcp/execute.ts` | Run lifecycle: registry status, run, truncate, count files. |
49
+ | `src/workspace/resolver.ts` | Resolution priority and project-config loading. |
50
+ | `src/workspace/validator.ts` | Existence, readability, canonicalization, allowed roots, git root. |
51
+ | `src/security/policy.ts` | Maps agent profiles to OpenCode permission configs. |
52
+ | `src/opencode/manager.ts` | Server lifecycle, auth, health, LRU, crash detection. |
53
+ | `src/opencode/run.ts` | `session.prompt` + polling progress + timeout/abort + result extraction. |
54
+ | `src/opencode/agents.ts` | Loads prompts, merges overrides, emits OpenCode agent config. |
55
+ | `src/opencode/sessions.ts` | Session registry with atomic persistence and pruning. |
56
+ | `src/opencode/permission-watcher.ts` | SSE watcher that rejects stray permission asks. |
57
+ | `src/util/git.ts` | Non-invasive baseline (`git stash create`) and diff since baseline. |
58
+
59
+ ## Request flow for `delegate_task`
60
+
61
+ ```text
62
+ 1. Resolve + validate workspace (workspace/*)
63
+ 2. Load project config, merge agents (config/, opencode/agents.ts)
64
+ 3. Ensure OpenCode server for the cwd (opencode/manager.ts)
65
+ · spawn `opencode serve` with OPENCODE_CONFIG_CONTENT
66
+ · wait for "listening", poll /global/health
67
+ 4. Resolve model (request → config → provider default)
68
+ 5. Create session, capture git baseline (opencode/sessions.ts, util/git.ts)
69
+ 6. POST /session/:id/message with agent + model + parts
70
+ 7. While pending: poll status/messages, emit MCP progress notifications
71
+ 8. On completion: read assistant message, map errors, extract findings
72
+ 9. Count files changed from the baseline, persist result, return JSON
73
+ ```
74
+
75
+ ## Why these choices
76
+
77
+ - **One server per workspace** keeps OpenCode's project configuration, cwd and
78
+ snapshots honest, and avoids per-request directory routing bugs.
79
+ Per-request `x-opencode-directory` remains available in the SDK but the
80
+ bridge does not rely on it for isolation.
81
+ - **Sync prompt + polling** instead of streaming: the v1 HTTP API returns the
82
+ final assistant message from `POST /session/:id/message`, which gives a
83
+ definitive completion signal. Progress is reported to Claude Code via MCP
84
+ notifications.
85
+ - **Git baseline for diffs**: OpenCode 1.18's `GET /session/:id/diff` returns
86
+ empty for API-driven sessions in testing, so the bridge computes
87
+ `git diff` against a baseline captured at session creation
88
+ (`git stash create` for dirty trees) plus new untracked files. Pre-existing
89
+ uncommitted work is therefore not attributed to the agent.
90
+ - **Inline agent config** (`OPENCODE_CONFIG_CONTENT`) instead of writing files
91
+ into the repository: permissions cannot be forgotten, and the project tree
92
+ stays untouched.
93
+ - **No ask rules + SSE auto-reject**: a headless bridge must never block on
94
+ human approval.
95
+
96
+ ## Error model
97
+
98
+ `BridgeError` carries a stable code, message, optional details, and a
99
+ `retryable` flag. Tools catch it and return `isError: true` with
100
+ `{ "error": { code, message, details?, retryable, request_id } }`. The codes are
101
+ listed in [troubleshooting.md](troubleshooting.md#error-codes) and in
102
+ `src/errors.ts`.
103
+
104
+ ## State on disk
105
+
106
+ | Path | Contents |
107
+ | --- | --- |
108
+ | `~/.config/claude-opencode-mcp/config.json` | global configuration |
109
+ | `<project>/.claude-opencode/config.json` | project configuration |
110
+ | `<project>/.claude-opencode/agents/*.md` | optional project agents |
111
+ | `~/.local/state/claude-opencode-mcp/sessions.json` | session registry |
112
+ | `~/.local/state/claude-opencode-mcp/bridge.log` | rotating log (5 MB) |
113
+
114
+ No state is written into the repository except what `init` creates inside
115
+ `.claude-opencode/`.
@@ -0,0 +1,116 @@
1
+ # Claude Code setup
2
+
3
+ ## Add the MCP server
4
+
5
+ ### Project scope (recommended)
6
+
7
+ Create `.mcp.json` at your project root:
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "opencode": {
13
+ "command": "npx",
14
+ "args": ["-y", "claude-opencode-mcp"],
15
+ "type": "stdio",
16
+ "timeout": 600000
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ Claude Code asks for approval the first time it loads a project-scoped server in
23
+ an interactive session. Commit `.mcp.json` to share the setup with your team.
24
+
25
+ ### CLI
26
+
27
+ ```bash
28
+ # project scope (writes/updates .mcp.json)
29
+ claude mcp add opencode --scope project -- npx -y claude-opencode-mcp
30
+
31
+ # user scope (all projects, private to you)
32
+ claude mcp add opencode --scope user -- npx -y claude-opencode-mcp
33
+ ```
34
+
35
+ The `--` separator is required: everything after it is the command Claude Code
36
+ runs.
37
+
38
+ ### Global install without npx
39
+
40
+ ```bash
41
+ npm install -g claude-opencode-mcp
42
+ claude mcp add opencode --scope user -- claude-opencode-mcp
43
+ ```
44
+
45
+ For a local checkout:
46
+
47
+ ```bash
48
+ npm install && npm run build
49
+ claude mcp add opencode --scope local -- node /absolute/path/to/dist/index.js
50
+ ```
51
+
52
+ ## Verify
53
+
54
+ ```bash
55
+ claude mcp list
56
+ # opencode: npx -y claude-opencode-mcp - ✔ Connected
57
+ ```
58
+
59
+ Inside Claude Code, `/mcp` shows the server and the seven tools with the
60
+ `mcp__opencode__` prefix.
61
+
62
+ ## Important timeout settings
63
+
64
+ Delegated runs are long. Three independent limits matter:
65
+
66
+ 1. **Per-tool timeout** — the `timeout` field in `.mcp.json` (milliseconds) or
67
+ `MCP_TOOL_TIMEOUT`. Set it to at least your `timeouts.execution`
68
+ (default 10 minutes).
69
+ 2. **Automatic backgrounding** — after two minutes Claude Code moves a
70
+ main-conversation tool call to a background task and continues working; the
71
+ result arrives as a task notification. No configuration needed.
72
+ 3. **Idle timeout** — 30 minutes for stdio servers: a tool call that produces
73
+ no response and no progress notification for that long is aborted. The
74
+ bridge sends MCP progress notifications during runs, so this does not fire
75
+ for active work.
76
+
77
+ ## How the workspace is chosen
78
+
79
+ Claude Code sets `CLAUDE_PROJECT_DIR` for stdio MCP servers. The bridge uses:
80
+
81
+ 1. an explicit `cwd` argument,
82
+ 2. `CLAUDE_PROJECT_DIR`,
83
+ 3. the server process working directory,
84
+ 4. `workspace.defaultCwd`.
85
+
86
+ The first two must be valid when present, so delegation cannot silently switch
87
+ projects.
88
+
89
+ ## Example prompts
90
+
91
+ ```text
92
+ Use delegate_task with deepseek-researcher to find where authentication is
93
+ implemented and list the relevant files.
94
+
95
+ Have deepseek-coder fix the bug in src/auth/session.ts, run the tests, and
96
+ report the changes. Then call get_diff on that session.
97
+
98
+ Create a session with deepseek-researcher, ask it to inspect the payments
99
+ module, then send follow-up messages about dependencies it found.
100
+
101
+ Ask the reviewer to check the diff, then the tester to run the suite.
102
+ ```
103
+
104
+ ## Multiple projects
105
+
106
+ Each workspace gets its own lazily started OpenCode server, so you can delegate
107
+ into more than one repository from the same Claude Code session by passing
108
+ `cwd`. Use `workspace.allowedRoots` to bound which directories are acceptable.
109
+
110
+ ## Uninstalling
111
+
112
+ ```bash
113
+ claude mcp remove opencode --scope project
114
+ ```
115
+
116
+ and remove the `.mcp.json` entry if you added one manually.