mcpill 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.0
4
+
5
+ - `blocking: true` field on `PILL.md` `## Hook:` blocks; `mcpill compile` generates a `.mjs` `PreToolUse` gate script (exits 1 until a required tool has run) and a `PostToolUse` flag script
6
+ - `required_tool`, `gate_window_seconds` (default 120 s), and multiline `instructions` fields supported on blocking hooks
7
+ - Compile-time validation: `required_tool` must name a known tool; error surfaced during `mcpill compile`
8
+
9
+ ## 1.8.0
10
+
11
+ - `mcpill run` (stdio) now auto-registers the server in Claude config on start and auto-unregisters on SIGINT, SIGTERM, and process exit — no manual step required
12
+ - `--register` flag and `mcpill unregister` command removed; lifecycle management is now automatic
13
+
3
14
  ## 1.7.0
4
15
 
5
16
  - `mcpill run` is now side-effect-free by default — stopping the process disconnects the server; no Claude config is written
package/README.md CHANGED
@@ -20,8 +20,7 @@ Describe your server in plain English, let Claude generate the source files.
20
20
  mcpill init # scaffolds PILL.md + example source files
21
21
  # fill PILL.md # describe your server: tools, resources, prompts
22
22
  # tell Claude: "Build this PILL.md" # agent generates source files + runs compile
23
- mcpill run # start the server (transient stops when you do)
24
- mcpill run --register # or: register so Claude manages it persistently
23
+ mcpill run # start the server — registered while running, removed on exit
25
24
  ```
26
25
 
27
26
  `PILL.md` embeds the agent instructions — Claude knows exactly what to generate and how.
@@ -79,6 +78,25 @@ Compile also writes a `PreToolUse` hook into `.claude/settings.json` that warns
79
78
 
80
79
  - **`AGENT.md`** — a `## Tools` table with a `Replaces` column (existing format). AGENT.md wins on name conflicts.
81
80
 
81
+ #### Blocking hooks
82
+
83
+ For mandatory workflow steps, add a `## Hook:` section to `PILL.md` with `blocking: true`. Compile generates a hard-enforcement gate instead of a soft reminder:
84
+
85
+ ```markdown
86
+ ## Hook: my-hook
87
+
88
+ trigger: PreToolUse
89
+ blocking: true
90
+ required_tool: analyze-english
91
+ gate_window_seconds: 120
92
+ instructions: |
93
+ Before calling any other tool, call mcp__my-pill__analyze-english first.
94
+ ```
95
+
96
+ At runtime the gate script exits 1 (blocking the tool call) until `mcp__{pill}__analyze-english` has run within the window. After it runs, the flag script records the timestamp and the gate passes for `gate_window_seconds` seconds (default 120). Pill's own MCP tools are always allowed through. The gate is fail-open — if the pill's state file is missing, the gate passes.
97
+
98
+ `required_tool` must name a tool defined in the same pill; `mcpill compile` aborts with an error if it doesn't.
99
+
82
100
  ### `mcpill validate`
83
101
 
84
102
  Validates all pill directories (`.<name>/`) in the project root.
@@ -88,23 +106,11 @@ Validates all pill directories (`.<name>/`) in the project root.
88
106
  Starts the MCP server from the pill artifact.
89
107
 
90
108
  ```bash
91
- mcpill run # stdio (default)
109
+ mcpill run # stdio (default) — registered while running, removed on exit
92
110
  mcpill run --transport http --port 3333
93
- mcpill run --register # also write server entry to Claude config
94
- ```
95
-
96
- `--register` is opt-in. Without it, running the server has no side effects on Claude's configuration — stopping the process fully disconnects it.
97
-
98
- ### `mcpill unregister`
99
-
100
- Removes the server entry from all Claude config files — the inverse of `mcpill run --register`.
101
-
102
- ```bash
103
- mcpill unregister
104
- mcpill unregister --dir <path>
105
111
  ```
106
112
 
107
- Removes from `.claude/settings.json` (project scope), `~/.claude.json` (user scope), and the Claude Desktop config. Prints what was removed, or reports if the server was not registered.
113
+ For stdio transport, the server entry is automatically written to Claude's config on start and removed on exit (SIGINT, SIGTERM, or normal process exit). HTTP transport is self-hosted — Claude connects to a URL, so no registration occurs.
108
114
 
109
115
  ### `mcpill pack`
110
116
 
package/dist/cli.js CHANGED
@@ -548,6 +548,7 @@ async function runInit(opts) {
548
548
  // src/commands/run.ts
549
549
  import path7 from "path";
550
550
  import fs6 from "fs";
551
+ import os from "os";
551
552
  import { z as z2 } from "mcpill-runtime";
552
553
  import { createServer, applySetup } from "mcpster";
553
554
 
@@ -755,12 +756,45 @@ function extractFencedBlock(body, lang) {
755
756
  }
756
757
  function parseKvBlock(body) {
757
758
  const result = {};
758
- for (const line of body.split("\n")) {
759
+ const lines = body.split("\n");
760
+ let i = 0;
761
+ while (i < lines.length) {
762
+ const line = lines[i];
759
763
  const colonIdx = line.indexOf(":");
760
- if (colonIdx === -1) continue;
764
+ if (colonIdx === -1) {
765
+ i++;
766
+ continue;
767
+ }
761
768
  const key = line.slice(0, colonIdx).trim();
762
769
  const value = line.slice(colonIdx + 1).trim();
763
- if (key) result[key] = value;
770
+ if (!key) {
771
+ i++;
772
+ continue;
773
+ }
774
+ if (value === "|") {
775
+ i++;
776
+ const blockLines = [];
777
+ let baseIndent = -1;
778
+ while (i < lines.length) {
779
+ const nextLine = lines[i];
780
+ const trimmed = nextLine.trimStart();
781
+ if (trimmed === "") {
782
+ blockLines.push("");
783
+ i++;
784
+ continue;
785
+ }
786
+ const indent = nextLine.length - trimmed.length;
787
+ if (baseIndent === -1) baseIndent = indent;
788
+ if (indent < baseIndent) break;
789
+ blockLines.push(nextLine.slice(baseIndent));
790
+ i++;
791
+ }
792
+ while (blockLines.length > 0 && blockLines[blockLines.length - 1] === "") blockLines.pop();
793
+ result[key] = blockLines.join("\n");
794
+ } else {
795
+ result[key] = value;
796
+ i++;
797
+ }
764
798
  }
765
799
  return result;
766
800
  }
@@ -975,13 +1009,31 @@ function parsePillMdHooks(content) {
975
1009
  for (const piece of pieces) {
976
1010
  if (!piece.toLowerCase().startsWith("hook:")) continue;
977
1011
  const nlIdx = piece.indexOf("\n");
1012
+ const sectionTitle = (nlIdx === -1 ? piece : piece.slice(0, nlIdx)).trim();
1013
+ const hookName = sectionTitle.slice("hook:".length).trim();
978
1014
  const body = nlIdx === -1 ? "" : piece.slice(nlIdx + 1);
979
1015
  const kv = parseKvBlock(body);
980
1016
  const trigger = kv["trigger"]?.trim();
981
1017
  const matcher = kv["matcher"]?.trim();
982
1018
  const command = kv["command"]?.trim();
983
- if (!trigger || !command) continue;
984
- hooks.push({ event: trigger, ...matcher ? { matcher } : {}, command });
1019
+ const blocking = kv["blocking"]?.trim().toLowerCase() === "true";
1020
+ if (!trigger || !command && !blocking) continue;
1021
+ const entry = {
1022
+ ...hookName ? { name: hookName } : {},
1023
+ event: trigger,
1024
+ ...matcher ? { matcher } : {},
1025
+ ...command ? { command } : {},
1026
+ ...blocking ? { blocking: true } : {}
1027
+ };
1028
+ if (blocking) {
1029
+ const requiredTool = kv["required_tool"]?.trim();
1030
+ if (requiredTool) entry.required_tool = requiredTool;
1031
+ const windowRaw = kv["gate_window_seconds"]?.trim();
1032
+ if (windowRaw) entry.gate_window_seconds = parseInt(windowRaw, 10);
1033
+ const instructions = kv["instructions"]?.trim();
1034
+ if (instructions) entry.instructions = instructions;
1035
+ }
1036
+ hooks.push(entry);
985
1037
  }
986
1038
  return hooks;
987
1039
  }
@@ -991,8 +1043,23 @@ function parseHookFile(content) {
991
1043
  const trigger = kv["trigger"]?.trim();
992
1044
  const matcher = kv["matcher"]?.trim();
993
1045
  const command = kv["command"]?.trim();
994
- if (!trigger || !command) return null;
995
- return { event: trigger, ...matcher ? { matcher } : {}, command };
1046
+ const blocking = kv["blocking"]?.trim().toLowerCase() === "true";
1047
+ if (!trigger || !command && !blocking) return null;
1048
+ const entry = {
1049
+ event: trigger,
1050
+ ...matcher ? { matcher } : {},
1051
+ ...command ? { command } : {},
1052
+ ...blocking ? { blocking: true } : {}
1053
+ };
1054
+ if (blocking) {
1055
+ const requiredTool = kv["required_tool"]?.trim();
1056
+ if (requiredTool) entry.required_tool = requiredTool;
1057
+ const windowRaw = kv["gate_window_seconds"]?.trim();
1058
+ if (windowRaw) entry.gate_window_seconds = parseInt(windowRaw, 10);
1059
+ const instructions = kv["instructions"]?.trim();
1060
+ if (instructions) entry.instructions = instructions;
1061
+ }
1062
+ return entry;
996
1063
  }
997
1064
  function mergeUserHooksIntoSettings(baseDir, hookBlocks) {
998
1065
  if (hookBlocks.length === 0) return;
@@ -1039,6 +1106,91 @@ function mergeHookIntoSettings(baseDir, hookEntry) {
1039
1106
  mkdirSync(claudeDir, { recursive: true });
1040
1107
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1041
1108
  }
1109
+ function generateGateScript(pillName, requiredTool, windowSeconds, instructions) {
1110
+ const instrJson = JSON.stringify(instructions);
1111
+ return [
1112
+ `import { readFileSync, existsSync } from 'fs';`,
1113
+ `import { homedir } from 'os';`,
1114
+ `import { join } from 'path';`,
1115
+ ``,
1116
+ `const PILL_NAME = ${JSON.stringify(pillName)};`,
1117
+ `const REQUIRED_TOOL = ${JSON.stringify(requiredTool)};`,
1118
+ `const GATE_WINDOW_SECONDS = ${windowSeconds};`,
1119
+ `const INSTRUCTIONS = ${instrJson};`,
1120
+ ``,
1121
+ `const raw = readFileSync(0, 'utf-8');`,
1122
+ `const input = JSON.parse(raw);`,
1123
+ `const toolName = input.tool_name ?? '';`,
1124
+ ``,
1125
+ `if (toolName.startsWith(\`mcp__\${PILL_NAME}__\`)) process.exit(0);`,
1126
+ ``,
1127
+ `const stateFile = join(homedir(), \`.\${PILL_NAME}\`, 'state.json');`,
1128
+ `if (!existsSync(stateFile)) process.exit(0);`,
1129
+ ``,
1130
+ `const state = JSON.parse(readFileSync(stateFile, 'utf-8'));`,
1131
+ `if (state.enabled === false) process.exit(0);`,
1132
+ ``,
1133
+ `const flagFile = join(homedir(), \`.\${PILL_NAME}\`, \`last_\${REQUIRED_TOOL}_run.json\`);`,
1134
+ `if (existsSync(flagFile)) {`,
1135
+ ` const flag = JSON.parse(readFileSync(flagFile, 'utf-8'));`,
1136
+ ` const elapsedMs = Date.now() - new Date(flag.timestamp).getTime();`,
1137
+ ` if (elapsedMs <= GATE_WINDOW_SECONDS * 1000) process.exit(0);`,
1138
+ `}`,
1139
+ ``,
1140
+ `process.stderr.write(INSTRUCTIONS + '\\n');`,
1141
+ `process.exit(1);`,
1142
+ ``
1143
+ ].join("\n");
1144
+ }
1145
+ function generateFlagScript(pillName, requiredTool) {
1146
+ return [
1147
+ `import { writeFileSync, mkdirSync } from 'fs';`,
1148
+ `import { homedir } from 'os';`,
1149
+ `import { join } from 'path';`,
1150
+ ``,
1151
+ `const PILL_NAME = ${JSON.stringify(pillName)};`,
1152
+ `const REQUIRED_TOOL = ${JSON.stringify(requiredTool)};`,
1153
+ ``,
1154
+ `const dir = join(homedir(), \`.\${PILL_NAME}\`);`,
1155
+ `const flagFile = join(dir, \`last_\${REQUIRED_TOOL}_run.json\`);`,
1156
+ ``,
1157
+ `try {`,
1158
+ ` mkdirSync(dir, { recursive: true });`,
1159
+ ` writeFileSync(flagFile, JSON.stringify({ timestamp: new Date().toISOString() }));`,
1160
+ `} catch (e) {`,
1161
+ ` process.stderr.write(\`[\${PILL_NAME}] warning: could not write flag file: \${e.message}\\n\`);`,
1162
+ `}`,
1163
+ ``
1164
+ ].join("\n");
1165
+ }
1166
+ function applyBlockingHookScripts(baseDir, pillName, hook) {
1167
+ const hooksDir = join3(baseDir, ".mcpill", "server", "hooks");
1168
+ mkdirSync(hooksDir, { recursive: true });
1169
+ const hookName = hook.name;
1170
+ const requiredTool = hook.required_tool;
1171
+ const windowSeconds = hook.gate_window_seconds ?? 120;
1172
+ const instructions = hook.instructions ?? `[mcpill:${pillName}] Run mcp__${pillName}__${requiredTool} before calling other tools.`;
1173
+ writeFileSync(
1174
+ join3(hooksDir, `${hookName}-gate.mjs`),
1175
+ generateGateScript(pillName, requiredTool, windowSeconds, instructions)
1176
+ );
1177
+ writeFileSync(
1178
+ join3(hooksDir, `${hookName}-flag.mjs`),
1179
+ generateFlagScript(pillName, requiredTool)
1180
+ );
1181
+ return [
1182
+ {
1183
+ event: "PreToolUse",
1184
+ matcher: ".*",
1185
+ command: `node .mcpill/server/hooks/${hookName}-gate.mjs`
1186
+ },
1187
+ {
1188
+ event: "PostToolUse",
1189
+ matcher: `mcp__${pillName}__${requiredTool}`,
1190
+ command: `node .mcpill/server/hooks/${hookName}-flag.mjs`
1191
+ }
1192
+ ];
1193
+ }
1042
1194
 
1043
1195
  // src/commands/validate.ts
1044
1196
  async function validateOne(mcpillDir) {
@@ -1106,6 +1258,32 @@ async function runValidate(baseDir) {
1106
1258
  }
1107
1259
 
1108
1260
  // src/commands/run.ts
1261
+ function patchJson(filePath, patcher) {
1262
+ if (!fs6.existsSync(filePath)) return;
1263
+ try {
1264
+ const obj = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
1265
+ patcher(obj);
1266
+ fs6.writeFileSync(filePath, JSON.stringify(obj, null, 2) + "\n", "utf-8");
1267
+ } catch {
1268
+ }
1269
+ }
1270
+ function desktopConfigPaths() {
1271
+ const home = os.homedir();
1272
+ if (process.platform === "win32") {
1273
+ const appData = process.env.APPDATA ?? path7.join(home, "AppData", "Roaming");
1274
+ return [path7.join(appData, "Claude", "claude_desktop_config.json")];
1275
+ }
1276
+ return [path7.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
1277
+ }
1278
+ function unregisterSync(name, baseDir) {
1279
+ const remove = (obj) => {
1280
+ const servers = obj.mcpServers;
1281
+ if (servers) delete servers[name];
1282
+ };
1283
+ patchJson(path7.join(baseDir, ".claude", "settings.json"), remove);
1284
+ patchJson(path7.join(os.homedir(), ".claude.json"), remove);
1285
+ for (const p of desktopConfigPaths()) patchJson(p, remove);
1286
+ }
1109
1287
  async function runServer(opts) {
1110
1288
  const baseDir = path7.resolve(opts.dir ?? process.cwd());
1111
1289
  await runValidate(baseDir);
@@ -1157,7 +1335,7 @@ async function runServer(opts) {
1157
1335
  resolver: async () => content
1158
1336
  });
1159
1337
  }
1160
- if (opts.register) {
1338
+ if (transport === "stdio") {
1161
1339
  applySetup(name, tools.map((t) => t.name), {
1162
1340
  projectPath: baseDir,
1163
1341
  permissions: "restrictive",
@@ -1167,6 +1345,16 @@ async function runServer(opts) {
1167
1345
  args: ["run", "--dir", baseDir]
1168
1346
  }
1169
1347
  });
1348
+ const cleanup = () => unregisterSync(name, baseDir);
1349
+ process.on("exit", cleanup);
1350
+ process.on("SIGINT", () => {
1351
+ cleanup();
1352
+ process.exit(0);
1353
+ });
1354
+ process.on("SIGTERM", () => {
1355
+ cleanup();
1356
+ process.exit(0);
1357
+ });
1170
1358
  }
1171
1359
  try {
1172
1360
  await server.start();
@@ -1512,9 +1700,26 @@ async function runCompile(opts) {
1512
1700
  }
1513
1701
  }
1514
1702
  const allUserHooks = [...pillHooks, ...fileHooks];
1515
- if (allUserHooks.length > 0) {
1516
- mergeUserHooksIntoSettings(baseDir, allUserHooks);
1517
- console.log(`\u2713 .claude/settings.json updated with ${allUserHooks.length} user-defined hook(s)`);
1703
+ const blockingHooks = allUserHooks.filter((h) => h.blocking);
1704
+ const regularHooks = allUserHooks.filter((h) => !h.blocking);
1705
+ for (const hook of blockingHooks) {
1706
+ if (!hook.name) throw new Error(`blocking hook is missing a name`);
1707
+ if (!hook.required_tool) throw new Error(`blocking hook '${hook.name}' is missing required_tool`);
1708
+ const toolExists = mergedDoc.tools.some((t) => t.name === hook.required_tool);
1709
+ if (!toolExists) {
1710
+ throw new Error(`blocking hook '${hook.name}' references unknown tool '${hook.required_tool}'`);
1711
+ }
1712
+ }
1713
+ if (regularHooks.length > 0) {
1714
+ mergeUserHooksIntoSettings(baseDir, regularHooks);
1715
+ console.log(`\u2713 .claude/settings.json updated with ${regularHooks.length} user-defined hook(s)`);
1716
+ }
1717
+ if (blockingHooks.length > 0) {
1718
+ const generatedEntries = blockingHooks.flatMap(
1719
+ (hook) => applyBlockingHookScripts(baseDir, mergedDoc.config.name, hook)
1720
+ );
1721
+ mergeUserHooksIntoSettings(baseDir, generatedEntries);
1722
+ console.log(`\u2713 .claude/settings.json updated with ${blockingHooks.length} blocking hook(s)`);
1518
1723
  }
1519
1724
  }
1520
1725
 
@@ -1561,72 +1766,6 @@ async function runPublish(dir, access) {
1561
1766
  }
1562
1767
  }
1563
1768
 
1564
- // src/commands/unregister.ts
1565
- import path10 from "path";
1566
- import fs8 from "fs";
1567
- import os from "os";
1568
- function patchJson(filePath, patcher) {
1569
- if (!fs8.existsSync(filePath)) return false;
1570
- try {
1571
- const obj = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
1572
- patcher(obj);
1573
- fs8.writeFileSync(filePath, JSON.stringify(obj, null, 2) + "\n", "utf-8");
1574
- return true;
1575
- } catch {
1576
- return false;
1577
- }
1578
- }
1579
- function desktopConfigPaths() {
1580
- const home = os.homedir();
1581
- if (process.platform === "win32") {
1582
- const appData = process.env.APPDATA ?? path10.join(home, "AppData", "Roaming");
1583
- return [path10.join(appData, "Claude", "claude_desktop_config.json")];
1584
- }
1585
- return [path10.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
1586
- }
1587
- async function runUnregister(opts) {
1588
- const baseDir = path10.resolve(opts.dir ?? process.cwd());
1589
- const mcpillDir = path10.join(baseDir, ".mcpill", "server");
1590
- const config = await loadConfig(mcpillDir);
1591
- const { name } = config;
1592
- let removed = 0;
1593
- const settingsPath = path10.join(baseDir, ".claude", "settings.json");
1594
- const removedProject = patchJson(settingsPath, (obj) => {
1595
- const servers = obj.mcpServers;
1596
- if (servers && name in servers) {
1597
- delete servers[name];
1598
- removed++;
1599
- }
1600
- });
1601
- if (removedProject) process.stderr.write(`removed "${name}" from .claude/settings.json
1602
- `);
1603
- const claudeJsonPath = path10.join(os.homedir(), ".claude.json");
1604
- const removedUser = patchJson(claudeJsonPath, (obj) => {
1605
- const servers = obj.mcpServers;
1606
- if (servers && name in servers) {
1607
- delete servers[name];
1608
- removed++;
1609
- }
1610
- });
1611
- if (removedUser) process.stderr.write(`removed "${name}" from ~/.claude.json
1612
- `);
1613
- for (const configPath of desktopConfigPaths()) {
1614
- const removedDesktop = patchJson(configPath, (obj) => {
1615
- const servers = obj.mcpServers;
1616
- if (servers && name in servers) {
1617
- delete servers[name];
1618
- removed++;
1619
- }
1620
- });
1621
- if (removedDesktop) process.stderr.write(`removed "${name}" from Claude Desktop config
1622
- `);
1623
- }
1624
- if (removed === 0) {
1625
- process.stderr.write(`"${name}" was not registered \u2014 nothing to remove
1626
- `);
1627
- }
1628
- }
1629
-
1630
1769
  // src/cli.ts
1631
1770
  var pkgDir = dirname(fileURLToPath(import.meta.url));
1632
1771
  var pkg = JSON.parse(
@@ -1637,7 +1776,7 @@ program.name("mcpill").version(pkg.version);
1637
1776
  program.command("init").description("Scaffold a new .mcpill/ directory").option("--dir <path>", "Target directory").action(async (opts) => {
1638
1777
  await runInit(opts);
1639
1778
  });
1640
- program.command("run").description("Start the MCP server").option("--transport <type>", "Transport: stdio (default) or http").option("--port <n>", "Port number (HTTP only)", parseInt).option("--dir <path>", "Project root containing .mcpill/").option("--register", "Write server entry to Claude config so Claude manages the process").action(
1779
+ program.command("run").description("Start the MCP server").option("--transport <type>", "Transport: stdio (default) or http").option("--port <n>", "Port number (HTTP only)", parseInt).option("--dir <path>", "Project root containing .mcpill/").action(
1641
1780
  async (opts) => {
1642
1781
  await runServer(opts);
1643
1782
  }
@@ -1649,9 +1788,6 @@ program.command("validate").description("Validate .mcpill/ configuration").optio
1649
1788
  program.command("compile").description("Compile server.md \u2194 .mcpill/ files").option("--dir <path>", "Directory containing server.md and .mcpill/").option("--to-md", "Reverse: generate server.md from .mcpill/ files").option("--strict", "Error on missing tool handlers instead of generating stubs").option("--no-hooks", "Skip writing the PreToolUse hook to .claude/settings.json").action(async (opts) => {
1650
1789
  await runCompile({ ...opts, noHooks: opts.hooks === false });
1651
1790
  });
1652
- program.command("unregister").description("Remove server entry from Claude config (undo mcpill run --register)").option("--dir <path>", "Project root containing .mcpill/").action(async (opts) => {
1653
- await runUnregister(opts);
1654
- });
1655
1791
  program.command("pack").description("Prepare pill for npm distribution").option("--dir <path>", "pill project root", ".").action(({ dir }) => runPack(dir));
1656
1792
  program.command("publish").description("Pack and publish pill to npm").option("--dir <path>", "pill project root", ".").option("--access <level>", "npm access level", "public").action(({ dir, access }) => runPublish(dir, access));
1657
1793
  program.parseAsync(process.argv).catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpill",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "type": "module",
5
5
  "description": "CLI for building, validating, and publishing MCP servers using the pill format.",
6
6
  "homepage": "https://mcpill.ruco.dev",