@systemdox/mcp-server 0.1.15 → 0.1.16

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/README.md CHANGED
@@ -45,7 +45,8 @@ HTTP. Point any HTTP-capable MCP client at it — nothing to install or update.
45
45
  which suits serverless hosting. There is no long-lived SSE stream.
46
46
  - **Scope** — the key is the boundary. A key bound to a single space (a
47
47
  "department" key) sees only that space's context; org-wide planner endpoints
48
- (`get_planning_context`) are barred to it and answer `403`. Give each
48
+ (`get_planning_context`, `get_plan_board`) are barred to it and answer `403`.
49
+ Give each
49
50
  department's repo its own key via `${SYSTEMDOX_API_KEY}`, and an agent in one
50
51
  department never sees another's checks, specs, or docs.
51
52
 
@@ -89,7 +90,7 @@ npx @systemdox/mcp-server setup
89
90
  ## Tools
90
91
 
91
92
  Read: `get_checks`, `get_specs`, `get_docs`, `get_tech_stack`,
92
- `get_planning_context`, `get_context`, `get_project_rules`,
93
+ `get_planning_context`, `get_plan_board`, `get_context`, `get_project_rules`,
93
94
  `get_prompt_templates`. Write: `create_check`, `update_spec`,
94
95
  `report_decision`, `create_project_rule`, `create_prompt_template`. Write tools
95
96
  require an API key with `editor`+ role.
package/dist/cli.js CHANGED
@@ -669,11 +669,21 @@ ${adrs}`);
669
669
 
670
670
  ${specs}`);
671
671
  }
672
+ if (sections.length === 0) {
673
+ return {
674
+ content: [
675
+ {
676
+ type: "text",
677
+ text: `No planning context available. Connect repositories at ${configLink("/settings/github", await workspaceBase(apiClient2))}`
678
+ }
679
+ ]
680
+ };
681
+ }
672
682
  return {
673
683
  content: [
674
684
  {
675
685
  type: "text",
676
- text: sections.join("\n\n") || "No planning context available."
686
+ text: sections.join("\n\n")
677
687
  }
678
688
  ]
679
689
  };
@@ -683,6 +693,199 @@ ${specs}`);
683
693
  var init_get_planning_context = __esm({
684
694
  "src/tools/get-planning-context.ts"() {
685
695
  "use strict";
696
+ init_config_links();
697
+ }
698
+ });
699
+
700
+ // src/tools/get-plan-board.ts
701
+ import { z as z5 } from "zod";
702
+ function bucketOf(item) {
703
+ const status = (item.fields?.Status ?? "").trim().toLowerCase();
704
+ if (status) {
705
+ if (status.includes("done") || status.includes("ship") || status.includes("complete")) {
706
+ return "done";
707
+ }
708
+ if (status.includes("progress") || status.includes("doing") || status.includes("review")) {
709
+ return "inProgress";
710
+ }
711
+ return "todo";
712
+ }
713
+ return item.state === "CLOSED" ? "done" : "todo";
714
+ }
715
+ function formatBoards(boards) {
716
+ const lines = boards.map((b) => {
717
+ const c = b.statusCounts ?? { todo: 0, inProgress: 0, done: 0 };
718
+ return `- **${b.title}** \u2014 board #${b.number} \xB7 ${b.itemCount} item(s) (todo ${c.todo} \xB7 in progress ${c.inProgress} \xB7 done ${c.done})` + (b.description ? `
719
+ ${b.description}` : "") + `
720
+ ${b.url}`;
721
+ });
722
+ return lines.join("\n");
723
+ }
724
+ function formatItem(item) {
725
+ const meta = [];
726
+ if (item.labels.length > 0) meta.push(`labels: ${item.labels.join(", ")}`);
727
+ if (item.assignees.length > 0)
728
+ meta.push(`assignees: ${item.assignees.join(", ")}`);
729
+ const epic = item.fields?.Epic;
730
+ if (epic) meta.push(`epic: ${epic}`);
731
+ return `- #${item.number} ${item.title}${item.repo ? ` \u2014 ${item.repo}` : ""}` + (meta.length > 0 ? `
732
+ ${meta.join(" \xB7 ")}` : "") + (item.url ? `
733
+ ${item.url}` : "");
734
+ }
735
+ function registerGetPlanBoard(server2, apiClient2) {
736
+ server2.registerTool(
737
+ "get_plan_board",
738
+ {
739
+ title: "Get Plan Board",
740
+ description: "Read the GitHub Projects boards connected to this workspace. With no arguments, lists the open boards with their item counts by status. With project_number, lists that board's issues grouped by status (In Progress / Todo / Done), each with issue number, title, repo, labels, assignees and URL \u2014 so you can see what is planned or in flight and pick up the right issue. Read-only: this tool cannot create, move, or close board items.",
741
+ inputSchema: {
742
+ project_number: z5.number().int().positive().optional().describe(
743
+ "Board number (the N in github.com/orgs/{org}/projects/N). Omit to list the open boards."
744
+ ),
745
+ status: z5.enum(["todo", "in_progress", "done"]).optional().describe(
746
+ "With project_number, return only the items in this status group"
747
+ ),
748
+ limit: z5.number().int().positive().max(200).optional().describe(
749
+ `Max items to list (default ${DEFAULT_LIMIT}). Group headings always show the true totals.`
750
+ )
751
+ }
752
+ },
753
+ async ({ project_number, status, limit }) => {
754
+ if (project_number === void 0) {
755
+ const boards = await apiClient2.get(
756
+ "/v1/planning/projects"
757
+ );
758
+ if (boards.length === 0) {
759
+ return {
760
+ content: [
761
+ {
762
+ type: "text",
763
+ text: `No project boards found. Connect a GitHub organisation and create a board at ${configLink("/plan", await workspaceBase(apiClient2))}`
764
+ }
765
+ ]
766
+ };
767
+ }
768
+ const open = boards.filter((b) => !b.closed);
769
+ if (open.length === 0) {
770
+ return {
771
+ content: [
772
+ {
773
+ type: "text",
774
+ text: `No open project boards (${boards.length} closed). Reopen one, or create a board at ${configLink("/plan", await workspaceBase(apiClient2))}`
775
+ }
776
+ ]
777
+ };
778
+ }
779
+ const closedNote = boards.length > open.length ? `
780
+
781
+ _${boards.length - open.length} closed board(s) not listed._` : "";
782
+ return {
783
+ content: [
784
+ {
785
+ type: "text",
786
+ text: `${open.length} open project board(s):
787
+
788
+ ${formatBoards(open)}` + closedNote + `
789
+
790
+ Call get_plan_board with project_number to list a board's issues.`
791
+ }
792
+ ]
793
+ };
794
+ }
795
+ const response = await apiClient2.get(
796
+ `/v1/planning/projects/${project_number}/items`
797
+ );
798
+ const allItems = response.items ?? [];
799
+ const boardName = response.projectTitle ? `**${response.projectTitle}** \u2014 board #${project_number}` : `Board #${project_number}`;
800
+ if (allItems.length === 0) {
801
+ return {
802
+ content: [
803
+ {
804
+ type: "text",
805
+ text: `${boardName} has no issues, or it is not visible to this workspace's GitHub connection. Check the board at ${configLink("/plan", await workspaceBase(apiClient2))}`
806
+ }
807
+ ]
808
+ };
809
+ }
810
+ const grouped = {
811
+ inProgress: [],
812
+ todo: [],
813
+ done: []
814
+ };
815
+ for (const item of allItems) grouped[bucketOf(item)].push(item);
816
+ const wanted = status ? [STATUS_ARG_TO_BUCKET[status]] : BUCKET_ORDER;
817
+ const shown = wanted.reduce((n, b) => n + grouped[b].length, 0);
818
+ if (shown === 0) {
819
+ return {
820
+ content: [
821
+ {
822
+ type: "text",
823
+ text: `${boardName}: no items with status "${status}" (of ${allItems.length} item(s) on the board).`
824
+ }
825
+ ]
826
+ };
827
+ }
828
+ const cap = limit ?? DEFAULT_LIMIT;
829
+ let budget = cap;
830
+ const sections = [];
831
+ let rendered = 0;
832
+ for (const b of wanted) {
833
+ const items = grouped[b];
834
+ if (items.length === 0) continue;
835
+ const slice = items.slice(0, Math.max(budget, 0));
836
+ budget -= slice.length;
837
+ rendered += slice.length;
838
+ const omitted = items.length - slice.length;
839
+ sections.push(
840
+ `### ${BUCKET_LABELS[b]} (${items.length})
841
+ ` + (slice.length > 0 ? slice.map(formatItem).join("\n") : "_(not listed \u2014 item limit reached)_") + (omitted > 0 && slice.length > 0 ? `
842
+ _\u2026${omitted} more not listed._` : "")
843
+ );
844
+ }
845
+ const notes = [];
846
+ if (rendered < shown) {
847
+ notes.push(
848
+ `_Listing ${rendered} of ${shown} matching item(s) \u2014 raise limit (max 200) or filter by status to see the rest._`
849
+ );
850
+ }
851
+ if (allItems.length >= API_ITEM_PAGE) {
852
+ notes.push(
853
+ `_The API reads the board's first ${API_ITEM_PAGE} items; a larger board may have more._`
854
+ );
855
+ }
856
+ return {
857
+ content: [
858
+ {
859
+ type: "text",
860
+ text: `${boardName} \u2014 ${allItems.length} item(s)
861
+
862
+ ` + sections.join("\n\n") + (notes.length > 0 ? `
863
+
864
+ ${notes.join("\n")}` : "")
865
+ }
866
+ ]
867
+ };
868
+ }
869
+ );
870
+ }
871
+ var API_ITEM_PAGE, DEFAULT_LIMIT, BUCKET_LABELS, BUCKET_ORDER, STATUS_ARG_TO_BUCKET;
872
+ var init_get_plan_board = __esm({
873
+ "src/tools/get-plan-board.ts"() {
874
+ "use strict";
875
+ init_config_links();
876
+ API_ITEM_PAGE = 100;
877
+ DEFAULT_LIMIT = 50;
878
+ BUCKET_LABELS = {
879
+ inProgress: "In Progress",
880
+ todo: "Todo",
881
+ done: "Done"
882
+ };
883
+ BUCKET_ORDER = ["inProgress", "todo", "done"];
884
+ STATUS_ARG_TO_BUCKET = {
885
+ todo: "todo",
886
+ in_progress: "inProgress",
887
+ done: "done"
888
+ };
686
889
  }
687
890
  });
688
891
 
@@ -1259,7 +1462,7 @@ var init_context_builder = __esm({
1259
1462
  });
1260
1463
 
1261
1464
  // src/tools/get-context.ts
1262
- import { z as z5 } from "zod";
1465
+ import { z as z6 } from "zod";
1263
1466
  function registerGetContext(server2, apiClient2) {
1264
1467
  server2.registerTool(
1265
1468
  "get_context",
@@ -1267,13 +1470,13 @@ function registerGetContext(server2, apiClient2) {
1267
1470
  title: "Get Context",
1268
1471
  description: "Intelligent context aggregator: fetches checks, specs, docs, and tech stack, ranks them by relevance to your current task, and returns the full text of the highest-ranked items within a token budget \u2014 each with the ID needed to fetch the rest. Use this instead of calling individual tools when you want curated, relevant context.",
1269
1472
  inputSchema: {
1270
- task_description: z5.string().describe(
1473
+ task_description: z6.string().describe(
1271
1474
  "What you're working on (e.g., 'add a new REST endpoint for user preferences')"
1272
1475
  ),
1273
- file_paths: z5.array(z5.string()).optional().describe(
1476
+ file_paths: z6.array(z6.string()).optional().describe(
1274
1477
  "File paths being edited \u2014 used for language/category detection"
1275
1478
  ),
1276
- max_tokens: z5.number().optional().describe("Maximum token budget for the response (default: 4000)")
1479
+ max_tokens: z6.number().optional().describe("Maximum token budget for the response (default: 4000)")
1277
1480
  }
1278
1481
  },
1279
1482
  async ({ task_description, file_paths, max_tokens }) => {
@@ -1296,7 +1499,7 @@ var init_get_context = __esm({
1296
1499
  });
1297
1500
 
1298
1501
  // src/tools/create-guardrail.ts
1299
- import { z as z6 } from "zod";
1502
+ import { z as z7 } from "zod";
1300
1503
  function registerCreateGuardrail(server2, apiClient2) {
1301
1504
  const handler = async ({
1302
1505
  title,
@@ -1376,19 +1579,19 @@ var init_create_guardrail = __esm({
1376
1579
  "data-access"
1377
1580
  ];
1378
1581
  inputSchema2 = () => ({
1379
- title: z6.string().describe("Short title for the check"),
1380
- description: z6.string().describe("Detailed description of what the check enforces and why"),
1381
- severity: z6.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
1382
- category: z6.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
1383
- pattern: z6.string().optional().describe("Bad code pattern that should be avoided"),
1384
- fix: z6.string().optional().describe("Correct code pattern to use instead"),
1385
- fitness_test: z6.string().optional().describe("Shell command to detect violations in CI")
1582
+ title: z7.string().describe("Short title for the check"),
1583
+ description: z7.string().describe("Detailed description of what the check enforces and why"),
1584
+ severity: z7.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
1585
+ category: z7.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
1586
+ pattern: z7.string().optional().describe("Bad code pattern that should be avoided"),
1587
+ fix: z7.string().optional().describe("Correct code pattern to use instead"),
1588
+ fitness_test: z7.string().optional().describe("Shell command to detect violations in CI")
1386
1589
  });
1387
1590
  }
1388
1591
  });
1389
1592
 
1390
1593
  // src/tools/update-spec.ts
1391
- import { z as z7 } from "zod";
1594
+ import { z as z8 } from "zod";
1392
1595
  function registerUpdateSpec(server2, apiClient2) {
1393
1596
  server2.registerTool(
1394
1597
  "update_spec",
@@ -1396,12 +1599,12 @@ function registerUpdateSpec(server2, apiClient2) {
1396
1599
  title: "Update Spec",
1397
1600
  description: "Update an existing SystemDox spec's proposal, tasks, or requirements. Only draft specs can be modified \u2014 published specs must be versioned first.",
1398
1601
  inputSchema: {
1399
- spec_id: z7.string().describe("ID of the spec to update"),
1400
- section: z7.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
1401
- content: z7.string().describe(
1602
+ spec_id: z8.string().describe("ID of the spec to update"),
1603
+ section: z8.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
1604
+ content: z8.string().describe(
1402
1605
  "New markdown content for the section. For requirements, provide the full requirement content."
1403
1606
  ),
1404
- requirement_name: z7.string().optional().describe(
1607
+ requirement_name: z8.string().optional().describe(
1405
1608
  "Name of the requirement (required when section is 'requirements')"
1406
1609
  )
1407
1610
  },
@@ -1456,7 +1659,7 @@ var init_update_spec = __esm({
1456
1659
  });
1457
1660
 
1458
1661
  // src/tools/report-decision.ts
1459
- import { z as z8 } from "zod";
1662
+ import { z as z9 } from "zod";
1460
1663
  function registerReportDecision(server2, apiClient2) {
1461
1664
  server2.registerTool(
1462
1665
  "report_decision",
@@ -1464,11 +1667,11 @@ function registerReportDecision(server2, apiClient2) {
1464
1667
  title: "Report Decision",
1465
1668
  description: "Record an architecture decision made during development. Creates a draft ADR in SystemDox for human review.",
1466
1669
  inputSchema: {
1467
- title: z8.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
1468
- context: z8.string().describe("What prompted this decision \u2014 the problem or constraint"),
1469
- decision: z8.string().describe("What was decided and why"),
1470
- consequences: z8.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
1471
- related_files: z8.array(z8.string()).optional().describe("File paths related to this decision")
1670
+ title: z9.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
1671
+ context: z9.string().describe("What prompted this decision \u2014 the problem or constraint"),
1672
+ decision: z9.string().describe("What was decided and why"),
1673
+ consequences: z9.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
1674
+ related_files: z9.array(z9.string()).optional().describe("File paths related to this decision")
1472
1675
  },
1473
1676
  annotations: {
1474
1677
  readOnlyHint: false
@@ -1524,7 +1727,7 @@ var init_report_decision = __esm({
1524
1727
  });
1525
1728
 
1526
1729
  // src/tools/get-project-rules.ts
1527
- import { z as z9 } from "zod";
1730
+ import { z as z10 } from "zod";
1528
1731
  function registerGetProjectRules(server2, apiClient2) {
1529
1732
  server2.registerTool(
1530
1733
  "get_project_rules",
@@ -1532,7 +1735,7 @@ function registerGetProjectRules(server2, apiClient2) {
1532
1735
  title: "Get Project Rules",
1533
1736
  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.",
1534
1737
  inputSchema: {
1535
- category: z9.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
1738
+ category: z10.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
1536
1739
  }
1537
1740
  },
1538
1741
  async ({ category }) => {
@@ -1599,7 +1802,7 @@ var init_get_project_rules = __esm({
1599
1802
  });
1600
1803
 
1601
1804
  // src/tools/create-project-rule.ts
1602
- import { z as z10 } from "zod";
1805
+ import { z as z11 } from "zod";
1603
1806
  function registerCreateProjectRule(server2, apiClient2) {
1604
1807
  server2.registerTool(
1605
1808
  "create_project_rule",
@@ -1607,10 +1810,10 @@ function registerCreateProjectRule(server2, apiClient2) {
1607
1810
  title: "Create Project Rule",
1608
1811
  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.",
1609
1812
  inputSchema: {
1610
- text: z10.string().describe(
1813
+ text: z11.string().describe(
1611
1814
  "The rule statement \u2014 one enforceable principle per rule (e.g. 'All deployments MUST go through GitHub Actions workflows')"
1612
1815
  ),
1613
- category: z10.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
1816
+ category: z11.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
1614
1817
  },
1615
1818
  annotations: {
1616
1819
  readOnlyHint: false
@@ -1692,7 +1895,7 @@ var init_get_prompt_templates = __esm({
1692
1895
  });
1693
1896
 
1694
1897
  // src/tools/create-prompt-template.ts
1695
- import { z as z11 } from "zod";
1898
+ import { z as z12 } from "zod";
1696
1899
  function registerCreatePromptTemplate(server2, apiClient2) {
1697
1900
  server2.registerTool(
1698
1901
  "create_prompt_template",
@@ -1700,9 +1903,9 @@ function registerCreatePromptTemplate(server2, apiClient2) {
1700
1903
  title: "Create Prompt Template",
1701
1904
  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.",
1702
1905
  inputSchema: {
1703
- name: z11.string().describe("Short name for the template"),
1704
- description: z11.string().optional().describe("Brief description of when to use this template"),
1705
- prompt: z11.string().describe("The full prompt text that Gen AI will use when generating content")
1906
+ name: z12.string().describe("Short name for the template"),
1907
+ description: z12.string().optional().describe("Brief description of when to use this template"),
1908
+ prompt: z12.string().describe("The full prompt text that Gen AI will use when generating content")
1706
1909
  },
1707
1910
  annotations: {
1708
1911
  readOnlyHint: false
@@ -1747,6 +1950,7 @@ function createServer(apiClient2) {
1747
1950
  registerGetDocs(server2, apiClient2);
1748
1951
  registerGetTechStack(server2, apiClient2);
1749
1952
  registerGetPlanningContext(server2, apiClient2);
1953
+ registerGetPlanBoard(server2, apiClient2);
1750
1954
  registerGetContext(server2, apiClient2);
1751
1955
  registerGetProjectRules(server2, apiClient2);
1752
1956
  registerGetPromptTemplates(server2, apiClient2);
@@ -1765,6 +1969,7 @@ var init_server = __esm({
1765
1969
  init_get_docs();
1766
1970
  init_get_tech_stack();
1767
1971
  init_get_planning_context();
1972
+ init_get_plan_board();
1768
1973
  init_get_context();
1769
1974
  init_create_guardrail();
1770
1975
  init_update_spec();
@@ -1952,7 +2157,7 @@ async function setup() {
1952
2157
  console.log(` Configured: ${target.name}`);
1953
2158
  }
1954
2159
  console.log(
1955
- "\n Done! Restart your AI coding tool to activate the SystemDox MCP server.\n The server provides 13 tools: get_context, get_checks,\n get_project_rules, get_prompt_templates, get_specs, get_docs,\n get_tech_stack, get_planning_context, create_check,\n create_project_rule, create_prompt_template, update_spec,\n report_decision.\n\n get_guardrails and create_guardrail remain registered as deprecated\n aliases of get_checks and create_check \u2014 existing configs keep working.\n"
2160
+ "\n Done! Restart your AI coding tool to activate the SystemDox MCP server.\n The server provides 14 tools: get_context, get_checks,\n get_project_rules, get_prompt_templates, get_specs, get_docs,\n get_tech_stack, get_planning_context, get_plan_board, create_check,\n create_project_rule, create_prompt_template, update_spec,\n report_decision.\n\n get_guardrails and create_guardrail remain registered as deprecated\n aliases of get_checks and create_check \u2014 existing configs keep working.\n"
1956
2161
  );
1957
2162
  }
1958
2163
  var args = process.argv.slice(2);