loopctl-mcp-server 1.4.0 → 1.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.
Files changed (3) hide show
  1. package/README.md +11 -2
  2. package/index.js +169 -1
  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 37 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 41 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
 
@@ -66,7 +66,7 @@ Or if installed locally:
66
66
 
67
67
  Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_KEY`.
68
68
 
69
- ## Tools (37)
69
+ ## Tools (41)
70
70
 
71
71
  ### Project Tools
72
72
 
@@ -149,6 +149,15 @@ Key resolution priority: `LOOPCTL_API_KEY` > tool-specific key > `LOOPCTL_ORCH_K
149
149
  | `knowledge_ingest_batch` | Submit up to 50 ingestion items in a single request. Each item has the same shape as `knowledge_ingest`. Returns per-item results. Required: `items`. Optional: batch-level `project_id` default. |
150
150
  | `knowledge_ingestion_jobs` | List recent content ingestion jobs (last 7 days, max 50). |
151
151
 
152
+ ### Knowledge Analytics Tools (orchestrator key)
153
+
154
+ | Tool | Description |
155
+ |---|---|
156
+ | `knowledge_analytics_top` | Top accessed knowledge articles for the tenant. Optional: `limit` (default 20, max 100), `since_days` (default 7), `access_type` (`search`, `get`, `context`, `index`). |
157
+ | `knowledge_article_stats` | Per-article usage stats: total accesses, unique agents, by-type breakdown, recent events. Required: `article_id`. |
158
+ | `knowledge_agent_usage` | Per-agent (api_key) knowledge usage: total reads, unique articles, top read articles. Required: `agent_id`. Optional: `limit`, `since_days`. |
159
+ | `knowledge_unused_articles` | Published articles with zero accesses in the window. Optional: `days_unused` (default 30), `limit` (default 50, max 200). |
160
+
152
161
  ### Discovery Tools
153
162
 
154
163
  | Tool | Description |
package/index.js CHANGED
@@ -159,11 +159,12 @@ async function listProjects() {
159
159
  return toContent(result);
160
160
  }
161
161
 
162
- async function createProject({ name, slug, repo_url, description, tech_stack }) {
162
+ async function createProject({ name, slug, repo_url, description, tech_stack, mission }) {
163
163
  const body = { name, slug };
164
164
  if (repo_url) body.repo_url = repo_url;
165
165
  if (description) body.description = description;
166
166
  if (tech_stack) body.tech_stack = tech_stack;
167
+ if (mission) body.mission = mission;
167
168
  const result = await apiCall("POST", "/api/v1/projects", body, process.env.LOOPCTL_ORCH_KEY);
168
169
  return toContent(result);
169
170
  }
@@ -528,6 +529,55 @@ async function knowledgeIngestionJobs() {
528
529
  return toContent(result);
529
530
  }
530
531
 
532
+ // --- Knowledge Analytics Tools (orch key) ---
533
+
534
+ async function knowledgeAnalyticsTop({ limit, since_days, access_type } = {}) {
535
+ const params = new URLSearchParams();
536
+ if (limit != null) params.set("limit", String(limit));
537
+ if (since_days != null) params.set("since_days", String(since_days));
538
+ if (access_type) params.set("access_type", access_type);
539
+ const qs = params.toString();
540
+ const path = qs
541
+ ? `/api/v1/knowledge/analytics/top-articles?${qs}`
542
+ : "/api/v1/knowledge/analytics/top-articles";
543
+ const result = await apiCall("GET", path, null, process.env.LOOPCTL_ORCH_KEY);
544
+ return toContent(result);
545
+ }
546
+
547
+ async function knowledgeArticleStats({ article_id }) {
548
+ const result = await apiCall(
549
+ "GET",
550
+ `/api/v1/knowledge/articles/${article_id}/stats`,
551
+ null,
552
+ process.env.LOOPCTL_ORCH_KEY
553
+ );
554
+ return toContent(result);
555
+ }
556
+
557
+ async function knowledgeAgentUsage({ agent_id, limit, since_days } = {}) {
558
+ const params = new URLSearchParams();
559
+ if (limit != null) params.set("limit", String(limit));
560
+ if (since_days != null) params.set("since_days", String(since_days));
561
+ const qs = params.toString();
562
+ const path = qs
563
+ ? `/api/v1/knowledge/analytics/agents/${agent_id}?${qs}`
564
+ : `/api/v1/knowledge/analytics/agents/${agent_id}`;
565
+ const result = await apiCall("GET", path, null, process.env.LOOPCTL_ORCH_KEY);
566
+ return toContent(result);
567
+ }
568
+
569
+ async function knowledgeUnusedArticles({ days_unused, limit } = {}) {
570
+ const params = new URLSearchParams();
571
+ if (days_unused != null) params.set("days_unused", String(days_unused));
572
+ if (limit != null) params.set("limit", String(limit));
573
+ const qs = params.toString();
574
+ const path = qs
575
+ ? `/api/v1/knowledge/analytics/unused-articles?${qs}`
576
+ : "/api/v1/knowledge/analytics/unused-articles";
577
+ const result = await apiCall("GET", path, null, process.env.LOOPCTL_ORCH_KEY);
578
+ return toContent(result);
579
+ }
580
+
531
581
  async function knowledgeExport({ project_id }) {
532
582
  const basePath = project_id
533
583
  ? `/api/v1/projects/${project_id}/knowledge/export`
@@ -588,6 +638,11 @@ const TOOLS = [
588
638
  repo_url: { type: "string", description: "GitHub repo URL." },
589
639
  description: { type: "string", description: "Project description." },
590
640
  tech_stack: { type: "string", description: "Tech stack summary." },
641
+ mission: {
642
+ type: "string",
643
+ description:
644
+ "Optional project mission/goal statement that cascades into story context. Surfaces in get_story responses as project_mission so agents see the why without a second fetch. Max 2000 chars.",
645
+ },
591
646
  },
592
647
  required: ["name", "slug"],
593
648
  },
@@ -1437,6 +1492,106 @@ const TOOLS = [
1437
1492
  },
1438
1493
  },
1439
1494
 
1495
+ // Knowledge Analytics Tools (orchestrator key)
1496
+ {
1497
+ name: "knowledge_analytics_top",
1498
+ description:
1499
+ "Return the top accessed knowledge articles for the tenant. " +
1500
+ "Use to identify which articles agents actually read. Requires orchestrator role.",
1501
+ inputSchema: {
1502
+ type: "object",
1503
+ properties: {
1504
+ limit: {
1505
+ type: "integer",
1506
+ description: "Max rows to return. Default 20, max 100.",
1507
+ minimum: 1,
1508
+ maximum: 100,
1509
+ },
1510
+ since_days: {
1511
+ type: "integer",
1512
+ description: "Look back this many days. Default 7.",
1513
+ minimum: 1,
1514
+ maximum: 365,
1515
+ },
1516
+ access_type: {
1517
+ type: "string",
1518
+ enum: ["search", "get", "context", "index"],
1519
+ description: "Optional: restrict to a single access type.",
1520
+ },
1521
+ },
1522
+ required: [],
1523
+ },
1524
+ },
1525
+ {
1526
+ name: "knowledge_article_stats",
1527
+ description:
1528
+ "Return per-article usage statistics: total accesses, unique agents, " +
1529
+ "by-type breakdown, and the 10 most recent events. Requires orchestrator role.",
1530
+ inputSchema: {
1531
+ type: "object",
1532
+ properties: {
1533
+ article_id: {
1534
+ type: "string",
1535
+ description: "The UUID of the article to inspect.",
1536
+ },
1537
+ },
1538
+ required: ["article_id"],
1539
+ },
1540
+ },
1541
+ {
1542
+ name: "knowledge_agent_usage",
1543
+ description:
1544
+ "Return knowledge usage for a specific agent (api_key): total reads, " +
1545
+ "unique articles, access type breakdown, and top read articles. " +
1546
+ "Requires orchestrator role.",
1547
+ inputSchema: {
1548
+ type: "object",
1549
+ properties: {
1550
+ agent_id: {
1551
+ type: "string",
1552
+ description: "API key UUID identifying the agent identity.",
1553
+ },
1554
+ limit: {
1555
+ type: "integer",
1556
+ description: "Max top articles to return. Default 20, max 100.",
1557
+ minimum: 1,
1558
+ maximum: 100,
1559
+ },
1560
+ since_days: {
1561
+ type: "integer",
1562
+ description: "Look back this many days. Default 7.",
1563
+ minimum: 1,
1564
+ maximum: 365,
1565
+ },
1566
+ },
1567
+ required: ["agent_id"],
1568
+ },
1569
+ },
1570
+ {
1571
+ name: "knowledge_unused_articles",
1572
+ description:
1573
+ "Return published articles that have not been accessed in the configured " +
1574
+ "time window. Use to identify dead-weight knowledge. Requires orchestrator role.",
1575
+ inputSchema: {
1576
+ type: "object",
1577
+ properties: {
1578
+ days_unused: {
1579
+ type: "integer",
1580
+ description: "Window length in days. Default 30.",
1581
+ minimum: 1,
1582
+ maximum: 365,
1583
+ },
1584
+ limit: {
1585
+ type: "integer",
1586
+ description: "Max rows to return. Default 50, max 200.",
1587
+ minimum: 1,
1588
+ maximum: 200,
1589
+ },
1590
+ },
1591
+ required: [],
1592
+ },
1593
+ },
1594
+
1440
1595
  // Discovery Tools
1441
1596
  {
1442
1597
  name: "list_routes",
@@ -1589,6 +1744,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1589
1744
  case "knowledge_ingestion_jobs":
1590
1745
  return await knowledgeIngestionJobs();
1591
1746
 
1747
+ // Knowledge Analytics Tools
1748
+ case "knowledge_analytics_top":
1749
+ return await knowledgeAnalyticsTop(args);
1750
+
1751
+ case "knowledge_article_stats":
1752
+ return await knowledgeArticleStats(args);
1753
+
1754
+ case "knowledge_agent_usage":
1755
+ return await knowledgeAgentUsage(args);
1756
+
1757
+ case "knowledge_unused_articles":
1758
+ return await knowledgeUnusedArticles(args);
1759
+
1592
1760
  // Discovery Tools
1593
1761
  case "list_routes":
1594
1762
  return await listRoutes();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "MCP server for loopctl — structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",