@systemdox/mcp-server 0.1.13 → 0.1.15

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
@@ -246,6 +246,27 @@ var init_deprecation = __esm({
246
246
  }
247
247
  });
248
248
 
249
+ // src/lib/config-links.ts
250
+ async function workspaceBase(apiClient2) {
251
+ try {
252
+ const access = await apiClient2.get(
253
+ "/v1/docs/portal-access"
254
+ );
255
+ const slug = (access.host ?? "").split(".")[0];
256
+ return slug ? `https://${slug}.systemdox.com` : null;
257
+ } catch {
258
+ return null;
259
+ }
260
+ }
261
+ function configLink(path, base) {
262
+ return base ? `${base}${path}` : `${path} in your SystemDox workspace`;
263
+ }
264
+ var init_config_links = __esm({
265
+ "src/lib/config-links.ts"() {
266
+ "use strict";
267
+ }
268
+ });
269
+
249
270
  // src/tools/get-guardrails.ts
250
271
  import { z } from "zod";
251
272
  function registerGetGuardrails(server2, apiClient2) {
@@ -268,13 +289,9 @@ function registerGetGuardrails(server2, apiClient2) {
268
289
  );
269
290
  }
270
291
  if (filtered.length === 0) {
292
+ const text2 = guardrails.length === 0 ? `No checks found. Add checks at ${configLink("/docs/guardrails", await workspaceBase(apiClient2))}` : `No checks found${lang ? ` for language "${lang}"` : ""}${category ? ` in category "${category}"` : ""}.`;
271
293
  return {
272
- content: [
273
- {
274
- type: "text",
275
- text: `No checks found${lang ? ` for language "${lang}"` : ""}${category ? ` in category "${category}"` : ""}.`
276
- }
277
- ]
294
+ content: [{ type: "text", text: text2 }]
278
295
  };
279
296
  }
280
297
  const text = filtered.map(
@@ -331,6 +348,7 @@ var init_get_guardrails = __esm({
331
348
  "use strict";
332
349
  init_language();
333
350
  init_deprecation();
351
+ init_config_links();
334
352
  DESCRIPTION = "Retrieve SystemDox checks (banned patterns, required patterns, coding rules). Filter by language, category, or file path to get only relevant checks.";
335
353
  inputSchema = () => ({
336
354
  language: z.string().optional().describe(
@@ -559,7 +577,7 @@ function registerGetTechStack(server2, apiClient2) {
559
577
  content: [
560
578
  {
561
579
  type: "text",
562
- text: "No tech stack entries configured."
580
+ text: `No tech stack entries configured. Add your stack at ${configLink("/docs/tech-stack", await workspaceBase(apiClient2))}`
563
581
  }
564
582
  ]
565
583
  };
@@ -593,6 +611,7 @@ ${sections}`
593
611
  var init_get_tech_stack = __esm({
594
612
  "src/tools/get-tech-stack.ts"() {
595
613
  "use strict";
614
+ init_config_links();
596
615
  }
597
616
  });
598
617
 
@@ -976,13 +995,19 @@ async function hydrate(api, item, meta) {
976
995
  }
977
996
  async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
978
997
  const budget = maxTokens ?? DEFAULT_BUDGET;
979
- const [guardrails, specs, docs, stack, rules] = await Promise.all([
998
+ const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
980
999
  api.get("/v1/guardrails").catch(() => []),
981
1000
  api.get("/v1/specs").catch(() => []),
982
1001
  api.get("/v1/docs").catch(() => ({ items: [] })),
983
1002
  api.get("/v1/ai/settings/stack").catch(() => []),
984
- api.get("/v1/standards/rules").catch(() => [])
1003
+ api.get("/v1/standards/rules").catch(() => []),
1004
+ // Capability states — FAIL-OPEN: an unreadable config (older backend, a
1005
+ // blip) gates nothing, it can never hide working context.
1006
+ api.get("/v1/doc-repos").catch(() => null)
985
1007
  ]);
1008
+ const disabled = (key2) => docRepos?.capabilities?.[key2]?.status === "disabled";
1009
+ const guardrails = disabled("fitness") ? [] : rawGuardrails;
1010
+ const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
986
1011
  const refs = /* @__PURE__ */ new Map();
987
1012
  const key = (type, id) => `${type}:${id}`;
988
1013
  const scorable = [
@@ -1521,13 +1546,9 @@ function registerGetProjectRules(server2, apiClient2) {
1521
1546
  );
1522
1547
  }
1523
1548
  if (filtered.length === 0) {
1549
+ const text2 = rules.length === 0 ? `No project rules found. Add rules at ${configLink("/docs/rules", await workspaceBase(apiClient2))}` : `No project rules found${category ? ` in category "${category}"` : ""}.`;
1524
1550
  return {
1525
- content: [
1526
- {
1527
- type: "text",
1528
- text: `No project rules found${category ? ` in category "${category}"` : ""}.`
1529
- }
1530
- ]
1551
+ content: [{ type: "text", text: text2 }]
1531
1552
  };
1532
1553
  }
1533
1554
  const grouped = /* @__PURE__ */ new Map();
@@ -1565,6 +1586,7 @@ var CATEGORY_LABELS;
1565
1586
  var init_get_project_rules = __esm({
1566
1587
  "src/tools/get-project-rules.ts"() {
1567
1588
  "use strict";
1589
+ init_config_links();
1568
1590
  CATEGORY_LABELS = {
1569
1591
  deployment: "Deployment",
1570
1592
  architecture: "Architecture",
@@ -1635,7 +1657,7 @@ function registerGetPromptTemplates(server2, apiClient2) {
1635
1657
  content: [
1636
1658
  {
1637
1659
  type: "text",
1638
- text: "No prompt templates found."
1660
+ text: `No prompt templates found. Add templates at ${configLink("/docs/prompts", await workspaceBase(apiClient2))}`
1639
1661
  }
1640
1662
  ]
1641
1663
  };
@@ -1665,6 +1687,7 @@ ${text}`
1665
1687
  var init_get_prompt_templates = __esm({
1666
1688
  "src/tools/get-prompt-templates.ts"() {
1667
1689
  "use strict";
1690
+ init_config_links();
1668
1691
  }
1669
1692
  });
1670
1693
 
package/dist/http.js CHANGED
@@ -200,6 +200,22 @@ function legacyToolDescription(canonical, description) {
200
200
  return `DEPRECATED \u2014 use \`${canonical}\` instead. This name still works and will not be removed before ${LEGACY_TOOL_REMOVAL_EARLIEST}. ${description}`;
201
201
  }
202
202
 
203
+ // src/lib/config-links.ts
204
+ async function workspaceBase(apiClient) {
205
+ try {
206
+ const access = await apiClient.get(
207
+ "/v1/docs/portal-access"
208
+ );
209
+ const slug = (access.host ?? "").split(".")[0];
210
+ return slug ? `https://${slug}.systemdox.com` : null;
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ function configLink(path, base) {
216
+ return base ? `${base}${path}` : `${path} in your SystemDox workspace`;
217
+ }
218
+
203
219
  // src/tools/get-guardrails.ts
204
220
  var DESCRIPTION = "Retrieve SystemDox checks (banned patterns, required patterns, coding rules). Filter by language, category, or file path to get only relevant checks.";
205
221
  var inputSchema = () => ({
@@ -233,13 +249,9 @@ function registerGetGuardrails(server, apiClient) {
233
249
  );
234
250
  }
235
251
  if (filtered.length === 0) {
252
+ const text2 = guardrails.length === 0 ? `No checks found. Add checks at ${configLink("/docs/guardrails", await workspaceBase(apiClient))}` : `No checks found${lang ? ` for language "${lang}"` : ""}${category ? ` in category "${category}"` : ""}.`;
236
253
  return {
237
- content: [
238
- {
239
- type: "text",
240
- text: `No checks found${lang ? ` for language "${lang}"` : ""}${category ? ` in category "${category}"` : ""}.`
241
- }
242
- ]
254
+ content: [{ type: "text", text: text2 }]
243
255
  };
244
256
  }
245
257
  const text = filtered.map(
@@ -494,7 +506,7 @@ function registerGetTechStack(server, apiClient) {
494
506
  content: [
495
507
  {
496
508
  type: "text",
497
- text: "No tech stack entries configured."
509
+ text: `No tech stack entries configured. Add your stack at ${configLink("/docs/tech-stack", await workspaceBase(apiClient))}`
498
510
  }
499
511
  ]
500
512
  };
@@ -927,13 +939,19 @@ async function hydrate(api, item, meta) {
927
939
  }
928
940
  async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
929
941
  const budget = maxTokens ?? DEFAULT_BUDGET;
930
- const [guardrails, specs, docs, stack, rules] = await Promise.all([
942
+ const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
931
943
  api.get("/v1/guardrails").catch(() => []),
932
944
  api.get("/v1/specs").catch(() => []),
933
945
  api.get("/v1/docs").catch(() => ({ items: [] })),
934
946
  api.get("/v1/ai/settings/stack").catch(() => []),
935
- api.get("/v1/standards/rules").catch(() => [])
947
+ api.get("/v1/standards/rules").catch(() => []),
948
+ // Capability states — FAIL-OPEN: an unreadable config (older backend, a
949
+ // blip) gates nothing, it can never hide working context.
950
+ api.get("/v1/doc-repos").catch(() => null)
936
951
  ]);
952
+ const disabled = (key2) => docRepos?.capabilities?.[key2]?.status === "disabled";
953
+ const guardrails = disabled("fitness") ? [] : rawGuardrails;
954
+ const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
937
955
  const refs = /* @__PURE__ */ new Map();
938
956
  const key = (type, id) => `${type}:${id}`;
939
957
  const scorable = [
@@ -1418,13 +1436,9 @@ function registerGetProjectRules(server, apiClient) {
1418
1436
  );
1419
1437
  }
1420
1438
  if (filtered.length === 0) {
1439
+ const text2 = rules.length === 0 ? `No project rules found. Add rules at ${configLink("/docs/rules", await workspaceBase(apiClient))}` : `No project rules found${category ? ` in category "${category}"` : ""}.`;
1421
1440
  return {
1422
- content: [
1423
- {
1424
- type: "text",
1425
- text: `No project rules found${category ? ` in category "${category}"` : ""}.`
1426
- }
1427
- ]
1441
+ content: [{ type: "text", text: text2 }]
1428
1442
  };
1429
1443
  }
1430
1444
  const grouped = /* @__PURE__ */ new Map();
@@ -1513,7 +1527,7 @@ function registerGetPromptTemplates(server, apiClient) {
1513
1527
  content: [
1514
1528
  {
1515
1529
  type: "text",
1516
- text: "No prompt templates found."
1530
+ text: `No prompt templates found. Add templates at ${configLink("/docs/prompts", await workspaceBase(apiClient))}`
1517
1531
  }
1518
1532
  ]
1519
1533
  };
package/dist/index.js CHANGED
@@ -216,6 +216,22 @@ function legacyToolDescription(canonical, description) {
216
216
  return `DEPRECATED \u2014 use \`${canonical}\` instead. This name still works and will not be removed before ${LEGACY_TOOL_REMOVAL_EARLIEST}. ${description}`;
217
217
  }
218
218
 
219
+ // src/lib/config-links.ts
220
+ async function workspaceBase(apiClient2) {
221
+ try {
222
+ const access = await apiClient2.get(
223
+ "/v1/docs/portal-access"
224
+ );
225
+ const slug = (access.host ?? "").split(".")[0];
226
+ return slug ? `https://${slug}.systemdox.com` : null;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+ function configLink(path, base) {
232
+ return base ? `${base}${path}` : `${path} in your SystemDox workspace`;
233
+ }
234
+
219
235
  // src/tools/get-guardrails.ts
220
236
  var DESCRIPTION = "Retrieve SystemDox checks (banned patterns, required patterns, coding rules). Filter by language, category, or file path to get only relevant checks.";
221
237
  var inputSchema = () => ({
@@ -249,13 +265,9 @@ function registerGetGuardrails(server2, apiClient2) {
249
265
  );
250
266
  }
251
267
  if (filtered.length === 0) {
268
+ const text2 = guardrails.length === 0 ? `No checks found. Add checks at ${configLink("/docs/guardrails", await workspaceBase(apiClient2))}` : `No checks found${lang ? ` for language "${lang}"` : ""}${category ? ` in category "${category}"` : ""}.`;
252
269
  return {
253
- content: [
254
- {
255
- type: "text",
256
- text: `No checks found${lang ? ` for language "${lang}"` : ""}${category ? ` in category "${category}"` : ""}.`
257
- }
258
- ]
270
+ content: [{ type: "text", text: text2 }]
259
271
  };
260
272
  }
261
273
  const text = filtered.map(
@@ -510,7 +522,7 @@ function registerGetTechStack(server2, apiClient2) {
510
522
  content: [
511
523
  {
512
524
  type: "text",
513
- text: "No tech stack entries configured."
525
+ text: `No tech stack entries configured. Add your stack at ${configLink("/docs/tech-stack", await workspaceBase(apiClient2))}`
514
526
  }
515
527
  ]
516
528
  };
@@ -943,13 +955,19 @@ async function hydrate(api, item, meta) {
943
955
  }
944
956
  async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
945
957
  const budget = maxTokens ?? DEFAULT_BUDGET;
946
- const [guardrails, specs, docs, stack, rules] = await Promise.all([
958
+ const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
947
959
  api.get("/v1/guardrails").catch(() => []),
948
960
  api.get("/v1/specs").catch(() => []),
949
961
  api.get("/v1/docs").catch(() => ({ items: [] })),
950
962
  api.get("/v1/ai/settings/stack").catch(() => []),
951
- api.get("/v1/standards/rules").catch(() => [])
963
+ api.get("/v1/standards/rules").catch(() => []),
964
+ // Capability states — FAIL-OPEN: an unreadable config (older backend, a
965
+ // blip) gates nothing, it can never hide working context.
966
+ api.get("/v1/doc-repos").catch(() => null)
952
967
  ]);
968
+ const disabled = (key2) => docRepos?.capabilities?.[key2]?.status === "disabled";
969
+ const guardrails = disabled("fitness") ? [] : rawGuardrails;
970
+ const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
953
971
  const refs = /* @__PURE__ */ new Map();
954
972
  const key = (type, id) => `${type}:${id}`;
955
973
  const scorable = [
@@ -1434,13 +1452,9 @@ function registerGetProjectRules(server2, apiClient2) {
1434
1452
  );
1435
1453
  }
1436
1454
  if (filtered.length === 0) {
1455
+ const text2 = rules.length === 0 ? `No project rules found. Add rules at ${configLink("/docs/rules", await workspaceBase(apiClient2))}` : `No project rules found${category ? ` in category "${category}"` : ""}.`;
1437
1456
  return {
1438
- content: [
1439
- {
1440
- type: "text",
1441
- text: `No project rules found${category ? ` in category "${category}"` : ""}.`
1442
- }
1443
- ]
1457
+ content: [{ type: "text", text: text2 }]
1444
1458
  };
1445
1459
  }
1446
1460
  const grouped = /* @__PURE__ */ new Map();
@@ -1529,7 +1543,7 @@ function registerGetPromptTemplates(server2, apiClient2) {
1529
1543
  content: [
1530
1544
  {
1531
1545
  type: "text",
1532
- text: "No prompt templates found."
1546
+ text: `No prompt templates found. Add templates at ${configLink("/docs/prompts", await workspaceBase(apiClient2))}`
1533
1547
  }
1534
1548
  ]
1535
1549
  };
@@ -0,0 +1,18 @@
1
+ import type { SystemDoxApiClient } from "./api-client.js";
2
+ /**
3
+ * The tenant's workspace base URL (https://{slug}.systemdox.com), or null
4
+ * when it can't be resolved. The slug is the first label of the portal host
5
+ * that GET /v1/docs/portal-access reports — the API builds that host as
6
+ * `{slug}.{DOCS_DOMAIN}`, so the first label is the slug whichever docs
7
+ * domain is live. The GET goes through the api client's response cache, so
8
+ * repeated empty states don't re-fetch.
9
+ */
10
+ export declare function workspaceBase(apiClient: SystemDoxApiClient): Promise<string | null>;
11
+ /**
12
+ * Where to add missing configuration: a full URL when the workspace base is
13
+ * known, the in-app path otherwise.
14
+ *
15
+ * Mirrored by apps/app/src/pages/ai/mcpPreview.ts — keep the output
16
+ * byte-identical when either side changes.
17
+ */
18
+ export declare function configLink(path: string, base: string | null): string;
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/config-links.ts
4
+ async function workspaceBase(apiClient) {
5
+ try {
6
+ const access = await apiClient.get(
7
+ "/v1/docs/portal-access"
8
+ );
9
+ const slug = (access.host ?? "").split(".")[0];
10
+ return slug ? `https://${slug}.systemdox.com` : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function configLink(path, base) {
16
+ return base ? `${base}${path}` : `${path} in your SystemDox workspace`;
17
+ }
18
+ export {
19
+ configLink,
20
+ workspaceBase
21
+ };
@@ -403,13 +403,19 @@ async function hydrate(api, item, meta) {
403
403
  }
404
404
  async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
405
405
  const budget = maxTokens ?? DEFAULT_BUDGET;
406
- const [guardrails, specs, docs, stack, rules] = await Promise.all([
406
+ const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
407
407
  api.get("/v1/guardrails").catch(() => []),
408
408
  api.get("/v1/specs").catch(() => []),
409
409
  api.get("/v1/docs").catch(() => ({ items: [] })),
410
410
  api.get("/v1/ai/settings/stack").catch(() => []),
411
- api.get("/v1/standards/rules").catch(() => [])
411
+ api.get("/v1/standards/rules").catch(() => []),
412
+ // Capability states — FAIL-OPEN: an unreadable config (older backend, a
413
+ // blip) gates nothing, it can never hide working context.
414
+ api.get("/v1/doc-repos").catch(() => null)
412
415
  ]);
416
+ const disabled = (key2) => docRepos?.capabilities?.[key2]?.status === "disabled";
417
+ const guardrails = disabled("fitness") ? [] : rawGuardrails;
418
+ const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
413
419
  const refs = /* @__PURE__ */ new Map();
414
420
  const key = (type, id) => `${type}:${id}`;
415
421
  const scorable = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@systemdox/mcp-server",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "description": "MCP server that exposes SystemDox checks, specs, docs, and tech stack to AI coding tools",
6
6
  "main": "./dist/index.js",