@elixium.ai/mcp-server 0.5.0 → 0.6.0

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
@@ -7,6 +7,7 @@ This server implements the [Model Context Protocol (MCP)](https://modelcontextpr
7
7
  - **Create Story**: Add new stories directly from your editor.
8
8
  - **Update Story**: Move stories between lanes and update fields.
9
9
  - **Iteration Context**: Provide the AI with the full context of your Current and Backlog lanes for better planning.
10
+ - **Board Context** (new in 0.6.0): One-call grounding tool (`get_board_context`) that returns a time-balanced workspace snapshot — in-flight work, queued/icebox/recently-completed stories and epics, recent decisions, objectives, learnings, and workspace configuration — so agents ground themselves before drafting or updating content. Requires a backend release that includes `GET /api/board-context` (SaaS: live since 2026-05-17; self-hosted: update to a matching release). Falls back gracefully with a usable message if the backend is older.
10
11
 
11
12
  ## Quick Start
12
13
 
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Error-fallback feature config used by the mcp-server when the backend
3
+ * `/config/features` endpoint is unreachable. Extracted from `src/index.ts`
4
+ * so it can be imported into tests without triggering the entrypoint's
5
+ * top-level await (`startStdioServer` / `startSseServer`).
6
+ *
7
+ * `useDeliveredState` defaults to `false` (3-state simple) per story 16f8ede4
8
+ * — matches the backend smart default, so a transient fetch failure won't
9
+ * silently re-introduce 4-state behavior on the agent side.
10
+ */
11
+ export const MCP_FEATURE_CONFIG_ERROR_FALLBACK = {
12
+ features: {
13
+ balancedTeam: false,
14
+ learningLoop: false,
15
+ tddWorkflow: true,
16
+ aiTools: true,
17
+ teamDecisions: false,
18
+ ragKnowledgeBase: false,
19
+ useDeliveredState: false,
20
+ },
21
+ source: {
22
+ balancedTeam: "error-fallback",
23
+ learningLoop: "error-fallback",
24
+ tddWorkflow: "error-fallback",
25
+ aiTools: "error-fallback",
26
+ teamDecisions: "error-fallback",
27
+ ragKnowledgeBase: "error-fallback",
28
+ useDeliveredState: "error-fallback",
29
+ },
30
+ };
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import * as http from "node:http";
8
8
  import { CREATE_STORY_INPUT_SCHEMA, UPDATE_STORY_INPUT_SCHEMA, GET_STAKEHOLDERS_INPUT_SCHEMA, PROPOSE_FIELD_DRAFT_INPUT_SCHEMA, ENDORSE_PROPOSAL_INPUT_SCHEMA, REJECT_PROPOSAL_INPUT_SCHEMA, DRAFT_HYPOTHESIS_INPUT_SCHEMA, GET_TRUST_LEAKAGE_INPUT_SCHEMA, } from "./toolSchemas.js";
9
9
  import { deriveStakeholders } from "./stakeholders.js";
10
10
  import { formatWorkflowModelLine } from "./constants/workflowModelLabels.js";
11
+ import { MCP_FEATURE_CONFIG_ERROR_FALLBACK } from "./constants/featureConfigFallback.js";
11
12
  const API_KEY = process.env.ELIXIUM_API_KEY;
12
13
  const API_URL = process.env.ELIXIUM_API_URL || "https://elixium.ai/api";
13
14
  const BOARD_SLUG = process.env.ELIXIUM_BOARD_SLUG;
@@ -314,27 +315,8 @@ const fetchFeatureConfig = async () => {
314
315
  return response.data;
315
316
  }
316
317
  catch (error) {
317
- console.error("Failed to fetch feature config, defaulting to all enabled:", error);
318
- return {
319
- features: {
320
- balancedTeam: true,
321
- learningLoop: true,
322
- tddWorkflow: true,
323
- aiTools: true,
324
- teamDecisions: false,
325
- ragKnowledgeBase: false,
326
- useDeliveredState: true,
327
- },
328
- source: {
329
- balancedTeam: "error-fallback",
330
- learningLoop: "error-fallback",
331
- tddWorkflow: "error-fallback",
332
- aiTools: "error-fallback",
333
- teamDecisions: "error-fallback",
334
- ragKnowledgeBase: "error-fallback",
335
- useDeliveredState: "error-fallback",
336
- },
337
- };
318
+ console.error("Failed to fetch feature config, using error fallback:", error);
319
+ return MCP_FEATURE_CONFIG_ERROR_FALLBACK;
338
320
  }
339
321
  };
340
322
  // Helper functions to check individual features
@@ -648,7 +630,7 @@ const createServer = () => {
648
630
  },
649
631
  {
650
632
  name: "create_story",
651
- description: "Create a new story on the Elixium board. Accepts storyType (feature/bug/chore/platform) at create time.",
633
+ description: "Create a story on the active board. New stories enter the Backlog lane and are attributed to the user bound to your API key. Stories you create without current board context risk duplicating in-flight work or contradicting recent decisions. Ensure you are grounded before writing — if you haven't grounded this session, call `get_board_context` (optionally with `intent` set to the user's request for similar-story matching). This is the normal path when a user asks you to draft a story; it is not a bypass of human review, which happens at downstream workflow gates. Accepts storyType (feature/bug/chore/platform) at create time. Use `propose_field_draft` for asynchronous suggestions on fields of an existing story.",
652
634
  inputSchema: CREATE_STORY_INPUT_SCHEMA,
653
635
  },
654
636
  {
@@ -659,6 +641,19 @@ const createServer = () => {
659
641
  properties: {},
660
642
  },
661
643
  },
644
+ {
645
+ name: "get_board_context",
646
+ description: "Get a snapshot of the workspace's board state for grounding before drafting or updating content. Returns time-balanced sections: inFlight (Current lane + in_progress epics), queued (Backlog + next/soon epics), icebox (Icebox + someday epics), recentlyCompleted (Done + archived epics), decisions, objectives, learnings, plus configuration (feature flags, workflow stages). Pass `intent` (free-form text of what you're about to do; the user's verbatim request is fine) to additionally receive `similar` — existing stories ranked by relevance. Stories you create or update without current board context risk duplicating in-flight work or contradicting recent decisions; ensure you are grounded before writing.",
647
+ inputSchema: {
648
+ type: "object",
649
+ properties: {
650
+ intent: {
651
+ type: "string",
652
+ description: "Optional free-form text describing what you're about to do (e.g., the user's verbatim request, or a short topic phrase). When provided, the response additionally includes a `similar` section ranked by relevance.",
653
+ },
654
+ },
655
+ },
656
+ },
662
657
  {
663
658
  name: "list_objectives",
664
659
  description: "List objectives for the current workspace",
@@ -677,7 +672,7 @@ const createServer = () => {
677
672
  },
678
673
  {
679
674
  name: "create_epic",
680
- description: "Create a new epic for the current board",
675
+ description: "Create an epic for the active board. Epics group related stories around an outcome hypothesis. Before creating, ensure you are grounded — call `get_board_context` to surface existing epics, workspace objectives, and recent decisions; duplicate or overlapping epics are easy to introduce without that context. Epics are attributed to the user bound to your API key. Use `update_epic` to revise an existing epic rather than duplicating.",
681
676
  inputSchema: {
682
677
  type: "object",
683
678
  properties: {
@@ -702,7 +697,7 @@ const createServer = () => {
702
697
  },
703
698
  {
704
699
  name: "update_epic",
705
- description: "Update an epic (title, description, stage, or outcome fields)",
700
+ description: "Update an epic's title, description, stage, or outcome fields (hypothesis, success metrics, outcome status, target date). Before updating, ensure you are grounded — call `get_board_context` for current board state and recent decisions that may affect this epic. Updates are attributed to the user bound to your API key.",
706
701
  inputSchema: {
707
702
  type: "object",
708
703
  properties: {
@@ -728,7 +723,7 @@ const createServer = () => {
728
723
  },
729
724
  {
730
725
  name: "update_story",
731
- description: "Update fields on an existing story. Accepts Learning Loop fields (hypothesis, confidence_score, hidden_unknowns, risk_profile) and storyType in addition to the basics.",
726
+ description: "Update fields on an existing story. Accepts Learning Loop fields (hypothesis, confidence_score, hidden_unknowns, risk_profile) and storyType in addition to the basics. Before updating, call `get_story` to inspect current state, and `get_board_context` if your update relates to a topic that may be governed by a recent team decision. Updates are attributed to the user bound to your API key.",
732
727
  inputSchema: UPDATE_STORY_INPUT_SCHEMA,
733
728
  },
734
729
  {
@@ -738,7 +733,7 @@ const createServer = () => {
738
733
  },
739
734
  {
740
735
  name: "propose_field_draft",
741
- description: "Submit an AI-drafted value for a Learning Loop field as a pending proposal. The draft does NOT change the story until a human endorses via endorse_proposal. Caller should disclose `provider` (LLM model) and `agent` (client+version) for trust attribution.",
736
+ description: "Submit an AI-drafted value for a Learning Loop field as a pending proposal. The draft does NOT change the story until a human endorses via endorse_proposal. Caller should disclose `provider` (LLM model) and `agent` (client+version) for trust attribution. Stories whose fields you propose without current board context risk contradicting recent decisions in this area. Ensure you are grounded before drafting — call `get_board_context` if you haven't grounded this session.",
742
737
  inputSchema: PROPOSE_FIELD_DRAFT_INPUT_SCHEMA,
743
738
  },
744
739
  {
@@ -753,7 +748,7 @@ const createServer = () => {
753
748
  },
754
749
  {
755
750
  name: "draft_hypothesis",
756
- description: "Reference consumer of the proposal primitive: backend calls AI_PROVIDER to generate a hypothesis draft for the given story, then submits it as a pending proposal. Pattern for §4 outcome-from-evidence and §6 v2 missing-field drafter to follow.",
751
+ description: "Generate an AI-drafted hypothesis for the given story and submit it as a pending proposal. The backend calls the configured AI_PROVIDER, drafts the hypothesis text, and creates a proposal that a human must endorse via `endorse_proposal` before the story's hypothesis field is updated. The draft does not change the story until endorsed.",
757
752
  inputSchema: DRAFT_HYPOTHESIS_INPUT_SCHEMA,
758
753
  },
759
754
  {
@@ -879,7 +874,7 @@ const createServer = () => {
879
874
  },
880
875
  {
881
876
  name: "propose_test_plan",
882
- description: "Submit a test plan for human review. Sets workflow_stage to tests_proposed. Implementation is BLOCKED until human approves.",
877
+ description: "Submit a test plan for human review. Sets workflow_stage to tests_proposed. Implementation is BLOCKED until human approves. Before proposing, call `get_story` to inspect the story's hypothesis, hidden_unknowns, and risk_profile (the test plan should ground in those fields). Use `draft_test_plan` first for a scaffold seeded from the story's premortem assumptions.",
883
878
  inputSchema: {
884
879
  type: "object",
885
880
  properties: {
@@ -987,7 +982,7 @@ const createServer = () => {
987
982
  const learningLoopTools = learningLoopEnabled ? [
988
983
  {
989
984
  name: "create_hypothesis",
990
- description: "Create a new assumption/hypothesis in the Icebox for validation",
985
+ description: "Create a new assumption/hypothesis in the Icebox for validation. Before creating, ensure you are grounded — call `get_board_context` to check for existing related hypotheses and recent decisions in this area. The hypothesis is attributed to the user bound to your API key.",
991
986
  inputSchema: {
992
987
  type: "object",
993
988
  properties: {
@@ -1005,7 +1000,7 @@ const createServer = () => {
1005
1000
  },
1006
1001
  {
1007
1002
  name: "record_learning",
1008
- description: "Record a learning outcome for a completed story",
1003
+ description: "Record a learning outcome for a completed story (sets the story's outcome_summary field). The learning is visible in the workspace's recent-learnings surface. Before recording, call `get_story` to inspect the story's current state, and `get_board_context` to surface recently-recorded learnings on related stories that may already capture the same insight.",
1009
1004
  inputSchema: {
1010
1005
  type: "object",
1011
1006
  properties: {
@@ -1023,7 +1018,7 @@ const createServer = () => {
1023
1018
  const teamDecisionsTools = teamDecisionsEnabled ? [
1024
1019
  {
1025
1020
  name: "record_decision",
1026
- description: "Record a team decision, meeting outcome, or architectural choice. These are shared across all team members' AI sessions.",
1021
+ description: "Record a team decision, meeting outcome, or architectural choice. These are shared across all team members' AI sessions. Before recording, ensure you are grounded — call `get_board_context` to check whether a recent decision already covers this area; duplicate or contradictory decisions are exactly what this tool exists to prevent.",
1027
1022
  inputSchema: {
1028
1023
  type: "object",
1029
1024
  properties: {
@@ -1366,6 +1361,58 @@ ${config.infrastructureProfile?.provider ? `- Provider: ${config.infrastructureP
1366
1361
  ],
1367
1362
  };
1368
1363
  }
1364
+ case "get_board_context": {
1365
+ // Thin wrapper around GET /api/board-context. The backend composes
1366
+ // the response; this handler forwards the optional `intent` query
1367
+ // param and wraps the response in MCP content format.
1368
+ //
1369
+ // Note: the backend also supports a `since` parameter for conditional
1370
+ // return (sentinel when workspace state hasn't shifted), but it is
1371
+ // intentionally NOT exposed at the MCP layer until the
1372
+ // context_version invariant ships (separate follow-on story:
1373
+ // "Wire context_version invariant with chosen atomicity pattern").
1374
+ // Without per-write version bumps, the sentinel would be unreliable
1375
+ // (stale `unchanged: true` for state that did change).
1376
+ //
1377
+ // Graceful 404 fallback: if the backend is older than 2026-05-17
1378
+ // (or a self-hosted release that doesn't include this endpoint),
1379
+ // return a structured message pointing the agent at the existing
1380
+ // multi-tool orchestration path rather than throwing an opaque
1381
+ // error. Version skew between mcp-server and backend is the most
1382
+ // common failure mode for new MCP tools; surfacing it well here
1383
+ // is cheaper than every customer filing the same bug report.
1384
+ const args = request.params.arguments ?? {};
1385
+ const params = {};
1386
+ if (typeof args.intent === "string" && args.intent.trim()) {
1387
+ params.intent = args.intent.trim();
1388
+ }
1389
+ try {
1390
+ const response = await client.get("/board-context", { params });
1391
+ return {
1392
+ content: [
1393
+ { type: "text", text: JSON.stringify(response.data, null, 2) },
1394
+ ],
1395
+ };
1396
+ }
1397
+ catch (err) {
1398
+ if (err?.response?.status === 404) {
1399
+ return {
1400
+ content: [
1401
+ {
1402
+ type: "text",
1403
+ text: JSON.stringify({
1404
+ error: "get_board_context is not available on this backend",
1405
+ reason: "The backend does not expose GET /api/board-context. This is likely a version-skew issue: the tool was added in mcp-server 0.6.0 (2026-05-17) and requires a backend release that includes the matching endpoint.",
1406
+ fallback: "Until the backend is updated, orchestrate grounding manually: call `get_iteration_context` (in-flight work), `list_epics` (queued and in-progress epics), `list_decisions` (recent team decisions in the workspace), and `get_feature_config` (workspace configuration). The trade-offs are more tool calls and no time-balanced sections or similar-story matching, but the agent still gets the underlying grounding data.",
1407
+ action: "Self-hosted: update the backend to a release that includes GET /api/board-context. SaaS (elixium.ai-hosted): contact support — the endpoint should already be live on hosted backends.",
1408
+ }, null, 2),
1409
+ },
1410
+ ],
1411
+ };
1412
+ }
1413
+ throw err;
1414
+ }
1415
+ }
1369
1416
  case "create_hypothesis": {
1370
1417
  const args = request.params.arguments;
1371
1418
  const normalizedLane = await normalizeLane("Icebox");
@@ -69,6 +69,11 @@ export const CREATE_STORY_INPUT_SCHEMA = {
69
69
  description: "Categorizes the story for filtering and Learning Loop signals (feature, bug, chore, platform).",
70
70
  enum: STORY_TYPE_ENUM,
71
71
  },
72
+ owners: {
73
+ type: "array",
74
+ items: { type: "string" },
75
+ description: "Story d9541094 — Firebase UIDs of the story's owners. If omitted, the backend auto-assigns the API key's owner UID (so MCP-created stories show up under the user's 'My Stories' filter). Pass an explicit array to assign teammates, or `[]` to create intentionally unowned.",
76
+ },
72
77
  },
73
78
  required: ["title"],
74
79
  };
@@ -230,6 +235,11 @@ export const UPDATE_STORY_INPUT_SCHEMA = {
230
235
  description: "Categorizes risk (User, Tech, Market, Compliance, Model_Drift). Earns trust with governance and compliance audiences.",
231
236
  enum: RISK_PROFILE_ENUM,
232
237
  },
238
+ owners: {
239
+ type: "array",
240
+ items: { type: "string" },
241
+ description: "Story d9541094 — Firebase UIDs of the story's owners. Replaces the existing array (PATCH semantics). Pass `[]` to clear owners. Use this to retroactively assign yourself or a teammate to a story whose owners were dropped before the auto-assign fix landed.",
242
+ },
233
243
  },
234
244
  required: ["storyId"],
235
245
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elixium.ai/mcp-server",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "MCP Server for Elixium.ai",
6
6
  "mcpName": "io.github.IndirectTek/mcp-server",
@@ -31,5 +31,10 @@
31
31
  "@types/node": "^20.0.0",
32
32
  "ts-node": "^10.9.0",
33
33
  "typescript": "^5.3.0"
34
+ },
35
+ "overrides": {
36
+ "ip-address": "^10.1.1",
37
+ "hono": "^4.12.18",
38
+ "fast-uri": "^3.1.2"
34
39
  }
35
40
  }