@elixium.ai/mcp-server 0.2.1 → 0.2.2

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 +65 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10,6 +10,14 @@ const API_URL = process.env.ELIXIUM_API_URL || "https://elixium.ai/api";
10
10
  const BOARD_SLUG = process.env.ELIXIUM_BOARD_SLUG;
11
11
  const LANE_STYLE_ENV = process.env.ELIXIUM_LANE_STYLE;
12
12
  const USER_EMAIL = process.env.ELIXIUM_USER_EMAIL; // Optional: Override requester email for stories
13
+ // UUID v4 format validation — prevents 500s from partial/truncated IDs
14
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15
+ function assertUUID(value, label) {
16
+ if (!UUID_RE.test(value)) {
17
+ throw new Error(`Invalid ${label}: "${value}" is not a valid UUID. ` +
18
+ `Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (full UUID from list_stories/list_epics).`);
19
+ }
20
+ }
13
21
  const CLI_ARGS = process.argv.slice(2);
14
22
  const hasArg = (flag) => CLI_ARGS.includes(flag);
15
23
  const getArgValue = (flag) => {
@@ -604,6 +612,20 @@ const createServer = () => {
604
612
  required: ["storyId", "testPlan"],
605
613
  },
606
614
  },
615
+ {
616
+ name: "approve_tests",
617
+ description: "Approve a proposed test plan so implementation can proceed. Transitions workflow from tests_proposed to tests_approved. This is the human approval gate in the TDD workflow.",
618
+ inputSchema: {
619
+ type: "object",
620
+ properties: {
621
+ storyId: {
622
+ type: "string",
623
+ description: "ID of the story whose test plan to approve",
624
+ },
625
+ },
626
+ required: ["storyId"],
627
+ },
628
+ },
607
629
  {
608
630
  name: "submit_for_review",
609
631
  description: "Submit implementation for human review. Only works if tests are approved. Sets state to finished.",
@@ -836,6 +858,7 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
836
858
  case "record_learning": {
837
859
  const args = request.params.arguments;
838
860
  const { storyId, outcome_summary } = args;
861
+ assertUUID(storyId, "storyId");
839
862
  const response = await client.patch(`/stories/${storyId}`, {
840
863
  outcome_summary,
841
864
  });
@@ -851,6 +874,7 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
851
874
  if (!storyId) {
852
875
  throw new Error("storyId is required");
853
876
  }
877
+ assertUUID(storyId, "storyId");
854
878
  // Guardrail: Block AI from setting accepted/rejected states (human-in-the-loop)
855
879
  const blockedStates = ["accepted", "rejected"];
856
880
  if (state && blockedStates.includes(state.toLowerCase())) {
@@ -904,6 +928,7 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
904
928
  if (!epicId) {
905
929
  throw new Error("epicId is required");
906
930
  }
931
+ assertUUID(epicId, "epicId");
907
932
  const payload = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
908
933
  const response = await client.patch(`/epics/${epicId}`, payload);
909
934
  return {
@@ -915,6 +940,7 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
915
940
  case "prepare_implementation": {
916
941
  const args = request.params.arguments;
917
942
  const { storyId } = args;
943
+ assertUUID(storyId, "storyId");
918
944
  const storyResponse = await client.get(`/stories/${storyId}`);
919
945
  const story = storyResponse.data;
920
946
  // Validation & Guardrails
@@ -959,6 +985,7 @@ Here’s the smallest change that will validate it:
959
985
  if (!storyId) {
960
986
  throw new Error("storyId is required");
961
987
  }
988
+ assertUUID(storyId, "storyId");
962
989
  const response = await client.post(`/stories/${storyId}/start`, {
963
990
  branchPrefix: branchPrefix || "feat",
964
991
  trunkBased,
@@ -1041,6 +1068,7 @@ ${result.workflow_reminder}
1041
1068
  if (!storyId) {
1042
1069
  throw new Error("storyId is required");
1043
1070
  }
1071
+ assertUUID(storyId, "storyId");
1044
1072
  if (!testPlan) {
1045
1073
  throw new Error("testPlan is required");
1046
1074
  }
@@ -1062,6 +1090,30 @@ ${(result.test_file_paths || []).map((p) => `- \`${p}\``).join("\n") || "No test
1062
1090
  ${result.message}
1063
1091
 
1064
1092
  > 🛑 **BLOCKED:** Implementation cannot proceed until a human approves this test plan.
1093
+ `;
1094
+ return {
1095
+ content: [{ type: "text", text: formattedResult.trim() }],
1096
+ };
1097
+ }
1098
+ case "approve_tests": {
1099
+ const args = request.params.arguments;
1100
+ const { storyId } = args;
1101
+ if (!storyId) {
1102
+ throw new Error("storyId is required");
1103
+ }
1104
+ assertUUID(storyId, "storyId");
1105
+ const response = await client.post(`/stories/${storyId}/approve-tests`);
1106
+ const result = response.data;
1107
+ const formattedResult = `
1108
+ # Test Plan Approved
1109
+
1110
+ **Story ID:** ${storyId}
1111
+ **Workflow Stage:** ${result.workflow_stage}
1112
+
1113
+ ## Status
1114
+ ${result.message}
1115
+
1116
+ > ✅ **Approved:** Agent may now proceed with implementation.
1065
1117
  `;
1066
1118
  return {
1067
1119
  content: [{ type: "text", text: formattedResult.trim() }],
@@ -1073,6 +1125,7 @@ ${result.message}
1073
1125
  if (!storyId) {
1074
1126
  throw new Error("storyId is required");
1075
1127
  }
1128
+ assertUUID(storyId, "storyId");
1076
1129
  const response = await client.post(`/stories/${storyId}/submit-review`, {
1077
1130
  commitHash,
1078
1131
  testResults,
@@ -1131,6 +1184,7 @@ ${result.next_step}
1131
1184
  let { provider } = args;
1132
1185
  if (!storyId)
1133
1186
  throw new Error("storyId is required");
1187
+ assertUUID(storyId, "storyId");
1134
1188
  // Default provider from infrastructure profile if not explicitly provided
1135
1189
  if (!provider) {
1136
1190
  const costConfig = await fetchFeatureConfig();
@@ -1200,6 +1254,7 @@ ${(estimate.assumptions || []).map((a) => `- ${a}`).join("\n")}
1200
1254
  const { epicId } = args;
1201
1255
  if (!epicId)
1202
1256
  throw new Error("epicId is required");
1257
+ assertUUID(epicId, "epicId");
1203
1258
  // Fetch all stories and filter by epicId
1204
1259
  const allStories = await fetchStories();
1205
1260
  const epicStories = allStories.filter((s) => s.epicId === epicId);
@@ -1302,11 +1357,20 @@ ${unestimated.length === 0
1302
1357
  if (error.response) {
1303
1358
  console.error("Response data:", error.response.data);
1304
1359
  }
1360
+ let errorText = `Error: ${error.message}`;
1361
+ if (error.response?.status) {
1362
+ errorText += ` (HTTP ${error.response.status})`;
1363
+ }
1364
+ if (error.response?.data) {
1365
+ const data = error.response.data;
1366
+ const detail = typeof data === "string" ? data : JSON.stringify(data);
1367
+ errorText += `\nDetails: ${detail}`;
1368
+ }
1305
1369
  return {
1306
1370
  content: [
1307
1371
  {
1308
1372
  type: "text",
1309
- text: `Error: ${error.message}`,
1373
+ text: errorText,
1310
1374
  },
1311
1375
  ],
1312
1376
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elixium.ai/mcp-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "MCP Server for Elixium.ai",
6
6
  "publishConfig": {