@thantos66/claude-context-mcp 0.1.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 +21 -0
- package/README.md +171 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.js +59 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/agent-frontmatter.d.ts +55 -0
- package/dist/schemas/agent-frontmatter.js +43 -0
- package/dist/schemas/agent-frontmatter.js.map +1 -0
- package/dist/schemas/mcp-config.d.ts +177 -0
- package/dist/schemas/mcp-config.js +38 -0
- package/dist/schemas/mcp-config.js.map +1 -0
- package/dist/schemas/settings.d.ts +70 -0
- package/dist/schemas/settings.js +28 -0
- package/dist/schemas/settings.js.map +1 -0
- package/dist/schemas/skill-frontmatter.d.ts +42 -0
- package/dist/schemas/skill-frontmatter.js +27 -0
- package/dist/schemas/skill-frontmatter.js.map +1 -0
- package/dist/services/agents.d.ts +56 -0
- package/dist/services/agents.js +155 -0
- package/dist/services/agents.js.map +1 -0
- package/dist/services/atomic-write.d.ts +10 -0
- package/dist/services/atomic-write.js +46 -0
- package/dist/services/atomic-write.js.map +1 -0
- package/dist/services/backup.d.ts +13 -0
- package/dist/services/backup.js +52 -0
- package/dist/services/backup.js.map +1 -0
- package/dist/services/mcp-config.d.ts +72 -0
- package/dist/services/mcp-config.js +163 -0
- package/dist/services/mcp-config.js.map +1 -0
- package/dist/services/paths.d.ts +42 -0
- package/dist/services/paths.js +91 -0
- package/dist/services/paths.js.map +1 -0
- package/dist/services/permissions.d.ts +38 -0
- package/dist/services/permissions.js +104 -0
- package/dist/services/permissions.js.map +1 -0
- package/dist/services/skills.d.ts +62 -0
- package/dist/services/skills.js +181 -0
- package/dist/services/skills.js.map +1 -0
- package/dist/tools/agents.d.ts +2 -0
- package/dist/tools/agents.js +134 -0
- package/dist/tools/agents.js.map +1 -0
- package/dist/tools/helpers.d.ts +25 -0
- package/dist/tools/helpers.js +36 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/mcp.d.ts +2 -0
- package/dist/tools/mcp.js +257 -0
- package/dist/tools/mcp.js.map +1 -0
- package/dist/tools/meta.d.ts +2 -0
- package/dist/tools/meta.js +79 -0
- package/dist/tools/meta.js.map +1 -0
- package/dist/tools/permissions.d.ts +2 -0
- package/dist/tools/permissions.js +144 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/skills.d.ts +2 -0
- package/dist/tools/skills.js +160 -0
- package/dist/tools/skills.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import { SkillFrontmatterSchema, } from "../schemas/skill-frontmatter.js";
|
|
5
|
+
import { atomicWriteFile } from "./atomic-write.js";
|
|
6
|
+
import { backupFile, backupDir } from "./backup.js";
|
|
7
|
+
import { skillsDirFor } from "./paths.js";
|
|
8
|
+
/** List all skill folders under the scope's skills dir. */
|
|
9
|
+
export async function listSkills(scope, projectPath) {
|
|
10
|
+
const dir = skillsDirFor(scope, projectPath);
|
|
11
|
+
let names;
|
|
12
|
+
try {
|
|
13
|
+
names = await fs.readdir(dir);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err.code === "ENOENT")
|
|
17
|
+
return [];
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const name of names) {
|
|
22
|
+
const skillDir = path.join(dir, name);
|
|
23
|
+
const stat = await fs.stat(skillDir).catch(() => null);
|
|
24
|
+
if (!stat?.isDirectory())
|
|
25
|
+
continue;
|
|
26
|
+
try {
|
|
27
|
+
const rec = await readSkillFolder(scope, skillDir);
|
|
28
|
+
results.push(rec);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
continue; // skip malformed skills
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
async function readSkillFolder(scope, skillDir) {
|
|
37
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
38
|
+
const raw = await fs.readFile(skillMdPath, "utf8");
|
|
39
|
+
const parsed = matter(raw);
|
|
40
|
+
const frontmatter = SkillFrontmatterSchema.parse(parsed.data);
|
|
41
|
+
return {
|
|
42
|
+
scope,
|
|
43
|
+
name: frontmatter.name,
|
|
44
|
+
dir: skillDir,
|
|
45
|
+
skillMdPath,
|
|
46
|
+
frontmatter,
|
|
47
|
+
body: parsed.content.trimStart(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function getSkill(params) {
|
|
51
|
+
const skillDir = path.join(skillsDirFor(params.scope, params.projectPath), params.name);
|
|
52
|
+
try {
|
|
53
|
+
return await readSkillFolder(params.scope, skillDir);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err.code === "ENOENT")
|
|
57
|
+
return null;
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Serialize a skill record to SKILL.md contents. */
|
|
62
|
+
export function serializeSkill(frontmatter, body) {
|
|
63
|
+
const ordered = {
|
|
64
|
+
name: frontmatter.name,
|
|
65
|
+
description: frontmatter.description,
|
|
66
|
+
};
|
|
67
|
+
const knownOrder = [
|
|
68
|
+
"allowed-tools",
|
|
69
|
+
"disable-model-invocation",
|
|
70
|
+
"user-invocable",
|
|
71
|
+
"model",
|
|
72
|
+
"effort",
|
|
73
|
+
"context",
|
|
74
|
+
"paths",
|
|
75
|
+
];
|
|
76
|
+
for (const k of knownOrder) {
|
|
77
|
+
const v = frontmatter[k];
|
|
78
|
+
if (v !== undefined)
|
|
79
|
+
ordered[k] = v;
|
|
80
|
+
}
|
|
81
|
+
for (const [k, v] of Object.entries(frontmatter)) {
|
|
82
|
+
if (v === undefined)
|
|
83
|
+
continue;
|
|
84
|
+
if (!(k in ordered))
|
|
85
|
+
ordered[k] = v;
|
|
86
|
+
}
|
|
87
|
+
const bodyText = body.endsWith("\n") ? body : body + "\n";
|
|
88
|
+
return matter.stringify(bodyText, ordered);
|
|
89
|
+
}
|
|
90
|
+
/** Scaffold a new skill folder with a SKILL.md. */
|
|
91
|
+
export async function createSkill(params) {
|
|
92
|
+
const frontmatter = SkillFrontmatterSchema.parse({
|
|
93
|
+
name: params.name,
|
|
94
|
+
description: params.description,
|
|
95
|
+
...(params.allowedTools !== undefined && { "allowed-tools": params.allowedTools }),
|
|
96
|
+
...(params.model !== undefined && { model: params.model }),
|
|
97
|
+
});
|
|
98
|
+
const body = params.body ?? `# ${frontmatter.name}\n\n${frontmatter.description}\n`;
|
|
99
|
+
const skillDir = path.join(skillsDirFor(params.scope, params.projectPath), frontmatter.name);
|
|
100
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
101
|
+
if (!params.overwrite) {
|
|
102
|
+
try {
|
|
103
|
+
await fs.access(skillMdPath);
|
|
104
|
+
throw new Error(`Skill "${frontmatter.name}" already exists at ${skillDir}. Pass overwrite: true to replace.`);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (err.code !== "ENOENT")
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const contents = serializeSkill(frontmatter, body);
|
|
112
|
+
if (params.dryRun) {
|
|
113
|
+
return { path: skillMdPath, backup: null, dryRun: true, preview: contents };
|
|
114
|
+
}
|
|
115
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
116
|
+
const backup = await backupFile(skillMdPath);
|
|
117
|
+
await atomicWriteFile(skillMdPath, contents);
|
|
118
|
+
return { path: skillMdPath, backup, dryRun: false };
|
|
119
|
+
}
|
|
120
|
+
/** Patch frontmatter fields or replace the SKILL.md body. */
|
|
121
|
+
export async function updateSkill(params) {
|
|
122
|
+
const current = await getSkill({
|
|
123
|
+
scope: params.scope,
|
|
124
|
+
name: params.name,
|
|
125
|
+
projectPath: params.projectPath,
|
|
126
|
+
});
|
|
127
|
+
if (!current)
|
|
128
|
+
throw new Error(`Skill "${params.name}" not found in scope ${params.scope}.`);
|
|
129
|
+
const merged = SkillFrontmatterSchema.parse({
|
|
130
|
+
...current.frontmatter,
|
|
131
|
+
...(params.description !== undefined && { description: params.description }),
|
|
132
|
+
...(params.allowedTools !== undefined && { "allowed-tools": params.allowedTools }),
|
|
133
|
+
...(params.model !== undefined && { model: params.model }),
|
|
134
|
+
});
|
|
135
|
+
const body = params.body ?? current.body;
|
|
136
|
+
const contents = serializeSkill(merged, body);
|
|
137
|
+
if (params.dryRun) {
|
|
138
|
+
return { path: current.skillMdPath, backup: null, dryRun: true, preview: contents };
|
|
139
|
+
}
|
|
140
|
+
const backup = await backupFile(current.skillMdPath);
|
|
141
|
+
await atomicWriteFile(current.skillMdPath, contents);
|
|
142
|
+
return { path: current.skillMdPath, backup, dryRun: false };
|
|
143
|
+
}
|
|
144
|
+
/** Toggle `disable-model-invocation` flag while preserving files. */
|
|
145
|
+
export async function setSkillEnabled(params) {
|
|
146
|
+
const current = await getSkill({
|
|
147
|
+
scope: params.scope,
|
|
148
|
+
name: params.name,
|
|
149
|
+
projectPath: params.projectPath,
|
|
150
|
+
});
|
|
151
|
+
if (!current)
|
|
152
|
+
throw new Error(`Skill "${params.name}" not found in scope ${params.scope}.`);
|
|
153
|
+
const merged = SkillFrontmatterSchema.parse({
|
|
154
|
+
...current.frontmatter,
|
|
155
|
+
"disable-model-invocation": !params.enabled,
|
|
156
|
+
});
|
|
157
|
+
const contents = serializeSkill(merged, current.body);
|
|
158
|
+
if (params.dryRun) {
|
|
159
|
+
return { path: current.skillMdPath, backup: null, dryRun: true, preview: contents };
|
|
160
|
+
}
|
|
161
|
+
const backup = await backupFile(current.skillMdPath);
|
|
162
|
+
await atomicWriteFile(current.skillMdPath, contents);
|
|
163
|
+
return { path: current.skillMdPath, backup, dryRun: false };
|
|
164
|
+
}
|
|
165
|
+
/** Remove a skill folder entirely. Backs up the full directory first. */
|
|
166
|
+
export async function deleteSkill(params) {
|
|
167
|
+
const current = await getSkill({
|
|
168
|
+
scope: params.scope,
|
|
169
|
+
name: params.name,
|
|
170
|
+
projectPath: params.projectPath,
|
|
171
|
+
});
|
|
172
|
+
if (!current)
|
|
173
|
+
throw new Error(`Skill "${params.name}" not found in scope ${params.scope}.`);
|
|
174
|
+
if (params.dryRun) {
|
|
175
|
+
return { path: current.dir, backup: null, dryRun: true };
|
|
176
|
+
}
|
|
177
|
+
const backup = await backupDir(current.dir);
|
|
178
|
+
await fs.rm(current.dir, { recursive: true, force: true });
|
|
179
|
+
return { path: current.dir, backup, dryRun: false };
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/services/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listAgents, getAgent, createAgent, updateAgent, deleteAgent, } from "../services/agents.js";
|
|
3
|
+
import { handle, okResponse, formatWriteResult } from "./helpers.js";
|
|
4
|
+
const CodeScope = z.enum(["user", "project"]);
|
|
5
|
+
const ProjectPathField = {
|
|
6
|
+
projectPath: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Absolute path to the project. Required when scope=project."),
|
|
10
|
+
};
|
|
11
|
+
const DryRunField = {
|
|
12
|
+
dryRun: z.boolean().optional(),
|
|
13
|
+
};
|
|
14
|
+
export function registerAgentTools(server) {
|
|
15
|
+
// ------------------------------------------------------------------
|
|
16
|
+
// agent_list
|
|
17
|
+
// ------------------------------------------------------------------
|
|
18
|
+
server.registerTool("agent_list", {
|
|
19
|
+
title: "List subagents",
|
|
20
|
+
description: "List subagents in a scope with their name and description.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
scope: CodeScope,
|
|
23
|
+
...ProjectPathField,
|
|
24
|
+
},
|
|
25
|
+
annotations: {
|
|
26
|
+
readOnlyHint: true,
|
|
27
|
+
destructiveHint: false,
|
|
28
|
+
idempotentHint: true,
|
|
29
|
+
openWorldHint: false,
|
|
30
|
+
},
|
|
31
|
+
}, async ({ scope, projectPath }) => handle(() => listAgents(scope, projectPath), (agents) => okResponse(agents.length === 0
|
|
32
|
+
? `No agents found in ${scope} scope.`
|
|
33
|
+
: agents
|
|
34
|
+
.map((a) => `[${a.scope}] ${a.name}\n path: ${a.path}\n description: ${truncate(a.frontmatter.description, 120)}`)
|
|
35
|
+
.join("\n\n"), { agents: agents.map((a) => ({ ...a, body: truncate(a.body, 200) })) })));
|
|
36
|
+
// ------------------------------------------------------------------
|
|
37
|
+
// agent_get
|
|
38
|
+
// ------------------------------------------------------------------
|
|
39
|
+
server.registerTool("agent_get", {
|
|
40
|
+
title: "Get a subagent",
|
|
41
|
+
description: "Return a subagent's full frontmatter and body.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
scope: CodeScope,
|
|
44
|
+
name: z.string().min(1),
|
|
45
|
+
...ProjectPathField,
|
|
46
|
+
},
|
|
47
|
+
annotations: {
|
|
48
|
+
readOnlyHint: true,
|
|
49
|
+
destructiveHint: false,
|
|
50
|
+
idempotentHint: true,
|
|
51
|
+
openWorldHint: false,
|
|
52
|
+
},
|
|
53
|
+
}, async (args) => handle(() => getAgent(args), (rec) => rec
|
|
54
|
+
? okResponse(`[${rec.scope}] ${rec.name}\n path: ${rec.path}\n\n--- frontmatter ---\n${JSON.stringify(rec.frontmatter, null, 2)}\n\n--- body ---\n${rec.body}`, { agent: rec })
|
|
55
|
+
: okResponse(`Agent "${args.name}" not found.`, { found: false })));
|
|
56
|
+
// ------------------------------------------------------------------
|
|
57
|
+
// agent_create
|
|
58
|
+
// ------------------------------------------------------------------
|
|
59
|
+
server.registerTool("agent_create", {
|
|
60
|
+
title: "Create a subagent",
|
|
61
|
+
description: "Scaffold a new subagent markdown file. `tools` accepts either a " +
|
|
62
|
+
"comma-separated string or a string array; it will be written as " +
|
|
63
|
+
"comma-separated on disk.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
scope: CodeScope,
|
|
66
|
+
name: z
|
|
67
|
+
.string()
|
|
68
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/, "agent name must be lowercase alphanumeric + hyphens"),
|
|
69
|
+
description: z.string().min(1),
|
|
70
|
+
model: z.string().optional(),
|
|
71
|
+
tools: z.union([z.string(), z.array(z.string())]).optional(),
|
|
72
|
+
color: z.string().optional(),
|
|
73
|
+
body: z.string().optional(),
|
|
74
|
+
overwrite: z.boolean().optional(),
|
|
75
|
+
...ProjectPathField,
|
|
76
|
+
...DryRunField,
|
|
77
|
+
},
|
|
78
|
+
annotations: {
|
|
79
|
+
readOnlyHint: false,
|
|
80
|
+
destructiveHint: false,
|
|
81
|
+
idempotentHint: false,
|
|
82
|
+
openWorldHint: false,
|
|
83
|
+
},
|
|
84
|
+
}, async (args) => handle(() => createAgent(args), (result) => okResponse(formatWriteResult(result, `create agent "${args.name}" at`, false), { result })));
|
|
85
|
+
// ------------------------------------------------------------------
|
|
86
|
+
// agent_update
|
|
87
|
+
// ------------------------------------------------------------------
|
|
88
|
+
server.registerTool("agent_update", {
|
|
89
|
+
title: "Update a subagent",
|
|
90
|
+
description: "Patch frontmatter fields and/or replace the body of an existing agent.",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
scope: CodeScope,
|
|
93
|
+
name: z.string().min(1),
|
|
94
|
+
description: z.string().optional(),
|
|
95
|
+
model: z.string().optional(),
|
|
96
|
+
tools: z.union([z.string(), z.array(z.string())]).optional(),
|
|
97
|
+
color: z.string().optional(),
|
|
98
|
+
body: z.string().optional(),
|
|
99
|
+
...ProjectPathField,
|
|
100
|
+
...DryRunField,
|
|
101
|
+
},
|
|
102
|
+
annotations: {
|
|
103
|
+
readOnlyHint: false,
|
|
104
|
+
destructiveHint: false,
|
|
105
|
+
idempotentHint: true,
|
|
106
|
+
openWorldHint: false,
|
|
107
|
+
},
|
|
108
|
+
}, async (args) => handle(() => updateAgent(args), (result) => okResponse(formatWriteResult(result, `update agent "${args.name}" at`, false), { result })));
|
|
109
|
+
// ------------------------------------------------------------------
|
|
110
|
+
// agent_delete
|
|
111
|
+
// ------------------------------------------------------------------
|
|
112
|
+
server.registerTool("agent_delete", {
|
|
113
|
+
title: "Delete a subagent",
|
|
114
|
+
description: "Delete a subagent markdown file. Backed up first.",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
scope: CodeScope,
|
|
117
|
+
name: z.string().min(1),
|
|
118
|
+
...ProjectPathField,
|
|
119
|
+
...DryRunField,
|
|
120
|
+
},
|
|
121
|
+
annotations: {
|
|
122
|
+
readOnlyHint: false,
|
|
123
|
+
destructiveHint: true,
|
|
124
|
+
idempotentHint: true,
|
|
125
|
+
openWorldHint: false,
|
|
126
|
+
},
|
|
127
|
+
}, async (args) => handle(() => deleteAgent(args), (result) => okResponse(formatWriteResult(result, `delete agent "${args.name}" at`, false), { result })));
|
|
128
|
+
}
|
|
129
|
+
function truncate(s, n) {
|
|
130
|
+
if (s.length <= n)
|
|
131
|
+
return s;
|
|
132
|
+
return s.slice(0, n - 1) + "…";
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/tools/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for tool handlers: building MCP tool responses with
|
|
3
|
+
* both a human-readable text block and structured JSON for modern clients.
|
|
4
|
+
*/
|
|
5
|
+
export interface ToolResponse {
|
|
6
|
+
content: Array<{
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}>;
|
|
10
|
+
structuredContent?: Record<string, unknown>;
|
|
11
|
+
isError?: boolean;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
export declare function okResponse(text: string, structured?: Record<string, unknown>): ToolResponse;
|
|
15
|
+
export declare function errorResponse(err: unknown): ToolResponse;
|
|
16
|
+
/** Wrap an async handler to catch thrown errors into MCP error responses. */
|
|
17
|
+
export declare function handle<T>(fn: () => Promise<T>, render: (value: T) => ToolResponse): Promise<ToolResponse>;
|
|
18
|
+
/** Build a human-readable write-result summary line. */
|
|
19
|
+
export declare function formatWriteResult(result: {
|
|
20
|
+
path: string;
|
|
21
|
+
backup: string | null;
|
|
22
|
+
dryRun: boolean;
|
|
23
|
+
diff?: string;
|
|
24
|
+
preview?: string;
|
|
25
|
+
}, verb: string, restartRequired?: boolean): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function okResponse(text, structured) {
|
|
2
|
+
return {
|
|
3
|
+
content: [{ type: "text", text }],
|
|
4
|
+
...(structured && { structuredContent: structured }),
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function errorResponse(err) {
|
|
8
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
11
|
+
isError: true,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Wrap an async handler to catch thrown errors into MCP error responses. */
|
|
15
|
+
export function handle(fn, render) {
|
|
16
|
+
return fn().then(render, (err) => errorResponse(err));
|
|
17
|
+
}
|
|
18
|
+
/** Build a human-readable write-result summary line. */
|
|
19
|
+
export function formatWriteResult(result, verb, restartRequired = false) {
|
|
20
|
+
if (result.dryRun) {
|
|
21
|
+
const head = `[dry-run] would ${verb} ${result.path}`;
|
|
22
|
+
if (result.diff)
|
|
23
|
+
return `${head}\n\n${result.diff}`;
|
|
24
|
+
if (result.preview)
|
|
25
|
+
return `${head}\n\n${result.preview}`;
|
|
26
|
+
return head;
|
|
27
|
+
}
|
|
28
|
+
const lines = [`${verb} ${result.path}`];
|
|
29
|
+
if (result.backup)
|
|
30
|
+
lines.push(` backup: ${result.backup}`);
|
|
31
|
+
if (restartRequired) {
|
|
32
|
+
lines.push(" NOTE: Claude Desktop must be fully quit and relaunched for this change to take effect.");
|
|
33
|
+
}
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,UAAoC;IAEpC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACjC,GAAG,CAAC,UAAU,IAAI,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,EAAE,EAAE,CAAC;QAClD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,MAAM,CACpB,EAAoB,EACpB,MAAkC;IAElC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iBAAiB,CAC/B,MAAiG,EACjG,IAAY,EACZ,eAAe,GAAG,KAAK;IAEvB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,mBAAmB,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACtD,IAAI,MAAM,CAAC,IAAI;YAAE,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;QACpD,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,IAAI,MAAM,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D,IAAI,eAAe,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CACR,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listServers, getServer, addServer, updateServer, removeServer, setServerDisabled, } from "../services/mcp-config.js";
|
|
3
|
+
import { StdioServerSchema, HttpServerSchema, } from "../schemas/mcp-config.js";
|
|
4
|
+
import { handle, okResponse, formatWriteResult } from "./helpers.js";
|
|
5
|
+
const ScopeSchema = z.enum(["desktop", "user", "project"]);
|
|
6
|
+
const WritableScopeSchema = ScopeSchema;
|
|
7
|
+
const ProjectPathField = {
|
|
8
|
+
projectPath: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Absolute path to the project. Required when scope=project."),
|
|
12
|
+
};
|
|
13
|
+
const DryRunField = {
|
|
14
|
+
dryRun: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("If true, return the diff of what would be written without touching disk."),
|
|
18
|
+
};
|
|
19
|
+
export function registerMcpTools(server) {
|
|
20
|
+
// ------------------------------------------------------------------
|
|
21
|
+
// mcp_list_servers
|
|
22
|
+
// ------------------------------------------------------------------
|
|
23
|
+
server.registerTool("mcp_list_servers", {
|
|
24
|
+
title: "List MCP servers",
|
|
25
|
+
description: "List configured MCP servers across scopes (desktop, user, project). " +
|
|
26
|
+
"Secret-looking env values are redacted unless revealSecrets=true.",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
scope: ScopeSchema
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("If omitted, list across all applicable scopes."),
|
|
31
|
+
...ProjectPathField,
|
|
32
|
+
revealSecrets: z.boolean().optional(),
|
|
33
|
+
},
|
|
34
|
+
annotations: {
|
|
35
|
+
readOnlyHint: true,
|
|
36
|
+
destructiveHint: false,
|
|
37
|
+
idempotentHint: true,
|
|
38
|
+
openWorldHint: false,
|
|
39
|
+
},
|
|
40
|
+
}, async ({ scope, projectPath, revealSecrets }) => {
|
|
41
|
+
return handle(async () => {
|
|
42
|
+
const scopes = scope
|
|
43
|
+
? [scope]
|
|
44
|
+
: projectPath
|
|
45
|
+
? ["desktop", "user", "project"]
|
|
46
|
+
: ["desktop", "user"];
|
|
47
|
+
const all = [];
|
|
48
|
+
for (const s of scopes) {
|
|
49
|
+
try {
|
|
50
|
+
const list = await listServers(s, {
|
|
51
|
+
projectPath,
|
|
52
|
+
revealSecrets,
|
|
53
|
+
});
|
|
54
|
+
all.push(...list);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Non-fatal: scope may not be configured yet.
|
|
58
|
+
all.push({
|
|
59
|
+
scope: s,
|
|
60
|
+
name: "[error]",
|
|
61
|
+
path: "",
|
|
62
|
+
entry: { error: err.message },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return all;
|
|
67
|
+
}, (entries) => okResponse(entries.length === 0
|
|
68
|
+
? "No MCP servers found."
|
|
69
|
+
: entries
|
|
70
|
+
.map((e) => `[${e.scope}] ${e.name}\n path: ${e.path}\n entry: ${JSON.stringify(e.entry)}`)
|
|
71
|
+
.join("\n\n"), { servers: entries }));
|
|
72
|
+
});
|
|
73
|
+
// ------------------------------------------------------------------
|
|
74
|
+
// mcp_get_server
|
|
75
|
+
// ------------------------------------------------------------------
|
|
76
|
+
server.registerTool("mcp_get_server", {
|
|
77
|
+
title: "Get one MCP server",
|
|
78
|
+
description: "Return the full config of a single MCP server by name and scope.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
scope: ScopeSchema,
|
|
81
|
+
name: z.string().min(1),
|
|
82
|
+
...ProjectPathField,
|
|
83
|
+
revealSecrets: z.boolean().optional(),
|
|
84
|
+
},
|
|
85
|
+
annotations: {
|
|
86
|
+
readOnlyHint: true,
|
|
87
|
+
destructiveHint: false,
|
|
88
|
+
idempotentHint: true,
|
|
89
|
+
openWorldHint: false,
|
|
90
|
+
},
|
|
91
|
+
}, async ({ scope, name, projectPath, revealSecrets }) => handle(() => getServer({ scope, name, projectPath, revealSecrets }), (entry) => entry
|
|
92
|
+
? okResponse(`[${entry.scope}] ${entry.name}\n path: ${entry.path}\n entry: ${JSON.stringify(entry.entry, null, 2)}`, { server: entry })
|
|
93
|
+
: okResponse(`Server "${name}" not found in scope ${scope}.`, {
|
|
94
|
+
found: false,
|
|
95
|
+
})));
|
|
96
|
+
// ------------------------------------------------------------------
|
|
97
|
+
// mcp_add_server
|
|
98
|
+
// ------------------------------------------------------------------
|
|
99
|
+
server.registerTool("mcp_add_server", {
|
|
100
|
+
title: "Add or replace an MCP server",
|
|
101
|
+
description: "Add a new MCP server entry. Provide either (command + args/env) for stdio " +
|
|
102
|
+
"or url + headers for HTTP transports. Use overwrite=true to replace an existing entry.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
scope: WritableScopeSchema,
|
|
105
|
+
name: z
|
|
106
|
+
.string()
|
|
107
|
+
.regex(/^[A-Za-z0-9_-]+$/, "server name must be alphanumeric/underscore/hyphen"),
|
|
108
|
+
command: z.string().optional(),
|
|
109
|
+
args: z.array(z.string()).optional(),
|
|
110
|
+
env: z.record(z.string()).optional(),
|
|
111
|
+
cwd: z.string().optional(),
|
|
112
|
+
url: z.string().url().optional(),
|
|
113
|
+
headers: z.record(z.string()).optional(),
|
|
114
|
+
overwrite: z.boolean().optional(),
|
|
115
|
+
...ProjectPathField,
|
|
116
|
+
...DryRunField,
|
|
117
|
+
},
|
|
118
|
+
annotations: {
|
|
119
|
+
readOnlyHint: false,
|
|
120
|
+
destructiveHint: false,
|
|
121
|
+
idempotentHint: false,
|
|
122
|
+
openWorldHint: false,
|
|
123
|
+
},
|
|
124
|
+
}, async (args) => {
|
|
125
|
+
return handle(async () => {
|
|
126
|
+
const entry = args.url
|
|
127
|
+
? HttpServerSchema.parse({
|
|
128
|
+
url: args.url,
|
|
129
|
+
...(args.headers && { headers: args.headers }),
|
|
130
|
+
})
|
|
131
|
+
: StdioServerSchema.parse({
|
|
132
|
+
command: args.command,
|
|
133
|
+
...(args.args && { args: args.args }),
|
|
134
|
+
...(args.env && { env: args.env }),
|
|
135
|
+
...(args.cwd && { cwd: args.cwd }),
|
|
136
|
+
});
|
|
137
|
+
return addServer({
|
|
138
|
+
scope: args.scope,
|
|
139
|
+
name: args.name,
|
|
140
|
+
entry,
|
|
141
|
+
projectPath: args.projectPath,
|
|
142
|
+
overwrite: args.overwrite,
|
|
143
|
+
dryRun: args.dryRun,
|
|
144
|
+
});
|
|
145
|
+
}, (result) => {
|
|
146
|
+
const restart = args.scope === "desktop" && !result.dryRun;
|
|
147
|
+
return okResponse(formatWriteResult(result, `add server "${args.name}" to`, restart), { result, restartRequired: restart });
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// ------------------------------------------------------------------
|
|
151
|
+
// mcp_update_server
|
|
152
|
+
// ------------------------------------------------------------------
|
|
153
|
+
server.registerTool("mcp_update_server", {
|
|
154
|
+
title: "Patch an MCP server entry",
|
|
155
|
+
description: "Update one or more fields of an existing MCP server entry. Only provided fields are changed.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
scope: WritableScopeSchema,
|
|
158
|
+
name: z.string().min(1),
|
|
159
|
+
command: z.string().optional(),
|
|
160
|
+
args: z.array(z.string()).optional(),
|
|
161
|
+
env: z.record(z.string()).optional(),
|
|
162
|
+
cwd: z.string().optional(),
|
|
163
|
+
url: z.string().url().optional(),
|
|
164
|
+
headers: z.record(z.string()).optional(),
|
|
165
|
+
...ProjectPathField,
|
|
166
|
+
...DryRunField,
|
|
167
|
+
},
|
|
168
|
+
annotations: {
|
|
169
|
+
readOnlyHint: false,
|
|
170
|
+
destructiveHint: false,
|
|
171
|
+
idempotentHint: true,
|
|
172
|
+
openWorldHint: false,
|
|
173
|
+
},
|
|
174
|
+
}, async (args) => handle(() => {
|
|
175
|
+
const patch = {};
|
|
176
|
+
if (args.command !== undefined)
|
|
177
|
+
patch.command = args.command;
|
|
178
|
+
if (args.args !== undefined)
|
|
179
|
+
patch.args = args.args;
|
|
180
|
+
if (args.env !== undefined)
|
|
181
|
+
patch.env = args.env;
|
|
182
|
+
if (args.cwd !== undefined)
|
|
183
|
+
patch.cwd = args.cwd;
|
|
184
|
+
if (args.url !== undefined)
|
|
185
|
+
patch.url = args.url;
|
|
186
|
+
if (args.headers !== undefined)
|
|
187
|
+
patch.headers = args.headers;
|
|
188
|
+
return updateServer({
|
|
189
|
+
scope: args.scope,
|
|
190
|
+
name: args.name,
|
|
191
|
+
patch: patch,
|
|
192
|
+
projectPath: args.projectPath,
|
|
193
|
+
dryRun: args.dryRun,
|
|
194
|
+
});
|
|
195
|
+
}, (result) => {
|
|
196
|
+
const restart = args.scope === "desktop" && !result.dryRun;
|
|
197
|
+
return okResponse(formatWriteResult(result, `update server "${args.name}" in`, restart), { result, restartRequired: restart });
|
|
198
|
+
}));
|
|
199
|
+
// ------------------------------------------------------------------
|
|
200
|
+
// mcp_remove_server
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
server.registerTool("mcp_remove_server", {
|
|
203
|
+
title: "Remove an MCP server",
|
|
204
|
+
description: "Delete an MCP server entry by name from a given scope.",
|
|
205
|
+
inputSchema: {
|
|
206
|
+
scope: WritableScopeSchema,
|
|
207
|
+
name: z.string().min(1),
|
|
208
|
+
...ProjectPathField,
|
|
209
|
+
...DryRunField,
|
|
210
|
+
},
|
|
211
|
+
annotations: {
|
|
212
|
+
readOnlyHint: false,
|
|
213
|
+
destructiveHint: true,
|
|
214
|
+
idempotentHint: true,
|
|
215
|
+
openWorldHint: false,
|
|
216
|
+
},
|
|
217
|
+
}, async (args) => handle(() => removeServer({
|
|
218
|
+
scope: args.scope,
|
|
219
|
+
name: args.name,
|
|
220
|
+
projectPath: args.projectPath,
|
|
221
|
+
dryRun: args.dryRun,
|
|
222
|
+
}), (result) => {
|
|
223
|
+
const restart = args.scope === "desktop" && !result.dryRun;
|
|
224
|
+
return okResponse(formatWriteResult(result, `remove server "${args.name}" from`, restart), { result, restartRequired: restart });
|
|
225
|
+
}));
|
|
226
|
+
// ------------------------------------------------------------------
|
|
227
|
+
// mcp_set_disabled
|
|
228
|
+
// ------------------------------------------------------------------
|
|
229
|
+
server.registerTool("mcp_set_disabled", {
|
|
230
|
+
title: "Enable or disable an MCP server",
|
|
231
|
+
description: "Toggle an MCP server on/off by setting its `disabled` flag. File is preserved.",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
scope: WritableScopeSchema,
|
|
234
|
+
name: z.string().min(1),
|
|
235
|
+
disabled: z.boolean(),
|
|
236
|
+
...ProjectPathField,
|
|
237
|
+
...DryRunField,
|
|
238
|
+
},
|
|
239
|
+
annotations: {
|
|
240
|
+
readOnlyHint: false,
|
|
241
|
+
destructiveHint: false,
|
|
242
|
+
idempotentHint: true,
|
|
243
|
+
openWorldHint: false,
|
|
244
|
+
},
|
|
245
|
+
}, async (args) => handle(() => setServerDisabled({
|
|
246
|
+
scope: args.scope,
|
|
247
|
+
name: args.name,
|
|
248
|
+
disabled: args.disabled,
|
|
249
|
+
projectPath: args.projectPath,
|
|
250
|
+
dryRun: args.dryRun,
|
|
251
|
+
}), (result) => {
|
|
252
|
+
const restart = args.scope === "desktop" && !result.dryRun;
|
|
253
|
+
const verb = args.disabled ? "disable" : "enable";
|
|
254
|
+
return okResponse(formatWriteResult(result, `${verb} server "${args.name}" in`, restart), { result, restartRequired: restart });
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=mcp.js.map
|