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.
Files changed (3) hide show
  1. package/README.md +21 -2
  2. package/index.js +335 -0
  3. 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 24 typed MCP tools so AI coding agents (Claude Code, etc.) can interact with loopctl without writing curl commands.
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 (24)
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for loopctl — structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",