claude-octopus 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/LICENSE +15 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +81 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +114 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +57 -0
- package/dist/lib.d.ts +38 -0
- package/dist/lib.js +134 -0
- package/dist/tools/factory.d.ts +2 -0
- package/dist/tools/factory.js +108 -0
- package/dist/tools/query.d.ts +3 -0
- package/dist/tools/query.js +134 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/package.json +32 -0
- package/src/config.ts +96 -0
- package/src/constants.ts +116 -0
- package/src/index.ts +72 -0
- package/src/lib.test.ts +374 -0
- package/src/lib.ts +196 -0
- package/src/tools/factory.ts +150 -0
- package/src/tools/query.ts +171 -0
- package/src/types.ts +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
4
|
+
import { isDescendantPath, mergeAllowedTools, mergeDisallowedTools, buildResultPayload, formatErrorMessage, } from "../lib.js";
|
|
5
|
+
async function runQuery(prompt, overrides, baseOptions) {
|
|
6
|
+
const options = { ...baseOptions };
|
|
7
|
+
if (overrides.cwd) {
|
|
8
|
+
const baseCwd = baseOptions.cwd || process.cwd();
|
|
9
|
+
if (isDescendantPath(overrides.cwd, baseCwd)) {
|
|
10
|
+
options.cwd = resolve(baseCwd, overrides.cwd);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (overrides.model)
|
|
14
|
+
options.model = overrides.model;
|
|
15
|
+
if (overrides.maxTurns !== undefined && overrides.maxTurns > 0) {
|
|
16
|
+
options.maxTurns = overrides.maxTurns;
|
|
17
|
+
}
|
|
18
|
+
if (overrides.maxBudgetUsd != null)
|
|
19
|
+
options.maxBudgetUsd = overrides.maxBudgetUsd;
|
|
20
|
+
if (overrides.resumeSessionId)
|
|
21
|
+
options.resume = overrides.resumeSessionId;
|
|
22
|
+
if (overrides.allowedTools?.length) {
|
|
23
|
+
options.allowedTools = mergeAllowedTools(baseOptions.allowedTools, overrides.allowedTools);
|
|
24
|
+
}
|
|
25
|
+
if (overrides.disallowedTools?.length) {
|
|
26
|
+
options.disallowedTools = mergeDisallowedTools(baseOptions.disallowedTools, overrides.disallowedTools);
|
|
27
|
+
}
|
|
28
|
+
if (overrides.systemPrompt) {
|
|
29
|
+
if (typeof baseOptions.systemPrompt === "object" &&
|
|
30
|
+
baseOptions.systemPrompt?.type === "preset") {
|
|
31
|
+
const baseAppend = baseOptions.systemPrompt.append || "";
|
|
32
|
+
options.systemPrompt = {
|
|
33
|
+
type: "preset",
|
|
34
|
+
preset: "claude_code",
|
|
35
|
+
append: [baseAppend, overrides.systemPrompt].filter(Boolean).join("\n"),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
else if (typeof baseOptions.systemPrompt === "string") {
|
|
39
|
+
options.systemPrompt = baseOptions.systemPrompt;
|
|
40
|
+
options.extraArgs = {
|
|
41
|
+
...options.extraArgs,
|
|
42
|
+
"append-system-prompt": overrides.systemPrompt,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
options.systemPrompt = {
|
|
47
|
+
type: "preset",
|
|
48
|
+
preset: "claude_code",
|
|
49
|
+
append: overrides.systemPrompt,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const q = query({ prompt, options });
|
|
54
|
+
let result;
|
|
55
|
+
for await (const message of q) {
|
|
56
|
+
if (message.type === "result") {
|
|
57
|
+
result = message;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!result) {
|
|
61
|
+
throw new Error("No result message received from Claude Code");
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function formatResult(result) {
|
|
66
|
+
const payload = buildResultPayload(result);
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{ type: "text", text: JSON.stringify(payload, null, 2) },
|
|
70
|
+
],
|
|
71
|
+
isError: result.is_error,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function formatError(error) {
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{ type: "text", text: `Error: ${formatErrorMessage(error)}` },
|
|
78
|
+
],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function registerQueryTools(server, baseOptions, toolName, toolDescription) {
|
|
83
|
+
const replyToolName = `${toolName}_reply`;
|
|
84
|
+
server.registerTool(toolName, {
|
|
85
|
+
description: toolDescription,
|
|
86
|
+
inputSchema: z.object({
|
|
87
|
+
prompt: z.string().describe("Task or question for Claude Code"),
|
|
88
|
+
cwd: z.string().optional().describe("Working directory (overrides CLAUDE_CWD)"),
|
|
89
|
+
model: z.string().optional().describe('Model override (e.g. "sonnet", "opus", "haiku")'),
|
|
90
|
+
allowedTools: z.array(z.string()).optional().describe("Tool whitelist override"),
|
|
91
|
+
disallowedTools: z.array(z.string()).optional().describe("Tool blacklist override"),
|
|
92
|
+
maxTurns: z.number().int().positive().optional().describe("Max conversation turns"),
|
|
93
|
+
maxBudgetUsd: z.number().optional().describe("Max spend in USD"),
|
|
94
|
+
systemPrompt: z.string().optional().describe("Additional system prompt (appended to server default)"),
|
|
95
|
+
}),
|
|
96
|
+
}, async ({ prompt, cwd, model, allowedTools, disallowedTools, maxTurns, maxBudgetUsd, systemPrompt }) => {
|
|
97
|
+
try {
|
|
98
|
+
const result = await runQuery(prompt, {
|
|
99
|
+
cwd, model, allowedTools, disallowedTools, maxTurns, maxBudgetUsd, systemPrompt,
|
|
100
|
+
}, baseOptions);
|
|
101
|
+
return formatResult(result);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return formatError(error);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
if (baseOptions.persistSession !== false) {
|
|
108
|
+
server.registerTool(replyToolName, {
|
|
109
|
+
description: [
|
|
110
|
+
`Continue a previous ${toolName} conversation by session ID.`,
|
|
111
|
+
"Use this for follow-up questions, iterative refinement,",
|
|
112
|
+
"or multi-step workflows that build on prior context.",
|
|
113
|
+
].join(" "),
|
|
114
|
+
inputSchema: z.object({
|
|
115
|
+
session_id: z.string().describe(`Session ID from a prior ${toolName} response`),
|
|
116
|
+
prompt: z.string().describe("Follow-up instruction or question"),
|
|
117
|
+
cwd: z.string().optional().describe("Working directory override"),
|
|
118
|
+
model: z.string().optional().describe("Model override"),
|
|
119
|
+
maxTurns: z.number().int().positive().optional().describe("Max conversation turns"),
|
|
120
|
+
maxBudgetUsd: z.number().optional().describe("Max spend in USD"),
|
|
121
|
+
}),
|
|
122
|
+
}, async ({ session_id, prompt, cwd, model, maxTurns, maxBudgetUsd }) => {
|
|
123
|
+
try {
|
|
124
|
+
const result = await runQuery(prompt, {
|
|
125
|
+
cwd, model, maxTurns, maxBudgetUsd, resumeSessionId: session_id,
|
|
126
|
+
}, baseOptions);
|
|
127
|
+
return formatResult(result);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return formatError(error);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Options } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
export interface InvocationOverrides {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
allowedTools?: string[];
|
|
6
|
+
disallowedTools?: string[];
|
|
7
|
+
maxTurns?: number;
|
|
8
|
+
maxBudgetUsd?: number;
|
|
9
|
+
systemPrompt?: string;
|
|
10
|
+
resumeSessionId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OptionCatalogEntry {
|
|
13
|
+
key: string;
|
|
14
|
+
envVar: string;
|
|
15
|
+
label: string;
|
|
16
|
+
hint: string;
|
|
17
|
+
example: string;
|
|
18
|
+
}
|
|
19
|
+
export type { Options };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-octopus",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "One brain, many arms — spawn multiple specialized Claude Code agents as MCP servers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-octopus": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.89",
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
|
+
"zod": "^4.3.6"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
25
|
+
"typescript": "^5.8.0",
|
|
26
|
+
"vitest": "^4.1.2"
|
|
27
|
+
},
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Options } from "./types.js";
|
|
2
|
+
import {
|
|
3
|
+
envStr,
|
|
4
|
+
envList,
|
|
5
|
+
envNum,
|
|
6
|
+
envBool,
|
|
7
|
+
envJson,
|
|
8
|
+
validatePermissionMode,
|
|
9
|
+
} from "./lib.js";
|
|
10
|
+
|
|
11
|
+
export function buildBaseOptions(): Options {
|
|
12
|
+
const opts: Options = {
|
|
13
|
+
cwd: envStr("CLAUDE_CWD") || process.cwd(),
|
|
14
|
+
persistSession: envBool("CLAUDE_PERSIST_SESSION", true),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const rawPerm = envStr("CLAUDE_PERMISSION_MODE") || "default";
|
|
18
|
+
const permMode = validatePermissionMode(rawPerm);
|
|
19
|
+
if (rawPerm !== permMode) {
|
|
20
|
+
console.error(
|
|
21
|
+
`claude-octopus: invalid CLAUDE_PERMISSION_MODE "${rawPerm}", using "default"`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
opts.permissionMode = permMode as Options["permissionMode"];
|
|
25
|
+
if (permMode === "bypassPermissions") {
|
|
26
|
+
opts.allowDangerouslySkipPermissions = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const model = envStr("CLAUDE_MODEL");
|
|
30
|
+
if (model) opts.model = model;
|
|
31
|
+
|
|
32
|
+
const allowed = envList("CLAUDE_ALLOWED_TOOLS");
|
|
33
|
+
if (allowed) opts.allowedTools = allowed;
|
|
34
|
+
const disallowed = envList("CLAUDE_DISALLOWED_TOOLS");
|
|
35
|
+
if (disallowed) opts.disallowedTools = disallowed;
|
|
36
|
+
|
|
37
|
+
const maxTurns = envNum("CLAUDE_MAX_TURNS");
|
|
38
|
+
if (maxTurns !== undefined && maxTurns > 0 && Number.isInteger(maxTurns)) {
|
|
39
|
+
opts.maxTurns = maxTurns;
|
|
40
|
+
}
|
|
41
|
+
const maxBudget = envNum("CLAUDE_MAX_BUDGET_USD");
|
|
42
|
+
if (maxBudget !== undefined && maxBudget > 0) {
|
|
43
|
+
opts.maxBudgetUsd = maxBudget;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sysPrompt = envStr("CLAUDE_SYSTEM_PROMPT");
|
|
47
|
+
const appendPrompt = envStr("CLAUDE_APPEND_PROMPT");
|
|
48
|
+
if (sysPrompt) {
|
|
49
|
+
opts.systemPrompt = sysPrompt;
|
|
50
|
+
} else if (appendPrompt) {
|
|
51
|
+
opts.systemPrompt = {
|
|
52
|
+
type: "preset",
|
|
53
|
+
preset: "claude_code",
|
|
54
|
+
append: appendPrompt,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dirs = envList("CLAUDE_ADDITIONAL_DIRS");
|
|
59
|
+
if (dirs) opts.additionalDirectories = dirs;
|
|
60
|
+
|
|
61
|
+
const plugins = envList("CLAUDE_PLUGINS");
|
|
62
|
+
if (plugins) {
|
|
63
|
+
opts.plugins = plugins.map((p) => ({ type: "local" as const, path: p }));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const mcpServers = envJson<Record<string, unknown>>("CLAUDE_MCP_SERVERS");
|
|
67
|
+
if (mcpServers) {
|
|
68
|
+
opts.mcpServers = mcpServers as Options["mcpServers"];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const effort = envStr("CLAUDE_EFFORT");
|
|
72
|
+
if (effort) opts.effort = effort as Options["effort"];
|
|
73
|
+
|
|
74
|
+
const sources = envList("CLAUDE_SETTING_SOURCES");
|
|
75
|
+
if (sources) {
|
|
76
|
+
opts.settingSources = sources as Options["settingSources"];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const settings = envStr("CLAUDE_SETTINGS");
|
|
80
|
+
if (settings) {
|
|
81
|
+
if (settings.startsWith("{")) {
|
|
82
|
+
try {
|
|
83
|
+
opts.settings = JSON.parse(settings);
|
|
84
|
+
} catch {
|
|
85
|
+
opts.settings = settings;
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
opts.settings = settings;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const betas = envList("CLAUDE_BETAS");
|
|
93
|
+
if (betas) opts.betas = betas as Options["betas"];
|
|
94
|
+
|
|
95
|
+
return opts;
|
|
96
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { OptionCatalogEntry } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const OPTION_CATALOG: OptionCatalogEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
key: "model",
|
|
6
|
+
envVar: "CLAUDE_MODEL",
|
|
7
|
+
label: "Model",
|
|
8
|
+
hint: "Which Claude model to use",
|
|
9
|
+
example: '"sonnet", "opus", "haiku", or full ID',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
key: "appendPrompt",
|
|
13
|
+
envVar: "CLAUDE_APPEND_PROMPT",
|
|
14
|
+
label: "Behavior prompt",
|
|
15
|
+
hint: "Instructions appended to the default Claude Code system prompt",
|
|
16
|
+
example: '"Always explain your reasoning step by step."',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: "systemPrompt",
|
|
20
|
+
envVar: "CLAUDE_SYSTEM_PROMPT",
|
|
21
|
+
label: "Full system prompt",
|
|
22
|
+
hint: "Completely replaces the default system prompt (use appendPrompt instead to keep defaults)",
|
|
23
|
+
example: '"You are a security auditor..."',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: "allowedTools",
|
|
27
|
+
envVar: "CLAUDE_ALLOWED_TOOLS",
|
|
28
|
+
label: "Allowed tools",
|
|
29
|
+
hint: "Only these tools are available (comma-separated)",
|
|
30
|
+
example: '"Read,Grep,Glob" for read-only; "Bash,Read,Write,Edit,Grep,Glob" for full access',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "disallowedTools",
|
|
34
|
+
envVar: "CLAUDE_DISALLOWED_TOOLS",
|
|
35
|
+
label: "Blocked tools",
|
|
36
|
+
hint: "These tools are removed (comma-separated)",
|
|
37
|
+
example: '"WebFetch,WebSearch" to block network access',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: "cwd",
|
|
41
|
+
envVar: "CLAUDE_CWD",
|
|
42
|
+
label: "Working directory",
|
|
43
|
+
hint: "Default directory the agent operates in",
|
|
44
|
+
example: '"/home/user/project"',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "additionalDirs",
|
|
48
|
+
envVar: "CLAUDE_ADDITIONAL_DIRS",
|
|
49
|
+
label: "Additional directories",
|
|
50
|
+
hint: "Extra directories the agent can access (comma-separated)",
|
|
51
|
+
example: '"/shared/libs,/data"',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "maxTurns",
|
|
55
|
+
envVar: "CLAUDE_MAX_TURNS",
|
|
56
|
+
label: "Max turns",
|
|
57
|
+
hint: "Maximum conversation turns per invocation",
|
|
58
|
+
example: '"10" for quick tasks, "50" for complex ones',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: "maxBudgetUsd",
|
|
62
|
+
envVar: "CLAUDE_MAX_BUDGET_USD",
|
|
63
|
+
label: "Budget cap (USD)",
|
|
64
|
+
hint: "Maximum spend per invocation",
|
|
65
|
+
example: '"0.05" for cheap, "2.0" for complex tasks',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: "effort",
|
|
69
|
+
envVar: "CLAUDE_EFFORT",
|
|
70
|
+
label: "Reasoning effort",
|
|
71
|
+
hint: "How hard the agent thinks",
|
|
72
|
+
example: '"low" (fast), "medium", "high" (thorough), "max" (deepest)',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "plugins",
|
|
76
|
+
envVar: "CLAUDE_PLUGINS",
|
|
77
|
+
label: "Plugins",
|
|
78
|
+
hint: "Local plugin paths (comma-separated)",
|
|
79
|
+
example: '"/path/to/my-plugin"',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: "permissionMode",
|
|
83
|
+
envVar: "CLAUDE_PERMISSION_MODE",
|
|
84
|
+
label: "Permission mode",
|
|
85
|
+
hint: "How tool executions are authorized",
|
|
86
|
+
example: '"bypassPermissions" (auto-approve), "acceptEdits" (auto-approve edits only), "plan" (no execution)',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "persistSession",
|
|
90
|
+
envVar: "CLAUDE_PERSIST_SESSION",
|
|
91
|
+
label: "Persist sessions",
|
|
92
|
+
hint: "Save sessions for later resume via _reply tool",
|
|
93
|
+
example: '"true" (default) or "false"',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: "settingSources",
|
|
97
|
+
envVar: "CLAUDE_SETTING_SOURCES",
|
|
98
|
+
label: "Setting sources",
|
|
99
|
+
hint: "Which settings files to load (comma-separated)",
|
|
100
|
+
example: '"user,project" to load CLAUDE.md and user settings',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: "mcpServers",
|
|
104
|
+
envVar: "CLAUDE_MCP_SERVERS",
|
|
105
|
+
label: "Inner MCP servers",
|
|
106
|
+
hint: "MCP servers available to the inner agent (JSON)",
|
|
107
|
+
example: '\'{"db":{"command":"node","args":["db-server.js"]}}\'',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
key: "betas",
|
|
111
|
+
envVar: "CLAUDE_BETAS",
|
|
112
|
+
label: "Beta features",
|
|
113
|
+
hint: "Enable beta capabilities (comma-separated)",
|
|
114
|
+
example: '"context-1m-2025-08-07" for 1M context window',
|
|
115
|
+
},
|
|
116
|
+
];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Octopus — one brain, many arms.
|
|
5
|
+
*
|
|
6
|
+
* Wraps the Claude Agent SDK as MCP servers, letting you spawn multiple
|
|
7
|
+
* specialized Claude Code agents — each with its own model, tools, prompt,
|
|
8
|
+
* and personality.
|
|
9
|
+
*
|
|
10
|
+
* See README or env var docs in config.ts for configuration.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { dirname, resolve } from "node:path";
|
|
17
|
+
import { envStr, envBool, sanitizeToolName } from "./lib.js";
|
|
18
|
+
import { buildBaseOptions } from "./config.js";
|
|
19
|
+
import { registerQueryTools } from "./tools/query.js";
|
|
20
|
+
import { registerFactoryTool } from "./tools/factory.js";
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
const SERVER_ENTRY = resolve(__dirname, "index.js");
|
|
25
|
+
|
|
26
|
+
// ── Configuration ──────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const BASE_OPTIONS = buildBaseOptions();
|
|
29
|
+
|
|
30
|
+
const TOOL_NAME = sanitizeToolName(envStr("CLAUDE_TOOL_NAME") || "claude_code");
|
|
31
|
+
const REPLY_TOOL_NAME = `${TOOL_NAME}_reply`;
|
|
32
|
+
const SERVER_NAME = envStr("CLAUDE_SERVER_NAME") || "claude-octopus";
|
|
33
|
+
const FACTORY_ONLY = envBool("CLAUDE_FACTORY_ONLY", false);
|
|
34
|
+
|
|
35
|
+
const DEFAULT_DESCRIPTION = [
|
|
36
|
+
"Send a task to an autonomous Claude Code agent.",
|
|
37
|
+
"It reads/writes files, runs shell commands, searches codebases,",
|
|
38
|
+
"and handles complex software engineering tasks end-to-end.",
|
|
39
|
+
`Returns the result text plus a session_id for follow-ups via ${REPLY_TOOL_NAME}.`,
|
|
40
|
+
].join(" ");
|
|
41
|
+
|
|
42
|
+
const TOOL_DESCRIPTION = envStr("CLAUDE_DESCRIPTION") || DEFAULT_DESCRIPTION;
|
|
43
|
+
|
|
44
|
+
// ── Server ─────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
const server = new McpServer({ name: SERVER_NAME, version: "1.0.0" });
|
|
47
|
+
|
|
48
|
+
if (!FACTORY_ONLY) {
|
|
49
|
+
registerQueryTools(server, BASE_OPTIONS, TOOL_NAME, TOOL_DESCRIPTION);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (FACTORY_ONLY) {
|
|
53
|
+
registerFactoryTool(server, SERVER_ENTRY);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Start ──────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
async function main() {
|
|
59
|
+
const transport = new StdioServerTransport();
|
|
60
|
+
await server.connect(transport);
|
|
61
|
+
const toolList = FACTORY_ONLY
|
|
62
|
+
? ["create_claude_code_mcp"]
|
|
63
|
+
: BASE_OPTIONS.persistSession !== false
|
|
64
|
+
? [TOOL_NAME, REPLY_TOOL_NAME]
|
|
65
|
+
: [TOOL_NAME];
|
|
66
|
+
console.error(`${SERVER_NAME}: running on stdio (tools: ${toolList.join(", ")})`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch((error) => {
|
|
70
|
+
console.error(`${SERVER_NAME}: fatal:`, error);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|