loopctl-mcp-server 2.55.0 → 2.57.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 +4 -1
  2. package/index.js +102 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -221,7 +221,7 @@ Epic 39 Repo Coordination Bus — a lightweight, tenant-isolated channel for age
221
221
  | `get_cost_summary` | orch | Get cost/token usage summary for a project, optionally broken down by `agent`, `epic`, or `model`. |
222
222
  | `get_story_token_usage` | orch | Get all token usage records for a single story. |
223
223
  | `get_cost_anomalies` | orch | Get cost anomaly alerts — stories or agents exceeding expected budgets. Optionally filter by project. |
224
- | `get_ingestion_anomalies` | orch | Get ingestion-health anomalies — capture_silence (a source_type stopped producing articles) and high_reject_rate (writes rejected at high rate, persisting no article row). Check whether knowledge capture is still landing AND being accepted. |
224
+ | `get_ingestion_anomalies` | orch | Get ingestion-health anomalies — capture_silence (a source_type stopped producing articles), high_reject_rate (writes rejected at high rate, persisting no article row), and sweep_stalled (the 30-day channel-post retention sweep is no longer enforcing retention, under the reserved source_type `channel_post_sweep`). Check whether knowledge capture is still landing AND being accepted, and whether coordination-bus retention is still enforced. |
225
225
  | `set_token_budget` | orch | Set a token budget (in millicents) for a project, epic, story, or agent scope. Requires orchestrator role. |
226
226
 
227
227
  ### Knowledge Wiki Tools (agent key)
@@ -240,6 +240,9 @@ Epic 39 Repo Coordination Bus — a lightweight, tenant-isolated channel for age
240
240
  | Tool | Description |
241
241
  |---|---|
242
242
  | `knowledge_index` | Browse/paginate the knowledge wiki catalog grouped by category. **Agent callers see only articles they own or marked `shared`.** Honors `category`, `tags`, `offset`, `limit` with deterministic ordering over the filtered set (`meta.categories` reports per-category totals within visibility). Use `fields` (default `id,title,category`; request `tags`/`status`/`updated_at` explicitly; `id` and `category` are always included) to keep the payload small. Optional: `project_id`, `story_id`, `category`, `tags`, `offset`, `limit`, `fields`. |
243
+ | `embedding_status` | This tenant's embedding-dimension state: active dimension, whether semantic recall is available (and the exact reason when it is not), the instance's supported dimension set, whether the shared system-scoped corpus has been materialized for this tenant, and per-dimension row counts. Call it when semantic search under-returns or reports `fallback_reason: semantic_recall_unavailable`. |
244
+ | `embedding_materialize_system_corpus` | Embed the shared SYSTEM-scoped corpus for THIS tenant at its active dimension with this tenant's own credential (system articles are keyword-only until then). Idempotent, batched. |
245
+ | `embedding_reembed` | Move the tenant's whole corpus (articles, per-tenant system-article materializations and agent memories) onto `target_dimension`. Recall keeps serving at the current dimension throughout; the pin flips and stale rows drop only when everything is present at the target. One-time and cost-bearing; requires `LOOPCTL_ORCH_KEY`. |
243
246
  | `knowledge_stats` | Aggregate article counts (`total`, `by_category`, `by_status`) via cheap `COUNT(*) GROUP BY` within agent's visible set — no article metadata loaded. Agent callers see only their own and `shared` articles. Counts span all statuses. Optional: `project_id`. |
244
247
  | `knowledge_count` | Count articles matching filters **without returning rows** within agent's visible set. Agent callers see only their own and `shared` articles. Same filters as `knowledge_list` (`category`, `status`, `tags`, `match`, `source_type`, `source_id`, `idempotency_key`, `project_id`). With `tags`+`match: all` (+`status`) → "how many published articles tagged both X and Y (that I can see)". Returns `{ count }`. |
245
248
  | `knowledge_facets` | Count articles grouped by **distinct tag** within agent's visible set, no rows. Agent callers see only their own and `shared` articles. `tag_prefix` (e.g. `book-`) gives the distinct count of a tag family plus per-member totals. Returns `{ data: { tag: count }, meta: { distinct_count } }`. Optional: `category`, `status`, `tags`, `match`, `project_id`, `limit`. |
package/index.js CHANGED
@@ -1101,6 +1101,41 @@ async function knowledgeIndex({ project_id, story_id, category, tags, match, off
1101
1101
  return toContent(result);
1102
1102
  }
1103
1103
 
1104
+ // US-41.1 — the per-tenant embedding DIMENSION surface. `status` and the system-corpus
1105
+ // materialization are agent-role; the re-embed is ORCHESTRATOR-role because its completion
1106
+ // step deletes the stale-dimension rows (loopctl reserves data-removing operations for
1107
+ // higher roles), so it deliberately does NOT fall back to the agent key.
1108
+ async function embeddingStatus() {
1109
+ const result = await apiCall("GET", "/api/v1/knowledge/embeddings", null, process.env.LOOPCTL_AGENT_KEY);
1110
+ return toContent(result);
1111
+ }
1112
+
1113
+ async function embeddingMaterializeSystemCorpus() {
1114
+ const result = await apiCall(
1115
+ "POST",
1116
+ "/api/v1/knowledge/embeddings/system-corpus",
1117
+ {},
1118
+ process.env.LOOPCTL_AGENT_KEY,
1119
+ );
1120
+ return toContent(result);
1121
+ }
1122
+
1123
+ async function embeddingReembed({ target_dimension }) {
1124
+ if (!Number.isInteger(target_dimension) || target_dimension <= 0) {
1125
+ return {
1126
+ content: [{ type: "text", text: "Error: target_dimension must be a positive integer." }],
1127
+ isError: true,
1128
+ };
1129
+ }
1130
+ const result = await apiCall(
1131
+ "POST",
1132
+ "/api/v1/knowledge/embeddings/reembed",
1133
+ { target_dimension },
1134
+ process.env.LOOPCTL_ORCH_KEY,
1135
+ );
1136
+ return toContent(result);
1137
+ }
1138
+
1104
1139
  async function knowledgeStats({ project_id }) {
1105
1140
  if (project_id && !UUID_RE.test(project_id)) {
1106
1141
  return {
@@ -3340,9 +3375,13 @@ const TOOLS = [
3340
3375
  name: "get_ingestion_anomalies",
3341
3376
  description:
3342
3377
  "Get ingestion-health anomalies — capture_silence (a source_type that was producing " +
3343
- "articles has gone silent) and high_reject_rate (writes attempted but rejected at high " +
3344
- "rate — 409 title_conflict / validation drops that persist no article row). Use to check " +
3345
- "whether knowledge capture is still landing AND being accepted. Paginated (page/page_size); " +
3378
+ "articles has gone silent), high_reject_rate (writes attempted but rejected at high " +
3379
+ "rate — 409 title_conflict / validation drops that persist no article row), and " +
3380
+ "sweep_stalled (the 30-day channel-post retention sweep is no longer enforcing " +
3381
+ "retention for this tenant — expired coordination-bus posts are still present well " +
3382
+ "past their expires_at; recorded under the reserved source_type channel_post_sweep). " +
3383
+ "Use to check whether knowledge capture is still landing AND being accepted, and " +
3384
+ "whether coordination-bus retention is still being enforced. Paginated (page/page_size); " +
3346
3385
  "advance `page` to enumerate all. Filter by source_type, anomaly_type, resolved status, or " +
3347
3386
  "include archived.",
3348
3387
  inputSchema: {
@@ -3356,10 +3395,11 @@ const TOOLS = [
3356
3395
  type: "string",
3357
3396
  // Keep in sync with Loopctl.Knowledge.IngestionAnomaly @anomaly_types (the
3358
3397
  // server-side Ecto.Enum + the ingestion_anomalies_anomaly_type_check DB CHECK).
3359
- enum: ["capture_silence", "high_reject_rate"],
3398
+ enum: ["capture_silence", "high_reject_rate", "sweep_stalled"],
3360
3399
  description:
3361
- 'Optional: filter by anomaly type — "capture_silence" (writes stopped) or ' +
3362
- '"high_reject_rate" (writes rejected at high rate).',
3400
+ 'Optional: filter by anomaly type — "capture_silence" (writes stopped), ' +
3401
+ '"high_reject_rate" (writes rejected at high rate), or "sweep_stalled" ' +
3402
+ '(the channel-post retention sweep is not enforcing the 30-day window).',
3363
3403
  },
3364
3404
  resolved: {
3365
3405
  type: "string",
@@ -3547,6 +3587,49 @@ const TOOLS = [
3547
3587
  required: [],
3548
3588
  },
3549
3589
  },
3590
+ {
3591
+ name: "embedding_status",
3592
+ description:
3593
+ "Report this tenant's EMBEDDING DIMENSION state: the active dimension, whether semantic " +
3594
+ "recall is currently available (and the exact reason when it is not — e.g. a non-default " +
3595
+ "dimension whose side-table reads have not been enabled yet), the instance's supported " +
3596
+ "dimension set, whether the shared SYSTEM-scoped corpus has been materialized for this " +
3597
+ "tenant (until it is, those articles are keyword-only), and per-dimension row counts. " +
3598
+ "Call this when semantic search returns fewer results than expected or reports " +
3599
+ "fallback_reason 'semantic_recall_unavailable' — it tells you WHY instead of leaving an " +
3600
+ "empty result set to be misread as 'nothing relevant'.",
3601
+ inputSchema: { type: "object", properties: {}, required: [] },
3602
+ },
3603
+ {
3604
+ name: "embedding_materialize_system_corpus",
3605
+ description:
3606
+ "Materialize the shared SYSTEM-scoped article corpus for THIS tenant at its active " +
3607
+ "embedding dimension, using this tenant's own embedding credential. System articles " +
3608
+ "cannot be embedded once for everyone (embeddings are BYO), so until this runs they are " +
3609
+ "matched by keyword only for you. Idempotent and batched; safe to call repeatedly.",
3610
+ inputSchema: { type: "object", properties: {}, required: [] },
3611
+ },
3612
+ {
3613
+ name: "embedding_reembed",
3614
+ description:
3615
+ "Move this tenant's WHOLE corpus (articles, per-tenant system-article materializations " +
3616
+ "and agent memories) onto target_dimension. Recall keeps serving at the CURRENT " +
3617
+ "dimension for the entire run; the recorded dimension is flipped and the stale-dimension " +
3618
+ "rows dropped only once everything is present at the target. ONE-TIME and COST-BEARING — " +
3619
+ "it re-bills your embedding provider for the entire corpus. Requires an ORCHESTRATOR key " +
3620
+ "(the completion step deletes data). An unsupported dimension is rejected with the " +
3621
+ "supported set named.",
3622
+ inputSchema: {
3623
+ type: "object",
3624
+ properties: {
3625
+ target_dimension: {
3626
+ type: "integer",
3627
+ description: "The embedding dimension to move to. Must be in the instance's supported set.",
3628
+ },
3629
+ },
3630
+ required: ["target_dimension"],
3631
+ },
3632
+ },
3550
3633
  {
3551
3634
  name: "knowledge_stats",
3552
3635
  description:
@@ -3780,6 +3863,10 @@ const TOOLS = [
3780
3863
  "a keyword and a semantic sub-search, each capped at 100, so up to ~200), or filtered_set " +
3781
3864
  "(list mode: the full set). Do NOT use a relevance-mode total_count to size the wiki — use " +
3782
3865
  "list mode or knowledge_stats. " +
3866
+ "SCORE: in combined mode each result's `score` is a Reciprocal Rank Fusion weight " +
3867
+ "(~0.008-0.016 at the top), NOT a normalized 0..1 confidence — use it only to compare " +
3868
+ "RANK/order within one response; for an absolute 0..1 confidence use knowledge_hybrid_search " +
3869
+ "(meta.confidence). " +
3783
3870
  "Pass story_id when working on a loopctl story so reads attribute correctly. " +
3784
3871
  "When you knowledge_get a result and it carries `potential_conflicts`, resolve it if it's " +
3785
3872
  "material to your task (see knowledge_get / the conflict-resolution wiki playbook). " +
@@ -5937,6 +6024,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5937
6024
  case "knowledge_index":
5938
6025
  return await knowledgeIndex(args);
5939
6026
 
6027
+ case "embedding_status":
6028
+ return await embeddingStatus();
6029
+
6030
+ case "embedding_materialize_system_corpus":
6031
+ return await embeddingMaterializeSystemCorpus();
6032
+
6033
+ case "embedding_reembed":
6034
+ return await embeddingReembed(args);
6035
+
5940
6036
  case "knowledge_stats":
5941
6037
  return await knowledgeStats(args);
5942
6038
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.55.0",
3
+ "version": "2.57.0",
4
4
  "description": "MCP server for loopctl \u2014 structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",