@systemdox/mcp-server 0.1.49 → 0.1.51

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/dist/cli.js CHANGED
@@ -301,14 +301,6 @@ var init_api_client = __esm({
301
301
  this.config = config2;
302
302
  this.cache = new ResponseCache(config2.cacheTtlMs);
303
303
  }
304
- async validate() {
305
- const res = await this.fetchRaw("/v1/health");
306
- if (!res.ok) {
307
- throw new Error(
308
- `SystemDox API authentication failed (${res.status}). Check your SYSTEMDOX_API_KEY.`
309
- );
310
- }
311
- }
312
304
  async get(path, params) {
313
305
  const url = this.buildUrl(path, params);
314
306
  const cacheKey = url.toString();
@@ -322,8 +314,8 @@ var init_api_client = __esm({
322
314
  }
323
315
  /** A read that skips the cache: an install must take the version that is
324
316
  * latest now, not the one cached before someone published another. */
325
- async getFresh(path) {
326
- const res = await this.fetchRaw(path);
317
+ async getFresh(path, signal) {
318
+ const res = await this.fetchRaw(path, void 0, signal);
327
319
  if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
328
320
  return await res.json();
329
321
  }
@@ -353,8 +345,8 @@ var init_api_client = __esm({
353
345
  // One method per operationId. Every read skips the cache: setup, sync and
354
346
  // plugin-path act on what the workspace holds now.
355
347
  /** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
356
- getOwnTeamSetup() {
357
- return this.getFresh(TEAM_SETUP);
348
+ getOwnTeamSetup(signal) {
349
+ return this.getFresh(TEAM_SETUP, signal);
358
350
  }
359
351
  getTeamSetup(workspace) {
360
352
  return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
@@ -458,14 +450,15 @@ var init_api_client = __esm({
458
450
  }
459
451
  return url;
460
452
  }
461
- async fetchRaw(path, params) {
453
+ async fetchRaw(path, params, signal) {
462
454
  const url = this.buildUrl(path, params);
463
455
  return fetch(url, {
464
456
  headers: {
465
457
  Authorization: `Bearer ${this.config.apiKey}`,
466
458
  "X-API-Key": this.config.apiKey,
467
459
  Accept: "application/json"
468
- }
460
+ },
461
+ signal
469
462
  });
470
463
  }
471
464
  };
@@ -1030,11 +1023,11 @@ async function people(opts, ws, files, report, hint) {
1030
1023
  report.people.push({ kind: g.kind, answer: ANSWER_WORDS[result.stored[0].answer], label: result.stored[0].label, personId: result.stored[0].personId });
1031
1024
  break;
1032
1025
  }
1033
- const refused = result.refused[0];
1034
- if (refused?.code === "SYD-K021") {
1035
- out(` ${refused.detail}. Add them in Staff first, or answer p to file them.`);
1026
+ const refused2 = result.refused[0];
1027
+ if (refused2?.code === "SYD-K021") {
1028
+ out(` ${refused2.detail}. Add them in Staff first, or answer p to file them.`);
1036
1029
  choices = choices.filter((c) => c !== "m");
1037
- } else if (refused) out(` ${refused.detail}`);
1030
+ } else if (refused2) out(` ${refused2.detail}`);
1038
1031
  }
1039
1032
  }
1040
1033
  for (let i = 0; i < filed.length; i += 50) {
@@ -1773,9 +1766,9 @@ async function runTeamSetup(opts) {
1773
1766
  const id = `${ws}-setup@${marketplaceName}`;
1774
1767
  const installed = await readJsonQuietly(join3(pluginsDir, "installed_plugins.json"));
1775
1768
  if (!installed?.plugins?.[id]) {
1776
- const known = await readJsonQuietly(join3(pluginsDir, "known_marketplaces.json"));
1769
+ const known2 = await readJsonQuietly(join3(pluginsDir, "known_marketplaces.json"));
1777
1770
  const install = `Run \`claude plugin install ${id}\` in your own terminal and accept the command it shows.`;
1778
- const steps = known && typeof known === "object" && known[marketplaceName] ? ["One step is left:", `1. ${install}`] : ["Two steps are left:", "1. Open Claude Code once in any folder, then close it.", `2. ${install}`];
1771
+ const steps = known2 && typeof known2 === "object" && known2[marketplaceName] ? ["One step is left:", `1. ${install}`] : ["Two steps are left:", "1. Open Claude Code once in any folder, then close it.", `2. ${install}`];
1779
1772
  out("");
1780
1773
  steps.forEach((l) => out(l));
1781
1774
  }
@@ -1835,6 +1828,7 @@ __export(setup_mcp_exports, {
1835
1828
  writeServerEntry: () => writeServerEntry
1836
1829
  });
1837
1830
  import { existsSync as existsSync3, readFileSync } from "fs";
1831
+ import { mkdir as mkdir4 } from "fs/promises";
1838
1832
  import { join as join4 } from "path";
1839
1833
  async function validateApiKey(apiKey, apiUrl) {
1840
1834
  try {
@@ -1962,6 +1956,7 @@ async function setupMcpServer(opts) {
1962
1956
  }
1963
1957
  try {
1964
1958
  if ((await readCredentials())[workspace]?.apiKey !== apiKey) await writeCredentials(workspace, apiKey);
1959
+ await mkdir4(join4(homeDir(), ".claude", "skills"), { recursive: true });
1965
1960
  const several = Object.keys(await readCredentials()).length > 1;
1966
1961
  for (const target of selected) {
1967
1962
  const removed = await writeServerEntry(target.path, apiUrl, several ? workspace : void 0);
@@ -1979,9 +1974,10 @@ async function setupMcpServer(opts) {
1979
1974
  Your key for ${workspace} is in ${credentialsPath()} (readable by you alone), not in these configs.
1980
1975
 
1981
1976
  Done! Restart your AI coding tool to activate the SystemDox MCP server.
1982
- The server provides 21 tools: get_context, get_checks,
1977
+ The server provides 25 tools: get_context, get_team_setup, get_checks,
1983
1978
  get_project_rules, get_prompt_templates, get_specs, get_docs,
1984
1979
  get_tech_stack, get_planning_context, get_plan_board, get_change,
1980
+ get_document_links, trace_document_path, get_graph_stats,
1985
1981
  create_check, create_project_rule, create_prompt_template,
1986
1982
  update_spec, report_decision, publish_skill, list_skills,
1987
1983
  install_skill, publish_setup_part, resolve_person, sync_memory.
@@ -2006,7 +2002,7 @@ var init_setup_mcp = __esm({
2006
2002
 
2007
2003
  // src/lib/skills.ts
2008
2004
  import { createHash as createHash3 } from "crypto";
2009
- import { lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
2005
+ import { lstat, mkdir as mkdir5, readdir as readdir3, readFile as readFile4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
2010
2006
  import { existsSync as existsSync4 } from "fs";
2011
2007
  import { homedir as homedir2 } from "os";
2012
2008
  import { dirname as dirname4, join as join5, relative, resolve as resolve3, sep } from "path";
@@ -2017,7 +2013,7 @@ function isValidName(name) {
2017
2013
  return name.length <= 64 && NAME_RE.test(name);
2018
2014
  }
2019
2015
  function isValidPath(path) {
2020
- return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== "..");
2016
+ return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== ".." && s !== PLUGIN_MANIFEST_FOLDER);
2021
2017
  }
2022
2018
  function frontmatterName(skillMd) {
2023
2019
  const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(skillMd);
@@ -2112,15 +2108,15 @@ async function installSkill(version, dest, { overwrite = false } = {}) {
2112
2108
  }
2113
2109
  const present = walked.files;
2114
2110
  const manifest = await readManifest(dest);
2115
- const known = Object.fromEntries(
2111
+ const known2 = Object.fromEntries(
2116
2112
  Object.entries(manifest?.files ?? {}).filter(([p, h]) => isValidPath(p) && typeof h === "string")
2117
2113
  );
2118
2114
  const edited = [];
2119
2115
  const untracked = [];
2120
2116
  for (const path of present) {
2121
2117
  const content = await readFile4(join5(dest, ...path.split("/")), "utf8");
2122
- if (Object.hasOwn(known, path)) {
2123
- if (sha2563(content) !== known[path]) edited.push(path);
2118
+ if (Object.hasOwn(known2, path)) {
2119
+ if (sha2563(content) !== known2[path]) edited.push(path);
2124
2120
  } else if (!incoming.has(path) || sha2563(content) !== incoming.get(path).sha256) {
2125
2121
  if (incoming.has(path) || !manifest) edited.push(path);
2126
2122
  else untracked.push(path);
@@ -2131,13 +2127,13 @@ async function installSkill(version, dest, { overwrite = false } = {}) {
2131
2127
  const written = [];
2132
2128
  for (const f of version.files) {
2133
2129
  const full = within(dest, f.path);
2134
- await mkdir4(dirname4(full), { recursive: true });
2130
+ await mkdir5(dirname4(full), { recursive: true });
2135
2131
  await writeFile4(full, f.content, "utf8");
2136
2132
  written.push(f.path);
2137
2133
  }
2138
2134
  const removed = [];
2139
2135
  const keptEdited = [];
2140
- for (const path of Object.keys(known)) {
2136
+ for (const path of Object.keys(known2)) {
2141
2137
  if (incoming.has(path) || !present.includes(path)) continue;
2142
2138
  if (edited.includes(path)) {
2143
2139
  keptEdited.push(path);
@@ -2166,6 +2162,9 @@ function fenceFor(text3) {
2166
2162
  const longest = Math.max(0, ...(text3.match(/`+/g) ?? []).map((r) => r.length));
2167
2163
  return "`".repeat(Math.max(3, longest + 1));
2168
2164
  }
2165
+ function afterInstall(skillMd) {
2166
+ return `No restart is needed: a running Claude Code session picks it up. If one does not list it yet, read ${skillMd} and follow it, or run /reload-skills.`;
2167
+ }
2169
2168
  function describeInstall(name, r) {
2170
2169
  const lines = [
2171
2170
  `Installed ${name} version ${r.version} into ${r.dest} (${r.written.length} file(s)).`
@@ -2175,15 +2174,16 @@ function describeInstall(name, r) {
2175
2174
  lines.push(`Kept, edited here and no longer in the skill: ${r.keptEdited.join(", ")}`);
2176
2175
  }
2177
2176
  if (r.keptUntracked.length) lines.push(`Kept, added here: ${r.keptUntracked.join(", ")}`);
2178
- lines.push("Start a new Claude Code session to load it.");
2177
+ lines.push(afterInstall(join5(r.dest, "SKILL.md")));
2179
2178
  return lines.join("\n");
2180
2179
  }
2181
- var MANIFEST_FILE, ENTRY_FILE, MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, NAME_RE, SEGMENT_RE, SKIPPED, SkillInstallRefused;
2180
+ var MANIFEST_FILE, ENTRY_FILE, PLUGIN_MANIFEST_FOLDER, MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, NAME_RE, SEGMENT_RE, SKIPPED, SkillInstallRefused;
2182
2181
  var init_skills = __esm({
2183
2182
  "src/lib/skills.ts"() {
2184
2183
  "use strict";
2185
2184
  MANIFEST_FILE = ".systemdox-skill.json";
2186
2185
  ENTRY_FILE = "SKILL.md";
2186
+ PLUGIN_MANIFEST_FOLDER = ".claude-plugin";
2187
2187
  MAX_FILES = 100;
2188
2188
  MAX_FILE_BYTES = 256 * 1024;
2189
2189
  MAX_TOTAL_BYTES = 1024 * 1024;
@@ -2301,7 +2301,7 @@ __export(plugin_path_exports, {
2301
2301
  });
2302
2302
  import { createHash as createHash4, randomBytes } from "crypto";
2303
2303
  import { existsSync as existsSync5 } from "fs";
2304
- import { mkdir as mkdir5, rename as rename2, writeFile as writeFile5 } from "fs/promises";
2304
+ import { mkdir as mkdir6, rename as rename2, writeFile as writeFile5 } from "fs/promises";
2305
2305
  import { dirname as dirname5, join as join6, resolve as resolve5, sep as sep2 } from "path";
2306
2306
  import { inflateRawSync } from "zlib";
2307
2307
  function parsePluginPathArgs(argv) {
@@ -2399,13 +2399,13 @@ async function unpackZip(zip, dest) {
2399
2399
  return full;
2400
2400
  });
2401
2401
  const data = entries2.map((e) => e.directory ? void 0 : entryData(zip, e));
2402
- await mkdir5(root, { recursive: true });
2402
+ await mkdir6(root, { recursive: true });
2403
2403
  for (const [i, entry] of entries2.entries()) {
2404
2404
  if (entry.directory) {
2405
- await mkdir5(targets[i], { recursive: true });
2405
+ await mkdir6(targets[i], { recursive: true });
2406
2406
  continue;
2407
2407
  }
2408
- await mkdir5(dirname5(targets[i]), { recursive: true });
2408
+ await mkdir6(dirname5(targets[i]), { recursive: true });
2409
2409
  await writeFile5(targets[i], data[i], { mode: entry.mode ?? 420 });
2410
2410
  }
2411
2411
  return entries2.filter((e) => !e.directory).length;
@@ -2508,6 +2508,148 @@ var init_plugin_path = __esm({
2508
2508
  }
2509
2509
  });
2510
2510
 
2511
+ // src/lib/session-brief.ts
2512
+ import { existsSync as existsSync6 } from "fs";
2513
+ import { readFile as readFile5 } from "fs/promises";
2514
+ import { join as join7 } from "path";
2515
+ function noKey(reason) {
2516
+ return `This computer has no SystemDox key it can use (${reason}), so the team's rules and skills cannot be read and every SystemDox tool fails. Tell the person once: \`${SETUP_COMMAND}\` in their own terminal asks for one.`;
2517
+ }
2518
+ function keyReason(message) {
2519
+ if (/holds keys for/.test(message)) return "keys for several workspaces are stored; set SYSTEMDOX_WORKSPACE to the one to use";
2520
+ if (/holds no key for/.test(message)) return "SYSTEMDOX_WORKSPACE names a workspace with no stored key";
2521
+ if (/No SystemDox key/.test(message)) return "none is stored";
2522
+ return "the stored keys could not be read";
2523
+ }
2524
+ function olderApi(setup) {
2525
+ return `SystemDox workspace ${setup.workspace}. Before writing code in one of its repositories, call get_context with the task and the files you expect to touch, and obey what it returns. get_team_setup lists the team setup.`;
2526
+ }
2527
+ async function readJsonQuietly2(path) {
2528
+ try {
2529
+ return JSON.parse(await readFile5(path, "utf8"));
2530
+ } catch {
2531
+ return void 0;
2532
+ }
2533
+ }
2534
+ async function computerState(setup) {
2535
+ const id = `${setup.workspace}-setup@systemdox-${setup.workspace}`;
2536
+ const installed = await readJsonQuietly2(join7(homeDir(), ".claude", "plugins", "installed_plugins.json"));
2537
+ const entries2 = installed?.plugins?.[id];
2538
+ const entry = Array.isArray(entries2) ? entries2.find((e) => (e?.scope ?? "user") === "user") : void 0;
2539
+ const state = {
2540
+ missing: [],
2541
+ behind: [],
2542
+ notFromSystemDox: [],
2543
+ pluginInstalled: !!entry,
2544
+ pluginVersion: entry?.version
2545
+ };
2546
+ const pluginCurrent = !!entry && !!setup.plugin && entry.version === `${setup.plugin.version}.0.0`;
2547
+ for (const skill of setup.sessionBrief?.skills ?? []) {
2548
+ const dir = defaultSkillDir(skill.name);
2549
+ if (!existsSync6(dir)) {
2550
+ if (!(pluginCurrent && setup.plugin?.skills?.[skill.name] !== void 0)) state.missing.push(skill.name);
2551
+ continue;
2552
+ }
2553
+ const manifest = await readJsonQuietly2(join7(dir, MANIFEST_FILE));
2554
+ if (typeof manifest?.version !== "number" || !Number.isInteger(manifest.version)) {
2555
+ state.notFromSystemDox.push(skill.name);
2556
+ } else if (manifest.version !== skill.latestVersion) {
2557
+ state.behind.push({ name: skill.name, here: manifest.version, latest: skill.latestVersion });
2558
+ }
2559
+ }
2560
+ return state;
2561
+ }
2562
+ function hasPersonParts(setup) {
2563
+ return Object.keys(setup.parts ?? {}).length > 0 || (setup.memory ?? []).length > 0 || (setup.sessionBrief?.heldSkills ?? []).some((h) => h.reason === "not-plain");
2564
+ }
2565
+ function listWithin(items, room) {
2566
+ const whole2 = items.join(", ");
2567
+ if (whole2.length <= room) return whole2;
2568
+ const kept = [];
2569
+ let used = 0;
2570
+ for (let i = 0; i < items.length; i++) {
2571
+ const rest = items.length - i - 1;
2572
+ const cost = items[i].length + (kept.length ? 2 : 0);
2573
+ if (used + cost + ` and ${MORE(rest)}`.length > room) break;
2574
+ kept.push(items[i]);
2575
+ used += cost;
2576
+ }
2577
+ return kept.length ? `${kept.join(", ")} and ${MORE(items.length - kept.length)}` : `all ${MORE(items.length)}`;
2578
+ }
2579
+ function localSection(setup, state, room) {
2580
+ const rest = hasPersonParts(setup) ? ` and that \`${SETUP_COMMAND}\` in their own terminal adds the rest of the team setup` : "";
2581
+ const latest = new Map((setup.sessionBrief?.skills ?? []).map((s) => [s.name, s.latestVersion]));
2582
+ const lines = [];
2583
+ if (state.missing.length) {
2584
+ lines.push({
2585
+ head: "On this computer, missing: ",
2586
+ items: state.missing.map((name) => `${name} version ${latest.get(name)}`),
2587
+ tail: `. Install each now with install_skill at that version, before your first answer. Then tell the person what you installed${rest}. When a skill's line matches the request and it is not loaded yet, read ~/.claude/skills/<name>/SKILL.md and follow it.`
2588
+ });
2589
+ }
2590
+ if (state.behind.length) {
2591
+ lines.push({
2592
+ head: "On this computer, behind: ",
2593
+ items: state.behind.map((b) => `${b.name} (version ${b.here} here, latest ${b.latest})`),
2594
+ tail: ". install_skill updates each to that latest version; if it refuses because files were edited, tell the person and leave them."
2595
+ });
2596
+ }
2597
+ if (state.notFromSystemDox.length) {
2598
+ lines.push({ head: "Not from SystemDox, left alone: ", items: state.notFromSystemDox, tail: "." });
2599
+ }
2600
+ const fixed = () => lines.reduce((n, l) => n + l.head.length + l.tail.length + LIST_MIN, 0) + Math.max(0, lines.length - 1);
2601
+ while (lines.length && fixed() > room) lines.pop();
2602
+ if (!lines.length) return "";
2603
+ const share = Math.floor((room - fixed()) / lines.length) + LIST_MIN;
2604
+ return lines.map((l) => `${l.head}${listWithin(l.items, share)}${l.tail}`).join("\n");
2605
+ }
2606
+ function fit(text3, section) {
2607
+ const whole2 = section ? `${text3}
2608
+
2609
+ ${section}` : text3;
2610
+ if (whole2.length <= INSTRUCTIONS_MAX) return whole2;
2611
+ return text3.length <= INSTRUCTIONS_MAX ? text3 : `${text3.slice(0, INSTRUCTIONS_MAX - 1)}\u2026`;
2612
+ }
2613
+ async function readTeamSetup(apiClient2, { keyProblem: keyProblem2, timeoutMs = BRIEF_TIMEOUT_MS } = {}) {
2614
+ if (keyProblem2) return { problem: noKey(keyProblem2) };
2615
+ try {
2616
+ const setup = await apiClient2.getOwnTeamSetup(AbortSignal.timeout(timeoutMs));
2617
+ return typeof setup?.workspace === "string" ? { setup } : { problem: NOT_CHECKED };
2618
+ } catch (err) {
2619
+ const status = err?.status;
2620
+ return { problem: status === 401 || status === 403 ? REFUSED : NOT_CHECKED };
2621
+ }
2622
+ }
2623
+ async function sessionInstructions(apiClient2, options = {}) {
2624
+ const read = await readTeamSetup(apiClient2, options);
2625
+ if ("problem" in read) return read.problem;
2626
+ const text3 = read.setup.sessionBrief?.text ?? olderApi(read.setup);
2627
+ try {
2628
+ if (!options.localFiles) return fit(text3, read.setup.sessionBrief?.skills?.length ? CANNOT_SEE : "");
2629
+ const room = INSTRUCTIONS_MAX - text3.length - 2;
2630
+ return fit(text3, localSection(read.setup, await computerState(read.setup), room));
2631
+ } catch {
2632
+ return fit(text3, "");
2633
+ }
2634
+ }
2635
+ var INSTRUCTIONS_MAX, BRIEF_TIMEOUT_MS, NO_KEY, SETUP_COMMAND, REFUSED, NOT_CHECKED, CANNOT_SEE, MORE, LIST_MIN;
2636
+ var init_session_brief = __esm({
2637
+ "src/lib/session-brief.ts"() {
2638
+ "use strict";
2639
+ init_skills();
2640
+ init_team_setup_files();
2641
+ INSTRUCTIONS_MAX = 2e3;
2642
+ BRIEF_TIMEOUT_MS = 8e3;
2643
+ NO_KEY = "no-key-on-this-computer";
2644
+ SETUP_COMMAND = "npx -y @systemdox/mcp-server setup";
2645
+ REFUSED = `SystemDox refused this computer's key, so the team's rules and skills cannot be read and every SystemDox tool fails. Tell the person once: a new key comes from the SystemDox console, Settings \u203A API keys, and \`${SETUP_COMMAND}\` in their own terminal stores it.`;
2646
+ NOT_CHECKED = "SystemDox could not be read when this session started, so the team's skills were not checked. Call get_team_setup to check again before relying on them.";
2647
+ CANNOT_SEE = "This server cannot see this computer's disk. For each skill listed under Team skills: if ~/.claude/skills/<name>/ is absent, or its .systemdox-skill.json names another version than get_team_setup lists, call install_skill with that version and write what it returns, unless the latest team plugin carries it. Leave a folder with no such file alone. If a file there differs from the hash that file holds for it, it was edited: ask the person before replacing it.";
2648
+ MORE = (n) => `${n} more (get_team_setup lists them)`;
2649
+ LIST_MIN = `all ${MORE(100)}`.length;
2650
+ }
2651
+ });
2652
+
2511
2653
  // src/lib/language.ts
2512
2654
  function detectLanguage(filePath) {
2513
2655
  const ext = filePath.slice(filePath.lastIndexOf("."));
@@ -2620,7 +2762,7 @@ var init_config_links = __esm({
2620
2762
 
2621
2763
  // src/tools/get-guardrails.ts
2622
2764
  import { z } from "zod";
2623
- function registerGetGuardrails(server2, apiClient2) {
2765
+ function registerGetGuardrails(server, apiClient2) {
2624
2766
  const handler = async ({
2625
2767
  language,
2626
2768
  category,
@@ -2674,7 +2816,7 @@ ${text3}`
2674
2816
  ]
2675
2817
  };
2676
2818
  };
2677
- server2.registerTool(
2819
+ server.registerTool(
2678
2820
  "get_checks",
2679
2821
  {
2680
2822
  title: "Get Checks",
@@ -2683,7 +2825,7 @@ ${text3}`
2683
2825
  },
2684
2826
  handler
2685
2827
  );
2686
- server2.registerTool(
2828
+ server.registerTool(
2687
2829
  "get_guardrails",
2688
2830
  {
2689
2831
  title: "Get Checks (legacy name)",
@@ -2717,8 +2859,8 @@ var init_get_guardrails = __esm({
2717
2859
 
2718
2860
  // src/tools/get-specs.ts
2719
2861
  import { z as z2 } from "zod";
2720
- function registerGetSpecs(server2, apiClient2) {
2721
- server2.registerTool(
2862
+ function registerGetSpecs(server, apiClient2) {
2863
+ server.registerTool(
2722
2864
  "get_specs",
2723
2865
  {
2724
2866
  title: "Get Specs",
@@ -2808,8 +2950,8 @@ function matchGlob(path, pattern) {
2808
2950
  const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
2809
2951
  return new RegExp(`^${regex}$`).test(path);
2810
2952
  }
2811
- function registerGetDocs(server2, apiClient2) {
2812
- server2.registerTool(
2953
+ function registerGetDocs(server, apiClient2) {
2954
+ server.registerTool(
2813
2955
  "get_docs",
2814
2956
  {
2815
2957
  title: "Get Docs",
@@ -2912,8 +3054,8 @@ var init_get_docs = __esm({
2912
3054
  });
2913
3055
 
2914
3056
  // src/tools/get-tech-stack.ts
2915
- function registerGetTechStack(server2, apiClient2) {
2916
- server2.registerTool(
3057
+ function registerGetTechStack(server, apiClient2) {
3058
+ server.registerTool(
2917
3059
  "get_tech_stack",
2918
3060
  {
2919
3061
  title: "Get Tech Stack",
@@ -2968,8 +3110,8 @@ var init_get_tech_stack = __esm({
2968
3110
 
2969
3111
  // src/tools/get-planning-context.ts
2970
3112
  import { z as z4 } from "zod";
2971
- function registerGetPlanningContext(server2, apiClient2) {
2972
- server2.registerTool(
3113
+ function registerGetPlanningContext(server, apiClient2) {
3114
+ server.registerTool(
2973
3115
  "get_planning_context",
2974
3116
  {
2975
3117
  title: "Get Planning Context",
@@ -3003,7 +3145,7 @@ ${r.conventions.map((c) => `- ${c}`).join("\n")}` : "")
3003
3145
  ${repos}`);
3004
3146
  }
3005
3147
  if (ctx.dependencies.length > 0) {
3006
- const deps = ctx.dependencies.map((d) => `- ${d.from} \u2192 ${d.to}`).join("\n");
3148
+ const deps = ctx.dependencies.map((d) => `- ${d.from} \u2192 ${d.to}${d.provenance === "inferred" ? " (inferred)" : ""}`).join("\n");
3007
3149
  sections.push(`## Cross-Repo Dependencies
3008
3150
 
3009
3151
  ${deps}`);
@@ -3083,8 +3225,8 @@ function formatItem(item2) {
3083
3225
  ${meta.join(" \xB7 ")}` : "") + (item2.url ? `
3084
3226
  ${item2.url}` : "");
3085
3227
  }
3086
- function registerGetPlanBoard(server2, apiClient2) {
3087
- server2.registerTool(
3228
+ function registerGetPlanBoard(server, apiClient2) {
3229
+ server.registerTool(
3088
3230
  "get_plan_board",
3089
3231
  {
3090
3232
  title: "Get Plan Board",
@@ -3341,8 +3483,8 @@ function formatChange(change) {
3341
3483
  briefVerdict(change.states)
3342
3484
  ].join("\n\n");
3343
3485
  }
3344
- function registerGetChange(server2, apiClient2) {
3345
- server2.registerTool(
3486
+ function registerGetChange(server, apiClient2) {
3487
+ server.registerTool(
3346
3488
  "get_change",
3347
3489
  {
3348
3490
  title: "Get Change",
@@ -3696,16 +3838,22 @@ async function hydrate(api, item2, meta) {
3696
3838
  }
3697
3839
  async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
3698
3840
  const budget = maxTokens ?? DEFAULT_BUDGET;
3841
+ const failures = [];
3842
+ const orEmpty = (empty) => (err) => {
3843
+ failures.push(err);
3844
+ return empty;
3845
+ };
3699
3846
  const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
3700
- api.get("/v1/guardrails").catch(() => []),
3701
- api.get("/v1/specs").catch(() => []),
3702
- api.get("/v1/docs").catch(() => ({ items: [] })),
3703
- api.get("/v1/ai/settings/stack").catch(() => []),
3704
- api.get("/v1/standards/rules").catch(() => []),
3847
+ api.get("/v1/guardrails").catch(orEmpty([])),
3848
+ api.get("/v1/specs").catch(orEmpty([])),
3849
+ api.get("/v1/docs").catch(orEmpty({ items: [] })),
3850
+ api.get("/v1/ai/settings/stack").catch(orEmpty([])),
3851
+ api.get("/v1/standards/rules").catch(orEmpty([])),
3705
3852
  // Capability states — FAIL-OPEN: an unreadable config (older backend, a
3706
3853
  // blip) gates nothing, it can never hide working context.
3707
3854
  api.get("/v1/doc-repos").catch(() => null)
3708
3855
  ]);
3856
+ if (failures.length === 5) throw failures[0];
3709
3857
  const disabled = (key2) => docRepos?.capabilities?.[key2]?.status === "disabled";
3710
3858
  const guardrails = disabled("fitness") ? [] : rawGuardrails;
3711
3859
  const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
@@ -3912,12 +4060,13 @@ _Ranked lower \u2014 fetch with \`get_specs\` / \`get_docs\` if you need them._
3912
4060
  guardrails: selectedGuardrails.length
3913
4061
  };
3914
4062
  if (sections.length === 0) {
3915
- return { text: "No relevant context found for this task.", stats };
4063
+ const text3 = failures.length ? `No context found for this task, but ${failures.length} of 5 reads failed, so this is not an all-clear: the team's rules may not have been read.` : "No relevant context found for this task.";
4064
+ return { text: text3, stats };
3916
4065
  }
3917
4066
  const footer = `
3918
4067
 
3919
4068
  ---
3920
- _Context: ${itemsWithBody} full text, ${itemsListed} listed, ${stats.itemsOmitted} omitted \xB7 ~${tokensUsed} of ${budget} tokens` + (bodyFailures > 0 ? ` \xB7 ${bodyFailures} body fetch(es) failed` : "") + `_`;
4069
+ _Context: ${itemsWithBody} full text, ${itemsListed} listed, ${stats.itemsOmitted} omitted \xB7 ~${tokensUsed} of ${budget} tokens` + (bodyFailures > 0 ? ` \xB7 ${bodyFailures} body fetch(es) failed` : "") + (failures.length > 0 ? ` \xB7 ${failures.length} of 5 reads failed` : "") + `_`;
3921
4070
  return { text: body + footer, stats };
3922
4071
  }
3923
4072
  var DEFAULT_BUDGET, RESERVE_TOKENS, GUARDRAIL_SHARE, MIN_BODY_TOKENS, MAX_HYDRATED, MAX_TAIL, TAIL_SHARE, TRUNCATION_NOTICE_TOKENS, SECTION_STOP_WORDS;
@@ -3961,8 +4110,8 @@ var init_context_builder = __esm({
3961
4110
 
3962
4111
  // src/tools/get-context.ts
3963
4112
  import { z as z7 } from "zod";
3964
- function registerGetContext(server2, apiClient2) {
3965
- server2.registerTool(
4113
+ function registerGetContext(server, apiClient2) {
4114
+ server.registerTool(
3966
4115
  "get_context",
3967
4116
  {
3968
4117
  title: "Get Context",
@@ -3996,9 +4145,254 @@ var init_get_context = __esm({
3996
4145
  }
3997
4146
  });
3998
4147
 
3999
- // src/tools/create-guardrail.ts
4148
+ // src/tools/get-team-setup.ts
4149
+ function describeSetup(setup) {
4150
+ const lines = [`# Team setup of ${setup.workspace}`, ""];
4151
+ const skills = setup.sessionBrief?.skills ?? [];
4152
+ const held = setup.sessionBrief?.heldSkills ?? [];
4153
+ lines.push(`## Skills a session installs by itself (${skills.length})`, "");
4154
+ if (skills.length) {
4155
+ for (const s of skills) lines.push(`- **${s.name}** version ${s.latestVersion}: ${s.description}`);
4156
+ } else if (!setup.sessionBrief && setup.skills.length) {
4157
+ lines.push(`- ${setup.skills.join(", ")}`);
4158
+ } else {
4159
+ lines.push("None.");
4160
+ }
4161
+ lines.push("");
4162
+ if (held.length) {
4163
+ lines.push(`## Skills that wait for the person (${held.length})`, "");
4164
+ for (const h of held) lines.push(`- **${h.name}**: ${HELD_BECAUSE[h.reason] ?? h.reason}`);
4165
+ lines.push("", "Do not install these yourself: each is the person's to decide.", "");
4166
+ }
4167
+ if (setup.plugin) {
4168
+ lines.push(`## Team plugin`, "", `\`${setup.workspace}-setup\` version ${setup.plugin.version}.`);
4169
+ for (const c of setup.plugin.hookCommands ?? []) lines.push(`- Hook: ${JSON.stringify(c)}`);
4170
+ for (const m of setup.plugin.mcpServers ?? []) {
4171
+ lines.push(`- MCP server ${JSON.stringify(m.name)}: ${JSON.stringify(m.url ?? [m.command, ...m.args ?? []].filter(Boolean).join(" "))}`);
4172
+ }
4173
+ lines.push("");
4174
+ }
4175
+ const parts = Object.entries(setup.parts ?? {});
4176
+ if (parts.length) {
4177
+ lines.push(`## Published parts`, "", ...parts.map(([p, v]) => `- ${p}: version ${v}`), "");
4178
+ }
4179
+ if (setup.memory?.length) {
4180
+ lines.push(`## Team memory`, "", ...setup.memory.map((m) => `- ${m.project}: ${m.fileCount} file(s)`), "");
4181
+ }
4182
+ return lines;
4183
+ }
4184
+ function describeComputer(setup, state) {
4185
+ const lines = ["## This computer", ""];
4186
+ if (!setup.sessionBrief) {
4187
+ return [...lines, "This SystemDox API is too old to compare this computer with the team setup."];
4188
+ }
4189
+ const skills = setup.sessionBrief.skills;
4190
+ const latest = new Map(skills.map((s) => [s.name, s.latestVersion]));
4191
+ lines.push(
4192
+ state.missing.length ? `- Missing: ${state.missing.map((name) => `${name} version ${latest.get(name)}`).join(", ")}` : `- Missing: none of ${skills.length}`
4193
+ );
4194
+ if (state.behind.length) {
4195
+ lines.push(`- Behind: ${state.behind.map((b) => `${b.name} (version ${b.here} here, latest ${b.latest})`).join(", ")}`);
4196
+ }
4197
+ if (state.notFromSystemDox.length) lines.push(`- Not from SystemDox, left alone: ${state.notFromSystemDox.join(", ")}`);
4198
+ if (setup.plugin) {
4199
+ lines.push(
4200
+ state.pluginInstalled ? `- Team plugin: installed${state.pluginVersion ? `, ${state.pluginVersion}` : ""}; the latest is version ${setup.plugin.version}` : "- Team plugin: not installed"
4201
+ );
4202
+ }
4203
+ lines.push("", "## What to do", "");
4204
+ const steps = [];
4205
+ if (state.missing.length) steps.push("Call install_skill for each missing skill, at the version named above. These are plain skills: installing writes files and runs nothing.");
4206
+ if (state.behind.length) {
4207
+ steps.push("Call install_skill for each skill behind, at the latest version named above. If it refuses because files were edited, tell the person and leave them.");
4208
+ }
4209
+ if (hasPersonParts(setup) && !state.pluginInstalled) {
4210
+ steps.push(
4211
+ `Hooks, MCP servers, team instructions and memory need the person: \`${SETUP_COMMAND}\` in their own terminal. Never answer its prompts for them.`
4212
+ );
4213
+ }
4214
+ lines.push(...steps.length ? steps.map((s, i) => `${i + 1}. ${s}`) : ["Nothing."]);
4215
+ return lines;
4216
+ }
4217
+ function registerGetTeamSetup(server, apiClient2, { localFiles, keyProblem: keyProblem2 }) {
4218
+ server.registerTool(
4219
+ "get_team_setup",
4220
+ {
4221
+ title: "Get Team Setup",
4222
+ description: localFiles ? "What this workspace's team setup holds (skills, plugin, hooks, MCP servers, memory) and what this computer lacks, with what to do about it. Call it to check whether this computer is set up. Reads only." : "What this workspace's team setup holds (skills, plugin, hooks, MCP servers, memory), and how to check this computer against it. Reads only.",
4223
+ inputSchema: {},
4224
+ annotations: { readOnlyHint: true }
4225
+ },
4226
+ async () => {
4227
+ const read = await readTeamSetup(apiClient2, { keyProblem: keyProblem2, timeoutMs: 15e3 });
4228
+ if ("problem" in read) return { content: [{ type: "text", text: read.problem }] };
4229
+ const lines = describeSetup(read.setup);
4230
+ if (localFiles) lines.push(...describeComputer(read.setup, await computerState(read.setup)));
4231
+ else lines.push(CANNOT_SEE);
4232
+ return { content: [{ type: "text", text: lines.join("\n") }] };
4233
+ }
4234
+ );
4235
+ }
4236
+ var HELD_BECAUSE;
4237
+ var init_get_team_setup = __esm({
4238
+ "src/tools/get-team-setup.ts"() {
4239
+ "use strict";
4240
+ init_session_brief();
4241
+ HELD_BECAUSE = {
4242
+ "not-plain": `it is not a plain skill: it has frontmatter beyond a name and a description, files outside its own folders, or a command that runs as it loads. The team plugin carries it, and \`${SETUP_COMMAND}\` shows that before the person accepts`,
4243
+ "outside-default-space": "it is outside the default space, so the team plugin does not carry it either. The person can read it in the console, then install it with `npx -y @systemdox/mcp-server install-skill <name>`",
4244
+ "not-checked": "it could not be checked. Ask again later"
4245
+ };
4246
+ }
4247
+ });
4248
+
4249
+ // src/tools/document-graph.ts
4000
4250
  import { z as z8 } from "zod";
4001
- function registerCreateGuardrail(server2, apiClient2) {
4251
+ function place(file, line) {
4252
+ return file ? `${file}:${line}` : "made by a person";
4253
+ }
4254
+ function known(provenance, confidence) {
4255
+ return `${provenance} ${confidence}`;
4256
+ }
4257
+ function formatNeighbours(answer) {
4258
+ const lines = [`# ${answer.doc.label}`, `\`${answer.doc.id}\``, ""];
4259
+ if (answer.links.length === 0) lines.push("No links.");
4260
+ for (const link of answer.links) {
4261
+ lines.push(
4262
+ `- ${link.direction} \xB7 ${link.relation} \xB7 ${known(link.provenance, link.confidence)} \xB7 ${link.other.label} \xB7 \`${link.other.id}\` \xB7 ${place(link.file, link.line)}`
4263
+ );
4264
+ }
4265
+ if (answer.truncated) {
4266
+ lines.push("", `Showing ${answer.links.length} of ${answer.total}. Raise limit to see more.`);
4267
+ }
4268
+ if (answer.ambiguous.length > 0) {
4269
+ lines.push("", "## Ambiguous: not drawn, because each could mean several documents");
4270
+ for (const entry of answer.ambiguous) {
4271
+ const candidates = entry.candidates.map((c) => `${c.label} (${c.repo}, \`${c.id}\`)`);
4272
+ lines.push(`- ${entry.reference} at ${place(entry.file, entry.line)}: ${candidates.join("; ")}`);
4273
+ }
4274
+ }
4275
+ return lines.join("\n");
4276
+ }
4277
+ function formatPath(answer, maxHops) {
4278
+ if (!answer.found) return `No path within ${maxHops} hops.`;
4279
+ const lines = answer.steps.map((step, index) => {
4280
+ const arrow = step.reversed ? "<-" : "->";
4281
+ return `${index + 1}. ${step.from.label} ${arrow} ${step.relation} ${arrow} ${step.to.label} \xB7 ${known(step.provenance, step.confidence)} \xB7 ${place(step.file, step.line)}`;
4282
+ });
4283
+ return [...lines, "", `Path confidence: ${answer.confidence}`].join("\n");
4284
+ }
4285
+ function formatStats(stats, repoLinks) {
4286
+ const { declared, extracted, inferred, ambiguous } = stats.links;
4287
+ const lines = [
4288
+ `# The graph: ${stats.nodeCount} documents, ${stats.edgeCount} links`,
4289
+ "",
4290
+ `Links: ${declared} declared \xB7 ${extracted} extracted \xB7 ${inferred} inferred \xB7 ${ambiguous} ambiguous (not drawn)`,
4291
+ "",
4292
+ "## Repositories"
4293
+ ];
4294
+ for (const repo of stats.repos) {
4295
+ const state = repo.behind ? `BEHIND: pushed ${repo.headCommit || "unknown"} at ${repo.pushedAt}, index at ${repo.indexedCommit || "unknown"}` : `index at ${repo.indexedCommit || "unknown"}`;
4296
+ lines.push(`- ${repo.repo}: ${repo.documents} documents, ${repo.links} links, ${state}`);
4297
+ }
4298
+ if (repoLinks.length > 0) {
4299
+ lines.push("", "## Repository links");
4300
+ for (const link of repoLinks) {
4301
+ lines.push(
4302
+ `- ${link.source} ${link.relation} ${link.target} \xB7 ${known(link.provenance, link.confidence)} \xB7 ${place(link.file, link.line)}`
4303
+ );
4304
+ }
4305
+ }
4306
+ return lines.join("\n");
4307
+ }
4308
+ function refused(err) {
4309
+ const status = err.status;
4310
+ if (status !== 404 && status !== 400) throw err;
4311
+ return {
4312
+ content: [{ type: "text", text: err.message }],
4313
+ isError: true
4314
+ };
4315
+ }
4316
+ function registerDocumentGraph(server, apiClient2) {
4317
+ server.registerTool(
4318
+ "get_document_links",
4319
+ {
4320
+ description: "What one document links to and from, strongest first. Each link says how it is known: declared (a person made it), extracted (the file says so), or inferred (a reading that could be wrong), with a confidence and the file and line it came from. A reference that could mean several documents is listed as ambiguous with its candidates, and is never drawn as a link. doc_id is the id get_docs returns. Read-only.",
4321
+ inputSchema: {
4322
+ doc_id: z8.string().min(1).max(400).describe("A document id, as get_docs returns it"),
4323
+ direction: z8.enum(["out", "in", "both"]).optional().describe("Default both"),
4324
+ relation: z8.enum(["links_to", "cites", "mentions", "satisfies", "references"]).optional().describe("Only links of this kind"),
4325
+ limit: z8.number().int().min(1).max(MAX_LIMIT).optional().describe(`Most links to show (default ${DEFAULT_LIMIT2})`)
4326
+ },
4327
+ annotations: { readOnlyHint: true }
4328
+ },
4329
+ async ({ doc_id, direction, relation, limit }) => {
4330
+ const params = { doc: doc_id, limit: String(limit ?? DEFAULT_LIMIT2) };
4331
+ if (direction) params.direction = direction;
4332
+ if (relation) params.relation = relation;
4333
+ try {
4334
+ const answer = await apiClient2.get("/v1/graph/neighbours", params);
4335
+ return { content: [{ type: "text", text: formatNeighbours(answer) }] };
4336
+ } catch (err) {
4337
+ return refused(err);
4338
+ }
4339
+ }
4340
+ );
4341
+ server.registerTool(
4342
+ "trace_document_path",
4343
+ {
4344
+ description: "The shortest way two documents connect, walking links in either direction. Each step says how it is known, and the path is only as strong as its weakest step. Read-only.",
4345
+ inputSchema: {
4346
+ from: z8.string().min(1).max(400).describe("A document id, as get_docs returns it"),
4347
+ to: z8.string().min(1).max(400).describe("A document id, as get_docs returns it"),
4348
+ max_hops: z8.number().int().min(1).max(MAX_HOPS).optional().describe(`Longest path to look for (default ${DEFAULT_HOPS})`)
4349
+ },
4350
+ annotations: { readOnlyHint: true }
4351
+ },
4352
+ async ({ from, to, max_hops }) => {
4353
+ const hops = max_hops ?? DEFAULT_HOPS;
4354
+ try {
4355
+ const answer = await apiClient2.get("/v1/graph/path", {
4356
+ from,
4357
+ to,
4358
+ maxHops: String(hops)
4359
+ });
4360
+ return { content: [{ type: "text", text: formatPath(answer, hops) }] };
4361
+ } catch (err) {
4362
+ return refused(err);
4363
+ }
4364
+ }
4365
+ );
4366
+ server.registerTool(
4367
+ "get_graph_stats",
4368
+ {
4369
+ description: "The state of the graph: links counted by how they are known, each repository's freshness (marked BEHIND when a push has not finished indexing), and how repositories depend on each other, from the files that declare it. Read-only.",
4370
+ inputSchema: {},
4371
+ annotations: { readOnlyHint: true }
4372
+ },
4373
+ async () => {
4374
+ const [stats, repos] = await Promise.all([
4375
+ apiClient2.getFresh("/v1/graph/stats"),
4376
+ apiClient2.getFresh("/v1/graph/repos")
4377
+ ]);
4378
+ return { content: [{ type: "text", text: formatStats(stats, repos.links) }] };
4379
+ }
4380
+ );
4381
+ }
4382
+ var DEFAULT_LIMIT2, MAX_LIMIT, DEFAULT_HOPS, MAX_HOPS;
4383
+ var init_document_graph = __esm({
4384
+ "src/tools/document-graph.ts"() {
4385
+ "use strict";
4386
+ DEFAULT_LIMIT2 = 50;
4387
+ MAX_LIMIT = 200;
4388
+ DEFAULT_HOPS = 6;
4389
+ MAX_HOPS = 8;
4390
+ }
4391
+ });
4392
+
4393
+ // src/tools/create-guardrail.ts
4394
+ import { z as z9 } from "zod";
4395
+ function registerCreateGuardrail(server, apiClient2) {
4002
4396
  const handler = async ({
4003
4397
  title,
4004
4398
  description,
@@ -4035,7 +4429,7 @@ function registerCreateGuardrail(server2, apiClient2) {
4035
4429
  ]
4036
4430
  };
4037
4431
  };
4038
- server2.registerTool(
4432
+ server.registerTool(
4039
4433
  "create_check",
4040
4434
  {
4041
4435
  title: "Create Check",
@@ -4047,7 +4441,7 @@ function registerCreateGuardrail(server2, apiClient2) {
4047
4441
  },
4048
4442
  handler
4049
4443
  );
4050
- server2.registerTool(
4444
+ server.registerTool(
4051
4445
  "create_guardrail",
4052
4446
  {
4053
4447
  title: "Create Check (legacy name)",
@@ -4077,32 +4471,32 @@ var init_create_guardrail = __esm({
4077
4471
  "data-access"
4078
4472
  ];
4079
4473
  inputSchema2 = () => ({
4080
- title: z8.string().describe("Short title for the check"),
4081
- description: z8.string().describe("Detailed description of what the check enforces and why"),
4082
- severity: z8.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
4083
- category: z8.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
4084
- pattern: z8.string().optional().describe("Bad code pattern that should be avoided"),
4085
- fix: z8.string().optional().describe("Correct code pattern to use instead"),
4086
- fitness_test: z8.string().optional().describe("Shell command to detect violations in CI")
4474
+ title: z9.string().describe("Short title for the check"),
4475
+ description: z9.string().describe("Detailed description of what the check enforces and why"),
4476
+ severity: z9.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
4477
+ category: z9.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
4478
+ pattern: z9.string().optional().describe("Bad code pattern that should be avoided"),
4479
+ fix: z9.string().optional().describe("Correct code pattern to use instead"),
4480
+ fitness_test: z9.string().optional().describe("Shell command to detect violations in CI")
4087
4481
  });
4088
4482
  }
4089
4483
  });
4090
4484
 
4091
4485
  // src/tools/update-spec.ts
4092
- import { z as z9 } from "zod";
4093
- function registerUpdateSpec(server2, apiClient2) {
4094
- server2.registerTool(
4486
+ import { z as z10 } from "zod";
4487
+ function registerUpdateSpec(server, apiClient2) {
4488
+ server.registerTool(
4095
4489
  "update_spec",
4096
4490
  {
4097
4491
  title: "Update Spec",
4098
4492
  description: "Update an existing SystemDox spec's proposal, tasks, or requirements. Only draft specs can be modified \u2014 published specs must be versioned first.",
4099
4493
  inputSchema: {
4100
- draft_id: z9.string().describe("ID of the spec to update"),
4101
- section: z9.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
4102
- content: z9.string().describe(
4494
+ draft_id: z10.string().describe("ID of the spec to update"),
4495
+ section: z10.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
4496
+ content: z10.string().describe(
4103
4497
  "New markdown content for the section. For requirements, provide the full requirement content."
4104
4498
  ),
4105
- requirement_name: z9.string().optional().describe(
4499
+ requirement_name: z10.string().optional().describe(
4106
4500
  "Name of the requirement (required when section is 'requirements')"
4107
4501
  )
4108
4502
  },
@@ -4157,19 +4551,19 @@ var init_update_spec = __esm({
4157
4551
  });
4158
4552
 
4159
4553
  // src/tools/report-decision.ts
4160
- import { z as z10 } from "zod";
4161
- function registerReportDecision(server2, apiClient2) {
4162
- server2.registerTool(
4554
+ import { z as z11 } from "zod";
4555
+ function registerReportDecision(server, apiClient2) {
4556
+ server.registerTool(
4163
4557
  "report_decision",
4164
4558
  {
4165
4559
  title: "Report Decision",
4166
4560
  description: "Record an architecture decision made during development. Creates a draft ADR in SystemDox for human review.",
4167
4561
  inputSchema: {
4168
- title: z10.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
4169
- context: z10.string().describe("What prompted this decision \u2014 the problem or constraint"),
4170
- decision: z10.string().describe("What was decided and why"),
4171
- consequences: z10.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
4172
- related_files: z10.array(z10.string()).optional().describe("File paths related to this decision")
4562
+ title: z11.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
4563
+ context: z11.string().describe("What prompted this decision \u2014 the problem or constraint"),
4564
+ decision: z11.string().describe("What was decided and why"),
4565
+ consequences: z11.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
4566
+ related_files: z11.array(z11.string()).optional().describe("File paths related to this decision")
4173
4567
  },
4174
4568
  annotations: {
4175
4569
  readOnlyHint: false
@@ -4225,15 +4619,15 @@ var init_report_decision = __esm({
4225
4619
  });
4226
4620
 
4227
4621
  // src/tools/get-project-rules.ts
4228
- import { z as z11 } from "zod";
4229
- function registerGetProjectRules(server2, apiClient2) {
4230
- server2.registerTool(
4622
+ import { z as z12 } from "zod";
4623
+ function registerGetProjectRules(server, apiClient2) {
4624
+ server.registerTool(
4231
4625
  "get_project_rules",
4232
4626
  {
4233
4627
  title: "Get Project Rules",
4234
4628
  description: "Retrieve project rules defined in SystemDox Standards. These are team-defined principles covering deployment, architecture, security, testing, and process. Filter by category to get only relevant rules.",
4235
4629
  inputSchema: {
4236
- category: z11.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
4630
+ category: z12.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
4237
4631
  }
4238
4632
  },
4239
4633
  async ({ category }) => {
@@ -4300,18 +4694,18 @@ var init_get_project_rules = __esm({
4300
4694
  });
4301
4695
 
4302
4696
  // src/tools/create-project-rule.ts
4303
- import { z as z12 } from "zod";
4304
- function registerCreateProjectRule(server2, apiClient2) {
4305
- server2.registerTool(
4697
+ import { z as z13 } from "zod";
4698
+ function registerCreateProjectRule(server, apiClient2) {
4699
+ server.registerTool(
4306
4700
  "create_project_rule",
4307
4701
  {
4308
4702
  title: "Create Project Rule",
4309
4703
  description: "Create a new project rule in SystemDox Standards. Use this when you discover a team principle or convention that should be enforced across the project.",
4310
4704
  inputSchema: {
4311
- text: z12.string().describe(
4705
+ text: z13.string().describe(
4312
4706
  "The rule statement \u2014 one enforceable principle per rule (e.g. 'All deployments MUST go through GitHub Actions workflows')"
4313
4707
  ),
4314
- category: z12.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
4708
+ category: z13.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
4315
4709
  },
4316
4710
  annotations: {
4317
4711
  readOnlyHint: false
@@ -4341,8 +4735,8 @@ var init_create_project_rule = __esm({
4341
4735
  });
4342
4736
 
4343
4737
  // src/tools/get-prompt-templates.ts
4344
- function registerGetPromptTemplates(server2, apiClient2) {
4345
- server2.registerTool(
4738
+ function registerGetPromptTemplates(server, apiClient2) {
4739
+ server.registerTool(
4346
4740
  "get_prompt_templates",
4347
4741
  {
4348
4742
  title: "Get Prompt Templates",
@@ -4393,17 +4787,17 @@ var init_get_prompt_templates = __esm({
4393
4787
  });
4394
4788
 
4395
4789
  // src/tools/create-prompt-template.ts
4396
- import { z as z13 } from "zod";
4397
- function registerCreatePromptTemplate(server2, apiClient2) {
4398
- server2.registerTool(
4790
+ import { z as z14 } from "zod";
4791
+ function registerCreatePromptTemplate(server, apiClient2) {
4792
+ server.registerTool(
4399
4793
  "create_prompt_template",
4400
4794
  {
4401
4795
  title: "Create Prompt Template",
4402
4796
  description: "Create a new reusable prompt template in SystemDox Standards. Use this to store preconfigured prompts for common AI tasks like code reviews, documentation generation, or analysis frameworks.",
4403
4797
  inputSchema: {
4404
- name: z13.string().describe("Short name for the template"),
4405
- description: z13.string().optional().describe("Brief description of when to use this template"),
4406
- prompt: z13.string().describe("The full prompt text that Gen AI will use when generating content")
4798
+ name: z14.string().describe("Short name for the template"),
4799
+ description: z14.string().optional().describe("Brief description of when to use this template"),
4800
+ prompt: z14.string().describe("The full prompt text that Gen AI will use when generating content")
4407
4801
  },
4408
4802
  annotations: {
4409
4803
  readOnlyHint: false
@@ -4437,7 +4831,7 @@ var init_create_prompt_template = __esm({
4437
4831
  });
4438
4832
 
4439
4833
  // src/tools/skills.ts
4440
- import { z as z14 } from "zod";
4834
+ import { z as z15 } from "zod";
4441
4835
  import { homedir as homedir3 } from "os";
4442
4836
  import { basename as basename2, resolve as resolve6 } from "path";
4443
4837
  function expandHome(path) {
@@ -4446,25 +4840,25 @@ function expandHome(path) {
4446
4840
  function text(t) {
4447
4841
  return { content: [{ type: "text", text: t }] };
4448
4842
  }
4449
- function registerSkillTools(server2, apiClient2, { localFiles }) {
4450
- const fileSchema = z14.object({
4451
- path: z14.string().describe('Path inside the skill folder, forward slashes, e.g. "references/guide.md"'),
4452
- content: z14.string().describe("The file's full text, exactly as it is")
4843
+ function registerSkillTools(server, apiClient2, { localFiles }) {
4844
+ const fileSchema = z15.object({
4845
+ path: z15.string().describe('Path inside the skill folder, forward slashes, e.g. "references/guide.md"'),
4846
+ content: z15.string().describe("The file's full text, exactly as it is")
4453
4847
  });
4454
- server2.registerTool(
4848
+ server.registerTool(
4455
4849
  "publish_skill",
4456
4850
  {
4457
4851
  title: "Publish Skill",
4458
4852
  description: localFiles ? "Publish a Claude Code skill folder (SKILL.md plus its references and scripts) to SystemDox so it can be installed on any computer. Pass `directory` (e.g. ~/.claude/skills/<name>): the server reads the files itself, byte for byte. Publishing a name again makes a new version. Never retype the files into `files` when the folder is on this computer." : "Publish a Claude Code skill (SKILL.md plus its references and scripts) to SystemDox so it can be installed on any computer. Pass every file of the folder in `files`, exactly as it is. Publishing a name again makes a new version.",
4459
4853
  inputSchema: {
4460
4854
  ...localFiles ? {
4461
- directory: z14.string().optional().describe("The skill folder on this computer, holding SKILL.md at its root")
4855
+ directory: z15.string().optional().describe("The skill folder on this computer, holding SKILL.md at its root")
4462
4856
  } : {},
4463
- files: z14.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the skill folder"),
4464
- name: z14.string().optional().describe("The skill's name; defaults to SKILL.md's frontmatter name and must match it"),
4465
- spaceId: z14.string().optional().describe("Space for a NEW skill (default: your default space)"),
4466
- note: z14.string().max(500).optional().describe(`A short change note for this version, shown in the console's history (e.g. "Fix the GA4 step")`),
4467
- baseVersion: z14.number().int().positive().optional().describe(
4857
+ files: z15.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the skill folder"),
4858
+ name: z15.string().optional().describe("The skill's name; defaults to SKILL.md's frontmatter name and must match it"),
4859
+ spaceId: z15.string().optional().describe("Space for a NEW skill (default: your default space)"),
4860
+ note: z15.string().max(500).optional().describe(`A short change note for this version, shown in the console's history (e.g. "Fix the GA4 step")`),
4861
+ baseVersion: z15.number().int().positive().optional().describe(
4468
4862
  "The version these files were edited from. When given, the publish is refused (SYD-K017) if someone published a newer version since, instead of replacing their changes"
4469
4863
  )
4470
4864
  },
@@ -4502,7 +4896,7 @@ Left out (hidden files are never part of a skill): ${skipped.join(", ")}` : "")
4502
4896
  );
4503
4897
  }
4504
4898
  );
4505
- server2.registerTool(
4899
+ server.registerTool(
4506
4900
  "list_skills",
4507
4901
  {
4508
4902
  title: "List Skills",
@@ -4524,16 +4918,16 @@ Left out (hidden files are never part of a skill): ${skipped.join(", ")}` : "")
4524
4918
  );
4525
4919
  }
4526
4920
  );
4527
- server2.registerTool(
4921
+ server.registerTool(
4528
4922
  "install_skill",
4529
4923
  {
4530
4924
  title: "Install Skill",
4531
4925
  description: localFiles ? "Install a skill published to SystemDox onto this computer (default ~/.claude/skills/<name>/). Refuses, naming the files, when a file there was edited since it was installed, unless overwrite is true. Never runs anything." : "Fetch a skill published to SystemDox so you can install it: returns every file and the folder to write them into (default ~/.claude/skills/<name>/). Write each file exactly, then the manifest it shows.",
4532
4926
  inputSchema: {
4533
- name: z14.string().describe("The skill's name"),
4534
- version: z14.number().int().positive().optional().describe("A version number (default: the latest)"),
4535
- target: z14.string().optional().describe("Folder to install into, e.g. a repo's .claude/skills/<name>"),
4536
- ...localFiles ? { overwrite: z14.boolean().optional().describe("Replace files edited since they were installed") } : {}
4927
+ name: z15.string().describe("The skill's name"),
4928
+ version: z15.number().int().positive().optional().describe("A version number (default: the latest)"),
4929
+ target: z15.string().optional().describe("Folder to install into, e.g. a repo's .claude/skills/<name>"),
4930
+ ...localFiles ? { overwrite: z15.boolean().optional().describe("Replace files edited since they were installed") } : {}
4537
4931
  },
4538
4932
  annotations: { readOnlyHint: !localFiles }
4539
4933
  },
@@ -4564,7 +4958,7 @@ ${fence}
4564
4958
  ${f.content}${f.content.endsWith("\n") ? "" : "\n"}${fence}`;
4565
4959
  }).join("\n\n");
4566
4960
  return text(
4567
- `Skill **${version.name}** version ${version.version}: write these ${version.files.length} file(s) into ${dest}/ exactly as shown (the content between the fences), creating folders as needed. If a file there differs from ${MANIFEST_FILE}'s hash for it, it was edited: ask before replacing it. Then write ${MANIFEST_FILE} with this content, and tell the user to start a new Claude Code session.
4961
+ `Skill **${version.name}** version ${version.version}: write these ${version.files.length} file(s) into ${dest}/ exactly as shown (the content between the fences), creating folders as needed. If a file there differs from ${MANIFEST_FILE}'s hash for it, it was edited: ask before replacing it. Then write ${MANIFEST_FILE} with this content. ${afterInstall(`${dest}/SKILL.md`)}
4568
4962
 
4569
4963
  ### ${MANIFEST_FILE}
4570
4964
 
@@ -4599,11 +4993,11 @@ var init_team_setup_types = __esm({
4599
4993
  });
4600
4994
 
4601
4995
  // src/tools/team-setup.ts
4602
- import { z as z15 } from "zod";
4603
- import { existsSync as existsSync6 } from "fs";
4996
+ import { z as z16 } from "zod";
4997
+ import { existsSync as existsSync7 } from "fs";
4604
4998
  import { lstat as lstat2 } from "fs/promises";
4605
4999
  import { homedir as homedir4 } from "os";
4606
- import { join as join7, resolve as resolve7 } from "path";
5000
+ import { join as join8, resolve as resolve7 } from "path";
4607
5001
  function expandHome2(path) {
4608
5002
  return path.replace(/^~(?=$|[\\/])/, homedir4());
4609
5003
  }
@@ -4629,21 +5023,21 @@ async function readPartDirectory(part, directory) {
4629
5023
  let present = true;
4630
5024
  for (let i = 1; i <= segments.length; i++) {
4631
5025
  const rel = segments.slice(0, i).join("/");
4632
- if (await isLink(join7(root, ...segments.slice(0, i)))) {
5026
+ if (await isLink(join8(root, ...segments.slice(0, i)))) {
4633
5027
  links.push(rel);
4634
5028
  present = false;
4635
5029
  break;
4636
5030
  }
4637
5031
  }
4638
- if (present && existsSync6(join7(root, ...segments))) paths.push(path);
5032
+ if (present && existsSync7(join8(root, ...segments))) paths.push(path);
4639
5033
  }
4640
5034
  for (const { dir, md } of layout.folders) {
4641
- const full = join7(root, dir);
5035
+ const full = join8(root, dir);
4642
5036
  if (await isLink(full)) {
4643
5037
  links.push(dir);
4644
5038
  continue;
4645
5039
  }
4646
- if (!existsSync6(full)) continue;
5040
+ if (!existsSync7(full)) continue;
4647
5041
  const walked = await walk(full, { skipHidden: true });
4648
5042
  links.push(...walked.links.map((p) => `${dir}/${p}`));
4649
5043
  leftOut.push(...walked.hidden.map((p) => `${dir}/${p}`));
@@ -4682,24 +5076,24 @@ function describePerson(id, person) {
4682
5076
  );
4683
5077
  return lines.join("\n");
4684
5078
  }
4685
- function registerTeamSetupTools(server2, apiClient2, { localFiles }) {
4686
- const fileSchema = z15.object({
4687
- path: z15.string().describe(`Path inside the part's folder, forward slashes, e.g. "hooks/hooks.json"`),
4688
- content: z15.string().describe("The file's full text, exactly as it is")
5079
+ function registerTeamSetupTools(server, apiClient2, { localFiles }) {
5080
+ const fileSchema = z16.object({
5081
+ path: z16.string().describe(`Path inside the part's folder, forward slashes, e.g. "hooks/hooks.json"`),
5082
+ content: z16.string().describe("The file's full text, exactly as it is")
4689
5083
  });
4690
- server2.registerTool(
5084
+ server.registerTool(
4691
5085
  "publish_setup_part",
4692
5086
  {
4693
5087
  title: "Publish Setup Part",
4694
5088
  description: "Publish one part of your team's Claude Code setup to SystemDox: `hooks` (hooks/hooks.json and the scripts/ it calls), `mcp` (.mcp.json), `agents` (agents/*.md), `commands` (commands/*.md) or `instructions` (CLAUDE.md). Every part but instructions makes a new version of the team plugin, which each member accepts at their next update. Publishing hooks, mcp, agents or commands needs the admin role. " + (localFiles ? "Pass `directory`: the server reads only the files the part accepts, byte for byte, and refuses a link, a binary file or a file too large before anything is sent. Never retype the files into `files` when the folder is on this computer." : "Pass every file of the part in `files`, exactly as it is."),
4695
5089
  inputSchema: {
4696
- part: z15.enum(PARTS2).describe("Which part of the setup"),
5090
+ part: z16.enum(PARTS2).describe("Which part of the setup"),
4697
5091
  ...localFiles ? {
4698
- directory: z15.string().optional().describe(
5092
+ directory: z16.string().optional().describe(
4699
5093
  "The folder on this computer: for hooks, the one holding hooks/hooks.json and scripts/; for mcp, the one holding .mcp.json; for instructions, the one holding CLAUDE.md; for agents or commands, the one holding agents/ or commands/"
4700
5094
  )
4701
5095
  } : {},
4702
- files: z15.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the part")
5096
+ files: z16.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the part")
4703
5097
  },
4704
5098
  annotations: { readOnlyHint: false }
4705
5099
  },
@@ -4729,13 +5123,13 @@ function registerTeamSetupTools(server2, apiClient2, { localFiles }) {
4729
5123
  return text2(lines.join("\n"));
4730
5124
  }
4731
5125
  );
4732
- server2.registerTool(
5126
+ server.registerTool(
4733
5127
  "resolve_person",
4734
5128
  {
4735
5129
  title: "Resolve Person",
4736
5130
  description: "Read back a person that a team memory names by identifier, written as `con_\u2026` (label), `ctc_\u2026` or `usr_\u2026`: their label, name, phone number and email address, from the vault that holds them. The details are for this session only: never write them into a memory file; the memory keeps the identifier. Writes nothing.",
4737
5131
  inputSchema: {
4738
- id: z15.string().regex(PERSON_ID_RE, "a con_, ctc_ or usr_ identifier, as written in the memory").describe("The identifier, e.g. con_01JC2Q8Y4M7T0X3K5N9P2R6S8V")
5132
+ id: z16.string().regex(PERSON_ID_RE, "a con_, ctc_ or usr_ identifier, as written in the memory").describe("The identifier, e.g. con_01JC2Q8Y4M7T0X3K5N9P2R6S8V")
4739
5133
  },
4740
5134
  annotations: { readOnlyHint: true }
4741
5135
  },
@@ -4746,14 +5140,14 @@ function registerTeamSetupTools(server2, apiClient2, { localFiles }) {
4746
5140
  }
4747
5141
  );
4748
5142
  if (!localFiles) return;
4749
- server2.registerTool(
5143
+ server.registerTool(
4750
5144
  "sync_memory",
4751
5145
  {
4752
5146
  title: "Sync Memory",
4753
5147
  description: `Sync one project's team memory on this computer with SystemDox: pull the files the team changed, push the ones changed here, keep both texts of a file changed on both sides. It never answers for a person: a file that still holds a phone number, an email address or a home-folder path is skipped and listed, with the command for the member to run in their own terminal (\`${SYNC_MEMORY_COMMAND}\`). Pass dryRun to see what it would do without writing or sending anything.`,
4754
5148
  inputSchema: {
4755
- project: z15.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "a project name: lower case letters, digits and -").optional().describe("The project's team memory, e.g. puglieseweb-ltd; needed when the workspace has more than one"),
4756
- dryRun: z15.boolean().optional().describe("Show what would be pulled, pushed, deleted, conflicted and skipped, and change nothing")
5149
+ project: z16.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "a project name: lower case letters, digits and -").optional().describe("The project's team memory, e.g. puglieseweb-ltd; needed when the workspace has more than one"),
5150
+ dryRun: z16.boolean().optional().describe("Show what would be pulled, pushed, deleted, conflicted and skipped, and change nothing")
4757
5151
  },
4758
5152
  annotations: { readOnlyHint: false }
4759
5153
  },
@@ -4788,29 +5182,34 @@ var init_team_setup = __esm({
4788
5182
 
4789
5183
  // src/server.ts
4790
5184
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4791
- function createServer(apiClient2, { localFiles = false } = {}) {
4792
- const server2 = new McpServer({
4793
- name: "systemdox-mcp-server",
4794
- version: SERVER_VERSION
4795
- });
4796
- registerGetGuardrails(server2, apiClient2);
4797
- registerGetSpecs(server2, apiClient2);
4798
- registerGetDocs(server2, apiClient2);
4799
- registerGetTechStack(server2, apiClient2);
4800
- registerGetPlanningContext(server2, apiClient2);
4801
- registerGetPlanBoard(server2, apiClient2);
4802
- registerGetChange(server2, apiClient2);
4803
- registerGetContext(server2, apiClient2);
4804
- registerGetProjectRules(server2, apiClient2);
4805
- registerGetPromptTemplates(server2, apiClient2);
4806
- registerCreateGuardrail(server2, apiClient2);
4807
- registerUpdateSpec(server2, apiClient2);
4808
- registerReportDecision(server2, apiClient2);
4809
- registerCreateProjectRule(server2, apiClient2);
4810
- registerCreatePromptTemplate(server2, apiClient2);
4811
- registerSkillTools(server2, apiClient2, { localFiles });
4812
- registerTeamSetupTools(server2, apiClient2, { localFiles });
4813
- return server2;
5185
+ function createServer(apiClient2, { localFiles = false, instructions, keyProblem: keyProblem2 } = {}) {
5186
+ const server = new McpServer(
5187
+ {
5188
+ name: "systemdox-mcp-server",
5189
+ version: SERVER_VERSION
5190
+ },
5191
+ instructions ? { instructions } : void 0
5192
+ );
5193
+ registerGetGuardrails(server, apiClient2);
5194
+ registerGetSpecs(server, apiClient2);
5195
+ registerGetDocs(server, apiClient2);
5196
+ registerGetTechStack(server, apiClient2);
5197
+ registerGetPlanningContext(server, apiClient2);
5198
+ registerGetPlanBoard(server, apiClient2);
5199
+ registerGetChange(server, apiClient2);
5200
+ registerGetContext(server, apiClient2);
5201
+ registerGetProjectRules(server, apiClient2);
5202
+ registerGetPromptTemplates(server, apiClient2);
5203
+ registerGetTeamSetup(server, apiClient2, { localFiles, keyProblem: keyProblem2 });
5204
+ registerDocumentGraph(server, apiClient2);
5205
+ registerCreateGuardrail(server, apiClient2);
5206
+ registerUpdateSpec(server, apiClient2);
5207
+ registerReportDecision(server, apiClient2);
5208
+ registerCreateProjectRule(server, apiClient2);
5209
+ registerCreatePromptTemplate(server, apiClient2);
5210
+ registerSkillTools(server, apiClient2, { localFiles });
5211
+ registerTeamSetupTools(server, apiClient2, { localFiles });
5212
+ return server;
4814
5213
  }
4815
5214
  var SERVER_VERSION;
4816
5215
  var init_server = __esm({
@@ -4824,6 +5223,8 @@ var init_server = __esm({
4824
5223
  init_get_plan_board();
4825
5224
  init_get_change();
4826
5225
  init_get_context();
5226
+ init_get_team_setup();
5227
+ init_document_graph();
4827
5228
  init_create_guardrail();
4828
5229
  init_update_spec();
4829
5230
  init_report_decision();
@@ -4833,7 +5234,7 @@ var init_server = __esm({
4833
5234
  init_create_prompt_template();
4834
5235
  init_skills2();
4835
5236
  init_team_setup();
4836
- SERVER_VERSION = true ? "0.1.49" : "0.0.0-dev";
5237
+ SERVER_VERSION = true ? "0.1.51" : "0.0.0-dev";
4837
5238
  }
4838
5239
  });
4839
5240
 
@@ -4846,15 +5247,18 @@ __export(index_exports, {
4846
5247
  });
4847
5248
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4848
5249
  async function main() {
4849
- await apiClient.validate();
5250
+ const instructions = await sessionInstructions(apiClient, { localFiles: true, keyProblem });
5251
+ const server = createServer(apiClient, { localFiles: true, instructions, keyProblem });
4850
5252
  const transport = new StdioServerTransport();
4851
5253
  await server.connect(transport);
4852
5254
  }
4853
- var config, apiClient, server;
5255
+ var config, keyProblem, apiClient;
4854
5256
  var init_index = __esm({
4855
5257
  async "src/index.ts"() {
4856
5258
  "use strict";
4857
5259
  init_api_client();
5260
+ init_config();
5261
+ init_session_brief();
4858
5262
  init_team_setup_files();
4859
5263
  init_server();
4860
5264
  init_server();
@@ -4862,10 +5266,15 @@ var init_index = __esm({
4862
5266
  config = await loadLocalConfig();
4863
5267
  } catch (err) {
4864
5268
  console.error(`SystemDox MCP server: ${err.message}`);
4865
- process.exit(1);
5269
+ keyProblem = keyReason(err.message);
5270
+ try {
5271
+ config = loadConfig(NO_KEY);
5272
+ } catch (urlErr) {
5273
+ console.error(`SystemDox MCP server: ${urlErr.message}`);
5274
+ process.exit(1);
5275
+ }
4866
5276
  }
4867
5277
  apiClient = new SystemDoxApiClient(config);
4868
- server = createServer(apiClient, { localFiles: true });
4869
5278
  main();
4870
5279
  }
4871
5280
  });