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.
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +514 -0
- package/SECURITY.md +80 -0
- package/agents/deepseek-coder.md +32 -0
- package/agents/deepseek-researcher.md +36 -0
- package/agents/deepseek-reviewer.md +31 -0
- package/agents/deepseek-tester.md +31 -0
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.js +208 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/config/config.d.ts +109 -0
- package/dist/config/config.js +163 -0
- package/dist/config/config.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +68 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/execute.d.ts +30 -0
- package/dist/mcp/execute.js +128 -0
- package/dist/mcp/execute.js.map +1 -0
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +38 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session-utils.d.ts +5 -0
- package/dist/mcp/session-utils.js +55 -0
- package/dist/mcp/session-utils.js.map +1 -0
- package/dist/mcp/tool-utils.d.ts +11 -0
- package/dist/mcp/tool-utils.js +44 -0
- package/dist/mcp/tool-utils.js.map +1 -0
- package/dist/mcp/tools/abort-session.d.ts +3 -0
- package/dist/mcp/tools/abort-session.js +48 -0
- package/dist/mcp/tools/abort-session.js.map +1 -0
- package/dist/mcp/tools/create-session.d.ts +3 -0
- package/dist/mcp/tools/create-session.js +53 -0
- package/dist/mcp/tools/create-session.js.map +1 -0
- package/dist/mcp/tools/delegate-task.d.ts +4 -0
- package/dist/mcp/tools/delegate-task.js +98 -0
- package/dist/mcp/tools/delegate-task.js.map +1 -0
- package/dist/mcp/tools/get-diff.d.ts +3 -0
- package/dist/mcp/tools/get-diff.js +97 -0
- package/dist/mcp/tools/get-diff.js.map +1 -0
- package/dist/mcp/tools/get-session.d.ts +3 -0
- package/dist/mcp/tools/get-session.js +79 -0
- package/dist/mcp/tools/get-session.js.map +1 -0
- package/dist/mcp/tools/list-agents.d.ts +3 -0
- package/dist/mcp/tools/list-agents.js +72 -0
- package/dist/mcp/tools/list-agents.js.map +1 -0
- package/dist/mcp/tools/send-message.d.ts +3 -0
- package/dist/mcp/tools/send-message.js +59 -0
- package/dist/mcp/tools/send-message.js.map +1 -0
- package/dist/opencode/agents.d.ts +46 -0
- package/dist/opencode/agents.js +239 -0
- package/dist/opencode/agents.js.map +1 -0
- package/dist/opencode/binary.d.ts +19 -0
- package/dist/opencode/binary.js +129 -0
- package/dist/opencode/binary.js.map +1 -0
- package/dist/opencode/client.d.ts +32 -0
- package/dist/opencode/client.js +55 -0
- package/dist/opencode/client.js.map +1 -0
- package/dist/opencode/manager.d.ts +66 -0
- package/dist/opencode/manager.js +492 -0
- package/dist/opencode/manager.js.map +1 -0
- package/dist/opencode/permission-watcher.d.ts +26 -0
- package/dist/opencode/permission-watcher.js +144 -0
- package/dist/opencode/permission-watcher.js.map +1 -0
- package/dist/opencode/run.d.ts +39 -0
- package/dist/opencode/run.js +318 -0
- package/dist/opencode/run.js.map +1 -0
- package/dist/opencode/sessions.d.ts +26 -0
- package/dist/opencode/sessions.js +105 -0
- package/dist/opencode/sessions.js.map +1 -0
- package/dist/opencode/setup.d.ts +38 -0
- package/dist/opencode/setup.js +109 -0
- package/dist/opencode/setup.js.map +1 -0
- package/dist/security/paths.d.ts +20 -0
- package/dist/security/paths.js +93 -0
- package/dist/security/paths.js.map +1 -0
- package/dist/security/policy.d.ts +17 -0
- package/dist/security/policy.js +86 -0
- package/dist/security/policy.js.map +1 -0
- package/dist/types/index.d.ts +131 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/util/fs.d.ts +12 -0
- package/dist/util/fs.js +73 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/git.d.ts +54 -0
- package/dist/util/git.js +202 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/logger.d.ts +21 -0
- package/dist/util/logger.js +129 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/text.d.ts +11 -0
- package/dist/util/text.js +126 -0
- package/dist/util/text.js.map +1 -0
- package/dist/workspace/context.d.ts +12 -0
- package/dist/workspace/context.js +67 -0
- package/dist/workspace/context.js.map +1 -0
- package/dist/workspace/resolver.d.ts +35 -0
- package/dist/workspace/resolver.js +112 -0
- package/dist/workspace/resolver.js.map +1 -0
- package/dist/workspace/validator.d.ts +13 -0
- package/dist/workspace/validator.js +102 -0
- package/dist/workspace/validator.js.map +1 -0
- package/docs/agent-configuration.md +100 -0
- package/docs/architecture.md +115 -0
- package/docs/claude-code-setup.md +116 -0
- package/docs/configuration.md +184 -0
- package/docs/contributing.md +71 -0
- package/docs/deepseek-setup.md +84 -0
- package/docs/development.md +146 -0
- package/docs/installation.md +88 -0
- package/docs/opencode-setup.md +97 -0
- package/docs/security-audit.md +80 -0
- package/docs/security.md +128 -0
- package/docs/troubleshooting.md +126 -0
- package/docs/workspace-model.md +126 -0
- 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.
|