@kaddo/mcp 3.102.0 → 3.104.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 (2) hide show
  1. package/dist/index.js +205 -47
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -464,7 +464,9 @@ var configSchema = z.object({
464
464
  guard: z.unknown().optional(),
465
465
  scan: z.unknown().optional(),
466
466
  telemetry: z.object({
467
- enabled: z.boolean().default(false)
467
+ consent: z.enum(["enabled", "disabled"]).optional(),
468
+ consentVersion: z.number().optional(),
469
+ enabled: z.boolean().optional()
468
470
  }).passthrough().optional()
469
471
  }).passthrough();
470
472
  function configPathFor(dir) {
@@ -10813,18 +10815,23 @@ import fs4 from "fs";
10813
10815
  import matter82 from "gray-matter";
10814
10816
  import matter92 from "gray-matter";
10815
10817
  import { parse as parseYaml92, stringify as stringifyYaml5 } from "yaml";
10816
- function exists2(p2) {
10817
- return fs3.existsSync(p2);
10818
+ import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
10819
+ function exists2(p) {
10820
+ return fs3.existsSync(p);
10818
10821
  }
10819
- function readFile2(p2) {
10820
- return fs3.readFileSync(p2, "utf-8");
10822
+ function readFile2(p) {
10823
+ return fs3.readFileSync(p, "utf-8");
10824
+ }
10825
+ function writeFile2(filePath, content) {
10826
+ fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
10827
+ fs3.writeFileSync(filePath, content, "utf-8");
10821
10828
  }
10822
10829
  function readDir2(dirPath) {
10823
10830
  if (!exists2(dirPath)) return [];
10824
10831
  return fs3.readdirSync(dirPath);
10825
10832
  }
10826
- function isFile2(p2) {
10827
- return exists2(p2) && fs3.statSync(p2).isFile();
10833
+ function isFile2(p) {
10834
+ return exists2(p) && fs3.statSync(p).isFile();
10828
10835
  }
10829
10836
  function join3(...parts) {
10830
10837
  return path3.join(...parts);
@@ -10895,7 +10902,9 @@ var configSchema2 = z3.object({
10895
10902
  guard: z3.unknown().optional(),
10896
10903
  scan: z3.unknown().optional(),
10897
10904
  telemetry: z3.object({
10898
- enabled: z3.boolean().default(false)
10905
+ consent: z3.enum(["enabled", "disabled"]).optional(),
10906
+ consentVersion: z3.number().optional(),
10907
+ enabled: z3.boolean().optional()
10899
10908
  }).passthrough().optional()
10900
10909
  }).passthrough();
10901
10910
  function configPathFor2(dir) {
@@ -11047,8 +11056,8 @@ function isActiveState2(s) {
11047
11056
  return ACTIVE_STATES3.includes(s);
11048
11057
  }
11049
11058
  function lifecycleFolderOf2(filePath) {
11050
- const p2 = filePath.replace(/\\/g, "/");
11051
- const m2 = p2.match(/\/delivery\/work-items\/([^/]+)\//);
11059
+ const p = filePath.replace(/\\/g, "/");
11060
+ const m2 = p.match(/\/delivery\/work-items\/([^/]+)\//);
11052
11061
  if (m2 && isLifecycleState2(m2[1])) return m2[1];
11053
11062
  return null;
11054
11063
  }
@@ -11061,8 +11070,8 @@ function lifecycleStateOf2(item) {
11061
11070
  return "ready";
11062
11071
  }
11063
11072
  var ARCH_DIR3 = "knowledge";
11064
- function toPosix6(p2) {
11065
- return p2.replace(/\\/g, "/");
11073
+ function toPosix6(p) {
11074
+ return p.replace(/\\/g, "/");
11066
11075
  }
11067
11076
  function relativeTo2(dir, filePath) {
11068
11077
  const base = toPosix6(dir).replace(/\/+$/, "");
@@ -12307,10 +12316,10 @@ ${QUALITY2}
12307
12316
  `;
12308
12317
  var EXTERNAL_PATH2 = ".kaddo/external.yml";
12309
12318
  function loadExternalRegistry2(dir) {
12310
- const p2 = join3(dir, EXTERNAL_PATH2);
12311
- if (!exists2(p2)) return [];
12319
+ const p = join3(dir, EXTERNAL_PATH2);
12320
+ if (!exists2(p)) return [];
12312
12321
  try {
12313
- const parsed = parseYaml62(readFile2(p2));
12322
+ const parsed = parseYaml62(readFile2(p));
12314
12323
  return Array.isArray(parsed?.external) ? parsed.external : [];
12315
12324
  } catch {
12316
12325
  return [];
@@ -12322,8 +12331,8 @@ var ALL_STATUSES2 = ["draft", "ready", "in-progress", "blocked", "completed"];
12322
12331
  function scopeStatuses2(scope) {
12323
12332
  return scope === "all" ? { included: ALL_STATUSES2, excluded: ["archived"] } : { included: ACTIVE_STATUSES2, excluded: ["completed", "archived"] };
12324
12333
  }
12325
- function toPosix22(p2) {
12326
- return p2.replace(/\\/g, "/");
12334
+ function toPosix22(p) {
12335
+ return p.replace(/\\/g, "/");
12327
12336
  }
12328
12337
  function slug3(s) {
12329
12338
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -12461,8 +12470,8 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
12461
12470
  };
12462
12471
  }
12463
12472
  var KNOWLEDGE32 = "knowledge";
12464
- function toPosix32(p2) {
12465
- return p2.replace(/\\/g, "/");
12473
+ function toPosix32(p) {
12474
+ return p.replace(/\\/g, "/");
12466
12475
  }
12467
12476
  function slug22(s) {
12468
12477
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -12471,9 +12480,9 @@ function isAdr22(a2) {
12471
12480
  return toPosix32(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
12472
12481
  }
12473
12482
  function capabilityHeadings2(dir) {
12474
- const p2 = join3(dir, KNOWLEDGE32, "product", "capabilities.md");
12475
- if (!exists2(p2)) return [];
12476
- return readFile2(p2).split(/\r?\n/).map((l2) => l2.match(/^#{2,3}\s+(.+?)\s*$/)).filter((m2) => Boolean(m2)).map((m2) => m2[1].trim()).filter((h2) => !/^(summary|resumen|overview|capabilities|capacidades)$/i.test(h2));
12483
+ const p = join3(dir, KNOWLEDGE32, "product", "capabilities.md");
12484
+ if (!exists2(p)) return [];
12485
+ return readFile2(p).split(/\r?\n/).map((l2) => l2.match(/^#{2,3}\s+(.+?)\s*$/)).filter((m2) => Boolean(m2)).map((m2) => m2[1].trim()).filter((h2) => !/^(summary|resumen|overview|capabilities|capacidades)$/i.test(h2));
12477
12486
  }
12478
12487
  function humanMissing2(field) {
12479
12488
  switch (field) {
@@ -15779,7 +15788,7 @@ var AGENT_PROMPTS2 = [
15779
15788
  // Graph relationship quality (metadata hints → precise front matter — VS-056)
15780
15789
  { fileName: "graph-agent.md", content: GRAPH_AGENT2 }
15781
15790
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
15782
- ].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace2(p2.fileName, p2.content) }));
15791
+ ].map((p) => ({ fileName: p.fileName, content: withResponsibilityTrace2(p.fileName, p.content) }));
15783
15792
  function withAgentFrontMatter2(fileName, content) {
15784
15793
  const name = fileName.replace(/\.md$/, "");
15785
15794
  const fm = [
@@ -16418,8 +16427,8 @@ var TECHNICAL_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
16418
16427
  "implemented-by"
16419
16428
  ]);
16420
16429
  var PROVENANCE = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewed"]);
16421
- function isRelative(p2) {
16422
- return typeof p2 === "string" && p2.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p2) && !p2.includes("..");
16430
+ function isRelative(p) {
16431
+ return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
16423
16432
  }
16424
16433
  function strList(v) {
16425
16434
  return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
@@ -16462,9 +16471,9 @@ function parseTopologyContent(raw, dir) {
16462
16471
  const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
16463
16472
  if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
16464
16473
  const rawImpl = e.implementation ?? e.implementation_refs;
16465
- const implementation = strList(rawImpl).filter((p2) => {
16466
- if (isRelative(p2)) return true;
16467
- findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p2}" is not a safe relative path and was dropped.` });
16474
+ const implementation = strList(rawImpl).filter((p) => {
16475
+ if (isRelative(p)) return true;
16476
+ findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
16468
16477
  return false;
16469
16478
  });
16470
16479
  const prov = e.provenance;
@@ -16672,8 +16681,8 @@ function splitSections(body) {
16672
16681
  flush();
16673
16682
  return sections;
16674
16683
  }
16675
- function isPlaceholder(text3) {
16676
- const t = text3.trim();
16684
+ function isPlaceholder(text22) {
16685
+ const t = text22.trim();
16677
16686
  if (!t) return true;
16678
16687
  if (/^tbd$/i.test(t)) return true;
16679
16688
  if (/^_[^_]*_$/.test(t) && !t.includes("\n")) return true;
@@ -16700,15 +16709,15 @@ function extractBullets(body) {
16700
16709
  for (const line of body.split(/\r?\n/)) {
16701
16710
  const m2 = line.match(/^\s*[-*+]\s+(.*)$/);
16702
16711
  if (!m2) continue;
16703
- let text3 = m2[1].trim();
16712
+ let text22 = m2[1].trim();
16704
16713
  let checked = null;
16705
- const cb = text3.match(/^\[([ xX])\]\s*(.*)$/);
16714
+ const cb = text22.match(/^\[([ xX])\]\s*(.*)$/);
16706
16715
  if (cb) {
16707
16716
  checked = cb[1].toLowerCase() === "x";
16708
- text3 = cb[2].trim();
16717
+ text22 = cb[2].trim();
16709
16718
  }
16710
- if (!text3 || isPlaceholder(text3)) continue;
16711
- out.push({ text: text3, checked });
16719
+ if (!text22 || isPlaceholder(text22)) continue;
16720
+ out.push({ text: text22, checked });
16712
16721
  }
16713
16722
  return out;
16714
16723
  }
@@ -17453,7 +17462,7 @@ var SECRET_PATTERNS = [
17453
17462
  /\.pfx$/i
17454
17463
  ];
17455
17464
  function filterSecretPaths(paths) {
17456
- return paths.filter((p2) => !SECRET_PATTERNS.some((re) => re.test(p2)));
17465
+ return paths.filter((p) => !SECRET_PATTERNS.some((re) => re.test(p)));
17457
17466
  }
17458
17467
  function collectImplementationEvidence(dir, workItemId, input) {
17459
17468
  const artifacts = discoverWorkItems2(dir);
@@ -18484,6 +18493,92 @@ function createDefaultRegistry() {
18484
18493
  return registry22;
18485
18494
  }
18486
18495
  var registry = createDefaultRegistry();
18496
+ var CURRENT_CONSENT_VERSION = 1;
18497
+ var DEFERRAL_DURATION_MS = 24 * 60 * 60 * 1e3;
18498
+ function getConsentState(config) {
18499
+ if (!config) return "unset";
18500
+ const t = config.telemetry;
18501
+ if (!t) return "unset";
18502
+ if (t.consent === "enabled") return "enabled";
18503
+ if (t.consent === "disabled") return "disabled";
18504
+ if (t.consent === void 0) {
18505
+ if (t.enabled === true) return "enabled";
18506
+ if (t.enabled === false) return "disabled";
18507
+ }
18508
+ return "unset";
18509
+ }
18510
+ function persistConsent(dir, consent) {
18511
+ const configPath = join3(dir, ".kaddo", "config.yml");
18512
+ if (!exists2(configPath)) return;
18513
+ const raw = readFile2(configPath);
18514
+ const doc = parseYaml102(raw) ?? {};
18515
+ const prev = doc.telemetry ?? {};
18516
+ delete prev.enabled;
18517
+ doc.telemetry = { ...prev, consent, consentVersion: CURRENT_CONSENT_VERSION };
18518
+ writeFile2(configPath, stringifyYaml6(doc));
18519
+ }
18520
+ function isDeferralActive(dir) {
18521
+ const p = join3(dir, ".kaddo", "telemetry-deferred.json");
18522
+ if (!exists2(p)) return false;
18523
+ try {
18524
+ const data = JSON.parse(readFile2(p));
18525
+ return data.deferredUntil > Date.now();
18526
+ } catch {
18527
+ return false;
18528
+ }
18529
+ }
18530
+ function deferConsent(dir) {
18531
+ const p = join3(dir, ".kaddo", "telemetry-deferred.json");
18532
+ writeFile2(p, JSON.stringify({ deferredUntil: Date.now() + DEFERRAL_DURATION_MS }));
18533
+ }
18534
+ function consentNotice() {
18535
+ return '[Telemetry consent] Anonymous usage telemetry has not been configured.\n Collects: command usage, version, lifecycle events.\n Never collects: source code, knowledge, prompts or PII.\n Use kaddo_set_telemetry_consent to enable, disable, or defer ("not-now").';
18536
+ }
18537
+ var TELEMETRY_DIR = ".kaddo/telemetry";
18538
+ var IDENTITY_FILE = "identity.json";
18539
+ function identityPath(dir) {
18540
+ return join3(dir, TELEMETRY_DIR, IDENTITY_FILE);
18541
+ }
18542
+ function loadIdentity(dir) {
18543
+ const p = identityPath(dir);
18544
+ if (!exists2(p)) return null;
18545
+ try {
18546
+ return JSON.parse(readFile2(p));
18547
+ } catch {
18548
+ return null;
18549
+ }
18550
+ }
18551
+ var TELEMETRY_DIR2 = ".kaddo/telemetry";
18552
+ var BUFFER_FILE = "buffer.json";
18553
+ function bufferPath(dir) {
18554
+ return join3(dir, TELEMETRY_DIR2, BUFFER_FILE);
18555
+ }
18556
+ function readBuffer(dir) {
18557
+ const p = bufferPath(dir);
18558
+ if (!exists2(p)) return [];
18559
+ try {
18560
+ const data = JSON.parse(readFile2(p));
18561
+ return Array.isArray(data) ? data : [];
18562
+ } catch {
18563
+ return [];
18564
+ }
18565
+ }
18566
+ function bufferSize(dir) {
18567
+ return readBuffer(dir).length;
18568
+ }
18569
+ function getStatus(dir) {
18570
+ const config = loadConfig2(dir);
18571
+ const consent = getConsentState(config);
18572
+ const enabled = consent === "enabled";
18573
+ const identity = loadIdentity(dir);
18574
+ return {
18575
+ consent,
18576
+ enabled,
18577
+ registered: identity?.registered ?? false,
18578
+ pendingEvents: enabled ? bufferSize(dir) : 0,
18579
+ lastError: null
18580
+ };
18581
+ }
18487
18582
 
18488
18583
  // src/tools.ts
18489
18584
  var ok = (data) => ({ ok: true, data });
@@ -18715,12 +18810,45 @@ function verifyWorkItemTool(root, args) {
18715
18810
  return fail("Work Item verification failed.");
18716
18811
  }
18717
18812
  }
18813
+ function telemetryStatusTool(root) {
18814
+ const status = getStatus(root);
18815
+ const result = {
18816
+ consent: status.consent,
18817
+ registered: status.registered,
18818
+ pendingEvents: status.pendingEvents
18819
+ };
18820
+ if (status.consent === "unset" && !isDeferralActive(root)) {
18821
+ result.notice = consentNotice();
18822
+ }
18823
+ return ok(result);
18824
+ }
18825
+ function setTelemetryConsentTool(root, args) {
18826
+ const valid = ["enabled", "disabled", "not-now"];
18827
+ if (!valid.includes(args.consent)) {
18828
+ return fail(`consent must be one of: ${valid.join(", ")}`);
18829
+ }
18830
+ if (!args.confirm) {
18831
+ const preview = {
18832
+ action: args.consent,
18833
+ effect: args.consent === "enabled" ? "Anonymous usage telemetry will be activated. Kaddo will send command usage, version, and lifecycle events to telemetry.kaddo.org. No source code, knowledge, prompts or PII is ever transmitted." : args.consent === "disabled" ? "Telemetry will be permanently disabled. No data will be sent. Kaddo will not ask again." : "Consent decision deferred for 24 hours. Telemetry remains inactive. Kaddo will remind again after the deferral expires.",
18834
+ instruction: "Call again with confirm=true to apply."
18835
+ };
18836
+ return ok(preview);
18837
+ }
18838
+ if (args.consent === "not-now") {
18839
+ deferConsent(root);
18840
+ return ok({ applied: true, consent: "unset", deferred: true, message: "Consent deferred for 24 hours." });
18841
+ }
18842
+ const consent = args.consent;
18843
+ persistConsent(root, consent);
18844
+ return ok({ applied: true, consent, message: `Telemetry ${consent}.` });
18845
+ }
18718
18846
 
18719
18847
  // src/multirepo.ts
18720
18848
  import fs5 from "fs";
18721
18849
  import path4 from "path";
18722
18850
  import matter12 from "gray-matter";
18723
- import { parse as parseYaml11, stringify as stringifyYaml6 } from "yaml";
18851
+ import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
18724
18852
  var ok2 = (data) => ({ ok: true, data });
18725
18853
  var fail2 = (message) => ({ ok: false, message });
18726
18854
  function moduleRepoPath(m2) {
@@ -18729,7 +18857,7 @@ function moduleRepoPath(m2) {
18729
18857
  function loadModulesDescriptor(root) {
18730
18858
  return readYaml(root, ".kaddo/modules.yml") ?? { modules: [] };
18731
18859
  }
18732
- function loadConfig3(root) {
18860
+ function loadConfig4(root) {
18733
18861
  return readYaml(root, ".kaddo/config.yml");
18734
18862
  }
18735
18863
  function projectRole2(config) {
@@ -18795,7 +18923,7 @@ function slugify3(s) {
18795
18923
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
18796
18924
  }
18797
18925
  function modulesListTool(root, args) {
18798
- const config = loadConfig3(root);
18926
+ const config = loadConfig4(root);
18799
18927
  if (!config) return fail2("No Kaddo config found. Run `kaddo init` first.");
18800
18928
  if (!isCore2(config)) return fail2("kaddo_modules_list can only be called from a core repository (project.role: core).");
18801
18929
  const descriptor = loadModulesDescriptor(root);
@@ -18823,7 +18951,7 @@ function modulesListTool(root, args) {
18823
18951
  });
18824
18952
  }
18825
18953
  function getModuleContextTool(root, args) {
18826
- const config = loadConfig3(root);
18954
+ const config = loadConfig4(root);
18827
18955
  if (!config) return fail2("No Kaddo config found.");
18828
18956
  const descriptor = loadModulesDescriptor(root);
18829
18957
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
@@ -18917,7 +19045,7 @@ function validateWorkItemModulesTool(root, args) {
18917
19045
  });
18918
19046
  }
18919
19047
  function getWorkItemContextTool(root, args) {
18920
- const config = loadConfig3(root);
19048
+ const config = loadConfig4(root);
18921
19049
  if (!config) return fail2("No Kaddo config found.");
18922
19050
  const items = listWorkItems(root);
18923
19051
  const wi = items.find((w3) => w3.id === args.workItemId);
@@ -19023,7 +19151,7 @@ function suggestBranchStrategyTool(root, args) {
19023
19151
  });
19024
19152
  }
19025
19153
  function exportCapsuleTool(root, args) {
19026
- const config = loadConfig3(root);
19154
+ const config = loadConfig4(root);
19027
19155
  if (!config) return fail2("No Kaddo config found.");
19028
19156
  const role = projectRole2(config);
19029
19157
  const warnings = [];
@@ -19153,7 +19281,7 @@ function exportCapsuleTool(root, args) {
19153
19281
  return ok2({ exported: true, type: capsuleType, module: args.module ?? void 0, paths: { json: jsonPath, md: mdPath }, warnings });
19154
19282
  }
19155
19283
  function modulesDiscoverTool(root, args) {
19156
- const config = loadConfig3(root);
19284
+ const config = loadConfig4(root);
19157
19285
  if (!config) return fail2("No Kaddo config found.");
19158
19286
  if (!isCore2(config)) return fail2("Module discovery is only available from a core repository.");
19159
19287
  const sysName = config.system?.name ?? config.project?.name ?? "";
@@ -19210,7 +19338,7 @@ function modulesDiscoverTool(root, args) {
19210
19338
  descriptor.modules.push({ id: d3.id, name: d3.id, path: d3.path, parent_system: sysName, status: "configured" });
19211
19339
  }
19212
19340
  }
19213
- const ymlContent = stringifyYaml6({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
19341
+ const ymlContent = stringifyYaml7({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
19214
19342
  writeDerived(root, ".kaddo/modules.yml", ymlContent);
19215
19343
  return ok2({ discovered, applied: true });
19216
19344
  }
@@ -19256,7 +19384,7 @@ ${list3(data.owners)}
19256
19384
  }
19257
19385
 
19258
19386
  // ../cli/src/core/system-topology.ts
19259
- import { parse as parseYaml12, stringify as stringifyYaml7 } from "yaml";
19387
+ import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
19260
19388
  import fs6 from "fs";
19261
19389
  import path5 from "path";
19262
19390
  import crypto from "crypto";
@@ -19771,7 +19899,7 @@ function systemImpactCandidatesTool(root, args) {
19771
19899
 
19772
19900
  // ../cli/src/services/integrations.ts
19773
19901
  import matter14 from "gray-matter";
19774
- import { parse as parseYaml13, stringify as stringifyYaml8 } from "yaml";
19902
+ import { parse as parseYaml13, stringify as stringifyYaml9 } from "yaml";
19775
19903
 
19776
19904
  // ../cli/src/core/work-item-write.ts
19777
19905
  import fs7 from "fs";
@@ -22015,7 +22143,16 @@ function createServer(root) {
22015
22143
  server.registerTool(
22016
22144
  "kaddo_project_status",
22017
22145
  { title: "Kaddo project status", description: "Compact project status (state, phase, work items, ownership, graph quality, capsules).", inputSchema: {} },
22018
- async () => toolText(guarded(root, () => projectStatus(root)))
22146
+ async () => {
22147
+ const result = guarded(root, () => projectStatus(root));
22148
+ if (result.ok) {
22149
+ const config = loadConfig2(root);
22150
+ if (getConsentState(config) === "unset" && !isDeferralActive(root)) {
22151
+ result.data.telemetryNotice = consentNotice();
22152
+ }
22153
+ }
22154
+ return toolText(result);
22155
+ }
22019
22156
  );
22020
22157
  server.registerTool(
22021
22158
  "kaddo_list_work_items",
@@ -22340,6 +22477,27 @@ function createServer(root) {
22340
22477
  },
22341
22478
  async (args) => toolText(guarded(root, () => verifyWorkItemTool(root, args)))
22342
22479
  );
22480
+ server.registerTool(
22481
+ "kaddo_telemetry_status",
22482
+ {
22483
+ title: "Telemetry status",
22484
+ description: "Read-only telemetry consent state, registration status, and pending events. Includes a consent notice when telemetry has not been configured.",
22485
+ inputSchema: {}
22486
+ },
22487
+ async () => toolText(guarded(root, () => telemetryStatusTool(root)))
22488
+ );
22489
+ server.registerTool(
22490
+ "kaddo_set_telemetry_consent",
22491
+ {
22492
+ title: "Set telemetry consent",
22493
+ description: "Set telemetry consent to enabled, disabled, or not-now (defer 24h). Without confirm=true returns a preview of the action. With confirm=true applies the change. Writes only .kaddo/config.yml (consent field).",
22494
+ inputSchema: {
22495
+ consent: z4.enum(["enabled", "disabled", "not-now"]).describe("Consent decision: enable telemetry, disable it, or defer for 24 hours"),
22496
+ confirm: z4.boolean().optional().describe("Must be true to apply the change")
22497
+ }
22498
+ },
22499
+ async (args) => toolText(guarded(root, () => setTelemetryConsentTool(root, args)))
22500
+ );
22343
22501
  let installedSkills = [];
22344
22502
  try {
22345
22503
  assertKaddoProject(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.102.0",
3
+ "version": "3.104.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": {