@systemdox/mcp-server 0.1.15 → 0.1.17

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/http.js CHANGED
@@ -592,11 +592,21 @@ ${adrs}`);
592
592
 
593
593
  ${specs}`);
594
594
  }
595
+ if (sections.length === 0) {
596
+ return {
597
+ content: [
598
+ {
599
+ type: "text",
600
+ text: `No planning context available. Connect repositories at ${configLink("/settings/github", await workspaceBase(apiClient))}`
601
+ }
602
+ ]
603
+ };
604
+ }
595
605
  return {
596
606
  content: [
597
607
  {
598
608
  type: "text",
599
- text: sections.join("\n\n") || "No planning context available."
609
+ text: sections.join("\n\n")
600
610
  }
601
611
  ]
602
612
  };
@@ -604,8 +614,193 @@ ${specs}`);
604
614
  );
605
615
  }
606
616
 
607
- // src/tools/get-context.ts
617
+ // src/tools/get-plan-board.ts
608
618
  import { z as z5 } from "zod";
619
+ var API_ITEM_PAGE = 100;
620
+ var DEFAULT_LIMIT = 50;
621
+ var BUCKET_LABELS = {
622
+ inProgress: "In Progress",
623
+ todo: "Todo",
624
+ done: "Done"
625
+ };
626
+ var BUCKET_ORDER = ["inProgress", "todo", "done"];
627
+ var STATUS_ARG_TO_BUCKET = {
628
+ todo: "todo",
629
+ in_progress: "inProgress",
630
+ done: "done"
631
+ };
632
+ function bucketOf(item) {
633
+ const status = (item.fields?.Status ?? "").trim().toLowerCase();
634
+ if (status) {
635
+ if (status.includes("done") || status.includes("ship") || status.includes("complete")) {
636
+ return "done";
637
+ }
638
+ if (status.includes("progress") || status.includes("doing") || status.includes("review")) {
639
+ return "inProgress";
640
+ }
641
+ return "todo";
642
+ }
643
+ return item.state === "CLOSED" ? "done" : "todo";
644
+ }
645
+ function formatBoards(boards) {
646
+ const lines = boards.map((b) => {
647
+ const c = b.statusCounts ?? { todo: 0, inProgress: 0, done: 0 };
648
+ 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 ? `
649
+ ${b.description}` : "") + `
650
+ ${b.url}`;
651
+ });
652
+ return lines.join("\n");
653
+ }
654
+ function formatItem(item) {
655
+ const meta = [];
656
+ if (item.labels.length > 0) meta.push(`labels: ${item.labels.join(", ")}`);
657
+ if (item.assignees.length > 0)
658
+ meta.push(`assignees: ${item.assignees.join(", ")}`);
659
+ const epic = item.fields?.Epic;
660
+ if (epic) meta.push(`epic: ${epic}`);
661
+ return `- #${item.number} ${item.title}${item.repo ? ` \u2014 ${item.repo}` : ""}` + (meta.length > 0 ? `
662
+ ${meta.join(" \xB7 ")}` : "") + (item.url ? `
663
+ ${item.url}` : "");
664
+ }
665
+ function registerGetPlanBoard(server, apiClient) {
666
+ server.registerTool(
667
+ "get_plan_board",
668
+ {
669
+ title: "Get Plan Board",
670
+ 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.",
671
+ inputSchema: {
672
+ project_number: z5.number().int().positive().optional().describe(
673
+ "Board number (the N in github.com/orgs/{org}/projects/N). Omit to list the open boards."
674
+ ),
675
+ status: z5.enum(["todo", "in_progress", "done"]).optional().describe(
676
+ "With project_number, return only the items in this status group"
677
+ ),
678
+ limit: z5.number().int().positive().max(200).optional().describe(
679
+ `Max items to list (default ${DEFAULT_LIMIT}). Group headings always show the true totals.`
680
+ )
681
+ }
682
+ },
683
+ async ({ project_number, status, limit }) => {
684
+ if (project_number === void 0) {
685
+ const boards = await apiClient.get(
686
+ "/v1/planning/projects"
687
+ );
688
+ if (boards.length === 0) {
689
+ return {
690
+ content: [
691
+ {
692
+ type: "text",
693
+ text: `No project boards found. Connect a GitHub organisation and create a board at ${configLink("/plan", await workspaceBase(apiClient))}`
694
+ }
695
+ ]
696
+ };
697
+ }
698
+ const open = boards.filter((b) => !b.closed);
699
+ if (open.length === 0) {
700
+ return {
701
+ content: [
702
+ {
703
+ type: "text",
704
+ text: `No open project boards (${boards.length} closed). Reopen one, or create a board at ${configLink("/plan", await workspaceBase(apiClient))}`
705
+ }
706
+ ]
707
+ };
708
+ }
709
+ const closedNote = boards.length > open.length ? `
710
+
711
+ _${boards.length - open.length} closed board(s) not listed._` : "";
712
+ return {
713
+ content: [
714
+ {
715
+ type: "text",
716
+ text: `${open.length} open project board(s):
717
+
718
+ ${formatBoards(open)}` + closedNote + `
719
+
720
+ Call get_plan_board with project_number to list a board's issues.`
721
+ }
722
+ ]
723
+ };
724
+ }
725
+ const response = await apiClient.get(
726
+ `/v1/planning/projects/${project_number}/items`
727
+ );
728
+ const allItems = response.items ?? [];
729
+ const boardName = response.projectTitle ? `**${response.projectTitle}** \u2014 board #${project_number}` : `Board #${project_number}`;
730
+ if (allItems.length === 0) {
731
+ return {
732
+ content: [
733
+ {
734
+ type: "text",
735
+ text: `${boardName} has no issues, or it is not visible to this workspace's GitHub connection. Check the board at ${configLink("/plan", await workspaceBase(apiClient))}`
736
+ }
737
+ ]
738
+ };
739
+ }
740
+ const grouped = {
741
+ inProgress: [],
742
+ todo: [],
743
+ done: []
744
+ };
745
+ for (const item of allItems) grouped[bucketOf(item)].push(item);
746
+ const wanted = status ? [STATUS_ARG_TO_BUCKET[status]] : BUCKET_ORDER;
747
+ const shown = wanted.reduce((n, b) => n + grouped[b].length, 0);
748
+ if (shown === 0) {
749
+ return {
750
+ content: [
751
+ {
752
+ type: "text",
753
+ text: `${boardName}: no items with status "${status}" (of ${allItems.length} item(s) on the board).`
754
+ }
755
+ ]
756
+ };
757
+ }
758
+ const cap = limit ?? DEFAULT_LIMIT;
759
+ let budget = cap;
760
+ const sections = [];
761
+ let rendered = 0;
762
+ for (const b of wanted) {
763
+ const items = grouped[b];
764
+ if (items.length === 0) continue;
765
+ const slice = items.slice(0, Math.max(budget, 0));
766
+ budget -= slice.length;
767
+ rendered += slice.length;
768
+ const omitted = items.length - slice.length;
769
+ sections.push(
770
+ `### ${BUCKET_LABELS[b]} (${items.length})
771
+ ` + (slice.length > 0 ? slice.map(formatItem).join("\n") : "_(not listed \u2014 item limit reached)_") + (omitted > 0 && slice.length > 0 ? `
772
+ _\u2026${omitted} more not listed._` : "")
773
+ );
774
+ }
775
+ const notes = [];
776
+ if (rendered < shown) {
777
+ notes.push(
778
+ `_Listing ${rendered} of ${shown} matching item(s) \u2014 raise limit (max 200) or filter by status to see the rest._`
779
+ );
780
+ }
781
+ if (allItems.length >= API_ITEM_PAGE) {
782
+ notes.push(
783
+ `_The API reads the board's first ${API_ITEM_PAGE} items; a larger board may have more._`
784
+ );
785
+ }
786
+ return {
787
+ content: [
788
+ {
789
+ type: "text",
790
+ text: `${boardName} \u2014 ${allItems.length} item(s)
791
+
792
+ ` + sections.join("\n\n") + (notes.length > 0 ? `
793
+
794
+ ${notes.join("\n")}` : "")
795
+ }
796
+ ]
797
+ };
798
+ }
799
+ );
800
+ }
801
+
802
+ // src/tools/get-context.ts
803
+ import { z as z6 } from "zod";
609
804
 
610
805
  // src/lib/scoring.ts
611
806
  var WEIGHT_PATH = 0.4;
@@ -1172,13 +1367,13 @@ function registerGetContext(server, apiClient) {
1172
1367
  title: "Get Context",
1173
1368
  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.",
1174
1369
  inputSchema: {
1175
- task_description: z5.string().describe(
1370
+ task_description: z6.string().describe(
1176
1371
  "What you're working on (e.g., 'add a new REST endpoint for user preferences')"
1177
1372
  ),
1178
- file_paths: z5.array(z5.string()).optional().describe(
1373
+ file_paths: z6.array(z6.string()).optional().describe(
1179
1374
  "File paths being edited \u2014 used for language/category detection"
1180
1375
  ),
1181
- max_tokens: z5.number().optional().describe("Maximum token budget for the response (default: 4000)")
1376
+ max_tokens: z6.number().optional().describe("Maximum token budget for the response (default: 4000)")
1182
1377
  }
1183
1378
  },
1184
1379
  async ({ task_description, file_paths, max_tokens }) => {
@@ -1195,7 +1390,7 @@ function registerGetContext(server, apiClient) {
1195
1390
  }
1196
1391
 
1197
1392
  // src/tools/create-guardrail.ts
1198
- import { z as z6 } from "zod";
1393
+ import { z as z7 } from "zod";
1199
1394
  var DESCRIPTION2 = "Create a new check in SystemDox. Use this when you discover a pattern that should be enforced (banned pattern, required pattern, coding rule).";
1200
1395
  var CATEGORIES = [
1201
1396
  "imports",
@@ -1208,13 +1403,13 @@ var CATEGORIES = [
1208
1403
  "data-access"
1209
1404
  ];
1210
1405
  var inputSchema2 = () => ({
1211
- title: z6.string().describe("Short title for the check"),
1212
- description: z6.string().describe("Detailed description of what the check enforces and why"),
1213
- severity: z6.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
1214
- category: z6.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
1215
- pattern: z6.string().optional().describe("Bad code pattern that should be avoided"),
1216
- fix: z6.string().optional().describe("Correct code pattern to use instead"),
1217
- fitness_test: z6.string().optional().describe("Shell command to detect violations in CI")
1406
+ title: z7.string().describe("Short title for the check"),
1407
+ description: z7.string().describe("Detailed description of what the check enforces and why"),
1408
+ severity: z7.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
1409
+ category: z7.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
1410
+ pattern: z7.string().optional().describe("Bad code pattern that should be avoided"),
1411
+ fix: z7.string().optional().describe("Correct code pattern to use instead"),
1412
+ fitness_test: z7.string().optional().describe("Shell command to detect violations in CI")
1218
1413
  });
1219
1414
  function registerCreateGuardrail(server, apiClient) {
1220
1415
  const handler2 = async ({
@@ -1280,7 +1475,7 @@ function registerCreateGuardrail(server, apiClient) {
1280
1475
  }
1281
1476
 
1282
1477
  // src/tools/update-spec.ts
1283
- import { z as z7 } from "zod";
1478
+ import { z as z8 } from "zod";
1284
1479
  function registerUpdateSpec(server, apiClient) {
1285
1480
  server.registerTool(
1286
1481
  "update_spec",
@@ -1288,12 +1483,12 @@ function registerUpdateSpec(server, apiClient) {
1288
1483
  title: "Update Spec",
1289
1484
  description: "Update an existing SystemDox spec's proposal, tasks, or requirements. Only draft specs can be modified \u2014 published specs must be versioned first.",
1290
1485
  inputSchema: {
1291
- spec_id: z7.string().describe("ID of the spec to update"),
1292
- section: z7.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
1293
- content: z7.string().describe(
1486
+ spec_id: z8.string().describe("ID of the spec to update"),
1487
+ section: z8.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
1488
+ content: z8.string().describe(
1294
1489
  "New markdown content for the section. For requirements, provide the full requirement content."
1295
1490
  ),
1296
- requirement_name: z7.string().optional().describe(
1491
+ requirement_name: z8.string().optional().describe(
1297
1492
  "Name of the requirement (required when section is 'requirements')"
1298
1493
  )
1299
1494
  },
@@ -1343,7 +1538,7 @@ function registerUpdateSpec(server, apiClient) {
1343
1538
  }
1344
1539
 
1345
1540
  // src/tools/report-decision.ts
1346
- import { z as z8 } from "zod";
1541
+ import { z as z9 } from "zod";
1347
1542
  function registerReportDecision(server, apiClient) {
1348
1543
  server.registerTool(
1349
1544
  "report_decision",
@@ -1351,11 +1546,11 @@ function registerReportDecision(server, apiClient) {
1351
1546
  title: "Report Decision",
1352
1547
  description: "Record an architecture decision made during development. Creates a draft ADR in SystemDox for human review.",
1353
1548
  inputSchema: {
1354
- title: z8.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
1355
- context: z8.string().describe("What prompted this decision \u2014 the problem or constraint"),
1356
- decision: z8.string().describe("What was decided and why"),
1357
- consequences: z8.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
1358
- related_files: z8.array(z8.string()).optional().describe("File paths related to this decision")
1549
+ title: z9.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
1550
+ context: z9.string().describe("What prompted this decision \u2014 the problem or constraint"),
1551
+ decision: z9.string().describe("What was decided and why"),
1552
+ consequences: z9.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
1553
+ related_files: z9.array(z9.string()).optional().describe("File paths related to this decision")
1359
1554
  },
1360
1555
  annotations: {
1361
1556
  readOnlyHint: false
@@ -1406,7 +1601,7 @@ Status: draft \u2014 review in SystemDox before publishing.`
1406
1601
  }
1407
1602
 
1408
1603
  // src/tools/get-project-rules.ts
1409
- import { z as z9 } from "zod";
1604
+ import { z as z10 } from "zod";
1410
1605
  var CATEGORY_LABELS = {
1411
1606
  deployment: "Deployment",
1412
1607
  architecture: "Architecture",
@@ -1422,7 +1617,7 @@ function registerGetProjectRules(server, apiClient) {
1422
1617
  title: "Get Project Rules",
1423
1618
  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.",
1424
1619
  inputSchema: {
1425
- category: z9.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
1620
+ category: z10.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
1426
1621
  }
1427
1622
  },
1428
1623
  async ({ category }) => {
@@ -1474,7 +1669,7 @@ function registerGetProjectRules(server, apiClient) {
1474
1669
  }
1475
1670
 
1476
1671
  // src/tools/create-project-rule.ts
1477
- import { z as z10 } from "zod";
1672
+ import { z as z11 } from "zod";
1478
1673
  function registerCreateProjectRule(server, apiClient) {
1479
1674
  server.registerTool(
1480
1675
  "create_project_rule",
@@ -1482,10 +1677,10 @@ function registerCreateProjectRule(server, apiClient) {
1482
1677
  title: "Create Project Rule",
1483
1678
  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.",
1484
1679
  inputSchema: {
1485
- text: z10.string().describe(
1680
+ text: z11.string().describe(
1486
1681
  "The rule statement \u2014 one enforceable principle per rule (e.g. 'All deployments MUST go through GitHub Actions workflows')"
1487
1682
  ),
1488
- category: z10.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
1683
+ category: z11.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
1489
1684
  },
1490
1685
  annotations: {
1491
1686
  readOnlyHint: false
@@ -1556,7 +1751,7 @@ ${text}`
1556
1751
  }
1557
1752
 
1558
1753
  // src/tools/create-prompt-template.ts
1559
- import { z as z11 } from "zod";
1754
+ import { z as z12 } from "zod";
1560
1755
  function registerCreatePromptTemplate(server, apiClient) {
1561
1756
  server.registerTool(
1562
1757
  "create_prompt_template",
@@ -1564,9 +1759,9 @@ function registerCreatePromptTemplate(server, apiClient) {
1564
1759
  title: "Create Prompt Template",
1565
1760
  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.",
1566
1761
  inputSchema: {
1567
- name: z11.string().describe("Short name for the template"),
1568
- description: z11.string().optional().describe("Brief description of when to use this template"),
1569
- prompt: z11.string().describe("The full prompt text that Gen AI will use when generating content")
1762
+ name: z12.string().describe("Short name for the template"),
1763
+ description: z12.string().optional().describe("Brief description of when to use this template"),
1764
+ prompt: z12.string().describe("The full prompt text that Gen AI will use when generating content")
1570
1765
  },
1571
1766
  annotations: {
1572
1767
  readOnlyHint: false
@@ -1595,16 +1790,18 @@ function registerCreatePromptTemplate(server, apiClient) {
1595
1790
  }
1596
1791
 
1597
1792
  // src/server.ts
1793
+ var SERVER_VERSION = true ? "0.1.17" : "0.0.0-dev";
1598
1794
  function createServer(apiClient) {
1599
1795
  const server = new McpServer({
1600
1796
  name: "systemdox-mcp-server",
1601
- version: "0.1.0"
1797
+ version: SERVER_VERSION
1602
1798
  });
1603
1799
  registerGetGuardrails(server, apiClient);
1604
1800
  registerGetSpecs(server, apiClient);
1605
1801
  registerGetDocs(server, apiClient);
1606
1802
  registerGetTechStack(server, apiClient);
1607
1803
  registerGetPlanningContext(server, apiClient);
1804
+ registerGetPlanBoard(server, apiClient);
1608
1805
  registerGetContext(server, apiClient);
1609
1806
  registerGetProjectRules(server, apiClient);
1610
1807
  registerGetPromptTemplates(server, apiClient);