mcpill 1.1.0 → 1.2.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +78 -10
  3. package/dist/cli.js +345 -129
  4. package/package.json +1 -1
  5. package/src/__tests__/init.test.ts +17 -16
  6. package/src/__tests__/pack.test.ts +1 -1
  7. package/src/__tests__/validate.test.ts +5 -5
  8. package/src/commands/compile.ts +20 -32
  9. package/src/commands/init.ts +305 -81
  10. package/src/commands/pack.ts +1 -15
  11. package/src/commands/run.ts +16 -12
  12. package/src/commands/validate.ts +3 -14
  13. package/src/compiler/parse.ts +2 -2
  14. package/src/compiler/serialize.ts +4 -4
  15. package/.claude/commands/add-card.md +0 -9
  16. package/.claude/commands/append-card.md +0 -10
  17. package/.claude/commands/play-card.md +0 -14
  18. package/.claude/commands/turn.md +0 -28
  19. package/.claude/settings.json +0 -10
  20. package/.flowdeck/.flowdeckignore +0 -5
  21. package/.flowdeck/AGENT.md +0 -46
  22. package/.flowdeck/TODO.md.template +0 -14
  23. package/.flowdeck/_discard/start/DISCARD.md +0 -8
  24. package/.flowdeck/_discard/start/TODO.md +0 -35
  25. package/.flowdeck/_discard/start/turn.log +0 -28
  26. package/.flowdeck/_energy/ADR.md.template +0 -100
  27. package/.flowdeck/_energy/CLAUDE.md.template +0 -95
  28. package/.flowdeck/_energy/GENERALINSIGHTS.md.template +0 -57
  29. package/.flowdeck/_energy/MISSION.md.template +0 -89
  30. package/.flowdeck/_energy/OPEN-QUESTIONS.md.template +0 -109
  31. package/.flowdeck/_energy/PROJECTINSIGHTS.md.template +0 -64
  32. package/.flowdeck/_energy/SPEC.md.template +0 -101
  33. package/.flowdeck/_frozen/FROZEN.md +0 -4
  34. package/.flowdeck/_meld/MELD.md +0 -4
  35. package/.flowdeck/_stock/STOCK.md +0 -4
  36. package/.flowdeck/reframe/TODO.md +0 -71
  37. package/FLOWDECK.md +0 -17
package/dist/cli.js CHANGED
@@ -9,44 +9,220 @@ import { readFileSync as readFileSync3 } from "fs";
9
9
  // src/commands/init.ts
10
10
  import fs from "fs";
11
11
  import path from "path";
12
- var TOOLS_JS = `import { z } from "mcpill-runtime";
13
-
14
- export default [
15
- {
16
- name: "read_file",
17
- description: "Read the contents of a file",
18
- schema: {
19
- path: z.string().describe("Path to the file to read"),
20
- },
21
- handler: async ({ path: filePath }) => {
22
- return { content: \`Contents of \${filePath}\` };
23
- },
24
- },
25
- ];
12
+ var PILL_AGENT_GUIDE_MD = `# pill-agent-guide
13
+
14
+ Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
15
+
16
+ ---
17
+
18
+ ## Steps
19
+
20
+ 1. **Parse** the sections in \`PILL.md\`:
21
+ - \`## Server\` \u2192 server name, description, transport (+ port if http)
22
+ - \`## Tool: <name>\` \u2192 one tool per section (repeatable)
23
+ - \`## Resource: <name>\` \u2192 static resource (optional, repeatable)
24
+ - \`## Prompt: <name>\` \u2192 prompt template (optional, repeatable)
25
+
26
+ 2. **Write** the server files into the pre-created scaffold at \`.mcpill/\`:
27
+
28
+ \`.mcpill/server.md\`
29
+ \`\`\`
30
+ ## Config
31
+ name: {name}
32
+ transport: {transport}
33
+ port: {port} \u2190 only if transport is http
34
+
35
+ ## Resources
36
+ uri: {uri}
37
+ name: {resource-name}
38
+ ---
39
+ {content}
40
+ \`\`\`
41
+ Omit \`## Resources\` entirely if none were specified.
42
+ Separate multiple resources with a blank line + \`---\` + blank line.
43
+
44
+ \`.mcpill/server/tools/{tool-name}.md\` \u2014 one file per \`## Tool\` section:
45
+ \`\`\`
46
+ # {tool-name}
47
+
48
+ {description}
49
+
50
+ ## Parameters
51
+
52
+ - {param} ({type}): {description}
53
+
54
+ ## Handler
55
+
56
+ \`\`\`js
57
+ async ({ {param} }) => {
58
+ return { content: [{ type: "text", text: {result} }] };
59
+ }
60
+ \`\`\`
61
+ \`\`\`
62
+
63
+ \`.mcpill/server/prompts/{prompt-name}.md\` \u2014 one file per \`## Prompt\` section:
64
+ \`\`\`
65
+ # {prompt-name}
66
+
67
+ {description}
68
+
69
+ ## Args
70
+
71
+ - {arg} ({type}): {description}
72
+
73
+ ## Message
74
+
75
+ > user: {message with {{arg}} placeholders}
76
+ \`\`\`
77
+ Omit \`## Args\` if the prompt takes no arguments.
78
+
79
+ 3. **Translate** each tool's \`behavior\` field into a working JS async handler.
80
+ - Implement the described logic fully \u2014 no stubs, no TODOs.
81
+ - Handler is self-contained: no top-level imports.
82
+ - For file I/O or network calls: use dynamic import inside the handler.
83
+ - Return shape: \`{ content: [{ type: "text", text: string }] }\`
84
+ - Allowed parameter types: \`string | number | boolean\`. Default unknown types to \`string\`.
85
+
86
+ 4. **Run** \`mcpill compile\`.
87
+
88
+ 5. **Report** what was generated and show the user the next commands:
89
+ \`\`\`
90
+ mcpill validate
91
+ mcpill run
92
+ mcpill pack
93
+ mcpill publish
94
+ \`\`\`
95
+
96
+ ---
97
+
98
+ ## Edge cases
99
+
100
+ | Situation | Handling |
101
+ |---|---|
102
+ | \`transport: http\` without port | Default to \`3333\` |
103
+ | Prompt message without role prefix | Wrap as \`> user: {message}\` |
104
+ | Server name with spaces | Convert to kebab-case for \`name\` field |
105
+ | Input type not \`string\\|number\\|boolean\` | Default to \`string\`, note the assumption |
106
+ | Vague \`behavior\` field | Implement the most literal reasonable interpretation |
26
107
  `;
27
- var PROMPTS_JSON = JSON.stringify(
28
- [
29
- {
30
- name: "summarize",
31
- description: "Summarize the given text",
32
- args: {
33
- text: { type: "string" }
34
- },
35
- messages: [
36
- {
37
- role: "user",
38
- content: "Please summarize the following text:\n\n{{text}}"
39
- }
40
- ]
41
- }
42
- ],
43
- null,
44
- 2
45
- );
46
- var RESOURCES_MD = `uri: config://app
47
- name: App Config
108
+ function makePillMd(projectName) {
109
+ return `<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
110
+ FOR THE AGENT \u2014 read this before doing anything else
111
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
112
+
113
+ You are building an mcpill MCP server from this document.
114
+ Follow the steps in .mcpill/pill-agent-guide.md.
115
+
116
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->
117
+
118
+ # Pill: ${projectName}
119
+
120
+ ---
121
+
122
+ ## Server
123
+
124
+ name: ${projectName}
125
+ description:
126
+ transport: stdio
127
+
128
+ ---
129
+
130
+ ## Tool: echo
131
+
132
+ description: Echo a message back to the caller.
133
+ inputs:
134
+ - message (string): The message to echo.
135
+ output: The same message, echoed back as text.
136
+ behavior: |
137
+ Return the message input unchanged.
138
+
48
139
  ---
49
- This resource exposes the application configuration.
140
+
141
+ ## Tool: greet
142
+
143
+ description: Generate a personalised greeting.
144
+ inputs:
145
+ - name (string): The name of the person to greet.
146
+ output: A greeting string addressed to the given name.
147
+ behavior: |
148
+ Return "Hello, {name}!" as a text response.
149
+
150
+ ---
151
+
152
+ ## Next Steps
153
+
154
+ Fill in your server above (replace or extend the example tools), then tell Claude:
155
+
156
+ > "Build this PILL.md"
157
+
158
+ After the source files are generated:
159
+
160
+ - [ ] \`mcpill compile\`
161
+ - [ ] \`mcpill validate\`
162
+ - [ ] \`mcpill run\`
163
+ - [ ] \`mcpill pack\`
164
+ - [ ] \`mcpill publish\`
165
+ `;
166
+ }
167
+ var HELLO_MCP_MD = `<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
168
+ HELLO-MCP \u2014 a ready-to-run example
169
+
170
+ Want to see mcpill in action before building your own server?
171
+
172
+ 1. Copy the content below into your PILL.md (replace everything).
173
+ 2. Tell Claude: "Build this PILL.md"
174
+ 3. Run: mcpill compile && mcpill validate && mcpill run
175
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->
176
+
177
+ # Pill: hello-mcp
178
+
179
+ ---
180
+
181
+ ## Server
182
+
183
+ name: hello-mcp
184
+ description: A hello-world MCP server \u2014 see mcpill in action.
185
+ transport: stdio
186
+
187
+ ---
188
+
189
+ ## Resource: server-info
190
+
191
+ uri: info://server
192
+ name: Server Info
193
+ content: |
194
+ hello-mcp is a demo MCP server built with mcpill.
195
+ Tools: ping, echo. Prompt: introduce.
196
+
197
+ ---
198
+
199
+ ## Tool: ping
200
+
201
+ description: Returns pong with the current UTC timestamp.
202
+ inputs:
203
+ output: A pong message with the current timestamp.
204
+ behavior: |
205
+ Return the string "pong \u2014 " followed by new Date().toISOString().
206
+
207
+ ---
208
+
209
+ ## Tool: echo
210
+
211
+ description: Echoes a message back to the caller.
212
+ inputs:
213
+ - message (string): The message to echo.
214
+ output: The same message, echoed back as text.
215
+ behavior: |
216
+ Return the message input unchanged.
217
+
218
+ ---
219
+
220
+ ## Prompt: introduce
221
+
222
+ description: Ask the server to introduce itself.
223
+ behavior: |
224
+ A prompt (no args) with a single user message:
225
+ "Introduce yourself \u2014 what is hello-mcp and what can it do?"
50
226
  `;
51
227
  var SERVER_MD_TEMPLATE = `## Config
52
228
  name: my-server
@@ -86,62 +262,111 @@ Generate a greeting
86
262
 
87
263
  > user: Say hello to {{name}}
88
264
  `;
265
+ function makePillUserGuideMd(projectName) {
266
+ return `# ${projectName} \u2014 mcpill user guide
267
+
268
+ ## Quickstart
269
+
270
+ 1. **Describe your server** in \`PILL.md\` \u2014 fill in the \`## Server\` block and add \`## Tool\` sections.
271
+ 2. **Build it** \u2014 open Claude Code and say:
272
+ > "Build this PILL.md"
273
+ Claude reads \`.mcpill/pill-agent-guide.md\` and generates the source files.
274
+ 3. **Compile**
275
+ \`\`\`sh
276
+ mcpill compile
277
+ \`\`\`
278
+ 4. **Test locally**
279
+ \`\`\`sh
280
+ mcpill validate # schema check
281
+ mcpill run # start the server
282
+ \`\`\`
283
+ 5. **Pack & publish**
284
+ \`\`\`sh
285
+ mcpill pack
286
+ mcpill publish
287
+ \`\`\`
288
+
289
+ ## Project layout
290
+
291
+ \`\`\`
292
+ ${projectName}/
293
+ \u251C\u2500\u2500 PILL.md \u2190 describe your server here
294
+ \u2514\u2500\u2500 .mcpill/
295
+ \u251C\u2500\u2500 server.md \u2190 server config (name, transport, resources)
296
+ \u251C\u2500\u2500 pill-agent-guide.md \u2190 instructions Claude follows when building
297
+ \u251C\u2500\u2500 pill-user-guide.md \u2190 this file
298
+ \u2514\u2500\u2500 server/
299
+ \u251C\u2500\u2500 mcpill.config.json \u2190 compiled config
300
+ \u251C\u2500\u2500 tools/ \u2190 one .md file per tool
301
+ \u2502 \u2514\u2500\u2500 echo.md
302
+ \u2514\u2500\u2500 prompts/ \u2190 one .md file per prompt (optional)
303
+ \u2514\u2500\u2500 greeting.md
304
+ \`\`\`
305
+
306
+ ## Editing tools manually
307
+
308
+ Each file in \`.mcpill/server/tools/\` follows this shape:
309
+
310
+ \`\`\`markdown
311
+ # tool-name
312
+
313
+ One-line description.
314
+
315
+ ## Parameters
316
+
317
+ - param (string): what it is
318
+
319
+ ## Handler
320
+
321
+ \`\`\`js
322
+ async ({ param }) => {
323
+ return { content: [{ type: "text", text: param }] };
324
+ }
325
+ \`\`\`
326
+ \`\`\`
327
+
328
+ Run \`mcpill compile\` after any edit to regenerate the server bundle.
329
+ `;
330
+ }
89
331
  async function runInit(opts) {
90
332
  const targetDir = path.resolve(opts.dir ?? process.cwd());
91
333
  const mcpillDir = path.join(targetDir, ".mcpill");
334
+ const serverDir = path.join(mcpillDir, "server");
92
335
  const projectName = path.basename(targetDir);
93
336
  if (fs.existsSync(mcpillDir)) {
94
337
  console.error(".mcpill/ already exists. Remove it manually to re-init.");
95
338
  process.exit(1);
96
339
  }
97
- const configJson = JSON.stringify(
98
- { name: projectName, transport: "stdio", port: 3333 },
99
- null,
100
- 2
101
- );
102
- fs.mkdirSync(mcpillDir, { recursive: true });
103
- fs.writeFileSync(path.join(mcpillDir, "tools.js"), TOOLS_JS);
104
- fs.writeFileSync(path.join(mcpillDir, "prompts.json"), PROMPTS_JSON);
105
- fs.writeFileSync(path.join(mcpillDir, "resources.md"), RESOURCES_MD);
106
- fs.writeFileSync(path.join(mcpillDir, "mcpill.config.json"), configJson);
107
- const toolsDir = path.join(targetDir, "tools");
108
- const promptsDir = path.join(targetDir, "prompts");
109
- fs.mkdirSync(toolsDir, { recursive: true });
110
- fs.mkdirSync(promptsDir, { recursive: true });
111
- fs.writeFileSync(path.join(toolsDir, "echo.md"), ECHO_TOOL_MD);
112
- fs.writeFileSync(path.join(promptsDir, "greeting.md"), GREETING_PROMPT_MD);
113
- const serverMd = SERVER_MD_TEMPLATE.replace(
114
- "name: my-server",
115
- `name: ${projectName}`
340
+ fs.mkdirSync(path.join(serverDir, "tools"), { recursive: true });
341
+ fs.mkdirSync(path.join(serverDir, "prompts"), { recursive: true });
342
+ const serverMd = SERVER_MD_TEMPLATE.replace("name: my-server", `name: ${projectName}`);
343
+ fs.writeFileSync(path.join(mcpillDir, "server.md"), serverMd);
344
+ fs.writeFileSync(path.join(serverDir, "tools", "echo.md"), ECHO_TOOL_MD);
345
+ fs.writeFileSync(path.join(serverDir, "prompts", "greeting.md"), GREETING_PROMPT_MD);
346
+ fs.writeFileSync(
347
+ path.join(serverDir, "mcpill.config.json"),
348
+ JSON.stringify({ name: projectName, transport: "stdio", port: 3333 }, null, 2)
116
349
  );
117
- fs.writeFileSync(path.join(targetDir, "server.md"), serverMd);
118
- const pkgJson = JSON.stringify(
119
- {
120
- name: projectName,
121
- version: "0.1.0",
122
- private: true,
123
- scripts: { pack: "mcpill pack", publish: "mcpill publish" }
124
- },
125
- null,
126
- 2
127
- );
128
- fs.writeFileSync(path.join(targetDir, "package.json"), pkgJson);
129
- console.log("\u2713 .mcpill/tools.js");
130
- console.log("\u2713 .mcpill/prompts.json");
131
- console.log("\u2713 .mcpill/resources.md");
132
- console.log("\u2713 .mcpill/mcpill.config.json");
133
- console.log("\u2713 tools/echo.md");
134
- console.log("\u2713 prompts/greeting.md");
135
- console.log("\u2713 server.md");
136
- console.log("\u2713 package.json");
137
- console.log("Edit your tools, then run: mcpill run");
350
+ fs.writeFileSync(path.join(mcpillDir, "pill-agent-guide.md"), PILL_AGENT_GUIDE_MD);
351
+ fs.writeFileSync(path.join(mcpillDir, "pill-user-guide.md"), makePillUserGuideMd(projectName));
352
+ fs.writeFileSync(path.join(mcpillDir, "HELLO-MCP.md"), HELLO_MCP_MD);
353
+ fs.writeFileSync(path.join(targetDir, "PILL.md"), makePillMd(projectName));
354
+ console.log('\u2713 PILL.md \u2190 describe your server, then tell Claude: "Build this PILL.md"');
355
+ console.log("\u2713 .mcpill/HELLO-MCP.md \u2190 copy into PILL.md to see a working example instantly");
356
+ console.log("\u2713 .mcpill/server.md \u2190 or edit source files directly");
357
+ console.log("\u2713 .mcpill/server/tools/echo.md");
358
+ console.log("\u2713 .mcpill/server/prompts/greeting.md");
359
+ console.log("\u2713 .mcpill/pill-agent-guide.md \u2190 agent instructions (read by Claude)");
360
+ console.log("\u2713 .mcpill/pill-user-guide.md \u2190 start here");
361
+ console.log("");
362
+ console.log("When ready: mcpill compile && mcpill run");
138
363
  }
139
364
 
140
365
  // src/commands/run.ts
141
366
  import path7 from "path";
142
367
  import fs6 from "fs";
143
368
  import { z as z2 } from "mcpill-runtime";
144
- import { createServer } from "mcpster";
369
+ import { createServer, applySetup } from "mcpster";
145
370
 
146
371
  // src/loaders/config.ts
147
372
  import fs2 from "fs";
@@ -333,26 +558,24 @@ async function validateOne(mcpillDir) {
333
558
  console.log(`\u2713 Valid: ${toolCount} tools, ${promptCount} prompts, ${resourceCount} resources`);
334
559
  }
335
560
  async function runValidate(baseDir) {
336
- const pillDirs = fs5.readdirSync(baseDir, { withFileTypes: true }).filter(
337
- (e) => e.isDirectory() && /^\.[a-z][a-z0-9-]*$/.test(e.name) && fs5.existsSync(path6.join(baseDir, e.name, "mcpill.config.json"))
338
- ).map((e) => path6.join(baseDir, e.name));
339
- if (pillDirs.length === 0) {
561
+ const serverDir = path6.join(baseDir, ".mcpill", "server");
562
+ if (!fs5.existsSync(path6.join(serverDir, "mcpill.config.json"))) {
340
563
  console.error("No pill directories found \u2014 run mcpill compile first");
341
564
  process.exit(1);
342
565
  }
343
- for (const mcpillDir of pillDirs) {
344
- await validateOne(mcpillDir);
345
- }
566
+ await validateOne(serverDir);
346
567
  }
347
568
 
348
569
  // src/commands/run.ts
349
570
  async function runServer(opts) {
350
571
  const baseDir = path7.resolve(opts.dir ?? process.cwd());
351
572
  await runValidate(baseDir);
352
- const pillDirs = fs6.readdirSync(baseDir, { withFileTypes: true }).filter(
353
- (e) => e.isDirectory() && /^\.[a-z][a-z0-9-]*$/.test(e.name) && fs6.existsSync(path7.join(baseDir, e.name, "mcpill.config.json"))
354
- ).map((e) => path7.join(baseDir, e.name));
355
- const mcpillDir = pillDirs[0];
573
+ const mcpillDir = path7.join(baseDir, ".mcpill", "server");
574
+ if (!fs6.existsSync(path7.join(mcpillDir, "mcpill.config.json"))) {
575
+ console.error("No pill found \u2014 run mcpill compile first");
576
+ process.exit(1);
577
+ }
578
+ ;
356
579
  const [tools, prompts, resources, config] = await Promise.all([
357
580
  loadTools(mcpillDir),
358
581
  loadPrompts(mcpillDir),
@@ -395,6 +618,15 @@ async function runServer(opts) {
395
618
  resolver: async () => content
396
619
  });
397
620
  }
621
+ applySetup(name, tools.map((t) => t.name), {
622
+ projectPath: baseDir,
623
+ permissions: "restrictive",
624
+ register: true,
625
+ cmdOverride: {
626
+ command: "mcpill",
627
+ args: ["run", "--dir", baseDir]
628
+ }
629
+ });
398
630
  try {
399
631
  await server.start();
400
632
  } catch (err) {
@@ -415,7 +647,7 @@ async function runServer(opts) {
415
647
 
416
648
  // src/commands/compile.ts
417
649
  import { resolve, join as join3 } from "path";
418
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync, readdirSync as readdirSync2 } from "fs";
650
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
419
651
  import { pathToFileURL as pathToFileURL2 } from "url";
420
652
 
421
653
  // src/compiler/parse.ts
@@ -562,7 +794,7 @@ function parseServerDir(dir) {
562
794
  };
563
795
  const resourcesBody = sections.get("Resources") ?? "";
564
796
  const resources = parseResourcesSection(resourcesBody);
565
- const toolsDir = join(dir, "tools");
797
+ const toolsDir = join(dir, "server", "tools");
566
798
  const tools = [];
567
799
  let toolFiles = [];
568
800
  try {
@@ -572,7 +804,7 @@ function parseServerDir(dir) {
572
804
  for (const file of toolFiles) {
573
805
  tools.push(parseToolFile(readFileSync(join(toolsDir, file), "utf-8")));
574
806
  }
575
- const promptsDir = join(dir, "prompts");
807
+ const promptsDir = join(dir, "server", "prompts");
576
808
  const prompts = [];
577
809
  let promptFiles = [];
578
810
  try {
@@ -601,8 +833,8 @@ ${r.content}`;
601
833
  }).join("\n---\n");
602
834
  }
603
835
  function serializeServerDir(doc, dir) {
604
- mkdirSync(join2(dir, "tools"), { recursive: true });
605
- mkdirSync(join2(dir, "prompts"), { recursive: true });
836
+ mkdirSync(join2(dir, "server", "tools"), { recursive: true });
837
+ mkdirSync(join2(dir, "server", "prompts"), { recursive: true });
606
838
  let serverMd = "## Config\n";
607
839
  serverMd += `name: ${doc.config.name}
608
840
  `;
@@ -642,7 +874,7 @@ ${cleanHandler}
642
874
  \`\`\`
643
875
  `;
644
876
  }
645
- writeFileSync(join2(dir, "tools", `${tool.name}.md`), md);
877
+ writeFileSync(join2(dir, "server", "tools", `${tool.name}.md`), md);
646
878
  }
647
879
  for (const prompt of doc.prompts) {
648
880
  let md = `# ${prompt.name}
@@ -666,7 +898,7 @@ ${cleanHandler}
666
898
  `;
667
899
  }
668
900
  }
669
- writeFileSync(join2(dir, "prompts", `${prompt.name}.md`), md);
901
+ writeFileSync(join2(dir, "server", "prompts", `${prompt.name}.md`), md);
670
902
  }
671
903
  console.log(`\u2713 tools/ updated (${doc.tools.length} files)`);
672
904
  console.log(`\u2713 prompts/ updated (${doc.prompts.length} files)`);
@@ -801,24 +1033,20 @@ mimeType: ${r.mimeType}`;
801
1033
  ${r.content}`;
802
1034
  }).join("\n---\n") + "\n";
803
1035
  }
804
- function findPillDirs(baseDir) {
805
- return readdirSync2(baseDir, { withFileTypes: true }).filter(
806
- (e) => e.isDirectory() && /^\.[a-z][a-z0-9-]*$/.test(e.name) && existsSync(join3(baseDir, e.name, "mcpill.config.json"))
807
- ).map((e) => join3(baseDir, e.name));
808
- }
809
1036
  async function runCompile(opts) {
810
1037
  const baseDir = resolve(opts.dir ?? process.cwd());
1038
+ const mcpillDir = join3(baseDir, ".mcpill");
1039
+ const serverDir = join3(mcpillDir, "server");
811
1040
  if (opts.toMd) {
812
- const pillDirs = findPillDirs(baseDir);
813
- if (pillDirs.length === 0) {
814
- throw new Error("No pill directories found \u2014 run mcpill compile first");
1041
+ const configPath = join3(serverDir, "mcpill.config.json");
1042
+ if (!existsSync(configPath)) {
1043
+ throw new Error("No pill found \u2014 run mcpill compile first");
815
1044
  }
816
- const mcpillDir2 = pillDirs[0];
817
- const config = await loadConfig(mcpillDir2);
818
- const resources = await loadResources(mcpillDir2);
819
- const promptsPath = join3(mcpillDir2, "prompts.json");
1045
+ const config = await loadConfig(serverDir);
1046
+ const resources = await loadResources(serverDir);
1047
+ const promptsPath = join3(serverDir, "prompts.json");
820
1048
  const prompts = JSON.parse(readFileSync2(promptsPath, "utf-8"));
821
- const toolsPath = join3(mcpillDir2, "tools.js");
1049
+ const toolsPath = join3(serverDir, "tools.js");
822
1050
  const handlers = existsSync(toolsPath) ? extractHandlers(readFileSync2(toolsPath, "utf-8")) : /* @__PURE__ */ new Map();
823
1051
  let tools = [];
824
1052
  if (existsSync(toolsPath)) {
@@ -848,12 +1076,11 @@ async function runCompile(opts) {
848
1076
  prompts,
849
1077
  resources
850
1078
  };
851
- serializeServerDir(doc2, baseDir);
1079
+ serializeServerDir(doc2, mcpillDir);
852
1080
  return;
853
1081
  }
854
- const doc = parseServerDir(baseDir);
855
- const mcpillDir = join3(baseDir, "." + doc.config.name);
856
- const toolsJsPath = join3(mcpillDir, "tools.js");
1082
+ const doc = parseServerDir(mcpillDir);
1083
+ const toolsJsPath = join3(serverDir, "tools.js");
857
1084
  const existing = existsSync(toolsJsPath) ? extractHandlers(readFileSync2(toolsJsPath, "utf-8")) : /* @__PURE__ */ new Map();
858
1085
  const { doc: mergedDoc, stubbed } = mergeHandlers(doc, existing);
859
1086
  if (opts.strict && stubbed.length > 0) {
@@ -862,33 +1089,22 @@ async function runCompile(opts) {
862
1089
  for (const name of stubbed) {
863
1090
  console.warn(`\u26A0 Stub generated for tool: ${name}`);
864
1091
  }
865
- mkdirSync2(mcpillDir, { recursive: true });
866
- writeFileSync2(join3(mcpillDir, "mcpill.config.json"), JSON.stringify(mergedDoc.config, null, 2));
867
- writeFileSync2(join3(mcpillDir, "prompts.json"), JSON.stringify(mergedDoc.prompts, null, 2));
868
- writeFileSync2(join3(mcpillDir, "resources.md"), serializeResourcesMd(mergedDoc.resources));
1092
+ mkdirSync2(serverDir, { recursive: true });
1093
+ writeFileSync2(join3(serverDir, "mcpill.config.json"), JSON.stringify(mergedDoc.config, null, 2));
1094
+ writeFileSync2(join3(serverDir, "prompts.json"), JSON.stringify(mergedDoc.prompts, null, 2));
1095
+ writeFileSync2(join3(serverDir, "resources.md"), serializeResourcesMd(mergedDoc.resources));
869
1096
  writeFileSync2(toolsJsPath, generateToolsJs(mergedDoc.tools));
870
- const pillDirName = "." + doc.config.name;
871
- console.log(`\u2713 ${pillDirName}/ updated from server.md, tools/, prompts/`);
1097
+ console.log(`\u2713 .mcpill/server/ updated from .mcpill/server.md, .mcpill/server/tools/, .mcpill/server/prompts/`);
872
1098
  }
873
1099
 
874
1100
  // src/commands/pack.ts
875
1101
  import fs7 from "fs";
876
1102
  import path8 from "path";
877
1103
  var SERVER_ENTRY_TEMPLATE = "import { runPill } from 'mcpill-runtime';\nrunPill();\n";
878
- function resolvePillDir(baseDir) {
879
- const entries = fs7.readdirSync(baseDir, { withFileTypes: true });
880
- const pill = entries.find(
881
- (e) => e.isDirectory() && /^\.[a-z][a-z0-9-]*$/.test(e.name) && fs7.existsSync(path8.join(baseDir, e.name, "mcpill.config.json"))
882
- );
883
- if (!pill) {
884
- throw new Error("No pill directory found \u2014 run mcpill compile first");
885
- }
886
- return path8.join(baseDir, pill.name);
887
- }
888
1104
  async function runPack(dir) {
889
1105
  const baseDir = path8.resolve(dir);
890
1106
  await runValidate(baseDir);
891
- const pillDir = resolvePillDir(baseDir);
1107
+ const pillDir = path8.join(baseDir, ".mcpill", "server");
892
1108
  const config = await loadConfig(pillDir);
893
1109
  const { name } = config;
894
1110
  fs7.mkdirSync(path8.join(baseDir, "bin"), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpill",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
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",
@@ -20,35 +20,36 @@ describe("runInit", () => {
20
20
  }
21
21
  });
22
22
 
23
- it("writes all four files with expected content in an empty dir", async () => {
23
+ it("scaffolds the expected files and directories", async () => {
24
24
  const base = mkTmp();
25
25
  vi.spyOn(console, "log").mockImplementation(() => {});
26
26
 
27
27
  await runInit({ dir: base });
28
28
 
29
29
  const mcpillDir = path.join(base, ".mcpill");
30
+ const serverDir = path.join(mcpillDir, "server");
31
+
30
32
  expect(fs.existsSync(mcpillDir)).toBe(true);
31
- expect(fs.existsSync(path.join(mcpillDir, "tools.js"))).toBe(true);
32
- expect(fs.existsSync(path.join(mcpillDir, "prompts.json"))).toBe(true);
33
- expect(fs.existsSync(path.join(mcpillDir, "resources.md"))).toBe(true);
34
- expect(fs.existsSync(path.join(mcpillDir, "mcpill.config.json"))).toBe(true);
33
+ expect(fs.existsSync(path.join(mcpillDir, "server.md"))).toBe(true);
34
+ expect(fs.existsSync(path.join(mcpillDir, "pill-agent-guide.md"))).toBe(true);
35
+ expect(fs.existsSync(path.join(mcpillDir, "pill-user-guide.md"))).toBe(true);
36
+ expect(fs.existsSync(path.join(serverDir, "mcpill.config.json"))).toBe(true);
37
+ expect(fs.existsSync(path.join(serverDir, "tools", "echo.md"))).toBe(true);
38
+ expect(fs.existsSync(path.join(serverDir, "prompts", "greeting.md"))).toBe(true);
39
+ expect(fs.existsSync(path.join(base, "PILL.md"))).toBe(true);
40
+ expect(fs.existsSync(path.join(base, "README.md"))).toBe(false);
41
+ expect(fs.existsSync(path.join(base, "package.json"))).toBe(false);
35
42
 
36
43
  const config = JSON.parse(
37
- fs.readFileSync(path.join(mcpillDir, "mcpill.config.json"), "utf-8")
44
+ fs.readFileSync(path.join(serverDir, "mcpill.config.json"), "utf-8")
38
45
  );
39
46
  expect(config).toMatchObject({ name: path.basename(base), transport: "stdio", port: 3333 });
40
47
 
41
- const prompts = JSON.parse(
42
- fs.readFileSync(path.join(mcpillDir, "prompts.json"), "utf-8")
43
- );
44
- expect(Array.isArray(prompts)).toBe(true);
45
- expect(prompts[0].name).toBe("summarize");
46
-
47
- const resources = fs.readFileSync(path.join(mcpillDir, "resources.md"), "utf-8");
48
- expect(resources).toContain("uri: config://app");
48
+ const serverMd = fs.readFileSync(path.join(mcpillDir, "server.md"), "utf-8");
49
+ expect(serverMd).toContain(`name: ${path.basename(base)}`);
49
50
 
50
- const tools = fs.readFileSync(path.join(mcpillDir, "tools.js"), "utf-8");
51
- expect(tools).toContain("read_file");
51
+ const pillMd = fs.readFileSync(path.join(base, "PILL.md"), "utf-8");
52
+ expect(pillMd).toContain(".mcpill/pill-agent-guide.md");
52
53
  });
53
54
 
54
55
  it("exits with code 1 and correct message when .mcpill/ already exists", async () => {
@@ -5,7 +5,7 @@ import os from "os";
5
5
  import { runPack, SERVER_ENTRY_TEMPLATE } from "../commands/pack.js";
6
6
 
7
7
  function scaffoldPill(baseDir: string, name = "test-pill") {
8
- const mcpillDir = path.join(baseDir, ".test-pill");
8
+ const mcpillDir = path.join(baseDir, ".mcpill", "server");
9
9
  fs.mkdirSync(mcpillDir, { recursive: true });
10
10
  fs.writeFileSync(
11
11
  path.join(mcpillDir, "tools.js"),