mcpill 1.3.0 → 1.5.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 +11 -0
- package/README.md +2 -1
- package/dist/cli.js +218 -48
- package/package.json +6 -1
- package/MANUAL-TEST-GUIDE-SPEC.md +0 -270
- package/src/__tests__/compile.test.ts +0 -203
- package/src/__tests__/init.test.ts +0 -75
- package/src/__tests__/loaders/config.test.ts +0 -54
- package/src/__tests__/loaders/prompts.test.ts +0 -116
- package/src/__tests__/loaders/resources.test.ts +0 -86
- package/src/__tests__/loaders/tools.test.ts +0 -128
- package/src/__tests__/pack.test.ts +0 -98
- package/src/__tests__/validate.test.ts +0 -152
- package/src/cli.ts +0 -77
- package/src/commands/compile.ts +0 -196
- package/src/commands/init.ts +0 -453
- package/src/commands/pack.ts +0 -38
- package/src/commands/publish.ts +0 -17
- package/src/commands/run.ts +0 -105
- package/src/commands/validate.ts +0 -87
- package/src/compiler/hooks.ts +0 -140
- package/src/compiler/merge-tools.ts +0 -99
- package/src/compiler/parse.ts +0 -236
- package/src/compiler/serialize.ts +0 -100
- package/src/compiler/types.ts +0 -27
- package/src/loaders/config.ts +0 -25
- package/src/loaders/prompts.ts +0 -60
- package/src/loaders/resources.ts +0 -54
- package/src/loaders/tools.ts +0 -68
- package/src/templates/prompts/greeting.md +0 -11
- package/src/templates/server.md +0 -9
- package/src/templates/tools/echo.md +0 -15
- package/tsconfig.json +0 -10
- package/tsup.config.ts +0 -13
- package/vitest.config.ts +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
- `mcpill init` scaffolds `.mcpill/server/hooks/` with a commented `example-hook.md` stub covering all four Claude Code event types (`PreToolUse`, `PostToolUse`, `PreCompact`, `Stop`)
|
|
6
|
+
- `PILL.md` template now includes commented-out `## Hook:` and `## Prompt:` section stubs with inline field explanations
|
|
7
|
+
- `mcpill compile` merges user-defined hooks from `PILL.md` `## Hook:` blocks and `.mcpill/server/hooks/*.md` files alongside auto-generated `replaces:` hooks into a unified `.claude/settings.json` list; all four event types supported
|
|
8
|
+
- `pill-agent-guide.md` documents `## Hook:` sections, `.mcpill/server/hooks/*.md` files, and the `--no-hooks` flag
|
|
9
|
+
|
|
10
|
+
## 1.4.0
|
|
11
|
+
|
|
12
|
+
- `mcpill compile` and `mcpill validate` now auto-run `npm install` in the server directory when `node_modules` is absent and the server has declared dependencies
|
|
13
|
+
|
|
3
14
|
## 1.3.0
|
|
4
15
|
|
|
5
16
|
- `PILL.md` tool sections now accept a `replaces` field (comma-separated native tool names, e.g. `replaces: Read, Bash`); `mcpill compile` reads these and generates the `PreToolUse` hook without requiring a separate `AGENT.md`
|
package/README.md
CHANGED
|
@@ -46,9 +46,10 @@ Scaffolds a new project in the current directory:
|
|
|
46
46
|
- `.mcpill/pill-agent-guide.md` — agent instructions read by Claude when building from `PILL.md`
|
|
47
47
|
- `.mcpill/server.md` — server config + resources
|
|
48
48
|
- `.mcpill/server/prompts/greeting.md` — example prompt
|
|
49
|
+
- `.mcpill/server/hooks/example-hook.md` — stub hook file (see file for format; all fields commented out by default)
|
|
49
50
|
- `.mcpill/HELLO-MCP.md` — ready-to-run example (copy into `PILL.md` to try it)
|
|
50
51
|
- `.claude/commands/create-pill.md` — `/create-pill` slash command; say `/create-pill` in Claude Code to start the guided pill-creation workflow
|
|
51
|
-
- `package.json` — `{ type: "module", dependencies: { mcpill-runtime } }` —
|
|
52
|
+
- `package.json` — `{ type: "module", dependencies: { mcpill-runtime } }` — deps are installed automatically by `mcpill compile` and `mcpill validate`
|
|
52
53
|
|
|
53
54
|
### `mcpill compile`
|
|
54
55
|
|
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { dirname, join as
|
|
7
|
-
import { readFileSync as
|
|
6
|
+
import { dirname, join as join6 } from "path";
|
|
7
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import fs from "fs";
|
|
@@ -22,6 +22,7 @@ Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
|
|
|
22
22
|
- \`## Tool: <name>\` \u2192 one tool per section (repeatable)
|
|
23
23
|
- \`## Resource: <name>\` \u2192 static resource (optional, repeatable)
|
|
24
24
|
- \`## Prompt: <name>\` \u2192 prompt template (optional, repeatable)
|
|
25
|
+
- \`## Hook: <name>\` \u2192 hook definition (optional, repeatable) \u2014 compiled directly by \`mcpill compile\`
|
|
25
26
|
|
|
26
27
|
2. **Write** the server files into the pre-created scaffold at \`.mcpill/\`:
|
|
27
28
|
|
|
@@ -76,6 +77,16 @@ Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
|
|
|
76
77
|
\`\`\`
|
|
77
78
|
Omit \`## Args\` if the prompt takes no arguments.
|
|
78
79
|
|
|
80
|
+
\`.mcpill/server/hooks/{hook-name}.md\` \u2014 optional; one file per standalone hook (alternative to \`## Hook:\` blocks in PILL.md):
|
|
81
|
+
\`\`\`
|
|
82
|
+
trigger: PreToolUse
|
|
83
|
+
matcher: .*
|
|
84
|
+
command: echo "example"
|
|
85
|
+
\`\`\`
|
|
86
|
+
Valid trigger values: \`PreToolUse\` | \`PostToolUse\` | \`PreCompact\` | \`Stop\`.
|
|
87
|
+
\`matcher\` is a regex matched against the tool name (PreToolUse/PostToolUse only); omit to match all tools.
|
|
88
|
+
\`command\` stdout is shown to Claude; exit code != 0 blocks the tool call (PreToolUse only).
|
|
89
|
+
|
|
79
90
|
3. **Translate** each tool's \`behavior\` field into a working JS async handler.
|
|
80
91
|
- Implement the described logic fully \u2014 no stubs, no TODOs.
|
|
81
92
|
- Handler is self-contained: no top-level imports.
|
|
@@ -84,8 +95,9 @@ Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
|
|
|
84
95
|
- Allowed parameter types: \`string | number | boolean\`. Default unknown types to \`string\`.
|
|
85
96
|
|
|
86
97
|
4. **Run** \`mcpill compile\`.
|
|
87
|
-
- If the pill's \`AGENT.md\` has a \`## Tools\` table with a \`replaces\` column, compile
|
|
88
|
-
-
|
|
98
|
+
- If the pill's \`AGENT.md\` has a \`## Tools\` table with a \`replaces\` column, compile writes a \`PreToolUse\` hook into \`.claude/settings.json\` that warns Claude to use the pill's MCP tools instead of native file tools.
|
|
99
|
+
- If PILL.md has \`## Hook:\` sections (or \`.mcpill/server/hooks/*.md\` files exist), compile writes those hooks into \`.claude/settings.json\` under the corresponding event key. These coexist with the auto-generated \`replaces:\` hooks \u2014 both are merged.
|
|
100
|
+
- To skip all hook generation: \`mcpill compile --no-hooks\`.
|
|
89
101
|
|
|
90
102
|
5. **Report** what was generated and show the user the next commands:
|
|
91
103
|
\`\`\`
|
|
@@ -153,6 +165,40 @@ behavior: |
|
|
|
153
165
|
|
|
154
166
|
---
|
|
155
167
|
|
|
168
|
+
<!-- Add a ## Prompt: section to define a reusable prompt template.
|
|
169
|
+
The build agent creates .mcpill/server/prompts/<name>.md for each.
|
|
170
|
+
Remove comment markers to activate.
|
|
171
|
+
|
|
172
|
+
## Prompt: my-prompt
|
|
173
|
+
|
|
174
|
+
description: Describe what this prompt does.
|
|
175
|
+
args:
|
|
176
|
+
- query (string): The question or topic.
|
|
177
|
+
behavior: |
|
|
178
|
+
Research {{query}} and provide a structured summary.
|
|
179
|
+
|
|
180
|
+
-->
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
<!-- Add a ## Hook: section to run a shell command when Claude Code fires an event.
|
|
185
|
+
Run: mcpill compile after editing to apply changes to .claude/settings.json.
|
|
186
|
+
Remove comment markers to activate.
|
|
187
|
+
|
|
188
|
+
trigger \u2014 event that fires the hook: PreToolUse | PostToolUse | PreCompact | Stop
|
|
189
|
+
matcher \u2014 regex matched against tool name (PreToolUse/PostToolUse only); blank = all tools
|
|
190
|
+
command \u2014 shell command; stdout shown to Claude; exit!=0 blocks (PreToolUse)
|
|
191
|
+
|
|
192
|
+
## Hook: example-hook
|
|
193
|
+
|
|
194
|
+
trigger: PreToolUse
|
|
195
|
+
matcher: .*
|
|
196
|
+
command: echo "example hook \u2014 replace this command"
|
|
197
|
+
|
|
198
|
+
-->
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
156
202
|
## Next Steps
|
|
157
203
|
|
|
158
204
|
Fill in your server above (replace or extend the example tools), then tell Claude:
|
|
@@ -238,6 +284,37 @@ name: Status
|
|
|
238
284
|
---
|
|
239
285
|
The server is running.
|
|
240
286
|
`;
|
|
287
|
+
var EXAMPLE_HOOK_MD = `# example-hook
|
|
288
|
+
|
|
289
|
+
<!--
|
|
290
|
+
Stub hook file \u2014 fill in the fields below and run: mcpill compile
|
|
291
|
+
|
|
292
|
+
TRIGGER \u2014 which event fires this hook (required):
|
|
293
|
+
PreToolUse \u2014 runs before Claude calls a tool
|
|
294
|
+
PostToolUse \u2014 runs after a tool returns a result
|
|
295
|
+
PreCompact \u2014 runs before Claude compacts/summarizes its context
|
|
296
|
+
Stop \u2014 runs when Claude finishes responding
|
|
297
|
+
|
|
298
|
+
MATCHER \u2014 regex matched against the tool name (optional):
|
|
299
|
+
Used for PreToolUse and PostToolUse only; ignored for PreCompact and Stop.
|
|
300
|
+
Omit or leave blank to match all tools.
|
|
301
|
+
Examples: Read|Write|Edit (specific tools)
|
|
302
|
+
Bash (Bash tool only)
|
|
303
|
+
.* (all tools \u2014 same as omitting)
|
|
304
|
+
|
|
305
|
+
COMMAND \u2014 shell command to run when the hook fires (required):
|
|
306
|
+
stdout is shown to Claude as context before/after the tool call.
|
|
307
|
+
Exit code != 0 blocks the tool call (PreToolUse only).
|
|
308
|
+
Examples: echo "use pill tools instead of native file tools"
|
|
309
|
+
./scripts/pre-check.sh
|
|
310
|
+
|
|
311
|
+
Copy the fields below the closing --> and fill them in to activate this hook:
|
|
312
|
+
|
|
313
|
+
trigger: PreToolUse
|
|
314
|
+
matcher: .*
|
|
315
|
+
command: echo "example hook \u2014 replace this command"
|
|
316
|
+
-->
|
|
317
|
+
`;
|
|
241
318
|
var GREETING_PROMPT_MD = `# greeting
|
|
242
319
|
|
|
243
320
|
Generate a greeting
|
|
@@ -365,8 +442,10 @@ ${projectName}/
|
|
|
365
442
|
\u251C\u2500\u2500 mcpill.config.json \u2190 compiled config
|
|
366
443
|
\u251C\u2500\u2500 tools/ \u2190 one .md file per tool
|
|
367
444
|
\u2502 \u2514\u2500\u2500 my-tool.md
|
|
368
|
-
\
|
|
369
|
-
|
|
445
|
+
\u251C\u2500\u2500 prompts/ \u2190 one .md file per prompt (optional)
|
|
446
|
+
\u2502 \u2514\u2500\u2500 greeting.md
|
|
447
|
+
\u2514\u2500\u2500 hooks/ \u2190 one .md file per hook (optional)
|
|
448
|
+
\u2514\u2500\u2500 example-hook.md
|
|
370
449
|
\`\`\`
|
|
371
450
|
|
|
372
451
|
## Editing tools manually
|
|
@@ -405,10 +484,12 @@ async function runInit(opts) {
|
|
|
405
484
|
}
|
|
406
485
|
fs.mkdirSync(path.join(serverDir, "tools"), { recursive: true });
|
|
407
486
|
fs.mkdirSync(path.join(serverDir, "prompts"), { recursive: true });
|
|
487
|
+
fs.mkdirSync(path.join(serverDir, "hooks"), { recursive: true });
|
|
408
488
|
fs.mkdirSync(path.join(targetDir, ".claude", "commands"), { recursive: true });
|
|
409
489
|
const serverMd = SERVER_MD_TEMPLATE.replace("name: my-server", `name: ${projectName}`);
|
|
410
490
|
fs.writeFileSync(path.join(mcpillDir, "server.md"), serverMd);
|
|
411
491
|
fs.writeFileSync(path.join(serverDir, "prompts", "greeting.md"), GREETING_PROMPT_MD);
|
|
492
|
+
fs.writeFileSync(path.join(serverDir, "hooks", "example-hook.md"), EXAMPLE_HOOK_MD);
|
|
412
493
|
fs.writeFileSync(
|
|
413
494
|
path.join(serverDir, "mcpill.config.json"),
|
|
414
495
|
JSON.stringify({ name: projectName, transport: "stdio", port: 3333 }, null, 2)
|
|
@@ -438,6 +519,7 @@ async function runInit(opts) {
|
|
|
438
519
|
console.log("\u2713 .mcpill/HELLO-MCP.md \u2190 copy into PILL.md to see a working example instantly");
|
|
439
520
|
console.log("\u2713 .mcpill/server.md \u2190 or edit source files directly");
|
|
440
521
|
console.log("\u2713 .mcpill/server/prompts/greeting.md");
|
|
522
|
+
console.log("\u2713 .mcpill/server/hooks/example-hook.md \u2190 stub hook (see file for format)");
|
|
441
523
|
console.log("\u2713 .mcpill/pill-agent-guide.md \u2190 agent instructions (read by Claude)");
|
|
442
524
|
console.log("\u2713 .mcpill/pill-user-guide.md \u2190 start here");
|
|
443
525
|
console.log("\u2713 .claude/commands/create-pill.md \u2190 /create-pill skill (say: /create-pill)");
|
|
@@ -610,13 +692,27 @@ async function loadResources(mcpillDir) {
|
|
|
610
692
|
import fs5 from "fs";
|
|
611
693
|
import path6 from "path";
|
|
612
694
|
|
|
695
|
+
// src/commands/ensure-deps.ts
|
|
696
|
+
import { execSync } from "child_process";
|
|
697
|
+
import { existsSync, readFileSync } from "fs";
|
|
698
|
+
import { join } from "path";
|
|
699
|
+
function ensureDeps(serverDir) {
|
|
700
|
+
if (!existsSync(join(serverDir, "mcpill.config.json"))) return;
|
|
701
|
+
if (!existsSync(join(serverDir, "package.json"))) return;
|
|
702
|
+
if (existsSync(join(serverDir, "node_modules"))) return;
|
|
703
|
+
const pkg2 = JSON.parse(readFileSync(join(serverDir, "package.json"), "utf-8"));
|
|
704
|
+
if (!pkg2.dependencies || Object.keys(pkg2.dependencies).length === 0) return;
|
|
705
|
+
console.log("Installing server dependencies\u2026");
|
|
706
|
+
execSync("npm install", { cwd: serverDir, stdio: "inherit" });
|
|
707
|
+
}
|
|
708
|
+
|
|
613
709
|
// src/compiler/hooks.ts
|
|
614
|
-
import { readFileSync as
|
|
615
|
-
import { join as
|
|
710
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2, writeFileSync, mkdirSync } from "fs";
|
|
711
|
+
import { join as join3 } from "path";
|
|
616
712
|
|
|
617
713
|
// src/compiler/parse.ts
|
|
618
|
-
import { readdirSync, readFileSync } from "fs";
|
|
619
|
-
import { join } from "path";
|
|
714
|
+
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
715
|
+
import { join as join2 } from "path";
|
|
620
716
|
function extractSections(content) {
|
|
621
717
|
const sections = /* @__PURE__ */ new Map();
|
|
622
718
|
const pieces = ("\n" + content).split("\n## ");
|
|
@@ -747,7 +843,7 @@ function parsePromptFile(content) {
|
|
|
747
843
|
return { name, description, args, messages };
|
|
748
844
|
}
|
|
749
845
|
function parseServerDir(dir) {
|
|
750
|
-
const serverMdContent =
|
|
846
|
+
const serverMdContent = readFileSync2(join2(dir, "server.md"), "utf-8");
|
|
751
847
|
const sections = extractSections(serverMdContent);
|
|
752
848
|
const configBody = sections.get("Config") ?? "";
|
|
753
849
|
const configKv = parseKvBlock(configBody);
|
|
@@ -758,7 +854,7 @@ function parseServerDir(dir) {
|
|
|
758
854
|
};
|
|
759
855
|
const resourcesBody = sections.get("Resources") ?? "";
|
|
760
856
|
const resources = parseResourcesSection(resourcesBody);
|
|
761
|
-
const toolsDir =
|
|
857
|
+
const toolsDir = join2(dir, "server", "tools");
|
|
762
858
|
const tools = [];
|
|
763
859
|
let toolFiles = [];
|
|
764
860
|
try {
|
|
@@ -766,9 +862,9 @@ function parseServerDir(dir) {
|
|
|
766
862
|
} catch {
|
|
767
863
|
}
|
|
768
864
|
for (const file of toolFiles) {
|
|
769
|
-
tools.push(parseToolFile(
|
|
865
|
+
tools.push(parseToolFile(readFileSync2(join2(toolsDir, file), "utf-8")));
|
|
770
866
|
}
|
|
771
|
-
const promptsDir =
|
|
867
|
+
const promptsDir = join2(dir, "server", "prompts");
|
|
772
868
|
const prompts = [];
|
|
773
869
|
let promptFiles = [];
|
|
774
870
|
try {
|
|
@@ -776,7 +872,7 @@ function parseServerDir(dir) {
|
|
|
776
872
|
} catch {
|
|
777
873
|
}
|
|
778
874
|
for (const file of promptFiles) {
|
|
779
|
-
prompts.push(parsePromptFile(
|
|
875
|
+
prompts.push(parsePromptFile(readFileSync2(join2(promptsDir, file), "utf-8")));
|
|
780
876
|
}
|
|
781
877
|
return { config, tools, prompts, resources };
|
|
782
878
|
}
|
|
@@ -854,12 +950,65 @@ function buildHookEntry(pillName, tools) {
|
|
|
854
950
|
hooks: [{ type: "command", command: `echo ${JSON.stringify(message)}` }]
|
|
855
951
|
};
|
|
856
952
|
}
|
|
953
|
+
function parsePillMdHooks(content) {
|
|
954
|
+
const stripped = content.replace(/<!--[\s\S]*?-->/g, "");
|
|
955
|
+
const hooks = [];
|
|
956
|
+
const pieces = ("\n" + stripped).split("\n## ");
|
|
957
|
+
for (const piece of pieces) {
|
|
958
|
+
if (!piece.toLowerCase().startsWith("hook:")) continue;
|
|
959
|
+
const nlIdx = piece.indexOf("\n");
|
|
960
|
+
const body = nlIdx === -1 ? "" : piece.slice(nlIdx + 1);
|
|
961
|
+
const kv = parseKvBlock(body);
|
|
962
|
+
const trigger = kv["trigger"]?.trim();
|
|
963
|
+
const matcher = kv["matcher"]?.trim();
|
|
964
|
+
const command = kv["command"]?.trim();
|
|
965
|
+
if (!trigger || !command) continue;
|
|
966
|
+
hooks.push({ event: trigger, ...matcher ? { matcher } : {}, command });
|
|
967
|
+
}
|
|
968
|
+
return hooks;
|
|
969
|
+
}
|
|
970
|
+
function parseHookFile(content) {
|
|
971
|
+
const stripped = content.replace(/<!--[\s\S]*?-->/g, "");
|
|
972
|
+
const kv = parseKvBlock(stripped);
|
|
973
|
+
const trigger = kv["trigger"]?.trim();
|
|
974
|
+
const matcher = kv["matcher"]?.trim();
|
|
975
|
+
const command = kv["command"]?.trim();
|
|
976
|
+
if (!trigger || !command) return null;
|
|
977
|
+
return { event: trigger, ...matcher ? { matcher } : {}, command };
|
|
978
|
+
}
|
|
979
|
+
function mergeUserHooksIntoSettings(baseDir, hookBlocks) {
|
|
980
|
+
if (hookBlocks.length === 0) return;
|
|
981
|
+
const claudeDir = join3(baseDir, ".claude");
|
|
982
|
+
const settingsPath = join3(claudeDir, "settings.json");
|
|
983
|
+
let settings = {};
|
|
984
|
+
if (existsSync2(settingsPath)) {
|
|
985
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
986
|
+
}
|
|
987
|
+
if (!settings.hooks) settings.hooks = {};
|
|
988
|
+
for (const block of hookBlocks) {
|
|
989
|
+
const existing = settings.hooks[block.event] ?? [];
|
|
990
|
+
const newEntry = {
|
|
991
|
+
...block.matcher !== void 0 ? { matcher: block.matcher } : {},
|
|
992
|
+
hooks: [{ type: "command", command: block.command }]
|
|
993
|
+
};
|
|
994
|
+
const matcherKey = block.matcher ?? "";
|
|
995
|
+
const idx = existing.findIndex((e) => (e.matcher ?? "") === matcherKey);
|
|
996
|
+
if (idx !== -1) {
|
|
997
|
+
existing[idx] = newEntry;
|
|
998
|
+
} else {
|
|
999
|
+
existing.push(newEntry);
|
|
1000
|
+
}
|
|
1001
|
+
settings.hooks[block.event] = existing;
|
|
1002
|
+
}
|
|
1003
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
1004
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1005
|
+
}
|
|
857
1006
|
function mergeHookIntoSettings(baseDir, hookEntry) {
|
|
858
|
-
const claudeDir =
|
|
859
|
-
const settingsPath =
|
|
1007
|
+
const claudeDir = join3(baseDir, ".claude");
|
|
1008
|
+
const settingsPath = join3(claudeDir, "settings.json");
|
|
860
1009
|
let settings = {};
|
|
861
|
-
if (
|
|
862
|
-
settings = JSON.parse(
|
|
1010
|
+
if (existsSync2(settingsPath)) {
|
|
1011
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
863
1012
|
}
|
|
864
1013
|
if (!settings.hooks) settings.hooks = {};
|
|
865
1014
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
@@ -917,6 +1066,7 @@ async function runValidate(baseDir) {
|
|
|
917
1066
|
console.error("No pill directories found \u2014 run mcpill compile first");
|
|
918
1067
|
process.exit(1);
|
|
919
1068
|
}
|
|
1069
|
+
ensureDeps(serverDir);
|
|
920
1070
|
await validateOne(serverDir);
|
|
921
1071
|
if (!fs5.existsSync(path6.join(mcpillDir, ".no-hooks"))) {
|
|
922
1072
|
const agentMdPath = fs5.existsSync(path6.join(baseDir, "AGENT.md")) ? path6.join(baseDir, "AGENT.md") : fs5.existsSync(path6.join(mcpillDir, "AGENT.md")) ? path6.join(mcpillDir, "AGENT.md") : null;
|
|
@@ -1017,13 +1167,13 @@ async function runServer(opts) {
|
|
|
1017
1167
|
}
|
|
1018
1168
|
|
|
1019
1169
|
// src/commands/compile.ts
|
|
1020
|
-
import { resolve, join as
|
|
1021
|
-
import { readFileSync as
|
|
1170
|
+
import { resolve, join as join5 } from "path";
|
|
1171
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
1022
1172
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1023
1173
|
|
|
1024
1174
|
// src/compiler/serialize.ts
|
|
1025
1175
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1026
|
-
import { join as
|
|
1176
|
+
import { join as join4 } from "path";
|
|
1027
1177
|
function serializeResources(resources) {
|
|
1028
1178
|
return resources.map((r) => {
|
|
1029
1179
|
let fm = `uri: ${r.uri}`;
|
|
@@ -1037,8 +1187,8 @@ ${r.content}`;
|
|
|
1037
1187
|
}).join("\n---\n");
|
|
1038
1188
|
}
|
|
1039
1189
|
function serializeServerDir(doc, dir) {
|
|
1040
|
-
mkdirSync2(
|
|
1041
|
-
mkdirSync2(
|
|
1190
|
+
mkdirSync2(join4(dir, "server", "tools"), { recursive: true });
|
|
1191
|
+
mkdirSync2(join4(dir, "server", "prompts"), { recursive: true });
|
|
1042
1192
|
let serverMd = "## Config\n";
|
|
1043
1193
|
serverMd += `name: ${doc.config.name}
|
|
1044
1194
|
`;
|
|
@@ -1054,7 +1204,7 @@ ${serializeResources(doc.resources)}
|
|
|
1054
1204
|
} else {
|
|
1055
1205
|
serverMd += "\n## Resources\n";
|
|
1056
1206
|
}
|
|
1057
|
-
writeFileSync2(
|
|
1207
|
+
writeFileSync2(join4(dir, "server.md"), serverMd);
|
|
1058
1208
|
console.log("\u2713 server.md updated");
|
|
1059
1209
|
for (const tool of doc.tools) {
|
|
1060
1210
|
let md = `# ${tool.name}
|
|
@@ -1078,7 +1228,7 @@ ${cleanHandler}
|
|
|
1078
1228
|
\`\`\`
|
|
1079
1229
|
`;
|
|
1080
1230
|
}
|
|
1081
|
-
writeFileSync2(
|
|
1231
|
+
writeFileSync2(join4(dir, "server", "tools", `${tool.name}.md`), md);
|
|
1082
1232
|
}
|
|
1083
1233
|
for (const prompt of doc.prompts) {
|
|
1084
1234
|
let md = `# ${prompt.name}
|
|
@@ -1102,7 +1252,7 @@ ${cleanHandler}
|
|
|
1102
1252
|
`;
|
|
1103
1253
|
}
|
|
1104
1254
|
}
|
|
1105
|
-
writeFileSync2(
|
|
1255
|
+
writeFileSync2(join4(dir, "server", "prompts", `${prompt.name}.md`), md);
|
|
1106
1256
|
}
|
|
1107
1257
|
console.log(`\u2713 tools/ updated (${doc.tools.length} files)`);
|
|
1108
1258
|
console.log(`\u2713 prompts/ updated (${doc.prompts.length} files)`);
|
|
@@ -1239,21 +1389,21 @@ ${r.content}`;
|
|
|
1239
1389
|
}
|
|
1240
1390
|
async function runCompile(opts) {
|
|
1241
1391
|
const baseDir = resolve(opts.dir ?? process.cwd());
|
|
1242
|
-
const mcpillDir =
|
|
1243
|
-
const serverDir =
|
|
1392
|
+
const mcpillDir = join5(baseDir, ".mcpill");
|
|
1393
|
+
const serverDir = join5(mcpillDir, "server");
|
|
1244
1394
|
if (opts.toMd) {
|
|
1245
|
-
const configPath =
|
|
1246
|
-
if (!
|
|
1395
|
+
const configPath = join5(serverDir, "mcpill.config.json");
|
|
1396
|
+
if (!existsSync3(configPath)) {
|
|
1247
1397
|
throw new Error("No pill found \u2014 run mcpill compile first");
|
|
1248
1398
|
}
|
|
1249
1399
|
const config = await loadConfig(serverDir);
|
|
1250
1400
|
const resources = await loadResources(serverDir);
|
|
1251
|
-
const promptsPath =
|
|
1252
|
-
const prompts = JSON.parse(
|
|
1253
|
-
const toolsPath =
|
|
1254
|
-
const handlers =
|
|
1401
|
+
const promptsPath = join5(serverDir, "prompts.json");
|
|
1402
|
+
const prompts = JSON.parse(readFileSync4(promptsPath, "utf-8"));
|
|
1403
|
+
const toolsPath = join5(serverDir, "tools.js");
|
|
1404
|
+
const handlers = existsSync3(toolsPath) ? extractHandlers(readFileSync4(toolsPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1255
1405
|
let tools = [];
|
|
1256
|
-
if (
|
|
1406
|
+
if (existsSync3(toolsPath)) {
|
|
1257
1407
|
const mod = await import(pathToFileURL2(toolsPath).href);
|
|
1258
1408
|
tools = mod.default.map((t) => {
|
|
1259
1409
|
const schema = {};
|
|
@@ -1284,13 +1434,13 @@ async function runCompile(opts) {
|
|
|
1284
1434
|
return;
|
|
1285
1435
|
}
|
|
1286
1436
|
const doc = parseServerDir(mcpillDir);
|
|
1287
|
-
const toolsJsPath =
|
|
1288
|
-
const existing =
|
|
1437
|
+
const toolsJsPath = join5(serverDir, "tools.js");
|
|
1438
|
+
const existing = existsSync3(toolsJsPath) ? extractHandlers(readFileSync4(toolsJsPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1289
1439
|
const { doc: mergedDoc, stubbed } = mergeHandlers(doc, existing);
|
|
1290
|
-
const pillMdPath =
|
|
1440
|
+
const pillMdPath = join5(baseDir, "PILL.md");
|
|
1291
1441
|
let pillContent = null;
|
|
1292
|
-
if (
|
|
1293
|
-
pillContent =
|
|
1442
|
+
if (existsSync3(pillMdPath)) {
|
|
1443
|
+
pillContent = readFileSync4(pillMdPath, "utf-8");
|
|
1294
1444
|
const serverSection = ("\n" + pillContent).split("\n## ").find((s) => s.startsWith("Server\n"));
|
|
1295
1445
|
if (serverSection) {
|
|
1296
1446
|
const kv = parseKvBlock(serverSection.slice("Server\n".length));
|
|
@@ -1307,18 +1457,19 @@ async function runCompile(opts) {
|
|
|
1307
1457
|
console.warn(`\u26A0 Stub generated for tool: ${name}`);
|
|
1308
1458
|
}
|
|
1309
1459
|
mkdirSync3(serverDir, { recursive: true });
|
|
1310
|
-
writeFileSync3(
|
|
1311
|
-
writeFileSync3(
|
|
1312
|
-
writeFileSync3(
|
|
1460
|
+
writeFileSync3(join5(serverDir, "mcpill.config.json"), JSON.stringify(mergedDoc.config, null, 2));
|
|
1461
|
+
writeFileSync3(join5(serverDir, "prompts.json"), JSON.stringify(mergedDoc.prompts, null, 2));
|
|
1462
|
+
writeFileSync3(join5(serverDir, "resources.md"), serializeResourcesMd(mergedDoc.resources));
|
|
1313
1463
|
writeFileSync3(toolsJsPath, generateToolsJs(mergedDoc.tools));
|
|
1464
|
+
ensureDeps(serverDir);
|
|
1314
1465
|
console.log(`\u2713 .mcpill/server/ updated from .mcpill/server.md, .mcpill/server/tools/, .mcpill/server/prompts/`);
|
|
1315
1466
|
if (opts.noHooks) {
|
|
1316
|
-
writeFileSync3(
|
|
1467
|
+
writeFileSync3(join5(mcpillDir, ".no-hooks"), "");
|
|
1317
1468
|
return;
|
|
1318
1469
|
}
|
|
1319
|
-
const agentMdPath =
|
|
1470
|
+
const agentMdPath = existsSync3(join5(baseDir, "AGENT.md")) ? join5(baseDir, "AGENT.md") : existsSync3(join5(mcpillDir, "AGENT.md")) ? join5(mcpillDir, "AGENT.md") : null;
|
|
1320
1471
|
const pillTools = pillContent ? parsePillMdTools(pillContent) : [];
|
|
1321
|
-
const agentTools = agentMdPath ? parseAgentMdTools(
|
|
1472
|
+
const agentTools = agentMdPath ? parseAgentMdTools(readFileSync4(agentMdPath, "utf-8")) : [];
|
|
1322
1473
|
const toolMap = new Map([...pillTools, ...agentTools].map((t) => [t.name, t]));
|
|
1323
1474
|
const allTools = Array.from(toolMap.values());
|
|
1324
1475
|
const hookEntry = buildHookEntry(mergedDoc.config.name, allTools);
|
|
@@ -1326,6 +1477,25 @@ async function runCompile(opts) {
|
|
|
1326
1477
|
mergeHookIntoSettings(baseDir, hookEntry);
|
|
1327
1478
|
console.log(`\u2713 .claude/settings.json updated with PreToolUse hook`);
|
|
1328
1479
|
}
|
|
1480
|
+
const pillHooks = pillContent ? parsePillMdHooks(pillContent) : [];
|
|
1481
|
+
const hooksDir = join5(mcpillDir, "server", "hooks");
|
|
1482
|
+
const fileHooks = [];
|
|
1483
|
+
if (existsSync3(hooksDir)) {
|
|
1484
|
+
let hookFiles = [];
|
|
1485
|
+
try {
|
|
1486
|
+
hookFiles = readdirSync2(hooksDir).filter((f) => f.endsWith(".md"));
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
for (const file of hookFiles) {
|
|
1490
|
+
const entry = parseHookFile(readFileSync4(join5(hooksDir, file), "utf-8"));
|
|
1491
|
+
if (entry) fileHooks.push(entry);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
const allUserHooks = [...pillHooks, ...fileHooks];
|
|
1495
|
+
if (allUserHooks.length > 0) {
|
|
1496
|
+
mergeUserHooksIntoSettings(baseDir, allUserHooks);
|
|
1497
|
+
console.log(`\u2713 .claude/settings.json updated with ${allUserHooks.length} user-defined hook(s)`);
|
|
1498
|
+
}
|
|
1329
1499
|
}
|
|
1330
1500
|
|
|
1331
1501
|
// src/commands/pack.ts
|
|
@@ -1374,7 +1544,7 @@ async function runPublish(dir, access) {
|
|
|
1374
1544
|
// src/cli.ts
|
|
1375
1545
|
var pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
1376
1546
|
var pkg = JSON.parse(
|
|
1377
|
-
|
|
1547
|
+
readFileSync5(join6(pkgDir, "../package.json"), "utf-8")
|
|
1378
1548
|
);
|
|
1379
1549
|
var program = new Command();
|
|
1380
1550
|
program.name("mcpill").version(pkg.version);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcpill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for building, validating, and publishing MCP servers using the pill format.",
|
|
6
6
|
"homepage": "https://mcpill.ruco.dev",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ruco-dev/mcpill/issues"
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
14
19
|
"bin": {
|
|
15
20
|
"mcpill": "./dist/cli.js"
|
|
16
21
|
},
|