@kaddo/mcp 3.20.0 → 3.22.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 +3 -1
  2. package/dist/index.js +229 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -49,7 +49,8 @@ directory set via the `KADDO_PROJECT_DIR` environment variable).
49
49
  | `kaddo://roadmap` | `knowledge/delivery/roadmap.md` | delivery roadmap |
50
50
  | `kaddo://capsules` | `.kaddo/external.yml` + `external/` | external Knowledge Capsules |
51
51
  | `kaddo://agents` | `knowledge/agents/` | installed agent prompts |
52
- | `kaddo://skills` | `knowledge/skills/` | installed skills (empty if none) |
52
+ | `kaddo://skills` | `knowledge/skills/` | installed reusable skills (empty if none) |
53
+ | `kaddo://skills/<id>` | `knowledge/skills/<id>/skill.md` | one reusable skill |
53
54
 
54
55
  ## Tools (read-only)
55
56
 
@@ -58,6 +59,7 @@ directory set via the `KADDO_PROJECT_DIR` environment variable).
58
59
  - `kaddo_get_work_item` — a Work Item by `id` (summary + full markdown).
59
60
  - `kaddo_list_capsules` / `kaddo_get_capsule` — external Knowledge Capsules.
60
61
  - `kaddo_list_agents` / `kaddo_get_agent_prompt` — installed agent prompts.
62
+ - `kaddo_list_skills` / `kaddo_get_skill` — installed reusable skills.
61
63
  - `kaddo_list_graph_hints` — graph hints, filter by `artifact_type` / `severity` / `active_only`.
62
64
 
63
65
  ## Derived tools (write only under `.kaddo/`)
package/dist/index.js CHANGED
@@ -207,8 +207,50 @@ function getAgentPrompt(root, name) {
207
207
  return { ...info, content };
208
208
  }
209
209
 
210
- // src/workitems.ts
210
+ // src/skills.ts
211
211
  import matter2 from "gray-matter";
212
+ function firstHeadingOrLine2(md) {
213
+ for (const line of md.split(/\r?\n/)) {
214
+ const t = line.trim();
215
+ if (!t || t.startsWith("#") || t.startsWith("---")) continue;
216
+ return t.replace(/\s+/g, " ").slice(0, 200);
217
+ }
218
+ return "";
219
+ }
220
+ function listSkills(root) {
221
+ const files = listFiles(root, "knowledge/skills", "skill.md");
222
+ const out = [];
223
+ for (const rel of files) {
224
+ const raw = readText(root, rel);
225
+ if (raw === null) continue;
226
+ let data = {};
227
+ try {
228
+ data = matter2(raw).data;
229
+ } catch {
230
+ }
231
+ if (data.type && String(data.type) !== "skill") continue;
232
+ const folder = rel.split("/").slice(-2)[0];
233
+ out.push({
234
+ id: String(data.id ?? folder),
235
+ title: String(data.title ?? folder),
236
+ group: String(data.group ?? "unknown"),
237
+ applies_to: Array.isArray(data.applies_to) ? data.applies_to.map(String).filter(Boolean) : [],
238
+ path: rel
239
+ });
240
+ }
241
+ return out.sort((a, b) => a.id.localeCompare(b.id));
242
+ }
243
+ function getSkill(root, id) {
244
+ const want = id.replace(/\.md$/, "");
245
+ const info = listSkills(root).find((s) => s.id === want);
246
+ if (!info) return null;
247
+ const content = readText(root, info.path);
248
+ if (content === null) return null;
249
+ return { ...info, content, description: firstHeadingOrLine2(content) || info.title };
250
+ }
251
+
252
+ // src/workitems.ts
253
+ import matter3 from "gray-matter";
212
254
  var ACTIVE_STATES = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
213
255
  function strArray(v) {
214
256
  return Array.isArray(v) ? v.map((x) => String(x)).filter(Boolean) : [];
@@ -235,7 +277,7 @@ function listWorkItems(root) {
235
277
  if (raw === null) continue;
236
278
  let parsed;
237
279
  try {
238
- parsed = matter2(raw);
280
+ parsed = matter3(raw);
239
281
  } catch {
240
282
  continue;
241
283
  }
@@ -396,18 +438,15 @@ var RESOURCES = [
396
438
  {
397
439
  uri: "kaddo://skills",
398
440
  name: "Kaddo skills",
399
- description: "Installed skills from knowledge/skills/ (empty if none).",
441
+ description: "Installed reusable skills from knowledge/skills/ (empty if none).",
400
442
  mimeType: "application/json",
401
- read: (root) => {
402
- const files = listFiles(root, "knowledge/skills", ".md");
403
- return [
404
- {
405
- uri: "kaddo://skills",
406
- text: JSON.stringify({ skills: files }, null, 2),
407
- mimeType: "application/json"
408
- }
409
- ];
410
- }
443
+ read: (root) => [
444
+ {
445
+ uri: "kaddo://skills",
446
+ text: JSON.stringify({ skills: listSkills(root) }, null, 2),
447
+ mimeType: "application/json"
448
+ }
449
+ ]
411
450
  }
412
451
  ];
413
452
 
@@ -465,8 +504,12 @@ function projectStatus(root) {
465
504
  withOwnership: explain.ownership?.workItemsWithOwnership ?? 0,
466
505
  total: explain.ownership?.workItemsTotal ?? 0
467
506
  },
468
- graphQuality: hints?.quality ?? "unknown (run `kaddo graph export`)",
469
- graphHints: hints?.summary?.hints ?? 0,
507
+ graph: {
508
+ scope: hints?.scope ?? "unknown (run `kaddo graph export`)",
509
+ scope_reason: hints?.scope_reason ?? "",
510
+ quality: hints?.quality ?? "unknown (run `kaddo graph export`)",
511
+ hints: hints?.summary?.hints ?? 0
512
+ },
470
513
  capsules: capsules.length
471
514
  });
472
515
  }
@@ -499,6 +542,14 @@ function getAgentPromptTool(root, name) {
499
542
  if (!agent) return fail(`Agent "${name}" is not installed. Run \`kaddo add agents\` first.`);
500
543
  return ok(agent);
501
544
  }
545
+ function listSkillsTool(root) {
546
+ return ok(listSkills(root));
547
+ }
548
+ function getSkillTool(root, id) {
549
+ const skill = getSkill(root, id);
550
+ if (!skill) return fail(`Skill "${id}" is not installed. Run \`kaddo add skills\` first.`);
551
+ return ok(skill);
552
+ }
502
553
  function listGraphHints(root, filter = {}) {
503
554
  const report = readJson(root, ".kaddo/graph-hints.json");
504
555
  if (!report) return fail("Graph hints not found. Run `kaddo graph export` first.");
@@ -506,7 +557,13 @@ function listGraphHints(root, filter = {}) {
506
557
  if (filter.artifact_type) hints = hints.filter((h) => h.artifact_type === filter.artifact_type);
507
558
  if (filter.severity) hints = hints.filter((h) => h.severity === filter.severity);
508
559
  if (filter.active_only) hints = hints.filter((h) => h.artifact_type === "work-item");
509
- return ok({ quality: report.quality ?? "unknown", count: hints.length, hints });
560
+ return ok({
561
+ scope: report.scope ?? "active",
562
+ scope_reason: report.scope_reason ?? "",
563
+ quality: report.quality ?? "unknown",
564
+ count: hints.length,
565
+ hints
566
+ });
510
567
  }
511
568
 
512
569
  // ../cli/src/core/config.ts
@@ -528,6 +585,9 @@ function readDir(dirPath) {
528
585
  function isFile(p) {
529
586
  return exists(p) && fs2.statSync(p).isFile();
530
587
  }
588
+ function isDir(p) {
589
+ return exists(p) && fs2.statSync(p).isDirectory();
590
+ }
531
591
  function join(...parts) {
532
592
  return path2.join(...parts);
533
593
  }
@@ -619,13 +679,13 @@ ${issues}`);
619
679
  }
620
680
 
621
681
  // ../cli/src/core/context-pack.ts
622
- import matter5 from "gray-matter";
682
+ import matter7 from "gray-matter";
623
683
 
624
684
  // ../cli/src/services/artifact-reader.ts
625
- import matter3 from "gray-matter";
685
+ import matter4 from "gray-matter";
626
686
  function parseArtifact(filePath, raw) {
627
687
  try {
628
- const { data } = matter3(raw);
688
+ const { data } = matter4(raw);
629
689
  const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
630
690
  return {
631
691
  filePath,
@@ -1283,7 +1343,7 @@ function assessPhase(input) {
1283
1343
  }
1284
1344
 
1285
1345
  // ../cli/src/core/capsule.ts
1286
- import matter4 from "gray-matter";
1346
+ import matter5 from "gray-matter";
1287
1347
  import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
1288
1348
 
1289
1349
  // ../cli/src/services/owners.ts
@@ -1436,7 +1496,7 @@ function sectionParagraph2(md, title) {
1436
1496
  return "";
1437
1497
  }
1438
1498
  function parseCapsule(id, path3, md) {
1439
- const { data } = matter4(md);
1499
+ const { data } = matter5(md);
1440
1500
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
1441
1501
  let ageDays = null;
1442
1502
  if (updatedAt) {
@@ -1471,6 +1531,11 @@ function loadExternalCapsules(dir) {
1471
1531
 
1472
1532
  // ../cli/src/core/graph.ts
1473
1533
  var KNOWLEDGE3 = "knowledge";
1534
+ var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
1535
+ var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
1536
+ function scopeStatuses(scope) {
1537
+ return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
1538
+ }
1474
1539
  function toPosix3(p) {
1475
1540
  return p.replace(/\\/g, "/");
1476
1541
  }
@@ -1513,7 +1578,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
1513
1578
  }
1514
1579
  const all = discoverKnowledge(dir);
1515
1580
  const workItems = all.filter((a) => a.isWorkItem);
1516
- const selectedWIs = scope === "active" ? workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)) : workItems;
1581
+ const { included, excluded } = scopeStatuses(scope);
1582
+ const includedSet = new Set(included);
1583
+ const activeWICount = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)).length;
1584
+ const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
1517
1585
  for (const wi of selectedWIs) {
1518
1586
  const id = wi.id || wi.title;
1519
1587
  const wiNodeId = `wi:${id}`;
@@ -1583,6 +1651,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
1583
1651
  }
1584
1652
  }
1585
1653
  }
1654
+ const scopeReason = scope === "all" ? "All supported Work Item statuses are included." : activeWICount === 0 ? "No active Work Items found. Completed Work Items are excluded from active scope." : "Active Work Items only; completed and archived are excluded.";
1586
1655
  return {
1587
1656
  generated_at: now.toISOString(),
1588
1657
  project: {
@@ -1590,6 +1659,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
1590
1659
  state: config.project.state,
1591
1660
  structure: config.project.structure
1592
1661
  },
1662
+ scope,
1663
+ scope_reason: scopeReason,
1664
+ included_statuses: included,
1665
+ excluded_statuses: excluded,
1593
1666
  nodes: [...nodes.values()],
1594
1667
  edges
1595
1668
  };
@@ -1637,8 +1710,13 @@ function loadGraphSummary(dir) {
1637
1710
  const connected = new Set(
1638
1711
  edges.filter((e) => e.type === "owns" && activeWiIds.has(e.from)).map((e) => e.from)
1639
1712
  );
1713
+ const scope = graph.scope === "all" ? "all" : "active";
1640
1714
  return {
1641
1715
  generatedAt: String(graph.generated_at ?? ""),
1716
+ scope,
1717
+ scopeReason: String(graph.scope_reason ?? ""),
1718
+ includedStatuses: Array.isArray(graph.included_statuses) ? graph.included_statuses : [],
1719
+ excludedStatuses: Array.isArray(graph.excluded_statuses) ? graph.excluded_statuses : [],
1642
1720
  nodes: nodes.length,
1643
1721
  edges: edges.length,
1644
1722
  activeWorkItemsConnectedToCode: connected.size
@@ -1790,6 +1868,8 @@ function buildGraphHints(dir, graph, now = /* @__PURE__ */ new Date()) {
1790
1868
  const quality = assessQuality(nodes, relationshipEdges, hints.length);
1791
1869
  return {
1792
1870
  generated_at: now.toISOString(),
1871
+ scope: graph.scope,
1872
+ scope_reason: graph.scope_reason,
1793
1873
  quality,
1794
1874
  summary: { nodes, edges: graph.edges.length, hints: hints.length },
1795
1875
  metrics,
@@ -1825,13 +1905,23 @@ function renderGraphHintsMarkdown(report) {
1825
1905
  lines.push("");
1826
1906
  lines.push("## Summary");
1827
1907
  lines.push("");
1908
+ lines.push(`- Scope: ${report.scope} \u2014 ${report.scope_reason}`);
1828
1909
  lines.push(`- Relationship quality: ${report.quality} \u2014 ${QUALITY_NOTE[report.quality]}`);
1829
1910
  lines.push(`- Nodes: ${report.summary.nodes}`);
1830
1911
  lines.push(`- Edges: ${report.summary.edges}`);
1831
1912
  lines.push(`- Hints: ${report.summary.hints}`);
1832
1913
  lines.push("");
1833
1914
  if (report.hints.length === 0) {
1834
- lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
1915
+ if (report.quality === "empty") {
1916
+ lines.push("No active relationship hints were generated.");
1917
+ lines.push("");
1918
+ lines.push(`The graph is ${report.quality} for the **${report.scope}** scope \u2014 ${report.scope_reason}`);
1919
+ if (report.scope !== "all") {
1920
+ lines.push("Run `kaddo graph export --scope all` to inspect completed Work Items and historical relationships.");
1921
+ }
1922
+ } else {
1923
+ lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
1924
+ }
1835
1925
  lines.push("");
1836
1926
  return lines.join("\n");
1837
1927
  }
@@ -1867,6 +1957,8 @@ function loadGraphHints(dir) {
1867
1957
  const report = JSON.parse(readFile(p));
1868
1958
  const hints = Array.isArray(report.hints) ? report.hints : [];
1869
1959
  return {
1960
+ scope: String(report.scope ?? "active"),
1961
+ scopeReason: String(report.scope_reason ?? ""),
1870
1962
  quality: report.quality ?? "empty",
1871
1963
  totalHints: hints.length,
1872
1964
  activeWorkItemHints: hints.filter((h) => h.artifact_type === "work-item").length,
@@ -1877,6 +1969,38 @@ function loadGraphHints(dir) {
1877
1969
  }
1878
1970
  }
1879
1971
 
1972
+ // ../cli/src/services/installed-skills.ts
1973
+ import matter6 from "gray-matter";
1974
+ var SKILLS_DIR = join("knowledge", "skills");
1975
+ function discoverInstalledSkills(dir) {
1976
+ const base = join(dir, SKILLS_DIR);
1977
+ if (!exists(base) || !isDir(base)) return [];
1978
+ const out = [];
1979
+ for (const entry of readDir(base)) {
1980
+ const skillFile = join(base, entry, "skill.md");
1981
+ if (!isFile(skillFile)) continue;
1982
+ try {
1983
+ const { data } = matter6(readFile(skillFile));
1984
+ if (data.type && String(data.type) !== "skill") continue;
1985
+ const id = String(data.id ?? entry);
1986
+ out.push({
1987
+ id,
1988
+ title: String(data.title ?? id),
1989
+ group: String(data.group ?? "unknown"),
1990
+ appliesTo: Array.isArray(data.applies_to) ? data.applies_to.map(String).filter(Boolean) : [],
1991
+ relPath: `${SKILLS_DIR.replace(/\\/g, "/")}/${entry}/skill.md`
1992
+ });
1993
+ } catch {
1994
+ }
1995
+ }
1996
+ return out.sort((a, b) => a.id.localeCompare(b.id));
1997
+ }
1998
+ function skillGroupCounts(skills) {
1999
+ const counts = {};
2000
+ for (const s of skills) counts[s.group] = (counts[s.group] ?? 0) + 1;
2001
+ return counts;
2002
+ }
2003
+
1880
2004
  // ../cli/src/core/context-pack.ts
1881
2005
  var CONTEXT_PACK_VERSION = "1";
1882
2006
  var ARCH_DIR2 = "knowledge";
@@ -1890,7 +2014,7 @@ function readScanJson(dir) {
1890
2014
  }
1891
2015
  }
1892
2016
  function firstParagraph3(markdown) {
1893
- const body = matter5(markdown).content.trim();
2017
+ const body = matter7(markdown).content.trim();
1894
2018
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
1895
2019
  return para ?? "";
1896
2020
  }
@@ -2063,6 +2187,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
2063
2187
  external: loadExternalCapsules(dir),
2064
2188
  graph: loadGraphSummary(dir),
2065
2189
  graphHints: loadGraphHints(dir),
2190
+ skills: discoverInstalledSkills(dir).map((s) => s.id),
2066
2191
  mappedModules,
2067
2192
  missing,
2068
2193
  // VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
@@ -2233,9 +2358,12 @@ function renderContextPack(pack) {
2233
2358
  parts.push(
2234
2359
  [
2235
2360
  "- Available: yes",
2361
+ `- Scope: ${pack.graph.scope}`,
2236
2362
  `- Nodes: ${pack.graph.nodes}`,
2237
2363
  `- Edges: ${pack.graph.edges}`,
2238
- `- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`
2364
+ ...pack.graphHints ? [`- Quality: ${pack.graphHints.quality}`] : [],
2365
+ `- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`,
2366
+ ...pack.graph.scopeReason ? [`- Reason: ${pack.graph.scopeReason}`] : []
2239
2367
  ].join("\n") + "\n"
2240
2368
  );
2241
2369
  parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
@@ -2256,6 +2384,12 @@ function renderContextPack(pack) {
2256
2384
  `);
2257
2385
  }
2258
2386
  }
2387
+ if (pack.skills.length > 0) {
2388
+ parts.push("## Skills\n");
2389
+ parts.push("Available reusable skills (agents apply these; content is not inlined):\n");
2390
+ parts.push(pack.skills.map((s) => `- ${s}`).join("\n") + "\n");
2391
+ parts.push("Read full skill definitions in `knowledge/skills/` or via the Kaddo MCP server (`kaddo://skills`).\n");
2392
+ }
2259
2393
  parts.push("## Missing Context\n");
2260
2394
  if (missing.length > 0) {
2261
2395
  parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
@@ -2475,6 +2609,10 @@ function buildProjectExplanation(dir) {
2475
2609
  externalCapsules: loadExternalCapsules(dir),
2476
2610
  graph: loadGraphSummary(dir),
2477
2611
  graphHints: loadGraphHints(dir),
2612
+ skills: (() => {
2613
+ const installed = discoverInstalledSkills(dir);
2614
+ return { total: installed.length, byGroup: skillGroupCounts(installed) };
2615
+ })(),
2478
2616
  layers,
2479
2617
  roadmap,
2480
2618
  mappedModules,
@@ -2616,13 +2754,27 @@ function renderExplanationHuman(exp) {
2616
2754
  }
2617
2755
  if (exp.graph) {
2618
2756
  lines.push("## Knowledge Graph");
2757
+ lines.push(`- Scope: ${exp.graph.scope}`);
2619
2758
  lines.push(`- Nodes: ${exp.graph.nodes}`);
2620
2759
  lines.push(`- Edges: ${exp.graph.edges}`);
2621
2760
  if (exp.graphHints) {
2622
2761
  lines.push(`- Quality: ${exp.graphHints.quality}`);
2623
2762
  lines.push(`- Hints: ${exp.graphHints.totalHints}`);
2624
2763
  }
2764
+ if (exp.graph.scopeReason) lines.push(`- Reason: ${exp.graph.scopeReason}`);
2625
2765
  if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
2766
+ if (exp.graph.scope === "active" && exp.graphHints?.quality === "empty") {
2767
+ lines.push("- Tip: Run `kaddo graph export --scope all` to include completed Work Items");
2768
+ }
2769
+ lines.push("");
2770
+ }
2771
+ if (exp.skills.total > 0) {
2772
+ lines.push(`## Skills installed: ${exp.skills.total}`);
2773
+ const groups = Object.entries(exp.skills.byGroup).sort((a, b) => a[0].localeCompare(b[0]));
2774
+ if (groups.length > 0) {
2775
+ lines.push("Groups:");
2776
+ for (const [g, n] of groups) lines.push(`- ${g}: ${n}`);
2777
+ }
2626
2778
  lines.push("");
2627
2779
  }
2628
2780
  if (exp.missingKnowledge.length > 0) {
@@ -2871,10 +3023,10 @@ function generateUnderstand(root) {
2871
3023
  next_suggested_resources: ["kaddo://understand"]
2872
3024
  };
2873
3025
  }
2874
- function generateGraph(root) {
3026
+ function generateGraph(root, scope = "active") {
2875
3027
  const config = requireConfig(root);
2876
3028
  requireKnowledge(root);
2877
- const graph = buildGraph(root, config, { scope: "active" });
3029
+ const graph = buildGraph(root, config, { scope });
2878
3030
  const hints = buildGraphHints(root, graph);
2879
3031
  writeDerived(root, ".kaddo/graph.json", serializeGraphJson(graph));
2880
3032
  writeDerived(root, ".kaddo/graph.mmd", renderGraphMermaid(graph));
@@ -2889,7 +3041,7 @@ function generateGraph(root) {
2889
3041
  ".kaddo/graph-hints.md",
2890
3042
  ".kaddo/graph-hints.json"
2891
3043
  ],
2892
- summary: "Knowledge graph and hints generated successfully.",
3044
+ summary: `Knowledge graph and hints generated successfully (${scope} scope).`,
2893
3045
  warnings,
2894
3046
  next_suggested_resources: ["kaddo://graph", "kaddo://graph-hints"]
2895
3047
  };
@@ -3021,6 +3173,38 @@ function createServer(root) {
3021
3173
  },
3022
3174
  async (args) => toolText(guarded(root, () => listGraphHints(root, args)))
3023
3175
  );
3176
+ server.registerTool(
3177
+ "kaddo_list_skills",
3178
+ { title: "List skills", description: "List installed reusable skills.", inputSchema: {} },
3179
+ async () => toolText(guarded(root, () => listSkillsTool(root)))
3180
+ );
3181
+ server.registerTool(
3182
+ "kaddo_get_skill",
3183
+ { title: "Get skill", description: "Get an installed reusable skill by id.", inputSchema: { id: z2.string() } },
3184
+ async (args) => toolText(guarded(root, () => getSkillTool(root, args.id)))
3185
+ );
3186
+ let installedSkills = [];
3187
+ try {
3188
+ assertKaddoProject(root);
3189
+ installedSkills = listSkills(root);
3190
+ } catch {
3191
+ installedSkills = [];
3192
+ }
3193
+ for (const s of installedSkills) {
3194
+ server.registerResource(
3195
+ `skill: ${s.id}`,
3196
+ `kaddo://skills/${s.id}`,
3197
+ { title: s.title, description: `Reusable skill "${s.id}" (${s.group}).`, mimeType: "text/markdown" },
3198
+ async (uri) => {
3199
+ const full = getSkill(root, s.id);
3200
+ return {
3201
+ contents: [
3202
+ { uri: uri.href, text: full?.content ?? `Skill "${s.id}" not found.`, mimeType: "text/markdown" }
3203
+ ]
3204
+ };
3205
+ }
3206
+ );
3207
+ }
3024
3208
  server.registerTool(
3025
3209
  "kaddo_generate_context",
3026
3210
  { title: "Generate context pack", description: `Regenerate .kaddo/context-pack.md + .json. ${DERIVED_NOTE}`, inputSchema: {} },
@@ -3038,8 +3222,12 @@ function createServer(root) {
3038
3222
  );
3039
3223
  server.registerTool(
3040
3224
  "kaddo_generate_graph",
3041
- { title: "Generate knowledge graph", description: `Regenerate .kaddo/graph.json/.mmd + graph-hints.md/.json. ${DERIVED_NOTE}`, inputSchema: {} },
3042
- async () => generated(root, "generate knowledge graph", () => generateGraph(root))
3225
+ {
3226
+ title: "Generate knowledge graph",
3227
+ description: `Regenerate .kaddo/graph.json/.mmd + graph-hints.md/.json. scope: active (default) or all. ${DERIVED_NOTE}`,
3228
+ inputSchema: { scope: z2.enum(["active", "all"]).optional() }
3229
+ },
3230
+ async (args) => generated(root, "generate knowledge graph", () => generateGraph(root, args.scope ?? "active"))
3043
3231
  );
3044
3232
  server.registerTool(
3045
3233
  "kaddo_generate_capsule_draft",
@@ -3064,6 +3252,17 @@ function createServer(root) {
3064
3252
  }
3065
3253
  );
3066
3254
  }
3255
+ for (const s of installedSkills) {
3256
+ server.registerPrompt(
3257
+ `skill-${s.id}`,
3258
+ { title: s.title, description: `Reusable skill "${s.id}" (${s.group}).` },
3259
+ async () => {
3260
+ const full = getSkill(root, s.id);
3261
+ const content = full?.content ?? `Skill "${s.id}" is not installed.`;
3262
+ return { messages: [{ role: "user", content: { type: "text", text: content } }] };
3263
+ }
3264
+ );
3265
+ }
3067
3266
  return server;
3068
3267
  }
3069
3268
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.20.0",
3
+ "version": "3.22.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {