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.
- package/CHANGELOG.md +15 -0
- package/README.md +78 -10
- package/dist/cli.js +345 -129
- 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 +305 -81
- 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/.claude/commands/add-card.md +0 -9
- package/.claude/commands/append-card.md +0 -10
- package/.claude/commands/play-card.md +0 -14
- package/.claude/commands/turn.md +0 -28
- package/.claude/settings.json +0 -10
- package/.flowdeck/.flowdeckignore +0 -5
- package/.flowdeck/AGENT.md +0 -46
- package/.flowdeck/TODO.md.template +0 -14
- package/.flowdeck/_discard/start/DISCARD.md +0 -8
- package/.flowdeck/_discard/start/TODO.md +0 -35
- package/.flowdeck/_discard/start/turn.log +0 -28
- package/.flowdeck/_energy/ADR.md.template +0 -100
- package/.flowdeck/_energy/CLAUDE.md.template +0 -95
- package/.flowdeck/_energy/GENERALINSIGHTS.md.template +0 -57
- package/.flowdeck/_energy/MISSION.md.template +0 -89
- package/.flowdeck/_energy/OPEN-QUESTIONS.md.template +0 -109
- package/.flowdeck/_energy/PROJECTINSIGHTS.md.template +0 -64
- package/.flowdeck/_energy/SPEC.md.template +0 -101
- package/.flowdeck/_frozen/FROZEN.md +0 -4
- package/.flowdeck/_meld/MELD.md +0 -4
- package/.flowdeck/_stock/STOCK.md +0 -4
- package/.flowdeck/reframe/TODO.md +0 -71
- 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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
fs.
|
|
103
|
-
fs.writeFileSync(
|
|
104
|
-
|
|
105
|
-
|
|
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(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
console.log("
|
|
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
|
|
337
|
-
|
|
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
|
-
|
|
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
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
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
|
|
813
|
-
if (
|
|
814
|
-
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");
|
|
815
1044
|
}
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
const
|
|
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(
|
|
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,
|
|
1079
|
+
serializeServerDir(doc2, mcpillDir);
|
|
852
1080
|
return;
|
|
853
1081
|
}
|
|
854
|
-
const doc = parseServerDir(
|
|
855
|
-
const
|
|
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(
|
|
866
|
-
writeFileSync2(join3(
|
|
867
|
-
writeFileSync2(join3(
|
|
868
|
-
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));
|
|
869
1096
|
writeFileSync2(toolsJsPath, generateToolsJs(mergedDoc.tools));
|
|
870
|
-
|
|
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 =
|
|
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
|
@@ -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"),
|