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
|
@@ -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,46 +1,222 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
const PILL_AGENT_GUIDE_MD = `# pill-agent-guide
|
|
5
|
+
|
|
6
|
+
Instructions for building an mcpill MCP server from a filled \`PILL.md\`.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
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)
|
|
17
|
+
|
|
18
|
+
2. **Write** the server files into the pre-created scaffold at \`.mcpill/\`:
|
|
19
|
+
|
|
20
|
+
\`.mcpill/server.md\`
|
|
21
|
+
\`\`\`
|
|
22
|
+
## Config
|
|
23
|
+
name: {name}
|
|
24
|
+
transport: {transport}
|
|
25
|
+
port: {port} ← only if transport is http
|
|
26
|
+
|
|
27
|
+
## Resources
|
|
28
|
+
uri: {uri}
|
|
29
|
+
name: {resource-name}
|
|
30
|
+
---
|
|
31
|
+
{content}
|
|
32
|
+
\`\`\`
|
|
33
|
+
Omit \`## Resources\` entirely if none were specified.
|
|
34
|
+
Separate multiple resources with a blank line + \`---\` + blank line.
|
|
35
|
+
|
|
36
|
+
\`.mcpill/server/tools/{tool-name}.md\` — one file per \`## Tool\` section:
|
|
37
|
+
\`\`\`
|
|
38
|
+
# {tool-name}
|
|
39
|
+
|
|
40
|
+
{description}
|
|
41
|
+
|
|
42
|
+
## Parameters
|
|
43
|
+
|
|
44
|
+
- {param} ({type}): {description}
|
|
45
|
+
|
|
46
|
+
## Handler
|
|
47
|
+
|
|
48
|
+
\`\`\`js
|
|
49
|
+
async ({ {param} }) => {
|
|
50
|
+
return { content: [{ type: "text", text: {result} }] };
|
|
51
|
+
}
|
|
52
|
+
\`\`\`
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
\`.mcpill/server/prompts/{prompt-name}.md\` — one file per \`## Prompt\` section:
|
|
56
|
+
\`\`\`
|
|
57
|
+
# {prompt-name}
|
|
58
|
+
|
|
59
|
+
{description}
|
|
60
|
+
|
|
61
|
+
## Args
|
|
62
|
+
|
|
63
|
+
- {arg} ({type}): {description}
|
|
64
|
+
|
|
65
|
+
## Message
|
|
66
|
+
|
|
67
|
+
> user: {message with {{arg}} placeholders}
|
|
68
|
+
\`\`\`
|
|
69
|
+
Omit \`## Args\` if the prompt takes no arguments.
|
|
70
|
+
|
|
71
|
+
3. **Translate** each tool's \`behavior\` field into a working JS async handler.
|
|
72
|
+
- Implement the described logic fully — no stubs, no TODOs.
|
|
73
|
+
- Handler is self-contained: no top-level imports.
|
|
74
|
+
- For file I/O or network calls: use dynamic import inside the handler.
|
|
75
|
+
- Return shape: \`{ content: [{ type: "text", text: string }] }\`
|
|
76
|
+
- Allowed parameter types: \`string | number | boolean\`. Default unknown types to \`string\`.
|
|
77
|
+
|
|
78
|
+
4. **Run** \`mcpill compile\`.
|
|
79
|
+
|
|
80
|
+
5. **Report** what was generated and show the user the next commands:
|
|
81
|
+
\`\`\`
|
|
82
|
+
mcpill validate
|
|
83
|
+
mcpill run
|
|
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.
|
|
108
|
+
|
|
109
|
+
════════════════════════════════════════════════════════════════ -->
|
|
110
|
+
|
|
111
|
+
# Pill: ${projectName}
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Server
|
|
116
|
+
|
|
117
|
+
name: ${projectName}
|
|
118
|
+
description:
|
|
119
|
+
transport: stdio
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Tool: echo
|
|
124
|
+
|
|
125
|
+
description: Echo a message back to the caller.
|
|
126
|
+
inputs:
|
|
127
|
+
- message (string): The message to echo.
|
|
128
|
+
output: The same message, echoed back as text.
|
|
129
|
+
behavior: |
|
|
130
|
+
Return the message input unchanged.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Tool: greet
|
|
135
|
+
|
|
136
|
+
description: Generate a personalised greeting.
|
|
137
|
+
inputs:
|
|
138
|
+
- name (string): The name of the person to greet.
|
|
139
|
+
output: A greeting string addressed to the given name.
|
|
140
|
+
behavior: |
|
|
141
|
+
Return "Hello, {name}!" as a text response.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Next Steps
|
|
146
|
+
|
|
147
|
+
Fill in your server above (replace or extend the example tools), then tell Claude:
|
|
148
|
+
|
|
149
|
+
> "Build this PILL.md"
|
|
150
|
+
|
|
151
|
+
After the source files are generated:
|
|
152
|
+
|
|
153
|
+
- [ ] \`mcpill compile\`
|
|
154
|
+
- [ ] \`mcpill validate\`
|
|
155
|
+
- [ ] \`mcpill run\`
|
|
156
|
+
- [ ] \`mcpill pack\`
|
|
157
|
+
- [ ] \`mcpill publish\`
|
|
18
158
|
`;
|
|
159
|
+
}
|
|
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.
|
|
19
190
|
|
|
20
|
-
const PROMPTS_JSON = JSON.stringify(
|
|
21
|
-
[
|
|
22
|
-
{
|
|
23
|
-
name: "summarize",
|
|
24
|
-
description: "Summarize the given text",
|
|
25
|
-
args: {
|
|
26
|
-
text: { type: "string" },
|
|
27
|
-
},
|
|
28
|
-
messages: [
|
|
29
|
-
{
|
|
30
|
-
role: "user",
|
|
31
|
-
content: "Please summarize the following text:\n\n{{text}}",
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
null,
|
|
37
|
-
2
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const RESOURCES_MD = `uri: config://app
|
|
41
|
-
name: App Config
|
|
42
191
|
---
|
|
43
|
-
|
|
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?"
|
|
44
220
|
`;
|
|
45
221
|
|
|
46
222
|
// Embedded content of src/templates/server.md — tsup does not copy non-TS assets,
|
|
@@ -86,9 +262,77 @@ Generate a greeting
|
|
|
86
262
|
> user: Say hello to {{name}}
|
|
87
263
|
`;
|
|
88
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
|
+
|
|
89
332
|
export async function runInit(opts: { dir?: string }): Promise<void> {
|
|
90
333
|
const targetDir = path.resolve(opts.dir ?? process.cwd());
|
|
91
334
|
const mcpillDir = path.join(targetDir, ".mcpill");
|
|
335
|
+
const serverDir = path.join(mcpillDir, "server");
|
|
92
336
|
const projectName = path.basename(targetDir);
|
|
93
337
|
|
|
94
338
|
if (fs.existsSync(mcpillDir)) {
|
|
@@ -96,50 +340,30 @@ export async function runInit(opts: { dir?: string }): Promise<void> {
|
|
|
96
340
|
process.exit(1);
|
|
97
341
|
}
|
|
98
342
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
null,
|
|
102
|
-
2,
|
|
103
|
-
);
|
|
343
|
+
fs.mkdirSync(path.join(serverDir, "tools"), { recursive: true });
|
|
344
|
+
fs.mkdirSync(path.join(serverDir, "prompts"), { recursive: true });
|
|
104
345
|
|
|
105
|
-
|
|
106
|
-
fs.writeFileSync(path.join(mcpillDir, "
|
|
107
|
-
fs.writeFileSync(path.join(
|
|
108
|
-
fs.writeFileSync(path.join(
|
|
109
|
-
fs.writeFileSync(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const promptsDir = path.join(targetDir, "prompts");
|
|
113
|
-
fs.mkdirSync(toolsDir, { recursive: true });
|
|
114
|
-
fs.mkdirSync(promptsDir, { recursive: true });
|
|
115
|
-
fs.writeFileSync(path.join(toolsDir, "echo.md"), ECHO_TOOL_MD);
|
|
116
|
-
fs.writeFileSync(path.join(promptsDir, "greeting.md"), GREETING_PROMPT_MD);
|
|
117
|
-
|
|
118
|
-
const serverMd = SERVER_MD_TEMPLATE.replace(
|
|
119
|
-
"name: my-server",
|
|
120
|
-
`name: ${projectName}`,
|
|
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),
|
|
121
353
|
);
|
|
122
|
-
fs.writeFileSync(path.join(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
console.log("
|
|
137
|
-
console.log("✓ .mcpill/prompts.json");
|
|
138
|
-
console.log("✓ .mcpill/resources.md");
|
|
139
|
-
console.log("✓ .mcpill/mcpill.config.json");
|
|
140
|
-
console.log("✓ tools/echo.md");
|
|
141
|
-
console.log("✓ prompts/greeting.md");
|
|
142
|
-
console.log("✓ server.md");
|
|
143
|
-
console.log("✓ package.json");
|
|
144
|
-
console.log("Edit your tools, then run: mcpill run");
|
|
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);
|
|
357
|
+
|
|
358
|
+
fs.writeFileSync(path.join(targetDir, "PILL.md"), makePillMd(projectName));
|
|
359
|
+
|
|
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");
|
|
367
|
+
console.log("");
|
|
368
|
+
console.log("When ready: mcpill compile && mcpill run");
|
|
145
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 {
|