loopctl-mcp-server 1.0.2 → 1.1.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 +15 -5
- package/index.js +227 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server for [loopctl](https://loopctl.com) -- structural trust for AI development loops.
|
|
4
4
|
|
|
5
|
-
Wraps the loopctl REST API into
|
|
5
|
+
Wraps the loopctl REST API into 24 typed MCP tools so AI coding agents (Claude Code, etc.) can interact with loopctl without writing curl commands.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -65,7 +65,7 @@ Or if installed locally:
|
|
|
65
65
|
|
|
66
66
|
Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_KEY`.
|
|
67
67
|
|
|
68
|
-
## Tools (
|
|
68
|
+
## Tools (24)
|
|
69
69
|
|
|
70
70
|
### Project Tools
|
|
71
71
|
|
|
@@ -74,14 +74,14 @@ Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_K
|
|
|
74
74
|
| `get_tenant` | Get current tenant info. Use to verify connectivity. |
|
|
75
75
|
| `list_projects` | List all projects in the current tenant. |
|
|
76
76
|
| `create_project` | Create a new project in the current tenant. |
|
|
77
|
-
| `get_progress` | Get progress summary for a project, including story counts by status. |
|
|
77
|
+
| `get_progress` | Get progress summary for a project, including story counts by status. Pass `include_cost=true` for cost data. |
|
|
78
78
|
| `import_stories` | Import stories into a project from a structured payload (Epic 12 import format). |
|
|
79
79
|
|
|
80
80
|
### Story Tools
|
|
81
81
|
|
|
82
82
|
| Tool | Description |
|
|
83
83
|
|---|---|
|
|
84
|
-
| `list_stories` | List stories for a project, optionally filtered by agent_status, verified_status, or epic_id. |
|
|
84
|
+
| `list_stories` | List stories for a project, optionally filtered by agent_status, verified_status, or epic_id. Pass `include_token_totals=true` for per-story token data. |
|
|
85
85
|
| `list_ready_stories` | List stories that are ready to be worked on (contracted, dependencies met). |
|
|
86
86
|
| `get_story` | Get full details for a single story by ID. |
|
|
87
87
|
|
|
@@ -98,7 +98,7 @@ Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_K
|
|
|
98
98
|
|
|
99
99
|
| Tool | Description |
|
|
100
100
|
|---|---|
|
|
101
|
-
| `report_story` | Reviewer confirms the implementation is done. Transitions implementing -> reported_done. |
|
|
101
|
+
| `report_story` | Reviewer confirms the implementation is done. Transitions implementing -> reported_done. Accepts optional `token_usage` object. |
|
|
102
102
|
| `review_complete` | Record that a review has been completed for a story. Required before verify. |
|
|
103
103
|
|
|
104
104
|
### Verification Tools (orchestrator key)
|
|
@@ -115,6 +115,16 @@ Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_K
|
|
|
115
115
|
| `bulk_mark_complete` | Bulk mark multiple stories as complete in a single API call. |
|
|
116
116
|
| `verify_all_in_epic` | Bulk verify all reported_done, unverified stories in an epic. |
|
|
117
117
|
|
|
118
|
+
### Token Efficiency Tools
|
|
119
|
+
|
|
120
|
+
| Tool | Auth Key | Description |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `report_token_usage` | agent | Report input/output token counts, model name, and cost for a story session. Calls `POST /api/v1/token-usage`. |
|
|
123
|
+
| `get_cost_summary` | orch | Get cost/token usage summary for a project, optionally broken down by `agent`, `epic`, or `model`. |
|
|
124
|
+
| `get_story_token_usage` | orch | Get all token usage records for a single story. |
|
|
125
|
+
| `get_cost_anomalies` | orch | Get cost anomaly alerts — stories or agents exceeding expected budgets. Optionally filter by project. |
|
|
126
|
+
| `set_token_budget` | orch | Set a token budget (in millicents) for a project, epic, story, or agent scope. Requires orchestrator role. |
|
|
127
|
+
|
|
118
128
|
### Discovery Tools
|
|
119
129
|
|
|
120
130
|
| Tool | Description |
|
package/index.js
CHANGED
|
@@ -166,8 +166,11 @@ async function createProject({ name, slug, repo_url, description, tech_stack })
|
|
|
166
166
|
return toContent(result);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
async function getProgress({ project_id }) {
|
|
170
|
-
const
|
|
169
|
+
async function getProgress({ project_id, include_cost }) {
|
|
170
|
+
const params = new URLSearchParams();
|
|
171
|
+
if (include_cost) params.set("include_cost", "true");
|
|
172
|
+
const query = params.toString() ? `?${params}` : "";
|
|
173
|
+
const result = await apiCall("GET", `/api/v1/projects/${project_id}/progress${query}`);
|
|
171
174
|
return toContent(result);
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -183,13 +186,14 @@ async function importStories({ project_id, payload }) {
|
|
|
183
186
|
|
|
184
187
|
// --- Story Tools ---
|
|
185
188
|
|
|
186
|
-
async function listStories({ project_id, agent_status, verified_status, epic_id, limit, offset }) {
|
|
189
|
+
async function listStories({ project_id, agent_status, verified_status, epic_id, limit, offset, include_token_totals }) {
|
|
187
190
|
const params = new URLSearchParams({ project_id });
|
|
188
191
|
if (agent_status) params.set("agent_status", agent_status);
|
|
189
192
|
if (verified_status) params.set("verified_status", verified_status);
|
|
190
193
|
if (epic_id) params.set("epic_id", epic_id);
|
|
191
194
|
params.set("limit", String(limit ?? 20));
|
|
192
195
|
if (offset != null) params.set("offset", String(offset));
|
|
196
|
+
if (include_token_totals) params.set("include_token_totals", "true");
|
|
193
197
|
|
|
194
198
|
const result = await apiCall("GET", `/api/v1/stories?${params}`);
|
|
195
199
|
return toContentCompact(result);
|
|
@@ -252,13 +256,16 @@ async function requestReview({ story_id }) {
|
|
|
252
256
|
|
|
253
257
|
// --- Reviewer Tools (orch key — reviewer uses orchestrator role) ---
|
|
254
258
|
|
|
255
|
-
async function reportStory({ story_id, artifact_type, artifact_path }) {
|
|
259
|
+
async function reportStory({ story_id, artifact_type, artifact_path, token_usage }) {
|
|
256
260
|
const body = {};
|
|
257
261
|
if (artifact_type || artifact_path) {
|
|
258
262
|
body.artifact = {};
|
|
259
263
|
if (artifact_type) body.artifact.artifact_type = artifact_type;
|
|
260
264
|
if (artifact_path) body.artifact.path = artifact_path;
|
|
261
265
|
}
|
|
266
|
+
if (token_usage) {
|
|
267
|
+
body.token_usage = token_usage;
|
|
268
|
+
}
|
|
262
269
|
|
|
263
270
|
const result = await apiCall(
|
|
264
271
|
"POST",
|
|
@@ -334,6 +341,58 @@ async function verifyAllInEpic({ epic_id, review_type, summary }) {
|
|
|
334
341
|
return toContent(result);
|
|
335
342
|
}
|
|
336
343
|
|
|
344
|
+
// --- Token Efficiency Tools ---
|
|
345
|
+
|
|
346
|
+
async function reportTokenUsage({ story_id, input_tokens, output_tokens, model_name, cost_millicents, phase, skill_version_id, session_id }) {
|
|
347
|
+
const body = { story_id, input_tokens, output_tokens, model_name, cost_millicents };
|
|
348
|
+
if (phase) body.phase = phase;
|
|
349
|
+
if (skill_version_id) body.skill_version_id = skill_version_id;
|
|
350
|
+
if (session_id) body.session_id = session_id;
|
|
351
|
+
|
|
352
|
+
const result = await apiCall(
|
|
353
|
+
"POST",
|
|
354
|
+
"/api/v1/token-usage",
|
|
355
|
+
body,
|
|
356
|
+
process.env.LOOPCTL_AGENT_KEY
|
|
357
|
+
);
|
|
358
|
+
return toContent(result);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function getCostSummary({ project_id, breakdown }) {
|
|
362
|
+
const params = new URLSearchParams({ project_id });
|
|
363
|
+
if (breakdown) params.set("breakdown", breakdown);
|
|
364
|
+
|
|
365
|
+
const result = await apiCall("GET", `/api/v1/projects/${project_id}/cost-summary?${params}`);
|
|
366
|
+
return toContent(result);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function getStoryTokenUsage({ story_id }) {
|
|
370
|
+
const result = await apiCall("GET", `/api/v1/stories/${story_id}/token-usage`);
|
|
371
|
+
return toContent(result);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function getCostAnomalies({ project_id }) {
|
|
375
|
+
const params = new URLSearchParams();
|
|
376
|
+
if (project_id) params.set("project_id", project_id);
|
|
377
|
+
|
|
378
|
+
const query = params.toString() ? `?${params}` : "";
|
|
379
|
+
const result = await apiCall("GET", `/api/v1/cost-anomalies${query}`);
|
|
380
|
+
return toContent(result);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function setTokenBudget({ scope_type, scope_id, budget_millicents, alert_threshold_pct }) {
|
|
384
|
+
const body = { scope_type, scope_id, budget_millicents };
|
|
385
|
+
if (alert_threshold_pct != null) body.alert_threshold_pct = alert_threshold_pct;
|
|
386
|
+
|
|
387
|
+
const result = await apiCall(
|
|
388
|
+
"POST",
|
|
389
|
+
"/api/v1/token-budgets",
|
|
390
|
+
body,
|
|
391
|
+
process.env.LOOPCTL_ORCH_KEY
|
|
392
|
+
);
|
|
393
|
+
return toContent(result);
|
|
394
|
+
}
|
|
395
|
+
|
|
337
396
|
// --- Discovery Tools ---
|
|
338
397
|
|
|
339
398
|
async function listRoutes() {
|
|
@@ -382,7 +441,7 @@ const TOOLS = [
|
|
|
382
441
|
},
|
|
383
442
|
{
|
|
384
443
|
name: "get_progress",
|
|
385
|
-
description: "Get progress summary for a project, including story counts by status.",
|
|
444
|
+
description: "Get progress summary for a project, including story counts by status. Pass include_cost=true to include cost data when available.",
|
|
386
445
|
inputSchema: {
|
|
387
446
|
type: "object",
|
|
388
447
|
properties: {
|
|
@@ -390,6 +449,10 @@ const TOOLS = [
|
|
|
390
449
|
type: "string",
|
|
391
450
|
description: "The UUID of the project.",
|
|
392
451
|
},
|
|
452
|
+
include_cost: {
|
|
453
|
+
type: "boolean",
|
|
454
|
+
description: "Optional: include cost/token summary data in the response.",
|
|
455
|
+
},
|
|
393
456
|
},
|
|
394
457
|
required: ["project_id"],
|
|
395
458
|
},
|
|
@@ -448,6 +511,10 @@ const TOOLS = [
|
|
|
448
511
|
type: "integer",
|
|
449
512
|
description: "Number of stories to skip (for pagination).",
|
|
450
513
|
},
|
|
514
|
+
include_token_totals: {
|
|
515
|
+
type: "boolean",
|
|
516
|
+
description: "Optional: include per-story token usage totals when available.",
|
|
517
|
+
},
|
|
451
518
|
},
|
|
452
519
|
required: ["project_id"],
|
|
453
520
|
},
|
|
@@ -583,6 +650,16 @@ const TOOLS = [
|
|
|
583
650
|
type: "string",
|
|
584
651
|
description: "Optional: path or URL of the artifact.",
|
|
585
652
|
},
|
|
653
|
+
token_usage: {
|
|
654
|
+
type: "object",
|
|
655
|
+
description: "Optional: token usage summary for the implementation work.",
|
|
656
|
+
properties: {
|
|
657
|
+
input_tokens: { type: "integer", description: "Total input tokens consumed." },
|
|
658
|
+
output_tokens: { type: "integer", description: "Total output tokens consumed." },
|
|
659
|
+
model_name: { type: "string", description: "Model name (e.g. claude-sonnet-4-5)." },
|
|
660
|
+
cost_millicents: { type: "integer", description: "Total cost in millicents (1/1000 of a cent)." },
|
|
661
|
+
},
|
|
662
|
+
},
|
|
586
663
|
},
|
|
587
664
|
required: ["story_id"],
|
|
588
665
|
},
|
|
@@ -732,6 +809,134 @@ const TOOLS = [
|
|
|
732
809
|
},
|
|
733
810
|
},
|
|
734
811
|
|
|
812
|
+
// Token Efficiency Tools
|
|
813
|
+
{
|
|
814
|
+
name: "report_token_usage",
|
|
815
|
+
description:
|
|
816
|
+
"Report token usage for a story implementation session. " +
|
|
817
|
+
"Stores input/output token counts, model name, and cost. Uses the AGENT key.",
|
|
818
|
+
inputSchema: {
|
|
819
|
+
type: "object",
|
|
820
|
+
properties: {
|
|
821
|
+
story_id: {
|
|
822
|
+
type: "string",
|
|
823
|
+
description: "The UUID of the story this usage is attributed to.",
|
|
824
|
+
},
|
|
825
|
+
input_tokens: {
|
|
826
|
+
type: "integer",
|
|
827
|
+
description: "Number of input (prompt) tokens consumed.",
|
|
828
|
+
},
|
|
829
|
+
output_tokens: {
|
|
830
|
+
type: "integer",
|
|
831
|
+
description: "Number of output (completion) tokens consumed.",
|
|
832
|
+
},
|
|
833
|
+
model_name: {
|
|
834
|
+
type: "string",
|
|
835
|
+
description: "Name of the model used (e.g. claude-sonnet-4-5, gpt-4o).",
|
|
836
|
+
},
|
|
837
|
+
cost_millicents: {
|
|
838
|
+
type: "integer",
|
|
839
|
+
description: "Total cost in millicents (1/1000 of a cent).",
|
|
840
|
+
},
|
|
841
|
+
phase: {
|
|
842
|
+
type: "string",
|
|
843
|
+
description: "Optional: phase of work (e.g. implement, review, verify).",
|
|
844
|
+
},
|
|
845
|
+
skill_version_id: {
|
|
846
|
+
type: "string",
|
|
847
|
+
description: "Optional: UUID of the skill version used.",
|
|
848
|
+
},
|
|
849
|
+
session_id: {
|
|
850
|
+
type: "string",
|
|
851
|
+
description: "Optional: agent session identifier for grouping records.",
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
required: ["story_id", "input_tokens", "output_tokens", "model_name", "cost_millicents"],
|
|
855
|
+
},
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
name: "get_cost_summary",
|
|
859
|
+
description:
|
|
860
|
+
"Get cost/token usage summary for a project. " +
|
|
861
|
+
"Optionally break down by agent, epic, or model.",
|
|
862
|
+
inputSchema: {
|
|
863
|
+
type: "object",
|
|
864
|
+
properties: {
|
|
865
|
+
project_id: {
|
|
866
|
+
type: "string",
|
|
867
|
+
description: "The UUID of the project.",
|
|
868
|
+
},
|
|
869
|
+
breakdown: {
|
|
870
|
+
type: "string",
|
|
871
|
+
enum: ["agent", "epic", "model"],
|
|
872
|
+
description: "Optional: dimension to group the summary by (agent, epic, or model).",
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
required: ["project_id"],
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
name: "get_story_token_usage",
|
|
880
|
+
description: "Get token usage records for a single story.",
|
|
881
|
+
inputSchema: {
|
|
882
|
+
type: "object",
|
|
883
|
+
properties: {
|
|
884
|
+
story_id: {
|
|
885
|
+
type: "string",
|
|
886
|
+
description: "The UUID of the story.",
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
required: ["story_id"],
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
name: "get_cost_anomalies",
|
|
894
|
+
description:
|
|
895
|
+
"Get cost anomaly alerts — stories or agents that exceed expected token budgets. " +
|
|
896
|
+
"Optionally filter by project.",
|
|
897
|
+
inputSchema: {
|
|
898
|
+
type: "object",
|
|
899
|
+
properties: {
|
|
900
|
+
project_id: {
|
|
901
|
+
type: "string",
|
|
902
|
+
description: "Optional: filter anomalies to a specific project UUID.",
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
required: [],
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
name: "set_token_budget",
|
|
910
|
+
description:
|
|
911
|
+
"Set a token budget for a scope (project, epic, story, or agent). " +
|
|
912
|
+
"Requires orchestrator or user role. Uses the ORCH key.",
|
|
913
|
+
inputSchema: {
|
|
914
|
+
type: "object",
|
|
915
|
+
properties: {
|
|
916
|
+
scope_type: {
|
|
917
|
+
type: "string",
|
|
918
|
+
enum: ["project", "epic", "story", "agent"],
|
|
919
|
+
description: "The type of scope to apply the budget to.",
|
|
920
|
+
},
|
|
921
|
+
scope_id: {
|
|
922
|
+
type: "string",
|
|
923
|
+
description: "The UUID of the scoped resource (project_id, epic_id, story_id, or agent_id).",
|
|
924
|
+
},
|
|
925
|
+
budget_millicents: {
|
|
926
|
+
type: "integer",
|
|
927
|
+
description: "Maximum allowed cost in millicents (1/1000 of a cent).",
|
|
928
|
+
},
|
|
929
|
+
alert_threshold_pct: {
|
|
930
|
+
type: "number",
|
|
931
|
+
description: "Optional: percentage of budget at which to trigger an alert (0–100).",
|
|
932
|
+
minimum: 0,
|
|
933
|
+
maximum: 100,
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
required: ["scope_type", "scope_id", "budget_millicents"],
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
|
|
735
940
|
// Discovery Tools
|
|
736
941
|
{
|
|
737
942
|
name: "list_routes",
|
|
@@ -751,7 +956,7 @@ const TOOLS = [
|
|
|
751
956
|
const server = new Server(
|
|
752
957
|
{
|
|
753
958
|
name: "loopctl",
|
|
754
|
-
version: "1.
|
|
959
|
+
version: "1.1.0",
|
|
755
960
|
},
|
|
756
961
|
{
|
|
757
962
|
capabilities: { tools: {} },
|
|
@@ -826,6 +1031,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
826
1031
|
case "verify_all_in_epic":
|
|
827
1032
|
return await verifyAllInEpic(args);
|
|
828
1033
|
|
|
1034
|
+
// Token Efficiency Tools
|
|
1035
|
+
case "report_token_usage":
|
|
1036
|
+
return await reportTokenUsage(args);
|
|
1037
|
+
|
|
1038
|
+
case "get_cost_summary":
|
|
1039
|
+
return await getCostSummary(args);
|
|
1040
|
+
|
|
1041
|
+
case "get_story_token_usage":
|
|
1042
|
+
return await getStoryTokenUsage(args);
|
|
1043
|
+
|
|
1044
|
+
case "get_cost_anomalies":
|
|
1045
|
+
return await getCostAnomalies(args);
|
|
1046
|
+
|
|
1047
|
+
case "set_token_budget":
|
|
1048
|
+
return await setTokenBudget(args);
|
|
1049
|
+
|
|
829
1050
|
// Discovery Tools
|
|
830
1051
|
case "list_routes":
|
|
831
1052
|
return await listRoutes();
|