loopctl-mcp-server 1.1.2 → 1.2.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 +21 -2
- package/index.js +335 -0
- 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 33 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 (33)
|
|
69
69
|
|
|
70
70
|
### Project Tools
|
|
71
71
|
|
|
@@ -125,6 +125,25 @@ Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_K
|
|
|
125
125
|
| `get_cost_anomalies` | orch | Get cost anomaly alerts — stories or agents exceeding expected budgets. Optionally filter by project. |
|
|
126
126
|
| `set_token_budget` | orch | Set a token budget (in millicents) for a project, epic, story, or agent scope. Requires orchestrator role. |
|
|
127
127
|
|
|
128
|
+
### Knowledge Wiki Tools (agent key)
|
|
129
|
+
|
|
130
|
+
| Tool | Description |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `knowledge_index` | Load the knowledge wiki catalog at session start. Returns lightweight article metadata grouped by category. |
|
|
133
|
+
| `knowledge_search` | Search the knowledge wiki by topic. Supports keyword, semantic, or combined search modes. Returns snippets. |
|
|
134
|
+
| `knowledge_get` | Get full article content by ID. Use after search to read an article in detail. |
|
|
135
|
+
| `knowledge_context` | Get relevance-and-recency-ranked full articles for a task query. Best knowledge for your current context. |
|
|
136
|
+
| `knowledge_create` | Create a new knowledge article. File findings, document patterns, or record decisions. |
|
|
137
|
+
|
|
138
|
+
### Knowledge Management Tools (orchestrator key)
|
|
139
|
+
|
|
140
|
+
| Tool | Description |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `knowledge_publish` | Publish a draft article, making it visible to all agents. Required: `article_id`. |
|
|
143
|
+
| `knowledge_drafts` | List all draft (unpublished) knowledge articles. Optional: `limit`, `offset`. |
|
|
144
|
+
| `knowledge_lint` | Run a lint check on the knowledge wiki to identify stale or low-coverage articles. Optional: `project_id`, `stale_days`, `min_coverage`. |
|
|
145
|
+
| `knowledge_export` | Export all knowledge articles as a ZIP archive. Returns a curl command for direct download (ZIP binary cannot be returned as MCP content). Optional: `project_id`. |
|
|
146
|
+
|
|
128
147
|
### Discovery Tools
|
|
129
148
|
|
|
130
149
|
| Tool | Description |
|
package/index.js
CHANGED
|
@@ -403,6 +403,101 @@ async function setTokenBudget({ scope_type, scope_id, budget_millicents, alert_t
|
|
|
403
403
|
return toContent(result);
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
// --- Knowledge Wiki Tools (agent key) ---
|
|
407
|
+
|
|
408
|
+
async function knowledgeIndex({ project_id }) {
|
|
409
|
+
const path = project_id
|
|
410
|
+
? `/api/v1/projects/${project_id}/knowledge/index`
|
|
411
|
+
: "/api/v1/knowledge/index";
|
|
412
|
+
const result = await apiCall("GET", path, null, process.env.LOOPCTL_AGENT_KEY);
|
|
413
|
+
return toContent(result);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function knowledgeSearch({ q, project_id, category, tags, mode, limit }) {
|
|
417
|
+
const params = new URLSearchParams({ q });
|
|
418
|
+
if (project_id) params.set("project_id", project_id);
|
|
419
|
+
if (category) params.set("category", category);
|
|
420
|
+
if (tags) params.set("tags", tags);
|
|
421
|
+
if (mode) params.set("mode", mode);
|
|
422
|
+
if (limit != null) params.set("limit", String(limit));
|
|
423
|
+
|
|
424
|
+
const result = await apiCall("GET", `/api/v1/knowledge/search?${params}`, null, process.env.LOOPCTL_AGENT_KEY);
|
|
425
|
+
return toContent(result);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function knowledgeGet({ article_id }) {
|
|
429
|
+
const result = await apiCall("GET", `/api/v1/articles/${article_id}`, null, process.env.LOOPCTL_AGENT_KEY);
|
|
430
|
+
return toContent(result);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function knowledgeContext({ query, project_id, limit, recency_weight }) {
|
|
434
|
+
const params = new URLSearchParams({ query });
|
|
435
|
+
if (project_id) params.set("project_id", project_id);
|
|
436
|
+
if (limit != null) params.set("limit", String(limit));
|
|
437
|
+
if (recency_weight != null) params.set("recency_weight", String(recency_weight));
|
|
438
|
+
|
|
439
|
+
const result = await apiCall("GET", `/api/v1/knowledge/context?${params}`, null, process.env.LOOPCTL_AGENT_KEY);
|
|
440
|
+
return toContent(result);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function knowledgeCreate({ title, body, category, tags, project_id }) {
|
|
444
|
+
const payload = { title, body };
|
|
445
|
+
if (category) payload.category = category;
|
|
446
|
+
if (tags) payload.tags = tags;
|
|
447
|
+
if (project_id) payload.project_id = project_id;
|
|
448
|
+
|
|
449
|
+
const result = await apiCall("POST", "/api/v1/articles", payload, process.env.LOOPCTL_AGENT_KEY);
|
|
450
|
+
return toContent(result);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// --- Knowledge Management Tools (orch key) ---
|
|
454
|
+
|
|
455
|
+
async function knowledgePublish({ article_id }) {
|
|
456
|
+
const result = await apiCall("POST", `/api/v1/articles/${article_id}/publish`, null, process.env.LOOPCTL_ORCH_KEY);
|
|
457
|
+
return toContent(result);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function knowledgeDrafts({ limit, offset }) {
|
|
461
|
+
const params = new URLSearchParams();
|
|
462
|
+
if (limit != null) params.set("limit", String(limit));
|
|
463
|
+
if (offset != null) params.set("offset", String(offset));
|
|
464
|
+
const qs = params.toString();
|
|
465
|
+
const path = qs ? `/api/v1/knowledge/drafts?${qs}` : "/api/v1/knowledge/drafts";
|
|
466
|
+
const result = await apiCall("GET", path, null, process.env.LOOPCTL_ORCH_KEY);
|
|
467
|
+
return toContent(result);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function knowledgeLint({ project_id, stale_days, min_coverage }) {
|
|
471
|
+
const params = new URLSearchParams();
|
|
472
|
+
if (stale_days != null) params.set("stale_days", String(stale_days));
|
|
473
|
+
if (min_coverage != null) params.set("min_coverage", String(min_coverage));
|
|
474
|
+
const qs = params.toString();
|
|
475
|
+
const basePath = project_id
|
|
476
|
+
? `/api/v1/projects/${project_id}/knowledge/lint`
|
|
477
|
+
: "/api/v1/knowledge/lint";
|
|
478
|
+
const path = qs ? `${basePath}?${qs}` : basePath;
|
|
479
|
+
const result = await apiCall("GET", path, null, process.env.LOOPCTL_ORCH_KEY);
|
|
480
|
+
return toContent(result);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function knowledgeExport({ project_id }) {
|
|
484
|
+
const basePath = project_id
|
|
485
|
+
? `/api/v1/projects/${project_id}/knowledge/export`
|
|
486
|
+
: "/api/v1/knowledge/export";
|
|
487
|
+
const baseUrl = getBaseUrl();
|
|
488
|
+
const downloadCmd = `curl -H "Authorization: Bearer $LOOPCTL_ORCH_KEY" "${baseUrl}${basePath}" -o knowledge-export.zip`;
|
|
489
|
+
return {
|
|
490
|
+
content: [{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: JSON.stringify({
|
|
493
|
+
message: "Knowledge export produces a ZIP file. Use the curl command below to download it directly.",
|
|
494
|
+
command: downloadCmd,
|
|
495
|
+
endpoint: `${baseUrl}${basePath}`,
|
|
496
|
+
}, null, 2),
|
|
497
|
+
}],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
406
501
|
// --- Discovery Tools ---
|
|
407
502
|
|
|
408
503
|
async function listRoutes() {
|
|
@@ -950,6 +1045,217 @@ const TOOLS = [
|
|
|
950
1045
|
},
|
|
951
1046
|
},
|
|
952
1047
|
|
|
1048
|
+
// Knowledge Wiki Tools (agent key)
|
|
1049
|
+
{
|
|
1050
|
+
name: "knowledge_index",
|
|
1051
|
+
description:
|
|
1052
|
+
"Load the knowledge wiki catalog at session start. Returns lightweight article metadata " +
|
|
1053
|
+
"(titles, categories, tags) grouped by category. Use this to discover available knowledge before searching.",
|
|
1054
|
+
inputSchema: {
|
|
1055
|
+
type: "object",
|
|
1056
|
+
properties: {
|
|
1057
|
+
project_id: {
|
|
1058
|
+
type: "string",
|
|
1059
|
+
description: "Optional: scope the index to a specific project UUID.",
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
required: [],
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "knowledge_search",
|
|
1067
|
+
description:
|
|
1068
|
+
"Search the knowledge wiki by topic. Supports keyword, semantic, or combined search modes. " +
|
|
1069
|
+
"Returns snippets, not full bodies. Use after index to find specific articles.",
|
|
1070
|
+
inputSchema: {
|
|
1071
|
+
type: "object",
|
|
1072
|
+
properties: {
|
|
1073
|
+
q: {
|
|
1074
|
+
type: "string",
|
|
1075
|
+
description: "Search query string.",
|
|
1076
|
+
},
|
|
1077
|
+
project_id: {
|
|
1078
|
+
type: "string",
|
|
1079
|
+
description: "Optional: scope search to a specific project UUID.",
|
|
1080
|
+
},
|
|
1081
|
+
category: {
|
|
1082
|
+
type: "string",
|
|
1083
|
+
description: "Optional: filter results by category.",
|
|
1084
|
+
},
|
|
1085
|
+
tags: {
|
|
1086
|
+
type: "string",
|
|
1087
|
+
description: "Optional: comma-separated tags to filter by.",
|
|
1088
|
+
},
|
|
1089
|
+
mode: {
|
|
1090
|
+
type: "string",
|
|
1091
|
+
enum: ["keyword", "semantic", "combined"],
|
|
1092
|
+
description: "Optional: search mode (keyword, semantic, or combined).",
|
|
1093
|
+
},
|
|
1094
|
+
limit: {
|
|
1095
|
+
type: "integer",
|
|
1096
|
+
description: "Optional: maximum number of results to return.",
|
|
1097
|
+
},
|
|
1098
|
+
},
|
|
1099
|
+
required: ["q"],
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
name: "knowledge_get",
|
|
1104
|
+
description:
|
|
1105
|
+
"Get full article content by ID. Use after search to read an article in detail.",
|
|
1106
|
+
inputSchema: {
|
|
1107
|
+
type: "object",
|
|
1108
|
+
properties: {
|
|
1109
|
+
article_id: {
|
|
1110
|
+
type: "string",
|
|
1111
|
+
description: "The UUID of the article.",
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
required: ["article_id"],
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
name: "knowledge_context",
|
|
1119
|
+
description:
|
|
1120
|
+
"Get relevance-and-recency-ranked full articles for a task query. Returns the best knowledge " +
|
|
1121
|
+
"for your current context with linked references. Use when starting a task that needs domain knowledge.",
|
|
1122
|
+
inputSchema: {
|
|
1123
|
+
type: "object",
|
|
1124
|
+
properties: {
|
|
1125
|
+
query: {
|
|
1126
|
+
type: "string",
|
|
1127
|
+
description: "The task or topic query to find relevant knowledge for.",
|
|
1128
|
+
},
|
|
1129
|
+
project_id: {
|
|
1130
|
+
type: "string",
|
|
1131
|
+
description: "Optional: scope context to a specific project UUID.",
|
|
1132
|
+
},
|
|
1133
|
+
limit: {
|
|
1134
|
+
type: "integer",
|
|
1135
|
+
description: "Optional: maximum number of articles to return.",
|
|
1136
|
+
},
|
|
1137
|
+
recency_weight: {
|
|
1138
|
+
type: "number",
|
|
1139
|
+
description: "Optional: weight for recency scoring (0.0-1.0).",
|
|
1140
|
+
minimum: 0,
|
|
1141
|
+
maximum: 1,
|
|
1142
|
+
},
|
|
1143
|
+
},
|
|
1144
|
+
required: ["query"],
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
name: "knowledge_create",
|
|
1149
|
+
description:
|
|
1150
|
+
"Create a new knowledge article. Use to file findings, document patterns, or record decisions " +
|
|
1151
|
+
"discovered during implementation.",
|
|
1152
|
+
inputSchema: {
|
|
1153
|
+
type: "object",
|
|
1154
|
+
properties: {
|
|
1155
|
+
title: {
|
|
1156
|
+
type: "string",
|
|
1157
|
+
description: "Article title.",
|
|
1158
|
+
},
|
|
1159
|
+
body: {
|
|
1160
|
+
type: "string",
|
|
1161
|
+
description: "Article body content (Markdown supported).",
|
|
1162
|
+
},
|
|
1163
|
+
category: {
|
|
1164
|
+
type: "string",
|
|
1165
|
+
description: "Optional: article category.",
|
|
1166
|
+
},
|
|
1167
|
+
tags: {
|
|
1168
|
+
type: "array",
|
|
1169
|
+
items: { type: "string" },
|
|
1170
|
+
description: "Optional: list of tags.",
|
|
1171
|
+
},
|
|
1172
|
+
project_id: {
|
|
1173
|
+
type: "string",
|
|
1174
|
+
description: "Optional: associate the article with a project UUID.",
|
|
1175
|
+
},
|
|
1176
|
+
},
|
|
1177
|
+
required: ["title", "body"],
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
|
|
1181
|
+
// Knowledge Management Tools (orchestrator key)
|
|
1182
|
+
{
|
|
1183
|
+
name: "knowledge_publish",
|
|
1184
|
+
description:
|
|
1185
|
+
"Publish a draft knowledge article, making it visible to all agents. Requires orchestrator role.",
|
|
1186
|
+
inputSchema: {
|
|
1187
|
+
type: "object",
|
|
1188
|
+
properties: {
|
|
1189
|
+
article_id: {
|
|
1190
|
+
type: "string",
|
|
1191
|
+
description: "The UUID of the draft article to publish.",
|
|
1192
|
+
},
|
|
1193
|
+
},
|
|
1194
|
+
required: ["article_id"],
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
name: "knowledge_drafts",
|
|
1199
|
+
description:
|
|
1200
|
+
"List all draft (unpublished) knowledge articles. Requires orchestrator role. Use to review pending articles before publishing.",
|
|
1201
|
+
inputSchema: {
|
|
1202
|
+
type: "object",
|
|
1203
|
+
properties: {
|
|
1204
|
+
limit: {
|
|
1205
|
+
type: "integer",
|
|
1206
|
+
description: "Optional: maximum number of drafts to return.",
|
|
1207
|
+
},
|
|
1208
|
+
offset: {
|
|
1209
|
+
type: "integer",
|
|
1210
|
+
description: "Optional: pagination offset.",
|
|
1211
|
+
},
|
|
1212
|
+
},
|
|
1213
|
+
required: [],
|
|
1214
|
+
},
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
name: "knowledge_lint",
|
|
1218
|
+
description:
|
|
1219
|
+
"Run a lint check on the knowledge wiki to identify stale, low-coverage, or broken articles. " +
|
|
1220
|
+
"Requires orchestrator role. Optionally scoped to a project.",
|
|
1221
|
+
inputSchema: {
|
|
1222
|
+
type: "object",
|
|
1223
|
+
properties: {
|
|
1224
|
+
project_id: {
|
|
1225
|
+
type: "string",
|
|
1226
|
+
description: "Optional: scope lint to a specific project UUID.",
|
|
1227
|
+
},
|
|
1228
|
+
stale_days: {
|
|
1229
|
+
type: "integer",
|
|
1230
|
+
description: "Optional: flag articles not updated in this many days as stale.",
|
|
1231
|
+
},
|
|
1232
|
+
min_coverage: {
|
|
1233
|
+
type: "number",
|
|
1234
|
+
description: "Optional: minimum required coverage score (0.0-1.0) to flag under-covered articles.",
|
|
1235
|
+
minimum: 0,
|
|
1236
|
+
maximum: 1,
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
required: [],
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
name: "knowledge_export",
|
|
1244
|
+
description:
|
|
1245
|
+
"Export all knowledge articles as a ZIP archive. Because ZIP binary cannot be returned as MCP content, " +
|
|
1246
|
+
"this tool returns a curl command you can run directly to download the archive.",
|
|
1247
|
+
inputSchema: {
|
|
1248
|
+
type: "object",
|
|
1249
|
+
properties: {
|
|
1250
|
+
project_id: {
|
|
1251
|
+
type: "string",
|
|
1252
|
+
description: "Optional: scope export to a specific project UUID.",
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
required: [],
|
|
1256
|
+
},
|
|
1257
|
+
},
|
|
1258
|
+
|
|
953
1259
|
// Discovery Tools
|
|
954
1260
|
{
|
|
955
1261
|
name: "list_routes",
|
|
@@ -1060,6 +1366,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1060
1366
|
case "set_token_budget":
|
|
1061
1367
|
return await setTokenBudget(args);
|
|
1062
1368
|
|
|
1369
|
+
// Knowledge Wiki Tools
|
|
1370
|
+
case "knowledge_index":
|
|
1371
|
+
return await knowledgeIndex(args);
|
|
1372
|
+
|
|
1373
|
+
case "knowledge_search":
|
|
1374
|
+
return await knowledgeSearch(args);
|
|
1375
|
+
|
|
1376
|
+
case "knowledge_get":
|
|
1377
|
+
return await knowledgeGet(args);
|
|
1378
|
+
|
|
1379
|
+
case "knowledge_context":
|
|
1380
|
+
return await knowledgeContext(args);
|
|
1381
|
+
|
|
1382
|
+
case "knowledge_create":
|
|
1383
|
+
return await knowledgeCreate(args);
|
|
1384
|
+
|
|
1385
|
+
// Knowledge Management Tools
|
|
1386
|
+
case "knowledge_publish":
|
|
1387
|
+
return await knowledgePublish(args);
|
|
1388
|
+
|
|
1389
|
+
case "knowledge_drafts":
|
|
1390
|
+
return await knowledgeDrafts(args);
|
|
1391
|
+
|
|
1392
|
+
case "knowledge_lint":
|
|
1393
|
+
return await knowledgeLint(args);
|
|
1394
|
+
|
|
1395
|
+
case "knowledge_export":
|
|
1396
|
+
return await knowledgeExport(args);
|
|
1397
|
+
|
|
1063
1398
|
// Discovery Tools
|
|
1064
1399
|
case "list_routes":
|
|
1065
1400
|
return await listRoutes();
|