@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 +1 -0
- package/dist/constants/featureConfigFallback.js +30 -0
- package/dist/index.js +78 -31
- package/dist/toolSchemas.js +10 -0
- package/package.json +6 -1
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,
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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");
|
package/dist/toolSchemas.js
CHANGED
|
@@ -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.
|
|
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
|
}
|