@elixium.ai/mcp-server 0.3.2 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +517 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -200,12 +200,16 @@ const fetchFeatureConfig = async () => {
200
200
  learningLoop: true,
201
201
  tddWorkflow: true,
202
202
  aiTools: true,
203
+ teamDecisions: false,
204
+ ragKnowledgeBase: false,
203
205
  },
204
206
  source: {
205
207
  balancedTeam: "error-fallback",
206
208
  learningLoop: "error-fallback",
207
209
  tddWorkflow: "error-fallback",
208
210
  aiTools: "error-fallback",
211
+ teamDecisions: "error-fallback",
212
+ ragKnowledgeBase: "error-fallback",
209
213
  },
210
214
  };
211
215
  }
@@ -219,6 +223,10 @@ const isLearningLoopEnabled = async () => {
219
223
  const config = await fetchFeatureConfig();
220
224
  return config.features.learningLoop;
221
225
  };
226
+ const isTeamDecisionsEnabled = async () => {
227
+ const config = await fetchFeatureConfig();
228
+ return config.features.teamDecisions;
229
+ };
222
230
  const isAiToolsEnabled = async () => {
223
231
  const config = await fetchFeatureConfig();
224
232
  return config.features.aiTools;
@@ -423,6 +431,8 @@ const createServer = () => {
423
431
  const featureConfig = await fetchFeatureConfig();
424
432
  const tddEnabled = featureConfig.features.tddWorkflow;
425
433
  const learningLoopEnabled = featureConfig.features.learningLoop;
434
+ const teamDecisionsEnabled = featureConfig.features.teamDecisions;
435
+ const ragKnowledgeBaseEnabled = featureConfig.features.ragKnowledgeBase;
426
436
  const baseTools = [
427
437
  {
428
438
  name: "get_feature_config",
@@ -434,10 +444,34 @@ const createServer = () => {
434
444
  },
435
445
  {
436
446
  name: "list_stories",
437
- description: "List all stories on the Elixium board",
447
+ description: "List stories on the Elixium board. Returns compact summaries (id, title, lane, points, owners, epic, state). Use lane filter to reduce results. Defaults to 25 stories max.",
438
448
  inputSchema: {
439
449
  type: "object",
440
- properties: {},
450
+ properties: {
451
+ lane: {
452
+ type: "string",
453
+ description: "Filter by lane (case-insensitive). Omit to list all lanes.",
454
+ enum: ["Current", "Backlog", "Icebox", "Done"],
455
+ },
456
+ limit: {
457
+ type: "number",
458
+ description: "Max stories to return (default 25, max 100)",
459
+ },
460
+ },
461
+ },
462
+ },
463
+ {
464
+ name: "get_story",
465
+ description: "Get full details for a single story by UUID. Returns all fields including description, acceptance criteria, tasks, labels, metrics, and workflow state.",
466
+ inputSchema: {
467
+ type: "object",
468
+ properties: {
469
+ id: {
470
+ type: "string",
471
+ description: "The full UUID of the story (from list_stories output)",
472
+ },
473
+ },
474
+ required: ["id"],
441
475
  },
442
476
  },
443
477
  {
@@ -477,6 +511,14 @@ const createServer = () => {
477
511
  type: "string",
478
512
  description: "Email of the person requesting this story. Defaults to ELIXIUM_USER_EMAIL env var or API key owner.",
479
513
  },
514
+ epicId: {
515
+ type: "string",
516
+ description: "Epic ID to link this story to (optional). Use list_epics to find epic IDs.",
517
+ },
518
+ testPlan: {
519
+ type: "string",
520
+ description: "Markdown test plan describing test strategy (optional). Sets the test_plan field without changing workflow_stage.",
521
+ },
480
522
  },
481
523
  required: ["title"],
482
524
  },
@@ -595,6 +637,18 @@ const createServer = () => {
595
637
  type: "string",
596
638
  description: "Acceptance criteria in Given/When/Then format",
597
639
  },
640
+ sortOrder: {
641
+ type: "number",
642
+ description: "Sort order within the lane (lower = higher priority)",
643
+ },
644
+ epicId: {
645
+ type: "string",
646
+ description: "Epic ID to link this story to. Set to empty string to unlink.",
647
+ },
648
+ testPlan: {
649
+ type: "string",
650
+ description: "Markdown test plan. Updates test_plan field without changing workflow_stage.",
651
+ },
598
652
  },
599
653
  required: ["storyId"],
600
654
  },
@@ -658,6 +712,25 @@ const createServer = () => {
658
712
  required: ["epicId"],
659
713
  },
660
714
  },
715
+ {
716
+ name: "prioritize_epic",
717
+ description: "AI-powered dependency analysis and story prioritization for an epic. Analyzes stories, proposes optimal execution order based on dependencies, and optionally applies the reorder. Uses the same server-side endpoint as the UI.",
718
+ inputSchema: {
719
+ type: "object",
720
+ properties: {
721
+ epicId: {
722
+ type: "string",
723
+ description: "ID of the epic to prioritize",
724
+ },
725
+ mode: {
726
+ type: "string",
727
+ description: "analyze (preview proposed order) or apply (commit the reorder)",
728
+ enum: ["analyze", "apply"],
729
+ },
730
+ },
731
+ required: ["epicId"],
732
+ },
733
+ },
661
734
  ];
662
735
  const tddTools = tddEnabled ? [
663
736
  // TDD Workflow Tools
@@ -725,6 +798,20 @@ const createServer = () => {
725
798
  required: ["storyId"],
726
799
  },
727
800
  },
801
+ {
802
+ name: "get_test_plan",
803
+ description: "Retrieve and display the full TDD test plan for a story. Shows the plan text, workflow stage, approval status, and test file paths.",
804
+ inputSchema: {
805
+ type: "object",
806
+ properties: {
807
+ storyId: {
808
+ type: "string",
809
+ description: "ID of the story whose test plan to display",
810
+ },
811
+ },
812
+ required: ["storyId"],
813
+ },
814
+ },
728
815
  {
729
816
  name: "submit_for_review",
730
817
  description: "Submit implementation for human review. Only works if tests are approved. Sets state to finished.",
@@ -802,14 +889,92 @@ const createServer = () => {
802
889
  },
803
890
  },
804
891
  ] : [];
805
- return { tools: [...baseTools, ...tddTools, ...learningLoopTools] };
892
+ // Team Decisions tools (conditional on feature flag)
893
+ const teamDecisionsTools = teamDecisionsEnabled ? [
894
+ {
895
+ name: "record_decision",
896
+ description: "Record a team decision, meeting outcome, or architectural choice. These are shared across all team members' AI sessions.",
897
+ inputSchema: {
898
+ type: "object",
899
+ properties: {
900
+ title: { type: "string", description: "Short title for the decision" },
901
+ content: { type: "string", description: "Full description of the decision, context, and rationale" },
902
+ category: {
903
+ type: "string",
904
+ description: "Category (e.g., architecture, meeting-note, convention, tooling, decision, general)",
905
+ },
906
+ tags: {
907
+ type: "array",
908
+ items: { type: "string" },
909
+ description: "Tags for filtering (e.g., ['auth', 'self-hosted', 'phase-2'])",
910
+ },
911
+ storyId: { type: "string", description: "Optional story UUID to link this decision to" },
912
+ epicId: { type: "string", description: "Optional epic UUID to link this decision to" },
913
+ },
914
+ required: ["title", "content"],
915
+ },
916
+ },
917
+ {
918
+ name: "list_decisions",
919
+ description: "List recent team decisions. Filter by category, tag, or date. Use this to check what the team has decided recently.",
920
+ inputSchema: {
921
+ type: "object",
922
+ properties: {
923
+ category: { type: "string", description: "Filter by category" },
924
+ tag: { type: "string", description: "Filter by tag" },
925
+ since: { type: "string", description: "ISO date string — only return decisions after this date" },
926
+ limit: { type: "number", description: "Max results to return (default 20)" },
927
+ },
928
+ },
929
+ },
930
+ {
931
+ name: "search_decisions",
932
+ description: "Full-text search across all team decisions. Use this when looking for decisions related to a specific topic or keyword.",
933
+ inputSchema: {
934
+ type: "object",
935
+ properties: {
936
+ query: { type: "string", description: "Search query (keywords)" },
937
+ category: { type: "string", description: "Optional category filter" },
938
+ limit: { type: "number", description: "Max results to return (default 10)" },
939
+ },
940
+ required: ["query"],
941
+ },
942
+ },
943
+ ] : [];
944
+ // RAG Knowledge Base tools (conditional on feature flag)
945
+ const ragTools = ragKnowledgeBaseEnabled ? [
946
+ {
947
+ name: "search_knowledge",
948
+ description: "Semantic search across team knowledge base (documents, wikis, runbooks, etc). Returns relevant chunks with source attribution.",
949
+ inputSchema: {
950
+ type: "object",
951
+ properties: {
952
+ query: { type: "string", description: "Search query (natural language)" },
953
+ category: { type: "string", description: "Optional source type filter (e.g., wiki, runbook, adr, confluence)" },
954
+ limit: { type: "number", description: "Max results to return (default 10)" },
955
+ },
956
+ required: ["query"],
957
+ },
958
+ },
959
+ {
960
+ name: "list_knowledge_sources",
961
+ description: "List all knowledge sources indexed in the RAG knowledge base, grouped by source type.",
962
+ inputSchema: {
963
+ type: "object",
964
+ properties: {
965
+ source_type: { type: "string", description: "Optional filter by source type" },
966
+ },
967
+ },
968
+ },
969
+ ] : [];
970
+ return { tools: [...baseTools, ...tddTools, ...learningLoopTools, ...teamDecisionsTools, ...ragTools] };
806
971
  });
807
972
  // Handle Requests
808
973
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
809
974
  try {
810
975
  const toolName = request.params.name;
811
976
  // Check TDD workflow tools
812
- const tddWorkflowTools = ["start_story", "propose_test_plan", "submit_for_review", "review_pr"];
977
+ const tddWorkflowTools = ["start_story", "propose_test_plan", "get_test_plan", "submit_for_review", "review_pr"];
813
978
  if (tddWorkflowTools.includes(toolName)) {
814
979
  const enabled = await isTddWorkflowEnabled();
815
980
  if (!enabled) {
@@ -844,6 +1009,42 @@ const createServer = () => {
844
1009
  };
845
1010
  }
846
1011
  }
1012
+ // Check Team Decisions tools
1013
+ const teamDecisionsToolNames = ["record_decision", "list_decisions", "search_decisions"];
1014
+ if (teamDecisionsToolNames.includes(toolName)) {
1015
+ const enabled = await isTeamDecisionsEnabled();
1016
+ if (!enabled) {
1017
+ return {
1018
+ content: [{
1019
+ type: "text",
1020
+ text: JSON.stringify({
1021
+ error: "Team Decisions is disabled",
1022
+ message: "The Team Decisions feature is currently disabled for this board. This feature enables shared institutional memory across all team members' AI sessions.",
1023
+ help: "Team Decisions can be enabled in workspace settings or per-board settings."
1024
+ }, null, 2)
1025
+ }],
1026
+ isError: true,
1027
+ };
1028
+ }
1029
+ }
1030
+ // Check RAG Knowledge Base tools
1031
+ const ragToolNames = ["search_knowledge", "list_knowledge_sources"];
1032
+ if (ragToolNames.includes(toolName)) {
1033
+ const config = await fetchFeatureConfig();
1034
+ if (!config.features.ragKnowledgeBase) {
1035
+ return {
1036
+ content: [{
1037
+ type: "text",
1038
+ text: JSON.stringify({
1039
+ error: "RAG Knowledge Base is disabled",
1040
+ message: "The RAG Knowledge Base feature is currently disabled for this board. This feature enables semantic search across team knowledge sources.",
1041
+ help: "RAG Knowledge Base can be enabled in workspace settings or per-board settings."
1042
+ }, null, 2)
1043
+ }],
1044
+ isError: true,
1045
+ };
1046
+ }
1047
+ }
847
1048
  switch (toolName) {
848
1049
  case "get_feature_config": {
849
1050
  const config = await fetchFeatureConfig();
@@ -855,6 +1056,8 @@ ${config.features.balancedTeam ? "✅" : "❌"} **Balanced Team** - Design URLs,
855
1056
  ${config.features.learningLoop ? "✅" : "❌"} **Learning Loop** - Hypothesis tracking, risk profiles, confidence scores
856
1057
  ${config.features.tddWorkflow ? "✅" : "❌"} **TDD Workflow** - Test-driven development enforcement
857
1058
  ${config.features.aiTools ? "✅" : "❌"} **AI Tools** - AI-powered suggestions and analysis
1059
+ ${config.features.teamDecisions ? "✅" : "❌"} **Team Decisions** - Shared institutional memory across AI sessions
1060
+ ${config.features.ragKnowledgeBase ? "✅" : "❌"} **RAG Knowledge Base** - Semantic search across team knowledge sources
858
1061
 
859
1062
  ## Team Profile
860
1063
  ${config.teamProfile ? `
@@ -870,6 +1073,8 @@ ${config.teamProfile ? `
870
1073
  - Learning Loop: ${config.source.learningLoop}
871
1074
  - TDD Workflow: ${config.source.tddWorkflow}
872
1075
  - AI Tools: ${config.source.aiTools}
1076
+ - Team Decisions: ${config.source.teamDecisions}
1077
+ - RAG Knowledge Base: ${config.source.ragKnowledgeBase}
873
1078
 
874
1079
  ## Branching Strategy Defaults
875
1080
  ${config.branchingDefaults ? `- Trunk-Based: ${config.branchingDefaults.trunkBased ? "Yes" : "No"}
@@ -890,10 +1095,49 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
890
1095
  };
891
1096
  }
892
1097
  case "list_stories": {
893
- const stories = await fetchStories();
1098
+ const args = request.params.arguments;
1099
+ let stories = await fetchStories();
1100
+ // Filter by lane if specified
1101
+ if (args?.lane) {
1102
+ const targetLane = args.lane.toLowerCase();
1103
+ stories = stories.filter((s) => (s.lane || "").toLowerCase() === targetLane);
1104
+ }
1105
+ // Apply limit (default 25, max 100)
1106
+ const limit = Math.min(Math.max(args?.limit || 25, 1), 100);
1107
+ const total = stories.length;
1108
+ stories = stories.slice(0, limit);
1109
+ // Return compact summaries to fit LLM context
1110
+ const compact = stories.map((s) => ({
1111
+ id: s.id,
1112
+ title: s.title,
1113
+ lane: s.lane,
1114
+ state: s.state,
1115
+ points: s.points,
1116
+ owners: s.owners,
1117
+ labels: s.labels,
1118
+ epicId: s.epicId,
1119
+ storyType: s.storyType || s.story_type,
1120
+ createdAt: s.createdAt || s.created_at,
1121
+ }));
1122
+ const result = {
1123
+ total,
1124
+ showing: compact.length,
1125
+ ...(args?.lane ? { lane: args.lane } : {}),
1126
+ stories: compact,
1127
+ };
1128
+ return {
1129
+ content: [
1130
+ { type: "text", text: JSON.stringify(result, null, 2) },
1131
+ ],
1132
+ };
1133
+ }
1134
+ case "get_story": {
1135
+ const args = request.params.arguments;
1136
+ assertUUID(args.id, "story id");
1137
+ const response = await client.get(`/stories/${args.id}`);
894
1138
  return {
895
1139
  content: [
896
- { type: "text", text: JSON.stringify(stories, null, 2) },
1140
+ { type: "text", text: JSON.stringify(response.data, null, 2) },
897
1141
  ],
898
1142
  };
899
1143
  }
@@ -1088,6 +1332,32 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
1088
1332
  fetchBoardSettings(),
1089
1333
  ]);
1090
1334
  const story = storyResponse.data;
1335
+ // Auto-search team decisions if feature flag is enabled (graceful degradation)
1336
+ let relevantDecisions = [];
1337
+ if (teamConfig.features.teamDecisions) {
1338
+ try {
1339
+ const decisionsResponse = await client.get("/decisions/search", {
1340
+ params: { q: story.title, limit: 5 },
1341
+ });
1342
+ relevantDecisions = decisionsResponse.data?.decisions || [];
1343
+ }
1344
+ catch {
1345
+ // Silently omit — decisions search failure should not break the brief
1346
+ }
1347
+ }
1348
+ // Auto-search knowledge base if feature flag is enabled (graceful degradation)
1349
+ let relatedKnowledge = [];
1350
+ if (teamConfig.features.ragKnowledgeBase) {
1351
+ try {
1352
+ const knowledgeResponse = await client.get("/knowledge/search", {
1353
+ params: { q: story.title, limit: 5 },
1354
+ });
1355
+ relatedKnowledge = knowledgeResponse.data?.results || [];
1356
+ }
1357
+ catch {
1358
+ // Silently omit — knowledge search failure should not break the brief
1359
+ }
1360
+ }
1091
1361
  // Validation & Guardrails
1092
1362
  const storyLane = typeof story.lane === "string" ? story.lane.trim().toLowerCase() : "";
1093
1363
  const statusWarning = storyLane !== "current"
@@ -1114,6 +1384,24 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
1114
1384
  r.concerns.map((c) => `- **${c.severity}:** ${c.description}`).join("\n") + "\n";
1115
1385
  }
1116
1386
  }
1387
+ // Format relevant team decisions section (only if matches exist)
1388
+ let decisionsSection = "";
1389
+ if (relevantDecisions.length > 0) {
1390
+ const formatted = relevantDecisions.map((d) => {
1391
+ const date = d.created_at ? new Date(d.created_at).toISOString().split("T")[0] : "unknown";
1392
+ return `- [${date}] **${d.title}** (${d.category || "general"})\n ${d.content}`;
1393
+ }).join("\n");
1394
+ decisionsSection = `\n## Relevant Team Decisions\n${formatted}\n`;
1395
+ }
1396
+ // Format related knowledge section (only if matches exist)
1397
+ let knowledgeSection = "";
1398
+ if (relatedKnowledge.length > 0) {
1399
+ const formatted = relatedKnowledge.map((k) => {
1400
+ const relevance = k.relevance ? ` (${(k.relevance * 100).toFixed(0)}% match)` : "";
1401
+ return `- **${k.source_title}** [${k.source_type}]${relevance}\n ${k.content}`;
1402
+ }).join("\n");
1403
+ knowledgeSection = `\n## Related Knowledge\n${formatted}\n`;
1404
+ }
1117
1405
  const formattedBrief = `
1118
1406
  # Implementation Brief: ${story.title}
1119
1407
 
@@ -1126,7 +1414,7 @@ ${acceptanceCriteria}
1126
1414
  ## Assumptions
1127
1415
  Here are the assumptions I think we’re testing:
1128
1416
  ${assumptions}
1129
-
1417
+ ${decisionsSection}${knowledgeSection}
1130
1418
  ${formatTeamContext(teamConfig)}
1131
1419
  ${formatDorDodSection(boardSettings, story.dorChecklist, story.dodChecklist)}
1132
1420
  ## Proposal
@@ -1234,6 +1522,56 @@ ${dorWarnings}`;
1234
1522
  content: [{ type: "text", text: formattedResult.trim() }],
1235
1523
  };
1236
1524
  }
1525
+ case "get_test_plan": {
1526
+ const args = request.params.arguments;
1527
+ const { storyId } = args;
1528
+ if (!storyId) {
1529
+ throw new Error("storyId is required");
1530
+ }
1531
+ assertUUID(storyId, "storyId");
1532
+ const response = await client.get(`/stories/${storyId}`);
1533
+ const story = response.data;
1534
+ const testPlan = story.test_plan || story.testPlan;
1535
+ const workflowStage = story.workflow_stage || story.workflowStage;
1536
+ const testFilePaths = story.test_file_paths || story.testFilePaths || [];
1537
+ const feedback = story.test_plan_feedback || story.testPlanFeedback;
1538
+ const title = story.title || "Untitled Story";
1539
+ if (!testPlan) {
1540
+ return {
1541
+ content: [
1542
+ {
1543
+ type: "text",
1544
+ text: `# No Test Plan\n\n**Story:** ${title}\n**Story ID:** ${storyId}\n**Workflow Stage:** ${workflowStage || "none"}\n\nThis story does not have a test plan yet. Use \`propose_test_plan\` to submit one.`,
1545
+ },
1546
+ ],
1547
+ };
1548
+ }
1549
+ const stageLabel = workflowStage === "tests_approved"
1550
+ ? "Approved"
1551
+ : workflowStage === "tests_proposed"
1552
+ ? "Proposed — Awaiting Approval"
1553
+ : workflowStage === "tests_revision_requested"
1554
+ ? "Revision Requested"
1555
+ : workflowStage || "unknown";
1556
+ const feedbackSection = feedback
1557
+ ? `\n## Revision Feedback\n> ${feedback.split("\n").join("\n> ")}\n`
1558
+ : "";
1559
+ const formattedResult = `# Test Plan: ${title}
1560
+
1561
+ **Story ID:** ${storyId}
1562
+ **Workflow Stage:** ${workflowStage}
1563
+ **Status:** ${stageLabel}
1564
+
1565
+ ## Test Plan
1566
+ ${testPlan}
1567
+ ${feedbackSection}
1568
+ ## Test Files
1569
+ ${testFilePaths.length > 0 ? testFilePaths.map((p) => `- \`${p}\``).join("\n") : "No test files specified"}
1570
+ `;
1571
+ return {
1572
+ content: [{ type: "text", text: formattedResult.trim() }],
1573
+ };
1574
+ }
1237
1575
  case "propose_test_plan": {
1238
1576
  const args = request.params.arguments;
1239
1577
  const { storyId, testPlan, testFilePaths } = args;
@@ -1540,6 +1878,49 @@ ${categoryBreakdown}
1540
1878
  ${unestimated.length === 0
1541
1879
  ? "All stories have been estimated!"
1542
1880
  : unestimated.map((s) => `- ${s.title} (${s.id})`).join("\n")}
1881
+ `;
1882
+ return {
1883
+ content: [{ type: "text", text: formattedResult.trim() }],
1884
+ };
1885
+ }
1886
+ case "prioritize_epic": {
1887
+ const args = request.params.arguments;
1888
+ const { epicId, mode = "analyze" } = args;
1889
+ if (!epicId)
1890
+ throw new Error("epicId is required");
1891
+ assertUUID(epicId, "epicId");
1892
+ const res = await client.post(`/prioritize-epic/${epicId}`, { mode });
1893
+ if (mode === "apply") {
1894
+ return {
1895
+ content: [{
1896
+ type: "text",
1897
+ text: `Prioritization applied to epic. ${res.data.updatedCount} stories reordered.`,
1898
+ }],
1899
+ };
1900
+ }
1901
+ // Analyze mode — format the preview
1902
+ const data = res.data;
1903
+ const orderList = (data.proposedOrder || [])
1904
+ .map((item, i) => `${i + 1}. **${item.storyTitle}** — ${item.reason}`)
1905
+ .join("\n");
1906
+ const depList = (data.dependencies || [])
1907
+ .map((dep) => `- ${dep.storyTitle} is blocked by ${dep.blockedByStoryTitle}: ${dep.reason}`)
1908
+ .join("\n");
1909
+ const circularWarning = data.circularDependencies?.length > 0
1910
+ ? `\n## Circular Dependencies Detected\n${data.circularDependencies.map((cd) => `- ${cd.description}`).join("\n")}\n`
1911
+ : "";
1912
+ const formattedResult = `
1913
+ # AI Prioritization Preview
1914
+
1915
+ ${data.summary}
1916
+ ${circularWarning}
1917
+ ## Proposed Order (do first → do last)
1918
+ ${orderList}
1919
+
1920
+ ## Dependencies
1921
+ ${depList || "No dependencies detected."}
1922
+
1923
+ > To apply this order, call \`prioritize_epic\` with mode: "apply"
1543
1924
  `;
1544
1925
  return {
1545
1926
  content: [{ type: "text", text: formattedResult.trim() }],
@@ -1580,6 +1961,135 @@ ${unestimated.length === 0
1580
1961
  content: [{ type: "text", text: sections.join("\n") }],
1581
1962
  };
1582
1963
  }
1964
+ // Team Decisions Handlers
1965
+ case "record_decision": {
1966
+ const args = request.params.arguments;
1967
+ const { title, content, category, tags, storyId, epicId } = args;
1968
+ if (!title || !content) {
1969
+ throw new Error("title and content are required");
1970
+ }
1971
+ if (storyId)
1972
+ assertUUID(storyId, "storyId");
1973
+ if (epicId)
1974
+ assertUUID(epicId, "epicId");
1975
+ const response = await client.post("/decisions", {
1976
+ title,
1977
+ content,
1978
+ category,
1979
+ tags,
1980
+ storyId,
1981
+ epicId,
1982
+ });
1983
+ const decision = response.data;
1984
+ const confirmText = `✅ Decision recorded: "${decision.title || title}"
1985
+ - **Category:** ${decision.category || category || "general"}
1986
+ - **Tags:** ${(decision.tags || tags || []).join(", ") || "none"}
1987
+ - **ID:** ${decision.id}`;
1988
+ return {
1989
+ content: [{ type: "text", text: confirmText }],
1990
+ };
1991
+ }
1992
+ case "list_decisions": {
1993
+ const args = request.params.arguments;
1994
+ const { category, tag, since, limit } = args || {};
1995
+ const params = {};
1996
+ if (category)
1997
+ params.category = category;
1998
+ if (tag)
1999
+ params.tag = tag;
2000
+ if (since)
2001
+ params.since = since;
2002
+ if (limit)
2003
+ params.limit = String(limit);
2004
+ const response = await client.get("/decisions", { params });
2005
+ const { decisions, total } = response.data;
2006
+ if (!decisions || decisions.length === 0) {
2007
+ return {
2008
+ content: [{ type: "text", text: "No team decisions found matching the filters." }],
2009
+ };
2010
+ }
2011
+ const formatted = decisions.map((d) => {
2012
+ const date = d.created_at ? new Date(d.created_at).toISOString().split("T")[0] : "unknown";
2013
+ const tagStr = d.tags?.length ? ` [${d.tags.join(", ")}]` : "";
2014
+ return `### [${date}] ${d.title} (${d.category || "general"})${tagStr}\n${d.content}`;
2015
+ }).join("\n\n---\n\n");
2016
+ const header = `# Team Decisions (${decisions.length} of ${total})\n\n`;
2017
+ return {
2018
+ content: [{ type: "text", text: header + formatted }],
2019
+ };
2020
+ }
2021
+ case "search_decisions": {
2022
+ const args = request.params.arguments;
2023
+ const { query, category, limit } = args || {};
2024
+ if (!query) {
2025
+ throw new Error("query is required");
2026
+ }
2027
+ const params = { q: query };
2028
+ if (category)
2029
+ params.category = category;
2030
+ if (limit)
2031
+ params.limit = String(limit);
2032
+ const response = await client.get("/decisions/search", { params });
2033
+ const { decisions } = response.data;
2034
+ if (!decisions || decisions.length === 0) {
2035
+ return {
2036
+ content: [{ type: "text", text: `No team decisions found matching "${query}".` }],
2037
+ };
2038
+ }
2039
+ const formatted = decisions.map((d) => {
2040
+ const date = d.created_at ? new Date(d.created_at).toISOString().split("T")[0] : "unknown";
2041
+ const tagStr = d.tags?.length ? ` [${d.tags.join(", ")}]` : "";
2042
+ return `### [${date}] ${d.title} (${d.category || "general"})${tagStr}\n${d.content}`;
2043
+ }).join("\n\n---\n\n");
2044
+ const header = `# Search Results for "${query}" (${decisions.length} matches)\n\n`;
2045
+ return {
2046
+ content: [{ type: "text", text: header + formatted }],
2047
+ };
2048
+ }
2049
+ case "search_knowledge": {
2050
+ const args = request.params.arguments;
2051
+ const { query, category, limit } = args || {};
2052
+ if (!query) {
2053
+ throw new Error("query is required");
2054
+ }
2055
+ const params = { q: query };
2056
+ if (category)
2057
+ params.category = category;
2058
+ if (limit)
2059
+ params.limit = String(limit);
2060
+ const response = await client.get("/knowledge/search", { params });
2061
+ const { results } = response.data;
2062
+ if (!results || results.length === 0) {
2063
+ return {
2064
+ content: [{ type: "text", text: `No knowledge results found matching "${query}".` }],
2065
+ };
2066
+ }
2067
+ const formatted = results.map((r) => {
2068
+ const relevance = r.relevance ? `(${(r.relevance * 100).toFixed(1)}% relevant)` : "";
2069
+ return `### ${r.source_title} [${r.source_type}] ${relevance}\n${r.content}`;
2070
+ }).join("\n\n---\n\n");
2071
+ const header = `# Knowledge Search Results for "${query}" (${results.length} matches)\n\n`;
2072
+ return {
2073
+ content: [{ type: "text", text: header + formatted }],
2074
+ };
2075
+ }
2076
+ case "list_knowledge_sources": {
2077
+ const args = request.params.arguments;
2078
+ const params = {};
2079
+ if (args?.source_type)
2080
+ params.source_type = args.source_type;
2081
+ const response = await client.get("/knowledge/sources", { params });
2082
+ const { sources } = response.data;
2083
+ if (!sources || sources.length === 0) {
2084
+ return {
2085
+ content: [{ type: "text", text: "No knowledge sources are currently indexed." }],
2086
+ };
2087
+ }
2088
+ const formatted = sources.map((s) => `- **${s.source_title}** (${s.source_type}) — ${s.chunk_count || 0} chunks`).join("\n");
2089
+ return {
2090
+ content: [{ type: "text", text: `# Knowledge Sources\n\n${formatted}` }],
2091
+ };
2092
+ }
1583
2093
  default:
1584
2094
  throw new Error("Unknown tool");
1585
2095
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@elixium.ai/mcp-server",
3
- "version": "0.3.2",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
5
  "description": "MCP Server for Elixium.ai",
6
- "mcpName": "io.github.elixium-ai/mcp-server",
6
+ "mcpName": "io.github.IndirectTek/mcp-server",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },