skill-rules 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +54 -0
  2. package/dist/index.js +414 -4
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -304,6 +304,60 @@ sr use --help
304
304
 
305
305
  ---
306
306
 
307
+ ## MCP Server
308
+
309
+ `skill-rules` ships a built-in [Model Context Protocol](https://modelcontextprotocol.io) server so Claude (or any MCP-compatible agent) can manage your skills directly — no terminal required.
310
+
311
+ ```bash
312
+ sr mcp # start with global install
313
+ npx skill-rules mcp # start without installing
314
+ ```
315
+
316
+ The server uses **stdio transport** and runs in the project directory where it is started. It exposes the following tools:
317
+
318
+ | Tool | Description |
319
+ | -------- | ---------------------------------------------------------------------- |
320
+ | `sync` | Sync active skills across all detected IDEs |
321
+ | `status` | Show active stage, detected IDEs, installed skills, and stash contents |
322
+ | `init` | Initialize `skills-lock.json`, `skills.rules`, and update `.gitignore` |
323
+ | `add` | Assign a skill to a stage (`skill`, `stage`, optional `track`) |
324
+ | `remove` | Remove a skill from a stage or all stages |
325
+ | `use` | Activate a stage or restore everything with `off: true` |
326
+ | `list` | List all skills with stages, install status, and git tracking |
327
+ | `ignore` | Regenerate the `.gitignore` block |
328
+
329
+ ### Configure in Claude Code
330
+
331
+ Add to your project's `.mcp.json`:
332
+
333
+ ```json
334
+ {
335
+ "mcpServers": {
336
+ "skill-rules": {
337
+ "command": "npx",
338
+ "args": ["-y", "skill-rules", "mcp"]
339
+ }
340
+ }
341
+ }
342
+ ```
343
+
344
+ Or add globally in `~/.claude/settings.json` (requires global install):
345
+
346
+ ```json
347
+ {
348
+ "mcpServers": {
349
+ "skill-rules": {
350
+ "command": "sr",
351
+ "args": ["mcp"]
352
+ }
353
+ }
354
+ }
355
+ ```
356
+
357
+ Once connected, Claude can call `sync`, `status`, `use`, and any other tool directly from the conversation — no CLI needed.
358
+
359
+ ---
360
+
307
361
  ## Configuration files
308
362
 
309
363
  ### `skills-lock.json`
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
  import { readFileSync as readFileSync5 } from "fs";
6
6
  import { fileURLToPath } from "url";
7
- import { join as join11, dirname } from "path";
7
+ import { join as join12, dirname } from "path";
8
8
 
9
9
  // src/commands/run.jsx
10
10
  import React2, { useEffect, useState as useState2 } from "react";
@@ -385,10 +385,10 @@ Available: ${available.join(", ")}` : "\nNo stages defined yet."}${hint}`
385
385
  const msg = activeStage ? `Stage [${activeStage}] has no skills. Run: skill-rules add --stage ${activeStage}` : "No skills yet. Run: skill-rules add";
386
386
  return /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, msg);
387
387
  }
388
- const ok = results.filter((r) => r.status === "ok");
388
+ const ok2 = results.filter((r) => r.status === "ok");
389
389
  const synced = results.filter((r) => r.status === "synced");
390
390
  const missing = results.filter((r) => r.status === "missing");
391
- return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React2.createElement(Header, null, "skill-rules", activeStage ? ` [${activeStage}]` : ""), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "IDEs (", ides.length, ")"), ides.map((ide) => /* @__PURE__ */ React2.createElement(IDEItem, { key: ide.id, ide }))), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "Skills (", results.length, ")"), results.map((r) => /* @__PURE__ */ React2.createElement(SkillStatus, { key: r.name, name: r.name, status: r.status, detail: r.detail }))), /* @__PURE__ */ React2.createElement(Box2, { gap: 3 }, ok.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, ok.length, " up to date"), synced.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, synced.length, " synced"), missing.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, missing.length, " missing \u2014 install via ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, "skills.sh"), " or", " ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, "autoskill"))));
391
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React2.createElement(Header, null, "skill-rules", activeStage ? ` [${activeStage}]` : ""), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "IDEs (", ides.length, ")"), ides.map((ide) => /* @__PURE__ */ React2.createElement(IDEItem, { key: ide.id, ide }))), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "Skills (", results.length, ")"), results.map((r) => /* @__PURE__ */ React2.createElement(SkillStatus, { key: r.name, name: r.name, status: r.status, detail: r.detail }))), /* @__PURE__ */ React2.createElement(Box2, { gap: 3 }, ok2.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, ok2.length, " up to date"), synced.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, synced.length, " synced"), missing.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, missing.length, " missing \u2014 install via ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, "skills.sh"), " or", " ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, "autoskill"))));
392
392
  }
393
393
 
394
394
  // src/commands/init.jsx
@@ -1106,9 +1106,418 @@ function DoneView({ data }) {
1106
1106
  return null;
1107
1107
  }
1108
1108
 
1109
+ // src/mcp/server.js
1110
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1111
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1112
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1113
+ import { existsSync as existsSync10, writeFileSync as writeFileSync5 } from "fs";
1114
+ import { join as join11 } from "path";
1115
+ var TOOLS = [
1116
+ {
1117
+ name: "sync",
1118
+ description: "Sync active skills across all detected IDEs. Copies skills from IDEs that have them to IDEs that are missing them.",
1119
+ inputSchema: {
1120
+ type: "object",
1121
+ properties: {
1122
+ stage: {
1123
+ type: "string",
1124
+ description: "Stage to filter skills by. Defaults to the currently active stage."
1125
+ }
1126
+ }
1127
+ }
1128
+ },
1129
+ {
1130
+ name: "status",
1131
+ description: "Show current state: active stage, detected IDEs, installed skills with their stage assignments, and stash contents.",
1132
+ inputSchema: { type: "object", properties: {} }
1133
+ },
1134
+ {
1135
+ name: "init",
1136
+ description: "Initialize skill-rules in the project. Creates skills-lock.json and skills.rules if absent, and updates .gitignore.",
1137
+ inputSchema: { type: "object", properties: {} }
1138
+ },
1139
+ {
1140
+ name: "add",
1141
+ description: "Assign a skill to a stage.",
1142
+ inputSchema: {
1143
+ type: "object",
1144
+ properties: {
1145
+ skill: { type: "string", description: "Skill name to assign" },
1146
+ stage: { type: "string", description: "Stage name to assign the skill to" },
1147
+ track: {
1148
+ type: "boolean",
1149
+ description: "Commit this skill to git instead of gitignoring it (default: false)"
1150
+ }
1151
+ },
1152
+ required: ["skill", "stage"]
1153
+ }
1154
+ },
1155
+ {
1156
+ name: "remove",
1157
+ description: "Remove a skill from a stage, or from all stages when no stage is given.",
1158
+ inputSchema: {
1159
+ type: "object",
1160
+ properties: {
1161
+ skill: { type: "string", description: "Skill name to remove" },
1162
+ stage: {
1163
+ type: "string",
1164
+ description: "Stage to remove from. Omit to remove from all stages."
1165
+ }
1166
+ },
1167
+ required: ["skill"]
1168
+ }
1169
+ },
1170
+ {
1171
+ name: "use",
1172
+ description: "Activate a stage: stash skills not assigned to it, restore skills that are. Pass off: true to restore everything and clear the active stage.",
1173
+ inputSchema: {
1174
+ type: "object",
1175
+ properties: {
1176
+ stage: { type: "string", description: "Stage name to activate" },
1177
+ off: {
1178
+ type: "boolean",
1179
+ description: "Restore all stashed skills and clear the active stage"
1180
+ }
1181
+ }
1182
+ }
1183
+ },
1184
+ {
1185
+ name: "list",
1186
+ description: "List all installed skills with their stage assignments, installation status, and git tracking. Use track/untrack to change tracking.",
1187
+ inputSchema: {
1188
+ type: "object",
1189
+ properties: {
1190
+ track: { type: "string", description: "Skill name to start committing to git" },
1191
+ untrack: { type: "string", description: "Skill name to exclude from git" }
1192
+ }
1193
+ }
1194
+ },
1195
+ {
1196
+ name: "ignore",
1197
+ description: "Regenerate the skill-rules block in .gitignore for all detected IDE directories.",
1198
+ inputSchema: { type: "object", properties: {} }
1199
+ }
1200
+ ];
1201
+ function ok(text) {
1202
+ return { content: [{ type: "text", text }] };
1203
+ }
1204
+ function fail(text) {
1205
+ return { content: [{ type: "text", text: `Error: ${text}` }], isError: true };
1206
+ }
1207
+ function toolSync(cwd, args) {
1208
+ const stage = args.stage ?? getActiveStage(cwd);
1209
+ const ides = detectIDEs(cwd);
1210
+ if (ides.length === 0)
1211
+ return fail(
1212
+ "No IDE directories detected. Expected: .claude/ .cursor/ .windsurf/ .agents/ .openhands/ .opencode/"
1213
+ );
1214
+ const lock = readLock(cwd);
1215
+ const rules = readRules(cwd);
1216
+ if (stage && rules && !(stage in (rules.stages ?? {}))) {
1217
+ const available = listStages(rules);
1218
+ return fail(
1219
+ `Stage "${stage}" not found.${available.length ? ` Available: ${available.join(", ")}` : " No stages defined yet."}`
1220
+ );
1221
+ }
1222
+ syncGitignore(cwd, ides, lock);
1223
+ const skillsToCheck = stage ? getActiveSkills(rules, stage) : Object.keys(lock.skills);
1224
+ if (skillsToCheck.length === 0)
1225
+ return ok(
1226
+ `No skills${stage ? ` in stage [${stage}]` : ""} to sync.
1227
+ IDEs (${ides.length}): ${ides.map((i) => i.name).join(", ")}`
1228
+ );
1229
+ const lines = [
1230
+ `Sync${stage ? ` [${stage}]` : ""} \u2014 ${ides.length} IDE${ides.length !== 1 ? "s" : ""}`,
1231
+ ""
1232
+ ];
1233
+ for (const skillName of skillsToCheck) {
1234
+ const sources = findSkillSources(cwd, ides, skillName);
1235
+ if (sources.length === 0) {
1236
+ lines.push(`missing ${skillName}`);
1237
+ } else if (sources.length < ides.length) {
1238
+ const copied = syncSkillToMissingIDEs(cwd, ides, skillName, sources[0]);
1239
+ lines.push(
1240
+ `synced ${skillName} \u2192 ${copied} IDE${copied !== 1 ? "s" : ""} from ${sources[0].name}`
1241
+ );
1242
+ } else {
1243
+ lines.push(`ok ${skillName}`);
1244
+ }
1245
+ }
1246
+ return ok(lines.join("\n"));
1247
+ }
1248
+ function toolStatus(cwd) {
1249
+ const ides = detectIDEs(cwd);
1250
+ const lock = readLock(cwd);
1251
+ const rules = readRules(cwd);
1252
+ const activeStage = getActiveStage(cwd);
1253
+ const stashed = listStashed(cwd);
1254
+ const stages = listStages(rules ?? { stages: {} });
1255
+ const skills = Object.entries(lock.skills);
1256
+ const lines = [
1257
+ `Active stage : ${activeStage ? `[${activeStage}]` : "none"}`,
1258
+ `IDEs : ${ides.length > 0 ? ides.map((i) => i.name).join(", ") : "none detected"}`,
1259
+ `Stages : ${stages.length > 0 ? stages.join(", ") : "none"}`,
1260
+ `Stashed : ${stashed.length > 0 ? stashed.join(", ") : "none"}`,
1261
+ ""
1262
+ ];
1263
+ if (skills.length === 0) {
1264
+ lines.push("No skills installed yet.");
1265
+ } else {
1266
+ lines.push("Skills:");
1267
+ for (const [name, info] of skills) {
1268
+ const skillStages = stages.filter((s) => (rules?.stages[s] ?? []).includes(name));
1269
+ const stageStr = skillStages.length ? skillStages.join(", ") : "\u2014";
1270
+ const tracking = info.track ? " [git tracked]" : "";
1271
+ lines.push(` ${name.padEnd(22)} ${stageStr}${tracking}`);
1272
+ }
1273
+ }
1274
+ return ok(lines.join("\n"));
1275
+ }
1276
+ function toolInit(cwd) {
1277
+ const ides = detectIDEs(cwd);
1278
+ const lines = [];
1279
+ const lockPath = join11(cwd, "skills-lock.json");
1280
+ if (!existsSync10(lockPath)) {
1281
+ writeFileSync5(lockPath, JSON.stringify({ version: 1, skills: {} }, null, 2) + "\n");
1282
+ lines.push("created skills-lock.json");
1283
+ } else {
1284
+ lines.push("exists skills-lock.json");
1285
+ }
1286
+ const rulesPath = join11(cwd, "skills.rules");
1287
+ if (!existsSync10(rulesPath)) {
1288
+ writeFileSync5(rulesPath, JSON.stringify(createEmptyRules(), null, 2) + "\n");
1289
+ lines.push("created skills.rules");
1290
+ } else {
1291
+ lines.push("exists skills.rules");
1292
+ }
1293
+ if (ides.length > 0) {
1294
+ const lock = readLock(cwd);
1295
+ syncGitignore(cwd, ides, lock);
1296
+ lines.push("updated .gitignore");
1297
+ lines.push(`IDEs ${ides.map((i) => i.name).join(", ")}`);
1298
+ } else {
1299
+ lines.push(
1300
+ "warning No IDE directories detected \u2014 .gitignore not updated\n Create one of: .claude/ .cursor/ .windsurf/ .agents/ .openhands/ .opencode/"
1301
+ );
1302
+ }
1303
+ return ok(lines.join("\n"));
1304
+ }
1305
+ function toolAdd(cwd, args) {
1306
+ const { skill, stage, track = false } = args;
1307
+ const lock = readLock(cwd);
1308
+ const ides = detectIDEs(cwd);
1309
+ const rules = readRules(cwd) ?? createEmptyRules();
1310
+ const inLock = !!lock.skills[skill];
1311
+ const alreadyInStage = (rules.stages[stage] ?? []).includes(skill);
1312
+ const isNewStage = !listStages(rules).includes(stage);
1313
+ if (!alreadyInStage) {
1314
+ addSkillToStage(rules, skill, stage);
1315
+ writeRules(rules, cwd);
1316
+ }
1317
+ if (track && inLock) {
1318
+ setSkillTracked(lock, skill, true);
1319
+ writeLock(lock, cwd);
1320
+ }
1321
+ if (ides.length > 0) syncGitignore(cwd, ides, lock);
1322
+ const lines = [];
1323
+ if (!inLock)
1324
+ lines.push(
1325
+ `warning "${skill}" not in skills-lock.json \u2014 install via skills.sh or autoskill first`
1326
+ );
1327
+ if (isNewStage && !alreadyInStage) lines.push(`created stage [${stage}]`);
1328
+ lines.push(
1329
+ alreadyInStage ? `exists "${skill}" already in [${stage}]` : `added "${skill}" \u2192 [${stage}]`
1330
+ );
1331
+ if (track)
1332
+ lines.push(
1333
+ inLock ? "tracked git tracking enabled" : "warning git tracking skipped (not in lock)"
1334
+ );
1335
+ return ok(lines.join("\n"));
1336
+ }
1337
+ function toolRemove(cwd, args) {
1338
+ const { skill, stage } = args;
1339
+ const rules = readRules(cwd);
1340
+ if (!rules) return fail("No skills.rules found. Run init first.");
1341
+ const stages = listStages(rules);
1342
+ const inAnyStage = stages.some((s) => (rules.stages[s] ?? []).includes(skill));
1343
+ if (!inAnyStage) return fail(`"${skill}" is not assigned to any stage.`);
1344
+ if (stage && !(rules.stages[stage] ?? []).includes(skill)) {
1345
+ const assignedTo = stages.filter((s) => (rules.stages[s] ?? []).includes(skill));
1346
+ return fail(`"${skill}" is not in [${stage}]. Assigned to: ${assignedTo.join(", ")}`);
1347
+ }
1348
+ removeSkillFromStage(rules, skill, stage ?? null);
1349
+ writeRules(rules, cwd);
1350
+ const lock = readLock(cwd);
1351
+ const ides = detectIDEs(cwd);
1352
+ if (ides.length > 0) syncGitignore(cwd, ides, lock);
1353
+ return ok(`removed "${skill}" from ${stage ? `[${stage}]` : "all stages"}`);
1354
+ }
1355
+ function toolUse(cwd, args) {
1356
+ const { stage, off } = args;
1357
+ const ides = detectIDEs(cwd);
1358
+ const lock = readLock(cwd);
1359
+ if (!stage && !off) {
1360
+ const activeStage = getActiveStage(cwd);
1361
+ const stashed = listStashed(cwd);
1362
+ if (!activeStage)
1363
+ return ok(
1364
+ `No active stage \u2014 all skills available.
1365
+ Stashed: ${stashed.length > 0 ? stashed.join(", ") : "none"}`
1366
+ );
1367
+ return ok(
1368
+ `Active stage: [${activeStage}]
1369
+ Stashed (${stashed.length}): ${stashed.join(", ") || "none"}`
1370
+ );
1371
+ }
1372
+ if (off) {
1373
+ const stashed = listStashed(cwd);
1374
+ if (stashed.length === 0) {
1375
+ clearActiveStage(cwd);
1376
+ return ok("Nothing stashed \u2014 no active stage to clear.");
1377
+ }
1378
+ if (ides.length === 0)
1379
+ return fail("No IDE directories detected \u2014 cannot restore stashed skills.");
1380
+ for (const s of stashed) restoreSkill(cwd, ides, s);
1381
+ clearActiveStage(cwd);
1382
+ syncGitignore(cwd, ides, lock);
1383
+ return ok(
1384
+ `Restored all stashed skills:
1385
+ ${stashed.map((s) => ` restored ${s}`).join("\n")}
1386
+
1387
+ No active stage \u2014 all skills available.`
1388
+ );
1389
+ }
1390
+ if (ides.length === 0) return fail("No IDE directories detected.");
1391
+ const rules = readRules(cwd);
1392
+ if (!rules) return fail("No skills.rules found. Run init first.");
1393
+ const available = listStages(rules);
1394
+ if (!available.includes(stage))
1395
+ return fail(
1396
+ `Stage "${stage}" not found.${available.length ? ` Available: ${available.join(", ")}` : " No stages defined yet."}`
1397
+ );
1398
+ const currentStage = getActiveStage(cwd);
1399
+ if (currentStage === stage) return ok(`Stage [${stage}] is already active.`);
1400
+ const targetSkills = new Set(rules.stages[stage] ?? []);
1401
+ const allStagedSkills = new Set(getActiveSkills(rules, null));
1402
+ const trackedSkills = new Set(
1403
+ Object.entries(lock.skills ?? {}).filter(([, info]) => info.track).map(([name]) => name)
1404
+ );
1405
+ const toActivate = [], toRestore = [], toMissing = [], toStash = [], toSkip = [];
1406
+ for (const skill of allStagedSkills) {
1407
+ if (trackedSkills.has(skill)) {
1408
+ toSkip.push(skill);
1409
+ continue;
1410
+ }
1411
+ if (targetSkills.has(skill)) {
1412
+ if (isStashed(cwd, skill)) toRestore.push(skill);
1413
+ else {
1414
+ const inIDEs = ides.some((ide) => existsSync10(join11(cwd, ide.skillsDir, skill)));
1415
+ if (inIDEs) toActivate.push(skill);
1416
+ else toMissing.push(skill);
1417
+ }
1418
+ } else if (!isStashed(cwd, skill)) {
1419
+ const inIDEs = ides.some((ide) => existsSync10(join11(cwd, ide.skillsDir, skill)));
1420
+ if (inIDEs) toStash.push(skill);
1421
+ }
1422
+ }
1423
+ for (const skill of toStash) stashSkill(cwd, ides, skill);
1424
+ for (const skill of toRestore) restoreSkill(cwd, ides, skill);
1425
+ setActiveStage(stage, cwd);
1426
+ syncGitignore(cwd, ides, lock);
1427
+ const lines = [`Active stage: [${stage}]`, ""];
1428
+ if (toActivate.length) lines.push(...toActivate.map((s) => `keep ${s}`));
1429
+ if (toRestore.length) lines.push(...toRestore.map((s) => `restored ${s}`));
1430
+ if (toStash.length) lines.push(...toStash.map((s) => `stashed ${s}`));
1431
+ if (toMissing.length) lines.push(...toMissing.map((s) => `missing ${s} (not installed)`));
1432
+ if (toSkip.length) lines.push(...toSkip.map((s) => `skip ${s} (tracked in git)`));
1433
+ return ok(lines.join("\n"));
1434
+ }
1435
+ function toolList(cwd, args) {
1436
+ const { track, untrack } = args;
1437
+ const lock = readLock(cwd);
1438
+ const ides = detectIDEs(cwd);
1439
+ if (track || untrack) {
1440
+ const skillName = track ?? untrack;
1441
+ if (!lock.skills[skillName]) return fail(`"${skillName}" not found in skills-lock.json`);
1442
+ setSkillTracked(lock, skillName, !!track);
1443
+ writeLock(lock, cwd);
1444
+ syncGitignore(cwd, ides, lock);
1445
+ return ok(
1446
+ `${track ? "tracked" : "untracked"} "${skillName}" \u2014 ${track ? "will be committed to git" : "excluded from git"}`
1447
+ );
1448
+ }
1449
+ const rules = readRules(cwd);
1450
+ const stages = listStages(rules ?? { stages: {} });
1451
+ const skills = Object.entries(lock.skills);
1452
+ if (skills.length === 0) return ok("No skills installed yet. Run: sr add");
1453
+ const header = `${"SKILL".padEnd(22)} ${"STAGES".padEnd(18)} INSTALLED TRACKING`;
1454
+ const lines = [header, ""];
1455
+ for (const [name, info] of skills) {
1456
+ const sources = findSkillSources(cwd, ides, name);
1457
+ const skillStages = stages.filter((s) => (rules?.stages[s] ?? []).includes(name));
1458
+ const installed = sources.length === 0 ? "missing" : sources.length === ides.length ? "all IDEs" : "partial";
1459
+ const tracking = info.track ? "git" : "ignored";
1460
+ lines.push(
1461
+ `${name.padEnd(22)} ${(skillStages.join(", ") || "\u2014").padEnd(18)} ${installed.padEnd(13)}${tracking}`
1462
+ );
1463
+ }
1464
+ return ok(lines.join("\n"));
1465
+ }
1466
+ function toolIgnore(cwd) {
1467
+ const ides = detectIDEs(cwd);
1468
+ if (ides.length === 0) return fail("No IDE directories detected. Nothing to ignore.");
1469
+ const lock = readLock(cwd);
1470
+ syncGitignore(cwd, ides, lock);
1471
+ return ok(
1472
+ `.gitignore updated for ${ides.length} IDE${ides.length !== 1 ? "s" : ""}: ${ides.map((i) => i.name).join(", ")}`
1473
+ );
1474
+ }
1475
+ async function startMcpServer() {
1476
+ const cwd = process.cwd();
1477
+ const server = new Server(
1478
+ { name: "skill-rules", version: "0.2.0" },
1479
+ { capabilities: { tools: {} } }
1480
+ );
1481
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
1482
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1483
+ const { name, arguments: args = {} } = request.params;
1484
+ try {
1485
+ switch (name) {
1486
+ case "sync":
1487
+ return toolSync(cwd, args);
1488
+ case "status":
1489
+ return toolStatus(cwd);
1490
+ case "init":
1491
+ return toolInit(cwd);
1492
+ case "add":
1493
+ return toolAdd(cwd, args);
1494
+ case "remove":
1495
+ return toolRemove(cwd, args);
1496
+ case "use":
1497
+ return toolUse(cwd, args);
1498
+ case "list":
1499
+ return toolList(cwd, args);
1500
+ case "ignore":
1501
+ return toolIgnore(cwd);
1502
+ default:
1503
+ return fail(`Unknown tool: ${name}`);
1504
+ }
1505
+ } catch (e) {
1506
+ return fail(e.message);
1507
+ }
1508
+ });
1509
+ const transport = new StdioServerTransport();
1510
+ await server.connect(transport);
1511
+ }
1512
+
1513
+ // src/commands/mcp.js
1514
+ async function mcp() {
1515
+ await startMcpServer();
1516
+ }
1517
+
1109
1518
  // src/cli.jsx
1110
1519
  var __dirname = dirname(fileURLToPath(import.meta.url));
1111
- var { version } = JSON.parse(readFileSync5(join11(__dirname, "../package.json"), "utf8"));
1520
+ var { version } = JSON.parse(readFileSync5(join12(__dirname, "../package.json"), "utf8"));
1112
1521
  var stageOption = ["-s, --stage <stage>", "limit to a specific stage (e.g. dev, qa)"];
1113
1522
  function createCli() {
1114
1523
  const program = new Command();
@@ -1120,6 +1529,7 @@ function createCli() {
1120
1529
  program.command("ignore").description("Regenerate .gitignore for detected IDE skill directories").action(ignore);
1121
1530
  program.command("use [stage]").description("Activate a stage \u2014 stashes skills not in it, restores those that are").option("--off", "restore all stashed skills and clear the active stage").action(use);
1122
1531
  program.command("help").description("Show help for all commands").action(help);
1532
+ program.command("mcp").description("Start the skill-rules MCP server (stdio transport)").action(mcp);
1123
1533
  return program;
1124
1534
  }
1125
1535
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-rules",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sync AI agent skills across Claude Code, Cursor, Windsurf and more — activate per-environment rule sets with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@inkjs/ui": "~2.0.0",
18
+ "@modelcontextprotocol/sdk": "~1.29.0",
18
19
  "commander": "~12.1.0",
19
20
  "figures": "~6.1.0",
20
21
  "ink": "~5.2.1",