mcpill 1.2.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.
- package/CHANGELOG.md +8 -1
- package/README.md +2 -0
- package/dist/cli.js +242 -118
- package/package.json +1 -1
- package/src/__tests__/init.test.ts +17 -16
- package/src/__tests__/pack.test.ts +1 -1
- package/src/__tests__/validate.test.ts +5 -5
- package/src/commands/compile.ts +20 -32
- package/src/commands/init.ts +202 -68
- package/src/commands/pack.ts +1 -15
- package/src/commands/run.ts +16 -12
- package/src/commands/validate.ts +3 -14
- package/src/compiler/parse.ts +2 -2
- package/src/compiler/serialize.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
- All init assets scoped under `.mcpill/` — only `PILL.md` is written to the project root
|
|
6
|
+
- `mcpill init` scaffolds `pill-user-guide.md` inside `.mcpill/` — covers quickstart, project layout, and manual tool editing; replaces any need for a root-level README
|
|
7
|
+
- `HELLO-MCP.md` added to `.mcpill/` — a ready-to-run example developers can copy into `PILL.md`
|
|
8
|
+
|
|
3
9
|
## 1.2.0
|
|
4
10
|
|
|
5
11
|
- `mcpill init` now scaffolds `PILL.md` — an intent-level spec the developer fills in plain English
|
|
6
|
-
- `
|
|
12
|
+
- `mcpill init` generates `pill-agent-guide.md` with the full agent instructions for building from `PILL.md`
|
|
13
|
+
- `PILL.md` references `pill-agent-guide.md`; tell Claude "Build this PILL.md" to generate source files and compile
|
|
7
14
|
- Example source files (`tools/echo.md`, `prompts/greeting.md`, `server.md`) still scaffolded for direct editing
|
|
8
15
|
- Removed `tools.js`, `prompts.json`, `resources.md` from `.mcpill/` initial scaffold (generated by `mcpill compile`)
|
|
9
16
|
- Updated `mcpill init` console output to guide both entry points
|
package/README.md
CHANGED
|
@@ -42,7 +42,9 @@ mcpill run # start the server
|
|
|
42
42
|
|
|
43
43
|
Scaffolds a new project in the current directory:
|
|
44
44
|
|
|
45
|
+
- `README.md` — quickstart guide for the scaffolded project
|
|
45
46
|
- `PILL.md` — intent-level spec; fill this and hand to Claude to generate source files
|
|
47
|
+
- `pill-agent-guide.md` — agent instructions read by Claude when building from `PILL.md`
|
|
46
48
|
- `server.md` — server config + resources
|
|
47
49
|
- `tools/echo.md` — example tool
|
|
48
50
|
- `prompts/greeting.md` — example prompt
|
package/dist/cli.js
CHANGED
|
@@ -9,25 +9,24 @@ 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
|
-
|
|
13
|
-
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
|
|
14
|
-
FOR THE AGENT \u2014 read this before doing anything else
|
|
15
|
-
\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
|
|
12
|
+
var PILL_AGENT_GUIDE_MD = `# pill-agent-guide
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
|
|
15
|
+
|
|
16
|
+
---
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
## Steps
|
|
20
19
|
|
|
21
|
-
1.
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
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)
|
|
26
25
|
|
|
27
|
-
2.
|
|
26
|
+
2. **Write** the server files into the pre-created scaffold at \`.mcpill/\`:
|
|
28
27
|
|
|
29
|
-
server.md
|
|
30
|
-
|
|
28
|
+
\`.mcpill/server.md\`
|
|
29
|
+
\`\`\`
|
|
31
30
|
## Config
|
|
32
31
|
name: {name}
|
|
33
32
|
transport: {transport}
|
|
@@ -38,11 +37,12 @@ STEPS:
|
|
|
38
37
|
name: {resource-name}
|
|
39
38
|
---
|
|
40
39
|
{content}
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
\`\`\`
|
|
41
|
+
Omit \`## Resources\` entirely if none were specified.
|
|
42
|
+
Separate multiple resources with a blank line + \`---\` + blank line.
|
|
43
43
|
|
|
44
|
-
tools/{tool-name}.md
|
|
45
|
-
|
|
44
|
+
\`.mcpill/server/tools/{tool-name}.md\` \u2014 one file per \`## Tool\` section:
|
|
45
|
+
\`\`\`
|
|
46
46
|
# {tool-name}
|
|
47
47
|
|
|
48
48
|
{description}
|
|
@@ -55,13 +55,13 @@ STEPS:
|
|
|
55
55
|
|
|
56
56
|
\`\`\`js
|
|
57
57
|
async ({ {param} }) => {
|
|
58
|
-
// real implementation \u2014 not a stub
|
|
59
58
|
return { content: [{ type: "text", text: {result} }] };
|
|
60
59
|
}
|
|
61
60
|
\`\`\`
|
|
61
|
+
\`\`\`
|
|
62
62
|
|
|
63
|
-
prompts/{prompt-name}.md
|
|
64
|
-
|
|
63
|
+
\`.mcpill/server/prompts/{prompt-name}.md\` \u2014 one file per \`## Prompt\` section:
|
|
64
|
+
\`\`\`
|
|
65
65
|
# {prompt-name}
|
|
66
66
|
|
|
67
67
|
{description}
|
|
@@ -73,26 +73,46 @@ STEPS:
|
|
|
73
73
|
## Message
|
|
74
74
|
|
|
75
75
|
> user: {message with {{arg}} placeholders}
|
|
76
|
-
|
|
76
|
+
\`\`\`
|
|
77
|
+
Omit \`## Args\` if the prompt takes no arguments.
|
|
77
78
|
|
|
78
|
-
3.
|
|
79
|
+
3. **Translate** each tool's \`behavior\` field into a working JS async handler.
|
|
79
80
|
- Implement the described logic fully \u2014 no stubs, no TODOs.
|
|
80
81
|
- Handler is self-contained: no top-level imports.
|
|
81
82
|
- For file I/O or network calls: use dynamic import inside the handler.
|
|
82
|
-
- Return shape
|
|
83
|
-
- Allowed parameter types: string | number | boolean
|
|
83
|
+
- Return shape: \`{ content: [{ type: "text", text: string }] }\`
|
|
84
|
+
- Allowed parameter types: \`string | number | boolean\`. Default unknown types to \`string\`.
|
|
84
85
|
|
|
85
|
-
4.
|
|
86
|
+
4. **Run** \`mcpill compile\`.
|
|
86
87
|
|
|
87
|
-
5.
|
|
88
|
+
5. **Report** what was generated and show the user the next commands:
|
|
89
|
+
\`\`\`
|
|
88
90
|
mcpill validate
|
|
89
91
|
mcpill run
|
|
90
|
-
mcpill pack
|
|
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 |
|
|
107
|
+
`;
|
|
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.
|
|
91
115
|
|
|
92
|
-
EDGE CASES:
|
|
93
|
-
- transport http without port \u2192 default to 3333
|
|
94
|
-
- prompt message without role prefix \u2192 wrap as \`> user: {message}\`
|
|
95
|
-
- server name with spaces \u2192 convert to kebab-case for directory and name field
|
|
96
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 -->
|
|
97
117
|
|
|
98
118
|
# Pill: ${projectName}
|
|
@@ -144,6 +164,66 @@ After the source files are generated:
|
|
|
144
164
|
- [ ] \`mcpill publish\`
|
|
145
165
|
`;
|
|
146
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?"
|
|
226
|
+
`;
|
|
147
227
|
var SERVER_MD_TEMPLATE = `## Config
|
|
148
228
|
name: my-server
|
|
149
229
|
transport: stdio
|
|
@@ -182,49 +262,102 @@ Generate a greeting
|
|
|
182
262
|
|
|
183
263
|
> user: Say hello to {{name}}
|
|
184
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
|
+
}
|
|
185
331
|
async function runInit(opts) {
|
|
186
332
|
const targetDir = path.resolve(opts.dir ?? process.cwd());
|
|
187
333
|
const mcpillDir = path.join(targetDir, ".mcpill");
|
|
334
|
+
const serverDir = path.join(mcpillDir, "server");
|
|
188
335
|
const projectName = path.basename(targetDir);
|
|
189
336
|
if (fs.existsSync(mcpillDir)) {
|
|
190
337
|
console.error(".mcpill/ already exists. Remove it manually to re-init.");
|
|
191
338
|
process.exit(1);
|
|
192
339
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
198
|
-
fs.
|
|
199
|
-
fs.writeFileSync(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
fs.mkdirSync(toolsDir, { recursive: true });
|
|
203
|
-
fs.mkdirSync(promptsDir, { recursive: true });
|
|
204
|
-
fs.writeFileSync(path.join(toolsDir, "echo.md"), ECHO_TOOL_MD);
|
|
205
|
-
fs.writeFileSync(path.join(promptsDir, "greeting.md"), GREETING_PROMPT_MD);
|
|
206
|
-
const serverMd = SERVER_MD_TEMPLATE.replace(
|
|
207
|
-
"name: my-server",
|
|
208
|
-
`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)
|
|
209
349
|
);
|
|
210
|
-
fs.writeFileSync(path.join(
|
|
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);
|
|
211
353
|
fs.writeFileSync(path.join(targetDir, "PILL.md"), makePillMd(projectName));
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
null,
|
|
220
|
-
2
|
|
221
|
-
);
|
|
222
|
-
fs.writeFileSync(path.join(targetDir, "package.json"), pkgJson);
|
|
223
|
-
console.log('\u2713 PILL.md \u2190 describe your server, then tell Claude: "Build this PILL.md"');
|
|
224
|
-
console.log("\u2713 server.md \u2190 or edit source files directly");
|
|
225
|
-
console.log("\u2713 tools/echo.md");
|
|
226
|
-
console.log("\u2713 prompts/greeting.md");
|
|
227
|
-
console.log("\u2713 package.json");
|
|
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");
|
|
228
361
|
console.log("");
|
|
229
362
|
console.log("When ready: mcpill compile && mcpill run");
|
|
230
363
|
}
|
|
@@ -233,7 +366,7 @@ async function runInit(opts) {
|
|
|
233
366
|
import path7 from "path";
|
|
234
367
|
import fs6 from "fs";
|
|
235
368
|
import { z as z2 } from "mcpill-runtime";
|
|
236
|
-
import { createServer } from "mcpster";
|
|
369
|
+
import { createServer, applySetup } from "mcpster";
|
|
237
370
|
|
|
238
371
|
// src/loaders/config.ts
|
|
239
372
|
import fs2 from "fs";
|
|
@@ -425,26 +558,24 @@ async function validateOne(mcpillDir) {
|
|
|
425
558
|
console.log(`\u2713 Valid: ${toolCount} tools, ${promptCount} prompts, ${resourceCount} resources`);
|
|
426
559
|
}
|
|
427
560
|
async function runValidate(baseDir) {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
).map((e) => path6.join(baseDir, e.name));
|
|
431
|
-
if (pillDirs.length === 0) {
|
|
561
|
+
const serverDir = path6.join(baseDir, ".mcpill", "server");
|
|
562
|
+
if (!fs5.existsSync(path6.join(serverDir, "mcpill.config.json"))) {
|
|
432
563
|
console.error("No pill directories found \u2014 run mcpill compile first");
|
|
433
564
|
process.exit(1);
|
|
434
565
|
}
|
|
435
|
-
|
|
436
|
-
await validateOne(mcpillDir);
|
|
437
|
-
}
|
|
566
|
+
await validateOne(serverDir);
|
|
438
567
|
}
|
|
439
568
|
|
|
440
569
|
// src/commands/run.ts
|
|
441
570
|
async function runServer(opts) {
|
|
442
571
|
const baseDir = path7.resolve(opts.dir ?? process.cwd());
|
|
443
572
|
await runValidate(baseDir);
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
;
|
|
448
579
|
const [tools, prompts, resources, config] = await Promise.all([
|
|
449
580
|
loadTools(mcpillDir),
|
|
450
581
|
loadPrompts(mcpillDir),
|
|
@@ -487,6 +618,15 @@ async function runServer(opts) {
|
|
|
487
618
|
resolver: async () => content
|
|
488
619
|
});
|
|
489
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
|
+
});
|
|
490
630
|
try {
|
|
491
631
|
await server.start();
|
|
492
632
|
} catch (err) {
|
|
@@ -507,7 +647,7 @@ async function runServer(opts) {
|
|
|
507
647
|
|
|
508
648
|
// src/commands/compile.ts
|
|
509
649
|
import { resolve, join as join3 } from "path";
|
|
510
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync
|
|
650
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
|
|
511
651
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
512
652
|
|
|
513
653
|
// src/compiler/parse.ts
|
|
@@ -654,7 +794,7 @@ function parseServerDir(dir) {
|
|
|
654
794
|
};
|
|
655
795
|
const resourcesBody = sections.get("Resources") ?? "";
|
|
656
796
|
const resources = parseResourcesSection(resourcesBody);
|
|
657
|
-
const toolsDir = join(dir, "tools");
|
|
797
|
+
const toolsDir = join(dir, "server", "tools");
|
|
658
798
|
const tools = [];
|
|
659
799
|
let toolFiles = [];
|
|
660
800
|
try {
|
|
@@ -664,7 +804,7 @@ function parseServerDir(dir) {
|
|
|
664
804
|
for (const file of toolFiles) {
|
|
665
805
|
tools.push(parseToolFile(readFileSync(join(toolsDir, file), "utf-8")));
|
|
666
806
|
}
|
|
667
|
-
const promptsDir = join(dir, "prompts");
|
|
807
|
+
const promptsDir = join(dir, "server", "prompts");
|
|
668
808
|
const prompts = [];
|
|
669
809
|
let promptFiles = [];
|
|
670
810
|
try {
|
|
@@ -693,8 +833,8 @@ ${r.content}`;
|
|
|
693
833
|
}).join("\n---\n");
|
|
694
834
|
}
|
|
695
835
|
function serializeServerDir(doc, dir) {
|
|
696
|
-
mkdirSync(join2(dir, "tools"), { recursive: true });
|
|
697
|
-
mkdirSync(join2(dir, "prompts"), { recursive: true });
|
|
836
|
+
mkdirSync(join2(dir, "server", "tools"), { recursive: true });
|
|
837
|
+
mkdirSync(join2(dir, "server", "prompts"), { recursive: true });
|
|
698
838
|
let serverMd = "## Config\n";
|
|
699
839
|
serverMd += `name: ${doc.config.name}
|
|
700
840
|
`;
|
|
@@ -734,7 +874,7 @@ ${cleanHandler}
|
|
|
734
874
|
\`\`\`
|
|
735
875
|
`;
|
|
736
876
|
}
|
|
737
|
-
writeFileSync(join2(dir, "tools", `${tool.name}.md`), md);
|
|
877
|
+
writeFileSync(join2(dir, "server", "tools", `${tool.name}.md`), md);
|
|
738
878
|
}
|
|
739
879
|
for (const prompt of doc.prompts) {
|
|
740
880
|
let md = `# ${prompt.name}
|
|
@@ -758,7 +898,7 @@ ${cleanHandler}
|
|
|
758
898
|
`;
|
|
759
899
|
}
|
|
760
900
|
}
|
|
761
|
-
writeFileSync(join2(dir, "prompts", `${prompt.name}.md`), md);
|
|
901
|
+
writeFileSync(join2(dir, "server", "prompts", `${prompt.name}.md`), md);
|
|
762
902
|
}
|
|
763
903
|
console.log(`\u2713 tools/ updated (${doc.tools.length} files)`);
|
|
764
904
|
console.log(`\u2713 prompts/ updated (${doc.prompts.length} files)`);
|
|
@@ -893,24 +1033,20 @@ mimeType: ${r.mimeType}`;
|
|
|
893
1033
|
${r.content}`;
|
|
894
1034
|
}).join("\n---\n") + "\n";
|
|
895
1035
|
}
|
|
896
|
-
function findPillDirs(baseDir) {
|
|
897
|
-
return readdirSync2(baseDir, { withFileTypes: true }).filter(
|
|
898
|
-
(e) => e.isDirectory() && /^\.[a-z][a-z0-9-]*$/.test(e.name) && existsSync(join3(baseDir, e.name, "mcpill.config.json"))
|
|
899
|
-
).map((e) => join3(baseDir, e.name));
|
|
900
|
-
}
|
|
901
1036
|
async function runCompile(opts) {
|
|
902
1037
|
const baseDir = resolve(opts.dir ?? process.cwd());
|
|
1038
|
+
const mcpillDir = join3(baseDir, ".mcpill");
|
|
1039
|
+
const serverDir = join3(mcpillDir, "server");
|
|
903
1040
|
if (opts.toMd) {
|
|
904
|
-
const
|
|
905
|
-
if (
|
|
906
|
-
throw new Error("No pill
|
|
1041
|
+
const configPath = join3(serverDir, "mcpill.config.json");
|
|
1042
|
+
if (!existsSync(configPath)) {
|
|
1043
|
+
throw new Error("No pill found \u2014 run mcpill compile first");
|
|
907
1044
|
}
|
|
908
|
-
const
|
|
909
|
-
const
|
|
910
|
-
const
|
|
911
|
-
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");
|
|
912
1048
|
const prompts = JSON.parse(readFileSync2(promptsPath, "utf-8"));
|
|
913
|
-
const toolsPath = join3(
|
|
1049
|
+
const toolsPath = join3(serverDir, "tools.js");
|
|
914
1050
|
const handlers = existsSync(toolsPath) ? extractHandlers(readFileSync2(toolsPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
915
1051
|
let tools = [];
|
|
916
1052
|
if (existsSync(toolsPath)) {
|
|
@@ -940,12 +1076,11 @@ async function runCompile(opts) {
|
|
|
940
1076
|
prompts,
|
|
941
1077
|
resources
|
|
942
1078
|
};
|
|
943
|
-
serializeServerDir(doc2,
|
|
1079
|
+
serializeServerDir(doc2, mcpillDir);
|
|
944
1080
|
return;
|
|
945
1081
|
}
|
|
946
|
-
const doc = parseServerDir(
|
|
947
|
-
const
|
|
948
|
-
const toolsJsPath = join3(mcpillDir, "tools.js");
|
|
1082
|
+
const doc = parseServerDir(mcpillDir);
|
|
1083
|
+
const toolsJsPath = join3(serverDir, "tools.js");
|
|
949
1084
|
const existing = existsSync(toolsJsPath) ? extractHandlers(readFileSync2(toolsJsPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
950
1085
|
const { doc: mergedDoc, stubbed } = mergeHandlers(doc, existing);
|
|
951
1086
|
if (opts.strict && stubbed.length > 0) {
|
|
@@ -954,33 +1089,22 @@ async function runCompile(opts) {
|
|
|
954
1089
|
for (const name of stubbed) {
|
|
955
1090
|
console.warn(`\u26A0 Stub generated for tool: ${name}`);
|
|
956
1091
|
}
|
|
957
|
-
mkdirSync2(
|
|
958
|
-
writeFileSync2(join3(
|
|
959
|
-
writeFileSync2(join3(
|
|
960
|
-
writeFileSync2(join3(
|
|
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));
|
|
961
1096
|
writeFileSync2(toolsJsPath, generateToolsJs(mergedDoc.tools));
|
|
962
|
-
|
|
963
|
-
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/`);
|
|
964
1098
|
}
|
|
965
1099
|
|
|
966
1100
|
// src/commands/pack.ts
|
|
967
1101
|
import fs7 from "fs";
|
|
968
1102
|
import path8 from "path";
|
|
969
1103
|
var SERVER_ENTRY_TEMPLATE = "import { runPill } from 'mcpill-runtime';\nrunPill();\n";
|
|
970
|
-
function resolvePillDir(baseDir) {
|
|
971
|
-
const entries = fs7.readdirSync(baseDir, { withFileTypes: true });
|
|
972
|
-
const pill = entries.find(
|
|
973
|
-
(e) => e.isDirectory() && /^\.[a-z][a-z0-9-]*$/.test(e.name) && fs7.existsSync(path8.join(baseDir, e.name, "mcpill.config.json"))
|
|
974
|
-
);
|
|
975
|
-
if (!pill) {
|
|
976
|
-
throw new Error("No pill directory found \u2014 run mcpill compile first");
|
|
977
|
-
}
|
|
978
|
-
return path8.join(baseDir, pill.name);
|
|
979
|
-
}
|
|
980
1104
|
async function runPack(dir) {
|
|
981
1105
|
const baseDir = path8.resolve(dir);
|
|
982
1106
|
await runValidate(baseDir);
|
|
983
|
-
const pillDir =
|
|
1107
|
+
const pillDir = path8.join(baseDir, ".mcpill", "server");
|
|
984
1108
|
const config = await loadConfig(pillDir);
|
|
985
1109
|
const { name } = config;
|
|
986
1110
|
fs7.mkdirSync(path8.join(baseDir, "bin"), { recursive: true });
|
package/package.json
CHANGED
|
@@ -20,35 +20,36 @@ describe("runInit", () => {
|
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it("
|
|
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, "
|
|
32
|
-
expect(fs.existsSync(path.join(mcpillDir, "
|
|
33
|
-
expect(fs.existsSync(path.join(mcpillDir, "
|
|
34
|
-
expect(fs.existsSync(path.join(
|
|
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(
|
|
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
|
|
42
|
-
|
|
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
|
|
51
|
-
expect(
|
|
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, ".
|
|
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"),
|
|
@@ -52,7 +52,7 @@ describe("runValidate", () => {
|
|
|
52
52
|
|
|
53
53
|
it("passes on a valid scaffolded pill dir", async () => {
|
|
54
54
|
const base = mkTmp();
|
|
55
|
-
const mcpillDir = path.join(base, ".mcpill");
|
|
55
|
+
const mcpillDir = path.join(base, ".mcpill", "server");
|
|
56
56
|
scaffoldValid(mcpillDir);
|
|
57
57
|
|
|
58
58
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
@@ -74,7 +74,7 @@ describe("runValidate", () => {
|
|
|
74
74
|
|
|
75
75
|
it("collects and prints tool error then exits 1", async () => {
|
|
76
76
|
const base = mkTmp();
|
|
77
|
-
const mcpillDir = path.join(base, ".mcpill");
|
|
77
|
+
const mcpillDir = path.join(base, ".mcpill", "server");
|
|
78
78
|
scaffoldValid(mcpillDir);
|
|
79
79
|
// Break tools.js: handler is not a function
|
|
80
80
|
fs.writeFileSync(
|
|
@@ -92,7 +92,7 @@ describe("runValidate", () => {
|
|
|
92
92
|
|
|
93
93
|
it("collects and prints prompt error then exits 1", async () => {
|
|
94
94
|
const base = mkTmp();
|
|
95
|
-
const mcpillDir = path.join(base, ".mcpill");
|
|
95
|
+
const mcpillDir = path.join(base, ".mcpill", "server");
|
|
96
96
|
scaffoldValid(mcpillDir);
|
|
97
97
|
fs.writeFileSync(path.join(mcpillDir, "prompts.json"), "not json {{{");
|
|
98
98
|
|
|
@@ -110,7 +110,7 @@ describe("runValidate", () => {
|
|
|
110
110
|
|
|
111
111
|
it("collects and prints resource error then exits 1", async () => {
|
|
112
112
|
const base = mkTmp();
|
|
113
|
-
const mcpillDir = path.join(base, ".mcpill");
|
|
113
|
+
const mcpillDir = path.join(base, ".mcpill", "server");
|
|
114
114
|
scaffoldValid(mcpillDir);
|
|
115
115
|
// Block missing uri
|
|
116
116
|
fs.writeFileSync(
|
|
@@ -130,7 +130,7 @@ describe("runValidate", () => {
|
|
|
130
130
|
|
|
131
131
|
it("collects errors from multiple loaders before exiting", async () => {
|
|
132
132
|
const base = mkTmp();
|
|
133
|
-
const mcpillDir = path.join(base, ".mcpill");
|
|
133
|
+
const mcpillDir = path.join(base, ".mcpill", "server");
|
|
134
134
|
scaffoldValid(mcpillDir);
|
|
135
135
|
// Break both tools and prompts
|
|
136
136
|
fs.writeFileSync(
|
package/src/commands/compile.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolve, join
|
|
2
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync
|
|
1
|
+
import { resolve, join } from 'path';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
3
3
|
import { pathToFileURL } from 'url';
|
|
4
4
|
import { parseServerDir } from '../compiler/parse.js';
|
|
5
5
|
import { serializeServerDir } from '../compiler/serialize.js';
|
|
@@ -62,38 +62,28 @@ function serializeResourcesMd(resources: ServerDoc['resources']): string {
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function findPillDirs(baseDir: string): string[] {
|
|
66
|
-
return readdirSync(baseDir, { withFileTypes: true })
|
|
67
|
-
.filter(
|
|
68
|
-
(e) =>
|
|
69
|
-
e.isDirectory() &&
|
|
70
|
-
/^\.[a-z][a-z0-9-]*$/.test(e.name) &&
|
|
71
|
-
existsSync(join(baseDir, e.name, 'mcpill.config.json')),
|
|
72
|
-
)
|
|
73
|
-
.map((e) => join(baseDir, e.name));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
65
|
export async function runCompile(opts: {
|
|
77
66
|
dir?: string;
|
|
78
67
|
toMd?: boolean;
|
|
79
68
|
strict?: boolean;
|
|
80
69
|
}): Promise<void> {
|
|
81
70
|
const baseDir = resolve(opts.dir ?? process.cwd());
|
|
71
|
+
const mcpillDir = join(baseDir, '.mcpill');
|
|
72
|
+
const serverDir = join(mcpillDir, 'server');
|
|
82
73
|
|
|
83
74
|
if (opts.toMd) {
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
throw new Error('No pill
|
|
75
|
+
const configPath = join(serverDir, 'mcpill.config.json');
|
|
76
|
+
if (!existsSync(configPath)) {
|
|
77
|
+
throw new Error('No pill found — run mcpill compile first');
|
|
87
78
|
}
|
|
88
|
-
const mcpillDir = pillDirs[0]!;
|
|
89
79
|
|
|
90
|
-
const config = await loadConfig(
|
|
91
|
-
const resources = await loadResources(
|
|
80
|
+
const config = await loadConfig(serverDir);
|
|
81
|
+
const resources = await loadResources(serverDir);
|
|
92
82
|
|
|
93
|
-
const promptsPath = join(
|
|
83
|
+
const promptsPath = join(serverDir, 'prompts.json');
|
|
94
84
|
const prompts = JSON.parse(readFileSync(promptsPath, 'utf-8')) as ServerDoc['prompts'];
|
|
95
85
|
|
|
96
|
-
const toolsPath = join(
|
|
86
|
+
const toolsPath = join(serverDir, 'tools.js');
|
|
97
87
|
const handlers = existsSync(toolsPath)
|
|
98
88
|
? extractHandlers(readFileSync(toolsPath, 'utf-8'))
|
|
99
89
|
: new Map<string, string>();
|
|
@@ -130,15 +120,14 @@ export async function runCompile(opts: {
|
|
|
130
120
|
resources,
|
|
131
121
|
};
|
|
132
122
|
|
|
133
|
-
serializeServerDir(doc,
|
|
123
|
+
serializeServerDir(doc, mcpillDir);
|
|
134
124
|
return;
|
|
135
125
|
}
|
|
136
126
|
|
|
137
|
-
// Forward direction: server.md + tools/ + prompts/ →
|
|
138
|
-
const doc = parseServerDir(
|
|
139
|
-
const mcpillDir = join(baseDir, '.' + doc.config.name);
|
|
127
|
+
// Forward direction: .mcpill/server.md + .mcpill/server/tools/ + .mcpill/server/prompts/ → .mcpill/server/
|
|
128
|
+
const doc = parseServerDir(mcpillDir);
|
|
140
129
|
|
|
141
|
-
const toolsJsPath = join(
|
|
130
|
+
const toolsJsPath = join(serverDir, 'tools.js');
|
|
142
131
|
const existing = existsSync(toolsJsPath)
|
|
143
132
|
? extractHandlers(readFileSync(toolsJsPath, 'utf-8'))
|
|
144
133
|
: new Map<string, string>();
|
|
@@ -152,12 +141,11 @@ export async function runCompile(opts: {
|
|
|
152
141
|
console.warn(`⚠ Stub generated for tool: ${name}`);
|
|
153
142
|
}
|
|
154
143
|
|
|
155
|
-
mkdirSync(
|
|
156
|
-
writeFileSync(join(
|
|
157
|
-
writeFileSync(join(
|
|
158
|
-
writeFileSync(join(
|
|
144
|
+
mkdirSync(serverDir, { recursive: true });
|
|
145
|
+
writeFileSync(join(serverDir, 'mcpill.config.json'), JSON.stringify(mergedDoc.config, null, 2));
|
|
146
|
+
writeFileSync(join(serverDir, 'prompts.json'), JSON.stringify(mergedDoc.prompts, null, 2));
|
|
147
|
+
writeFileSync(join(serverDir, 'resources.md'), serializeResourcesMd(mergedDoc.resources));
|
|
159
148
|
writeFileSync(toolsJsPath, generateToolsJs(mergedDoc.tools));
|
|
160
149
|
|
|
161
|
-
|
|
162
|
-
console.log(`✓ ${pillDirName}/ updated from server.md, tools/, prompts/`);
|
|
150
|
+
console.log(`✓ .mcpill/server/ updated from .mcpill/server.md, .mcpill/server/tools/, .mcpill/server/prompts/`);
|
|
163
151
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
return `<!-- ═══════════════════════════════════════════════════════════════
|
|
6
|
-
FOR THE AGENT — read this before doing anything else
|
|
7
|
-
═══════════════════════════════════════════════════════════════
|
|
4
|
+
const PILL_AGENT_GUIDE_MD = `# pill-agent-guide
|
|
8
5
|
|
|
9
|
-
|
|
6
|
+
Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
|
|
7
|
+
|
|
8
|
+
---
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
## Steps
|
|
12
11
|
|
|
13
|
-
1.
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
12
|
+
1. **Parse** the sections in \`PILL.md\`:
|
|
13
|
+
- \`## Server\` → server name, description, transport (+ port if http)
|
|
14
|
+
- \`## Tool: <name>\` → one tool per section (repeatable)
|
|
15
|
+
- \`## Resource: <name>\` → static resource (optional, repeatable)
|
|
16
|
+
- \`## Prompt: <name>\` → prompt template (optional, repeatable)
|
|
18
17
|
|
|
19
|
-
2.
|
|
18
|
+
2. **Write** the server files into the pre-created scaffold at \`.mcpill/\`:
|
|
20
19
|
|
|
21
|
-
server.md
|
|
22
|
-
|
|
20
|
+
\`.mcpill/server.md\`
|
|
21
|
+
\`\`\`
|
|
23
22
|
## Config
|
|
24
23
|
name: {name}
|
|
25
24
|
transport: {transport}
|
|
@@ -30,11 +29,12 @@ STEPS:
|
|
|
30
29
|
name: {resource-name}
|
|
31
30
|
---
|
|
32
31
|
{content}
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
\`\`\`
|
|
33
|
+
Omit \`## Resources\` entirely if none were specified.
|
|
34
|
+
Separate multiple resources with a blank line + \`---\` + blank line.
|
|
35
35
|
|
|
36
|
-
tools/{tool-name}.md
|
|
37
|
-
|
|
36
|
+
\`.mcpill/server/tools/{tool-name}.md\` — one file per \`## Tool\` section:
|
|
37
|
+
\`\`\`
|
|
38
38
|
# {tool-name}
|
|
39
39
|
|
|
40
40
|
{description}
|
|
@@ -47,13 +47,13 @@ STEPS:
|
|
|
47
47
|
|
|
48
48
|
\`\`\`js
|
|
49
49
|
async ({ {param} }) => {
|
|
50
|
-
// real implementation — not a stub
|
|
51
50
|
return { content: [{ type: "text", text: {result} }] };
|
|
52
51
|
}
|
|
53
52
|
\`\`\`
|
|
53
|
+
\`\`\`
|
|
54
54
|
|
|
55
|
-
prompts/{prompt-name}.md
|
|
56
|
-
|
|
55
|
+
\`.mcpill/server/prompts/{prompt-name}.md\` — one file per \`## Prompt\` section:
|
|
56
|
+
\`\`\`
|
|
57
57
|
# {prompt-name}
|
|
58
58
|
|
|
59
59
|
{description}
|
|
@@ -65,26 +65,47 @@ STEPS:
|
|
|
65
65
|
## Message
|
|
66
66
|
|
|
67
67
|
> user: {message with {{arg}} placeholders}
|
|
68
|
-
|
|
68
|
+
\`\`\`
|
|
69
|
+
Omit \`## Args\` if the prompt takes no arguments.
|
|
69
70
|
|
|
70
|
-
3.
|
|
71
|
+
3. **Translate** each tool's \`behavior\` field into a working JS async handler.
|
|
71
72
|
- Implement the described logic fully — no stubs, no TODOs.
|
|
72
73
|
- Handler is self-contained: no top-level imports.
|
|
73
74
|
- For file I/O or network calls: use dynamic import inside the handler.
|
|
74
|
-
- Return shape
|
|
75
|
-
- Allowed parameter types: string | number | boolean
|
|
75
|
+
- Return shape: \`{ content: [{ type: "text", text: string }] }\`
|
|
76
|
+
- Allowed parameter types: \`string | number | boolean\`. Default unknown types to \`string\`.
|
|
76
77
|
|
|
77
|
-
4.
|
|
78
|
+
4. **Run** \`mcpill compile\`.
|
|
78
79
|
|
|
79
|
-
5.
|
|
80
|
+
5. **Report** what was generated and show the user the next commands:
|
|
81
|
+
\`\`\`
|
|
80
82
|
mcpill validate
|
|
81
83
|
mcpill run
|
|
82
|
-
mcpill pack
|
|
84
|
+
mcpill pack
|
|
85
|
+
mcpill publish
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Edge cases
|
|
91
|
+
|
|
92
|
+
| Situation | Handling |
|
|
93
|
+
|---|---|
|
|
94
|
+
| \`transport: http\` without port | Default to \`3333\` |
|
|
95
|
+
| Prompt message without role prefix | Wrap as \`> user: {message}\` |
|
|
96
|
+
| Server name with spaces | Convert to kebab-case for \`name\` field |
|
|
97
|
+
| Input type not \`string\\|number\\|boolean\` | Default to \`string\`, note the assumption |
|
|
98
|
+
| Vague \`behavior\` field | Implement the most literal reasonable interpretation |
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
function makePillMd(projectName: string): string {
|
|
102
|
+
return `<!-- ═══════════════════════════════════════════════════════════════
|
|
103
|
+
FOR THE AGENT — read this before doing anything else
|
|
104
|
+
═══════════════════════════════════════════════════════════════
|
|
105
|
+
|
|
106
|
+
You are building an mcpill MCP server from this document.
|
|
107
|
+
Follow the steps in .mcpill/pill-agent-guide.md.
|
|
83
108
|
|
|
84
|
-
EDGE CASES:
|
|
85
|
-
- transport http without port → default to 3333
|
|
86
|
-
- prompt message without role prefix → wrap as \`> user: {message}\`
|
|
87
|
-
- server name with spaces → convert to kebab-case for directory and name field
|
|
88
109
|
════════════════════════════════════════════════════════════════ -->
|
|
89
110
|
|
|
90
111
|
# Pill: ${projectName}
|
|
@@ -137,6 +158,67 @@ After the source files are generated:
|
|
|
137
158
|
`;
|
|
138
159
|
}
|
|
139
160
|
|
|
161
|
+
const HELLO_MCP_MD = `<!-- ═══════════════════════════════════════════════════════════════
|
|
162
|
+
HELLO-MCP — a ready-to-run example
|
|
163
|
+
|
|
164
|
+
Want to see mcpill in action before building your own server?
|
|
165
|
+
|
|
166
|
+
1. Copy the content below into your PILL.md (replace everything).
|
|
167
|
+
2. Tell Claude: "Build this PILL.md"
|
|
168
|
+
3. Run: mcpill compile && mcpill validate && mcpill run
|
|
169
|
+
═══════════════════════════════════════════════════════════════ -->
|
|
170
|
+
|
|
171
|
+
# Pill: hello-mcp
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Server
|
|
176
|
+
|
|
177
|
+
name: hello-mcp
|
|
178
|
+
description: A hello-world MCP server — see mcpill in action.
|
|
179
|
+
transport: stdio
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Resource: server-info
|
|
184
|
+
|
|
185
|
+
uri: info://server
|
|
186
|
+
name: Server Info
|
|
187
|
+
content: |
|
|
188
|
+
hello-mcp is a demo MCP server built with mcpill.
|
|
189
|
+
Tools: ping, echo. Prompt: introduce.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Tool: ping
|
|
194
|
+
|
|
195
|
+
description: Returns pong with the current UTC timestamp.
|
|
196
|
+
inputs:
|
|
197
|
+
output: A pong message with the current timestamp.
|
|
198
|
+
behavior: |
|
|
199
|
+
Return the string "pong — " followed by new Date().toISOString().
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Tool: echo
|
|
204
|
+
|
|
205
|
+
description: Echoes a message back to the caller.
|
|
206
|
+
inputs:
|
|
207
|
+
- message (string): The message to echo.
|
|
208
|
+
output: The same message, echoed back as text.
|
|
209
|
+
behavior: |
|
|
210
|
+
Return the message input unchanged.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Prompt: introduce
|
|
215
|
+
|
|
216
|
+
description: Ask the server to introduce itself.
|
|
217
|
+
behavior: |
|
|
218
|
+
A prompt (no args) with a single user message:
|
|
219
|
+
"Introduce yourself — what is hello-mcp and what can it do?"
|
|
220
|
+
`;
|
|
221
|
+
|
|
140
222
|
// Embedded content of src/templates/server.md — tsup does not copy non-TS assets,
|
|
141
223
|
// so the template is inlined here. Keep in sync with src/templates/server.md.
|
|
142
224
|
const SERVER_MD_TEMPLATE = `## Config
|
|
@@ -180,9 +262,77 @@ Generate a greeting
|
|
|
180
262
|
> user: Say hello to {{name}}
|
|
181
263
|
`;
|
|
182
264
|
|
|
265
|
+
function makePillUserGuideMd(projectName: string): string {
|
|
266
|
+
return `# ${projectName} — mcpill user guide
|
|
267
|
+
|
|
268
|
+
## Quickstart
|
|
269
|
+
|
|
270
|
+
1. **Describe your server** in \`PILL.md\` — fill in the \`## Server\` block and add \`## Tool\` sections.
|
|
271
|
+
2. **Build it** — 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
|
+
├── PILL.md ← describe your server here
|
|
294
|
+
└── .mcpill/
|
|
295
|
+
├── server.md ← server config (name, transport, resources)
|
|
296
|
+
├── pill-agent-guide.md ← instructions Claude follows when building
|
|
297
|
+
├── pill-user-guide.md ← this file
|
|
298
|
+
└── server/
|
|
299
|
+
├── mcpill.config.json ← compiled config
|
|
300
|
+
├── tools/ ← one .md file per tool
|
|
301
|
+
│ └── echo.md
|
|
302
|
+
└── prompts/ ← one .md file per prompt (optional)
|
|
303
|
+
└── 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
|
+
}
|
|
331
|
+
|
|
183
332
|
export async function runInit(opts: { dir?: string }): Promise<void> {
|
|
184
333
|
const targetDir = path.resolve(opts.dir ?? process.cwd());
|
|
185
334
|
const mcpillDir = path.join(targetDir, ".mcpill");
|
|
335
|
+
const serverDir = path.join(mcpillDir, "server");
|
|
186
336
|
const projectName = path.basename(targetDir);
|
|
187
337
|
|
|
188
338
|
if (fs.existsSync(mcpillDir)) {
|
|
@@ -190,46 +340,30 @@ export async function runInit(opts: { dir?: string }): Promise<void> {
|
|
|
190
340
|
process.exit(1);
|
|
191
341
|
}
|
|
192
342
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
null,
|
|
196
|
-
2,
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
fs.mkdirSync(mcpillDir, { recursive: true });
|
|
200
|
-
fs.writeFileSync(path.join(mcpillDir, "mcpill.config.json"), configJson);
|
|
201
|
-
|
|
202
|
-
const toolsDir = path.join(targetDir, "tools");
|
|
203
|
-
const promptsDir = path.join(targetDir, "prompts");
|
|
204
|
-
fs.mkdirSync(toolsDir, { recursive: true });
|
|
205
|
-
fs.mkdirSync(promptsDir, { recursive: true });
|
|
206
|
-
fs.writeFileSync(path.join(toolsDir, "echo.md"), ECHO_TOOL_MD);
|
|
207
|
-
fs.writeFileSync(path.join(promptsDir, "greeting.md"), GREETING_PROMPT_MD);
|
|
343
|
+
fs.mkdirSync(path.join(serverDir, "tools"), { recursive: true });
|
|
344
|
+
fs.mkdirSync(path.join(serverDir, "prompts"), { recursive: true });
|
|
208
345
|
|
|
209
|
-
const serverMd = SERVER_MD_TEMPLATE.replace(
|
|
210
|
-
|
|
211
|
-
|
|
346
|
+
const serverMd = SERVER_MD_TEMPLATE.replace("name: my-server", `name: ${projectName}`);
|
|
347
|
+
fs.writeFileSync(path.join(mcpillDir, "server.md"), serverMd);
|
|
348
|
+
fs.writeFileSync(path.join(serverDir, "tools", "echo.md"), ECHO_TOOL_MD);
|
|
349
|
+
fs.writeFileSync(path.join(serverDir, "prompts", "greeting.md"), GREETING_PROMPT_MD);
|
|
350
|
+
fs.writeFileSync(
|
|
351
|
+
path.join(serverDir, "mcpill.config.json"),
|
|
352
|
+
JSON.stringify({ name: projectName, transport: "stdio", port: 3333 }, null, 2),
|
|
212
353
|
);
|
|
213
|
-
fs.writeFileSync(path.join(
|
|
214
|
-
fs.writeFileSync(path.join(
|
|
354
|
+
fs.writeFileSync(path.join(mcpillDir, "pill-agent-guide.md"), PILL_AGENT_GUIDE_MD);
|
|
355
|
+
fs.writeFileSync(path.join(mcpillDir, "pill-user-guide.md"), makePillUserGuideMd(projectName));
|
|
356
|
+
fs.writeFileSync(path.join(mcpillDir, "HELLO-MCP.md"), HELLO_MCP_MD);
|
|
215
357
|
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
name: projectName,
|
|
219
|
-
version: "0.1.0",
|
|
220
|
-
private: true,
|
|
221
|
-
scripts: { pack: "mcpill pack", publish: "mcpill publish" },
|
|
222
|
-
},
|
|
223
|
-
null,
|
|
224
|
-
2,
|
|
225
|
-
);
|
|
226
|
-
fs.writeFileSync(path.join(targetDir, "package.json"), pkgJson);
|
|
358
|
+
fs.writeFileSync(path.join(targetDir, "PILL.md"), makePillMd(projectName));
|
|
227
359
|
|
|
228
|
-
console.log("✓ PILL.md
|
|
229
|
-
console.log("✓
|
|
230
|
-
console.log("✓
|
|
231
|
-
console.log("✓
|
|
232
|
-
console.log("✓
|
|
360
|
+
console.log("✓ PILL.md ← describe your server, then tell Claude: \"Build this PILL.md\"");
|
|
361
|
+
console.log("✓ .mcpill/HELLO-MCP.md ← copy into PILL.md to see a working example instantly");
|
|
362
|
+
console.log("✓ .mcpill/server.md ← or edit source files directly");
|
|
363
|
+
console.log("✓ .mcpill/server/tools/echo.md");
|
|
364
|
+
console.log("✓ .mcpill/server/prompts/greeting.md");
|
|
365
|
+
console.log("✓ .mcpill/pill-agent-guide.md ← agent instructions (read by Claude)");
|
|
366
|
+
console.log("✓ .mcpill/pill-user-guide.md ← start here");
|
|
233
367
|
console.log("");
|
|
234
368
|
console.log("When ready: mcpill compile && mcpill run");
|
|
235
369
|
}
|
package/src/commands/pack.ts
CHANGED
|
@@ -6,25 +6,11 @@ import { loadConfig } from "../loaders/config.js";
|
|
|
6
6
|
export const SERVER_ENTRY_TEMPLATE =
|
|
7
7
|
"import { runPill } from 'mcpill-runtime';\nrunPill();\n";
|
|
8
8
|
|
|
9
|
-
function resolvePillDir(baseDir: string): string {
|
|
10
|
-
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
11
|
-
const pill = entries.find(
|
|
12
|
-
(e) =>
|
|
13
|
-
e.isDirectory() &&
|
|
14
|
-
/^\.[a-z][a-z0-9-]*$/.test(e.name) &&
|
|
15
|
-
fs.existsSync(path.join(baseDir, e.name, "mcpill.config.json")),
|
|
16
|
-
);
|
|
17
|
-
if (!pill) {
|
|
18
|
-
throw new Error("No pill directory found — run mcpill compile first");
|
|
19
|
-
}
|
|
20
|
-
return path.join(baseDir, pill.name);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
export async function runPack(dir: string): Promise<void> {
|
|
24
10
|
const baseDir = path.resolve(dir);
|
|
25
11
|
await runValidate(baseDir);
|
|
26
12
|
|
|
27
|
-
const pillDir =
|
|
13
|
+
const pillDir = path.join(baseDir, ".mcpill", "server");
|
|
28
14
|
const config = await loadConfig(pillDir);
|
|
29
15
|
const { name } = config;
|
|
30
16
|
|
package/src/commands/run.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { z } from "mcpill-runtime";
|
|
4
|
-
import { createServer } from "mcpster";
|
|
4
|
+
import { createServer, applySetup } from "mcpster";
|
|
5
5
|
import { loadConfig } from "../loaders/config.js";
|
|
6
6
|
import { loadTools } from "../loaders/tools.js";
|
|
7
7
|
import { loadPrompts } from "../loaders/prompts.js";
|
|
@@ -19,17 +19,11 @@ export async function runServer(opts: {
|
|
|
19
19
|
|
|
20
20
|
await runValidate(baseDir);
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/^\.[a-z][a-z0-9-]*$/.test(e.name) &&
|
|
28
|
-
fs.existsSync(path.join(baseDir, e.name, "mcpill.config.json")),
|
|
29
|
-
)
|
|
30
|
-
.map((e) => path.join(baseDir, e.name));
|
|
31
|
-
|
|
32
|
-
const mcpillDir = pillDirs[0]!;
|
|
22
|
+
const mcpillDir = path.join(baseDir, ".mcpill", "server");
|
|
23
|
+
if (!fs.existsSync(path.join(mcpillDir, "mcpill.config.json"))) {
|
|
24
|
+
console.error("No pill found — run mcpill compile first");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
};
|
|
33
27
|
|
|
34
28
|
const [tools, prompts, resources, config] = await Promise.all([
|
|
35
29
|
loadTools(mcpillDir),
|
|
@@ -82,6 +76,16 @@ export async function runServer(opts: {
|
|
|
82
76
|
});
|
|
83
77
|
}
|
|
84
78
|
|
|
79
|
+
applySetup(name, tools.map((t) => t.name), {
|
|
80
|
+
projectPath: baseDir,
|
|
81
|
+
permissions: 'restrictive',
|
|
82
|
+
register: true,
|
|
83
|
+
cmdOverride: {
|
|
84
|
+
command: 'mcpill',
|
|
85
|
+
args: ['run', '--dir', baseDir],
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
85
89
|
try {
|
|
86
90
|
await server.start();
|
|
87
91
|
} catch (err) {
|
package/src/commands/validate.ts
CHANGED
|
@@ -49,22 +49,11 @@ async function validateOne(mcpillDir: string): Promise<void> {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export async function runValidate(baseDir: string): Promise<void> {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
.filter(
|
|
55
|
-
(e) =>
|
|
56
|
-
e.isDirectory() &&
|
|
57
|
-
/^\.[a-z][a-z0-9-]*$/.test(e.name) &&
|
|
58
|
-
fs.existsSync(path.join(baseDir, e.name, "mcpill.config.json")),
|
|
59
|
-
)
|
|
60
|
-
.map((e) => path.join(baseDir, e.name));
|
|
61
|
-
|
|
62
|
-
if (pillDirs.length === 0) {
|
|
52
|
+
const serverDir = path.join(baseDir, ".mcpill", "server");
|
|
53
|
+
if (!fs.existsSync(path.join(serverDir, "mcpill.config.json"))) {
|
|
63
54
|
console.error("No pill directories found — run mcpill compile first");
|
|
64
55
|
process.exit(1);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
await validateOne(mcpillDir);
|
|
69
|
-
}
|
|
58
|
+
await validateOne(serverDir);
|
|
70
59
|
}
|
package/src/compiler/parse.ts
CHANGED
|
@@ -208,7 +208,7 @@ export function parseServerDir(dir: string): ServerDoc {
|
|
|
208
208
|
const resourcesBody = sections.get('Resources') ?? '';
|
|
209
209
|
const resources = parseResourcesSection(resourcesBody);
|
|
210
210
|
|
|
211
|
-
const toolsDir = join(dir, 'tools');
|
|
211
|
+
const toolsDir = join(dir, 'server', 'tools');
|
|
212
212
|
const tools: ToolDoc[] = [];
|
|
213
213
|
let toolFiles: string[] = [];
|
|
214
214
|
try {
|
|
@@ -220,7 +220,7 @@ export function parseServerDir(dir: string): ServerDoc {
|
|
|
220
220
|
tools.push(parseToolFile(readFileSync(join(toolsDir, file), 'utf-8')));
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
const promptsDir = join(dir, 'prompts');
|
|
223
|
+
const promptsDir = join(dir, 'server', 'prompts');
|
|
224
224
|
const prompts: PromptDoc[] = [];
|
|
225
225
|
let promptFiles: string[] = [];
|
|
226
226
|
try {
|
|
@@ -43,8 +43,8 @@ export function serializeServerDoc(doc: ServerDoc): string {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function serializeServerDir(doc: ServerDoc, dir: string): void {
|
|
46
|
-
mkdirSync(join(dir, 'tools'), { recursive: true });
|
|
47
|
-
mkdirSync(join(dir, 'prompts'), { recursive: true });
|
|
46
|
+
mkdirSync(join(dir, 'server', 'tools'), { recursive: true });
|
|
47
|
+
mkdirSync(join(dir, 'server', 'prompts'), { recursive: true });
|
|
48
48
|
|
|
49
49
|
let serverMd = '## Config\n';
|
|
50
50
|
serverMd += `name: ${doc.config.name}\n`;
|
|
@@ -73,7 +73,7 @@ export function serializeServerDir(doc: ServerDoc, dir: string): void {
|
|
|
73
73
|
.trim();
|
|
74
74
|
md += `\n## Handler\n\n\`\`\`js\n${cleanHandler}\n\`\`\`\n`;
|
|
75
75
|
}
|
|
76
|
-
writeFileSync(join(dir, 'tools', `${tool.name}.md`), md);
|
|
76
|
+
writeFileSync(join(dir, 'server', 'tools', `${tool.name}.md`), md);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
for (const prompt of doc.prompts) {
|
|
@@ -92,7 +92,7 @@ export function serializeServerDir(doc: ServerDoc, dir: string): void {
|
|
|
92
92
|
md += `> ${msg.role}: ${msg.content}\n`;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
writeFileSync(join(dir, 'prompts', `${prompt.name}.md`), md);
|
|
95
|
+
writeFileSync(join(dir, 'server', 'prompts', `${prompt.name}.md`), md);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
console.log(`✓ tools/ updated (${doc.tools.length} files)`);
|