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 ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Xiaolai Li
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,2 @@
1
+ import type { Options } from "./types.js";
2
+ export declare function buildBaseOptions(): Options;
package/dist/config.js ADDED
@@ -0,0 +1,81 @@
1
+ import { envStr, envList, envNum, envBool, envJson, validatePermissionMode, } from "./lib.js";
2
+ export function buildBaseOptions() {
3
+ const opts = {
4
+ cwd: envStr("CLAUDE_CWD") || process.cwd(),
5
+ persistSession: envBool("CLAUDE_PERSIST_SESSION", true),
6
+ };
7
+ const rawPerm = envStr("CLAUDE_PERMISSION_MODE") || "default";
8
+ const permMode = validatePermissionMode(rawPerm);
9
+ if (rawPerm !== permMode) {
10
+ console.error(`claude-octopus: invalid CLAUDE_PERMISSION_MODE "${rawPerm}", using "default"`);
11
+ }
12
+ opts.permissionMode = permMode;
13
+ if (permMode === "bypassPermissions") {
14
+ opts.allowDangerouslySkipPermissions = true;
15
+ }
16
+ const model = envStr("CLAUDE_MODEL");
17
+ if (model)
18
+ opts.model = model;
19
+ const allowed = envList("CLAUDE_ALLOWED_TOOLS");
20
+ if (allowed)
21
+ opts.allowedTools = allowed;
22
+ const disallowed = envList("CLAUDE_DISALLOWED_TOOLS");
23
+ if (disallowed)
24
+ opts.disallowedTools = disallowed;
25
+ const maxTurns = envNum("CLAUDE_MAX_TURNS");
26
+ if (maxTurns !== undefined && maxTurns > 0 && Number.isInteger(maxTurns)) {
27
+ opts.maxTurns = maxTurns;
28
+ }
29
+ const maxBudget = envNum("CLAUDE_MAX_BUDGET_USD");
30
+ if (maxBudget !== undefined && maxBudget > 0) {
31
+ opts.maxBudgetUsd = maxBudget;
32
+ }
33
+ const sysPrompt = envStr("CLAUDE_SYSTEM_PROMPT");
34
+ const appendPrompt = envStr("CLAUDE_APPEND_PROMPT");
35
+ if (sysPrompt) {
36
+ opts.systemPrompt = sysPrompt;
37
+ }
38
+ else if (appendPrompt) {
39
+ opts.systemPrompt = {
40
+ type: "preset",
41
+ preset: "claude_code",
42
+ append: appendPrompt,
43
+ };
44
+ }
45
+ const dirs = envList("CLAUDE_ADDITIONAL_DIRS");
46
+ if (dirs)
47
+ opts.additionalDirectories = dirs;
48
+ const plugins = envList("CLAUDE_PLUGINS");
49
+ if (plugins) {
50
+ opts.plugins = plugins.map((p) => ({ type: "local", path: p }));
51
+ }
52
+ const mcpServers = envJson("CLAUDE_MCP_SERVERS");
53
+ if (mcpServers) {
54
+ opts.mcpServers = mcpServers;
55
+ }
56
+ const effort = envStr("CLAUDE_EFFORT");
57
+ if (effort)
58
+ opts.effort = effort;
59
+ const sources = envList("CLAUDE_SETTING_SOURCES");
60
+ if (sources) {
61
+ opts.settingSources = sources;
62
+ }
63
+ const settings = envStr("CLAUDE_SETTINGS");
64
+ if (settings) {
65
+ if (settings.startsWith("{")) {
66
+ try {
67
+ opts.settings = JSON.parse(settings);
68
+ }
69
+ catch {
70
+ opts.settings = settings;
71
+ }
72
+ }
73
+ else {
74
+ opts.settings = settings;
75
+ }
76
+ }
77
+ const betas = envList("CLAUDE_BETAS");
78
+ if (betas)
79
+ opts.betas = betas;
80
+ return opts;
81
+ }
@@ -0,0 +1,2 @@
1
+ import type { OptionCatalogEntry } from "./types.js";
2
+ export declare const OPTION_CATALOG: OptionCatalogEntry[];
@@ -0,0 +1,114 @@
1
+ export const OPTION_CATALOG = [
2
+ {
3
+ key: "model",
4
+ envVar: "CLAUDE_MODEL",
5
+ label: "Model",
6
+ hint: "Which Claude model to use",
7
+ example: '"sonnet", "opus", "haiku", or full ID',
8
+ },
9
+ {
10
+ key: "appendPrompt",
11
+ envVar: "CLAUDE_APPEND_PROMPT",
12
+ label: "Behavior prompt",
13
+ hint: "Instructions appended to the default Claude Code system prompt",
14
+ example: '"Always explain your reasoning step by step."',
15
+ },
16
+ {
17
+ key: "systemPrompt",
18
+ envVar: "CLAUDE_SYSTEM_PROMPT",
19
+ label: "Full system prompt",
20
+ hint: "Completely replaces the default system prompt (use appendPrompt instead to keep defaults)",
21
+ example: '"You are a security auditor..."',
22
+ },
23
+ {
24
+ key: "allowedTools",
25
+ envVar: "CLAUDE_ALLOWED_TOOLS",
26
+ label: "Allowed tools",
27
+ hint: "Only these tools are available (comma-separated)",
28
+ example: '"Read,Grep,Glob" for read-only; "Bash,Read,Write,Edit,Grep,Glob" for full access',
29
+ },
30
+ {
31
+ key: "disallowedTools",
32
+ envVar: "CLAUDE_DISALLOWED_TOOLS",
33
+ label: "Blocked tools",
34
+ hint: "These tools are removed (comma-separated)",
35
+ example: '"WebFetch,WebSearch" to block network access',
36
+ },
37
+ {
38
+ key: "cwd",
39
+ envVar: "CLAUDE_CWD",
40
+ label: "Working directory",
41
+ hint: "Default directory the agent operates in",
42
+ example: '"/home/user/project"',
43
+ },
44
+ {
45
+ key: "additionalDirs",
46
+ envVar: "CLAUDE_ADDITIONAL_DIRS",
47
+ label: "Additional directories",
48
+ hint: "Extra directories the agent can access (comma-separated)",
49
+ example: '"/shared/libs,/data"',
50
+ },
51
+ {
52
+ key: "maxTurns",
53
+ envVar: "CLAUDE_MAX_TURNS",
54
+ label: "Max turns",
55
+ hint: "Maximum conversation turns per invocation",
56
+ example: '"10" for quick tasks, "50" for complex ones',
57
+ },
58
+ {
59
+ key: "maxBudgetUsd",
60
+ envVar: "CLAUDE_MAX_BUDGET_USD",
61
+ label: "Budget cap (USD)",
62
+ hint: "Maximum spend per invocation",
63
+ example: '"0.05" for cheap, "2.0" for complex tasks',
64
+ },
65
+ {
66
+ key: "effort",
67
+ envVar: "CLAUDE_EFFORT",
68
+ label: "Reasoning effort",
69
+ hint: "How hard the agent thinks",
70
+ example: '"low" (fast), "medium", "high" (thorough), "max" (deepest)',
71
+ },
72
+ {
73
+ key: "plugins",
74
+ envVar: "CLAUDE_PLUGINS",
75
+ label: "Plugins",
76
+ hint: "Local plugin paths (comma-separated)",
77
+ example: '"/path/to/my-plugin"',
78
+ },
79
+ {
80
+ key: "permissionMode",
81
+ envVar: "CLAUDE_PERMISSION_MODE",
82
+ label: "Permission mode",
83
+ hint: "How tool executions are authorized",
84
+ example: '"bypassPermissions" (auto-approve), "acceptEdits" (auto-approve edits only), "plan" (no execution)',
85
+ },
86
+ {
87
+ key: "persistSession",
88
+ envVar: "CLAUDE_PERSIST_SESSION",
89
+ label: "Persist sessions",
90
+ hint: "Save sessions for later resume via _reply tool",
91
+ example: '"true" (default) or "false"',
92
+ },
93
+ {
94
+ key: "settingSources",
95
+ envVar: "CLAUDE_SETTING_SOURCES",
96
+ label: "Setting sources",
97
+ hint: "Which settings files to load (comma-separated)",
98
+ example: '"user,project" to load CLAUDE.md and user settings',
99
+ },
100
+ {
101
+ key: "mcpServers",
102
+ envVar: "CLAUDE_MCP_SERVERS",
103
+ label: "Inner MCP servers",
104
+ hint: "MCP servers available to the inner agent (JSON)",
105
+ example: '\'{"db":{"command":"node","args":["db-server.js"]}}\'',
106
+ },
107
+ {
108
+ key: "betas",
109
+ envVar: "CLAUDE_BETAS",
110
+ label: "Beta features",
111
+ hint: "Enable beta capabilities (comma-separated)",
112
+ example: '"context-1m-2025-08-07" for 1M context window',
113
+ },
114
+ ];
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Octopus — one brain, many arms.
4
+ *
5
+ * Wraps the Claude Agent SDK as MCP servers, letting you spawn multiple
6
+ * specialized Claude Code agents — each with its own model, tools, prompt,
7
+ * and personality.
8
+ *
9
+ * See README or env var docs in config.ts for configuration.
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Octopus — one brain, many arms.
4
+ *
5
+ * Wraps the Claude Agent SDK as MCP servers, letting you spawn multiple
6
+ * specialized Claude Code agents — each with its own model, tools, prompt,
7
+ * and personality.
8
+ *
9
+ * See README or env var docs in config.ts for configuration.
10
+ */
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { fileURLToPath } from "node:url";
14
+ import { dirname, resolve } from "node:path";
15
+ import { envStr, envBool, sanitizeToolName } from "./lib.js";
16
+ import { buildBaseOptions } from "./config.js";
17
+ import { registerQueryTools } from "./tools/query.js";
18
+ import { registerFactoryTool } from "./tools/factory.js";
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
21
+ const SERVER_ENTRY = resolve(__dirname, "index.js");
22
+ // ── Configuration ──────────────────────────────────────────────────
23
+ const BASE_OPTIONS = buildBaseOptions();
24
+ const TOOL_NAME = sanitizeToolName(envStr("CLAUDE_TOOL_NAME") || "claude_code");
25
+ const REPLY_TOOL_NAME = `${TOOL_NAME}_reply`;
26
+ const SERVER_NAME = envStr("CLAUDE_SERVER_NAME") || "claude-octopus";
27
+ const FACTORY_ONLY = envBool("CLAUDE_FACTORY_ONLY", false);
28
+ const DEFAULT_DESCRIPTION = [
29
+ "Send a task to an autonomous Claude Code agent.",
30
+ "It reads/writes files, runs shell commands, searches codebases,",
31
+ "and handles complex software engineering tasks end-to-end.",
32
+ `Returns the result text plus a session_id for follow-ups via ${REPLY_TOOL_NAME}.`,
33
+ ].join(" ");
34
+ const TOOL_DESCRIPTION = envStr("CLAUDE_DESCRIPTION") || DEFAULT_DESCRIPTION;
35
+ // ── Server ─────────────────────────────────────────────────────────
36
+ const server = new McpServer({ name: SERVER_NAME, version: "1.0.0" });
37
+ if (!FACTORY_ONLY) {
38
+ registerQueryTools(server, BASE_OPTIONS, TOOL_NAME, TOOL_DESCRIPTION);
39
+ }
40
+ if (FACTORY_ONLY) {
41
+ registerFactoryTool(server, SERVER_ENTRY);
42
+ }
43
+ // ── Start ──────────────────────────────────────────────────────────
44
+ async function main() {
45
+ const transport = new StdioServerTransport();
46
+ await server.connect(transport);
47
+ const toolList = FACTORY_ONLY
48
+ ? ["create_claude_code_mcp"]
49
+ : BASE_OPTIONS.persistSession !== false
50
+ ? [TOOL_NAME, REPLY_TOOL_NAME]
51
+ : [TOOL_NAME];
52
+ console.error(`${SERVER_NAME}: running on stdio (tools: ${toolList.join(", ")})`);
53
+ }
54
+ main().catch((error) => {
55
+ console.error(`${SERVER_NAME}: fatal:`, error);
56
+ process.exit(1);
57
+ });
package/dist/lib.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure, testable logic extracted from index.ts.
3
+ */
4
+ export declare function envStr(key: string, env?: Record<string, string | undefined>): string | undefined;
5
+ export declare function envList(key: string, env?: Record<string, string | undefined>): string[] | undefined;
6
+ export declare function envNum(key: string, env?: Record<string, string | undefined>): number | undefined;
7
+ export declare function envBool(key: string, fallback: boolean, env?: Record<string, string | undefined>): boolean;
8
+ export declare function envJson<T>(key: string, env?: Record<string, string | undefined>): T | undefined;
9
+ export declare const MAX_TOOL_NAME_LEN: number;
10
+ export declare function sanitizeToolName(raw: string): string;
11
+ export declare function isDescendantPath(requested: string, baseCwd: string): boolean;
12
+ export declare function mergeAllowedTools(serverList: string[] | undefined, callList: string[]): string[];
13
+ export declare function mergeDisallowedTools(serverList: string[] | undefined, callList: string[]): string[];
14
+ export declare const VALID_PERM_MODES: Set<string>;
15
+ export declare function validatePermissionMode(mode: string): string;
16
+ export declare function deriveServerName(description: string): string;
17
+ export declare function deriveToolName(name: string): string;
18
+ export declare function serializeArrayEnv(val: unknown[]): string;
19
+ export interface ResultPayload {
20
+ session_id: string;
21
+ cost_usd: number;
22
+ duration_ms: number;
23
+ num_turns: number;
24
+ is_error: boolean;
25
+ result?: string;
26
+ errors?: string[];
27
+ }
28
+ export declare function buildResultPayload(result: {
29
+ session_id: string;
30
+ total_cost_usd: number;
31
+ duration_ms: number;
32
+ num_turns: number;
33
+ is_error: boolean;
34
+ subtype: string;
35
+ result?: string;
36
+ errors?: string[];
37
+ }): ResultPayload;
38
+ export declare function formatErrorMessage(error: unknown): string;
package/dist/lib.js ADDED
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Pure, testable logic extracted from index.ts.
3
+ */
4
+ import { normalize, resolve, sep } from "node:path";
5
+ // ── Env helpers ────────────────────────────────────────────────────
6
+ export function envStr(key, env = process.env) {
7
+ return env[key] || undefined;
8
+ }
9
+ export function envList(key, env = process.env) {
10
+ const val = env[key];
11
+ if (!val)
12
+ return undefined;
13
+ if (val.startsWith("[")) {
14
+ try {
15
+ const parsed = JSON.parse(val);
16
+ if (Array.isArray(parsed))
17
+ return parsed.map(String).filter(Boolean);
18
+ }
19
+ catch {
20
+ // fall through to comma-split
21
+ }
22
+ }
23
+ return val
24
+ .split(",")
25
+ .map((s) => s.trim())
26
+ .filter(Boolean);
27
+ }
28
+ export function envNum(key, env = process.env) {
29
+ const val = env[key];
30
+ if (!val)
31
+ return undefined;
32
+ const n = Number(val);
33
+ return Number.isNaN(n) ? undefined : n;
34
+ }
35
+ export function envBool(key, fallback, env = process.env) {
36
+ const val = env[key];
37
+ if (val === undefined)
38
+ return fallback;
39
+ return val === "true" || val === "1";
40
+ }
41
+ export function envJson(key, env = process.env) {
42
+ const val = env[key];
43
+ if (!val)
44
+ return undefined;
45
+ try {
46
+ return JSON.parse(val);
47
+ }
48
+ catch {
49
+ return undefined;
50
+ }
51
+ }
52
+ // ── Tool name sanitization ─────────────────────────────────────────
53
+ export const MAX_TOOL_NAME_LEN = 64 - "_reply".length;
54
+ export function sanitizeToolName(raw) {
55
+ const sanitized = raw
56
+ .replace(/[^a-zA-Z0-9_]/g, "_")
57
+ .slice(0, MAX_TOOL_NAME_LEN);
58
+ return sanitized || "claude_code";
59
+ }
60
+ // ── cwd security check ────────────────────────────────────────────
61
+ export function isDescendantPath(requested, baseCwd) {
62
+ const normalBase = normalize(baseCwd);
63
+ const normalReq = normalize(resolve(normalBase, requested));
64
+ if (normalReq === normalBase)
65
+ return true;
66
+ const baseWithSep = normalBase.endsWith(sep)
67
+ ? normalBase
68
+ : normalBase + sep;
69
+ return normalReq.startsWith(baseWithSep);
70
+ }
71
+ // ── Tool restriction merging ───────────────────────────────────────
72
+ export function mergeAllowedTools(serverList, callList) {
73
+ if (serverList?.length) {
74
+ const serverSet = new Set(serverList);
75
+ return callList.filter((t) => serverSet.has(t));
76
+ }
77
+ return callList;
78
+ }
79
+ export function mergeDisallowedTools(serverList, callList) {
80
+ const merged = new Set([...(serverList || []), ...callList]);
81
+ return [...merged];
82
+ }
83
+ // ── Permission mode validation ─────────────────────────────────────
84
+ export const VALID_PERM_MODES = new Set([
85
+ "default",
86
+ "acceptEdits",
87
+ "bypassPermissions",
88
+ "plan",
89
+ "dontAsk",
90
+ "auto",
91
+ ]);
92
+ export function validatePermissionMode(mode) {
93
+ return VALID_PERM_MODES.has(mode) ? mode : "default";
94
+ }
95
+ // ── Factory name derivation ────────────────────────────────────────
96
+ export function deriveServerName(description) {
97
+ const slug = description
98
+ .toLowerCase()
99
+ .replace(/[^a-z0-9]+/g, "-")
100
+ .replace(/^-|-$/g, "")
101
+ .slice(0, 30);
102
+ return slug || `agent-${Date.now()}`;
103
+ }
104
+ export function deriveToolName(name) {
105
+ const slug = name
106
+ .replace(/[^a-zA-Z0-9]+/g, "_")
107
+ .replace(/^_|_$/g, "")
108
+ .slice(0, MAX_TOOL_NAME_LEN);
109
+ return slug || "agent";
110
+ }
111
+ // ── Factory env serialization ──────────────────────────────────────
112
+ export function serializeArrayEnv(val) {
113
+ const hasComma = val.some((v) => String(v).includes(","));
114
+ return hasComma ? JSON.stringify(val) : val.join(",");
115
+ }
116
+ export function buildResultPayload(result) {
117
+ const payload = {
118
+ session_id: result.session_id,
119
+ cost_usd: result.total_cost_usd,
120
+ duration_ms: result.duration_ms,
121
+ num_turns: result.num_turns,
122
+ is_error: result.is_error,
123
+ };
124
+ if (result.subtype === "success") {
125
+ payload.result = result.result;
126
+ }
127
+ else {
128
+ payload.errors = result.errors;
129
+ }
130
+ return payload;
131
+ }
132
+ export function formatErrorMessage(error) {
133
+ return error instanceof Error ? error.message : String(error);
134
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerFactoryTool(server: McpServer, serverEntry: string): void;
@@ -0,0 +1,108 @@
1
+ import { z } from "zod/v4";
2
+ import { OPTION_CATALOG } from "../constants.js";
3
+ import { sanitizeToolName, deriveServerName, deriveToolName, serializeArrayEnv, } from "../lib.js";
4
+ function buildEnvFromParams(params) {
5
+ const env = {};
6
+ for (const opt of OPTION_CATALOG) {
7
+ const val = params[opt.key];
8
+ if (val === undefined || val === null)
9
+ continue;
10
+ if (Array.isArray(val)) {
11
+ if (val.length > 0)
12
+ env[opt.envVar] = serializeArrayEnv(val);
13
+ }
14
+ else if (typeof val === "object") {
15
+ env[opt.envVar] = JSON.stringify(val);
16
+ }
17
+ else if (typeof val === "boolean") {
18
+ env[opt.envVar] = String(val);
19
+ }
20
+ else if (val !== "") {
21
+ env[opt.envVar] = String(val);
22
+ }
23
+ }
24
+ return env;
25
+ }
26
+ export function registerFactoryTool(server, serverEntry) {
27
+ server.registerTool("create_claude_code_mcp", {
28
+ description: [
29
+ "Create a new specialized Claude Code agent as an MCP server.",
30
+ "WHEN TO USE: user says 'create agent', 'new agent', 'set up agent',",
31
+ "'configure agent', 'add a reviewer', 'add a test writer',",
32
+ "'make me a code reviewer', 'I need a specialized agent', etc.",
33
+ "DO NOT USE when user just wants to run a task — use the main tool for that.",
34
+ "This is a wizard: only a description is required.",
35
+ "Returns a ready-to-use .mcp.json config and lists all customization options.",
36
+ "Call again with more parameters to refine.",
37
+ ].join(" "),
38
+ inputSchema: z.object({
39
+ description: z.string().describe("What this agent should do, in plain language. Example: 'a strict code reviewer that only reads files'"),
40
+ name: z.string().optional().describe("Server name / alias (derived from description if omitted)"),
41
+ toolName: z.string().optional().describe("Custom tool name prefix"),
42
+ model: z.string().optional(),
43
+ systemPrompt: z.string().optional(),
44
+ appendPrompt: z.string().optional(),
45
+ allowedTools: z.array(z.string()).optional(),
46
+ disallowedTools: z.array(z.string()).optional(),
47
+ cwd: z.string().optional(),
48
+ additionalDirs: z.array(z.string()).optional(),
49
+ plugins: z.array(z.string()).optional(),
50
+ maxTurns: z.number().optional(),
51
+ maxBudgetUsd: z.number().optional(),
52
+ effort: z.enum(["low", "medium", "high", "max"]).optional(),
53
+ persistSession: z.boolean().optional(),
54
+ permissionMode: z.enum(["bypassPermissions", "acceptEdits", "default", "plan"]).optional(),
55
+ settingSources: z.array(z.string()).optional(),
56
+ mcpServers: z.record(z.string(), z.unknown()).optional(),
57
+ betas: z.array(z.string()).optional(),
58
+ }),
59
+ }, async (params) => {
60
+ const { description, name: nameParam, toolName: toolNameParam } = params;
61
+ const name = nameParam || deriveServerName(description);
62
+ const derivedToolName = toolNameParam
63
+ ? sanitizeToolName(toolNameParam)
64
+ : deriveToolName(name);
65
+ const env = {
66
+ CLAUDE_TOOL_NAME: derivedToolName,
67
+ CLAUDE_SERVER_NAME: name,
68
+ CLAUDE_DESCRIPTION: description,
69
+ };
70
+ const optionEnv = buildEnvFromParams(params);
71
+ Object.assign(env, optionEnv);
72
+ const configured = Object.keys(optionEnv);
73
+ const notConfigured = OPTION_CATALOG.filter((o) => !configured.includes(o.envVar));
74
+ const mcpEntry = {
75
+ [name]: {
76
+ command: "node",
77
+ args: [serverEntry],
78
+ env,
79
+ },
80
+ };
81
+ const sections = [];
82
+ sections.push("## Generated config", "", "Add to `mcpServers` in your `.mcp.json`:", "", "```json", JSON.stringify(mcpEntry, null, 2), "```");
83
+ sections.push("", "## What's configured");
84
+ sections.push("", `| Setting | Value |`, `|---|---|`);
85
+ sections.push(`| Name | \`${name}\` |`);
86
+ sections.push(`| Tool names | \`${derivedToolName}\`, \`${derivedToolName}_reply\` |`);
87
+ for (const key of configured) {
88
+ const opt = OPTION_CATALOG.find((o) => o.envVar === key);
89
+ if (opt) {
90
+ sections.push(`| ${opt.label} | \`${env[key]}\` |`);
91
+ }
92
+ }
93
+ if (configured.length === 0) {
94
+ sections.push(`| _(all defaults)_ | — |`);
95
+ }
96
+ if (notConfigured.length > 0) {
97
+ sections.push("", "## Available customizations", "", "These options are not yet set. Ask the user if they'd like to configure any:", "");
98
+ for (const opt of notConfigured) {
99
+ sections.push(`- **${opt.label}** — ${opt.hint}`);
100
+ sections.push(` Example: ${opt.example}`);
101
+ }
102
+ sections.push("", "_Call this tool again with additional parameters to refine the config._");
103
+ }
104
+ return {
105
+ content: [{ type: "text", text: sections.join("\n") }],
106
+ };
107
+ });
108
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Options } from "../types.js";
3
+ export declare function registerQueryTools(server: McpServer, baseOptions: Options, toolName: string, toolDescription: string): void;