loopctl-mcp-server 2.37.0 → 2.39.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 +5 -3
  2. package/index.js +120 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -121,7 +121,7 @@ REST endpoint (`PATCH /api/v1/tenants/me/llm-config`), and the docs — so you (
121
121
  autonomous agent) can self-remediate without a human. Full agent-tenant lifecycle:
122
122
  [`docs/onboarding-agent-tenant.md`](../docs/onboarding-agent-tenant.md).
123
123
 
124
- ## Tools (82)
124
+ ## Tools (84)
125
125
 
126
126
  ### Project Tools
127
127
 
@@ -214,6 +214,7 @@ autonomous agent) can self-remediate without a human. Full agent-tenant lifecycl
214
214
  | `knowledge_conflicts` | List potential-conflict article pairs — published articles flagged "too similar to comfortably coexist" by the auto-linker / nightly lint sweep, highest-overlap first. The KB only FLAGS the pair; it does NOT decide redundancy-vs-contradiction — that's your call with live context. Each entry has both articles (id/title/status/category) + similarity. Then merge (supersede one, `knowledge_create` the merged article, or PATCH) or reconcile if they genuinely disagree. Paginated with `total_count` in meta. Agent role. Optional: `limit` (default 50, max 1000, clamped), `offset`. |
215
215
  | `knowledge_resolve_conflict` | Record YOUR verdict on a potential-conflict pair (from `knowledge_conflicts`). Dispositions: `dismiss` (false positive, drops from queue), `supersede` (one wins — pass `authoritative_article_id`; nightly executor links + retires loser, only at `confidence:"high"`, reversible/audited), `merge` (at high confidence an LLM synthesizes both into ONE new DRAFT, sources preserved, never auto-published). Non-destructive at agent role — you record intent, the privileged nightly job executes. Last-write-wins per pair. Required: `source_article_id`, `target_article_id`, `disposition`. Optional: `authoritative_article_id`, `classification`, `evidence`, `confidence`. |
216
216
  | `knowledge_create` | Create a new knowledge article. File findings, document patterns, or record decisions. **Published immediately by default** (visible per `metadata.visibility` — default `owner` for agent authors, only visible to that agent; `shared` for visibility to all agents) — the response `note` says which outcome occurred. Pass `draft: true` to stage it for later review instead (publish afterwards with `knowledge_publish`). Pass `metadata: {visibility: "shared"}` to make the article visible to other agents; higher roles can set visibility and agent_id explicitly. Pass `idempotency_key` for idempotent capture (re-creating with the same key is a no-op returning the existing article — no partial duplicates). Optional: `category`, `tags`, `project_id`, `draft`, `idempotency_key`, `source_type`, `source_id`, `metadata`. |
217
+ | `knowledge_update` | Edit an EXISTING article IN PLACE, **preserving its ID** (IDs are load-bearing — cited in project CLAUDE.mds and cross-links). Fold in a new fact, tidy a hub, retag, or reclassify without churning a new row. Send only the fields to change; `tags` REPLACES the whole array. A changed body/tags re-triggers embedding + auto-linking. Agent role — KB-content curation (reversible + audited); visibility-scoped, so another agent's private/owner memory 404s. Required: `article_id`. Optional: `title`, `body`, `category`, `tags`, `metadata`. |
217
218
  | `knowledge_okf_export` | **Requires `LOOPCTL_USER_KEY`.** Export the wiki as a portable OKF (Open Knowledge Format) v0.1 bundle of markdown files. Writes to `out_dir`, or returns `{files, meta}` inline. |
218
219
  | `knowledge_okf_import` | **Requires `LOOPCTL_USER_KEY`.** Import an OKF v0.1 bundle from a local directory. Creates or (with `merge`) updates articles; tolerates and preserves unknown frontmatter. |
219
220
 
@@ -233,6 +234,7 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
233
234
  | `memory_recall` | Semantically recall your own long-term memories most similar to `query`. When embedding generation is unavailable the response degrades to a recent-first text match with `meta.fallback: true` and a stable `meta.reason` (score is `null` on that path) — check `meta.fallback` before treating a short/empty result as a genuinely empty scope. `meta.total_count`/`meta.underfilled` are also returned. Optional: `limit`, `include_superseded`. |
234
235
  | `memory_list` | List your own long-term memories, newest first, paginated with `meta.total_count/limit/offset` (the true scoped count, never silently capped by `limit`). Optional: `limit`, `offset`, `include_superseded`, `all_subjects` (superadmin only; ignored for non-superadmin keys). |
235
236
  | `memory_forget` | Delete one of your own long-term memories by id. A foreign-subject, foreign-tenant, or unknown id returns 404 (no existence leak). Required: `id`. |
237
+ | `memory_promote` | Call at session end to compile this session's short-term (`session`-tier) memory into durable `long_term` memory — unlike `memory_remember` (a single explicit write), this compiles the whole session in one shot; fire it once at session end, not per turn. Returns 202 with `{job_id, session_id, status: "enqueued"}` — promotion runs asynchronously, so the resulting memory is recallable via `memory_recall` only after the worker drains. You can only promote your own sessions (scope resolved server-side from your key). Required: `session_id`. |
236
238
 
237
239
  ### Knowledge Management Tools (orchestrator key)
238
240
 
@@ -242,8 +244,8 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
242
244
  | `knowledge_bulk_publish` | **Requires `LOOPCTL_USER_KEY`.** Publish drafts, partial-success style: every valid draft publishes; others are reported per-id as `skipped` (already published — idempotent — or archived/superseded), `not_found`, or `errored`. No 100-id cap (auto-chunked); duplicates ignored; safe to retry. `meta.count` = published; `meta.counts`/`meta.results` give the breakdown. Required: `article_ids` (array). |
243
245
  | `knowledge_unpublish` | **Requires `LOOPCTL_USER_KEY`.** Revert a published article back to draft (hidden from search/context, not deleted). Required: `article_id`. |
244
246
  | `knowledge_bulk_unpublish` | **Requires `LOOPCTL_USER_KEY`.** Revert published articles to draft in bulk, partial-success style (mirror of `knowledge_bulk_publish`): per-id `unpublished`/`skipped` (already draft, or archived/superseded)/`not_found`/`errored`. No 100-id cap (auto-chunked, ≤5000); duplicates ignored; safe to retry. Not deleted (re-publish to restore; `knowledge_bulk_delete` to archive). `meta.count`/`meta.counts`/`meta.results` give the breakdown. Required: `article_ids` (array). |
245
- | `knowledge_archive` | **Requires `LOOPCTL_USER_KEY`.** Soft-delete an article (draft or published). Row retained for audit; hidden from all reads. Required: `article_id`. |
246
- | `knowledge_delete` | **Requires `LOOPCTL_USER_KEY`.** Alias for `knowledge_archive` — DELETE verb on the REST API archives under the hood. Required: `article_id`. |
247
+ | `knowledge_archive` | Soft-delete an article (draft or published). Row retained for audit; hidden from all reads (reversible — re-publish/edit it back). Agent role — KB-content curation, visibility-scoped (another agent's private/owner memory 404s). Required: `article_id`. |
248
+ | `knowledge_delete` | Alias for `knowledge_archive` — DELETE verb on the REST API archives under the hood (soft delete, reversible, audited). Agent role. (Irreversible HARD delete is `knowledge_bulk_delete hard:true`, which stays `LOOPCTL_USER_KEY`.) Required: `article_id`. |
247
249
  | `knowledge_bulk_delete` | **Requires `LOOPCTL_USER_KEY`.** Bulk archive (default, reversible) or IRREVERSIBLE hard-delete by selector. Provide exactly one selector: `article_ids` (list), `source_type`+`source_id` (every active article from a source), or `tag`+`confirm:true` (every active article with the tag — high blast radius). Default = set-based soft archive (idempotent; `meta.count`=archived, `meta.counts`/`meta.results` give the breakdown; ≤5000). **Dry-run** (`dry_run:true`) mutates nothing, returns `meta.would_affect` (with `hard:true` also a single-use `meta.token`, or `meta.confirm_hash` for oversized selectors). **Hard delete** (irreversible): dry-run with `hard:true` for a token, then call again with `hard:true`+`token` to FK-correctly delete the frozen id-set (links first, access events cascade). |
248
250
  | `knowledge_drafts` | List draft (unpublished) knowledge articles with pagination. Optional: `limit` (default 20, max 1000 — over-max → 400, no silent clamp), `offset` (default 0), `project_id`. Returns `meta.total_count`. |
249
251
  | `knowledge_lint` | Run a lint check on the knowledge wiki to identify stale or low-coverage articles. Optional: `project_id`, `stale_days`, `min_coverage`, `max_per_category` (default 50, max 500). True totals returned in `summary.total_per_category`. |
package/index.js CHANGED
@@ -1065,6 +1065,26 @@ async function knowledgeCreate({
1065
1065
  return toContent(result);
1066
1066
  }
1067
1067
 
1068
+ // In-place edit of an existing article (#331). PATCH preserves the ID; only the
1069
+ // provided fields change. Agent role — KB-content curation, visibility-scoped
1070
+ // server-side (another agent's private/owner memory 404s).
1071
+ async function knowledgeUpdate({ article_id, title, body, category, tags, metadata }) {
1072
+ const payload = {};
1073
+ if (title != null) payload.title = title;
1074
+ if (body != null) payload.body = body;
1075
+ if (category != null) payload.category = category;
1076
+ if (tags != null) payload.tags = tags;
1077
+ if (metadata != null) payload.metadata = metadata;
1078
+
1079
+ const result = await apiCall(
1080
+ "PATCH",
1081
+ `/api/v1/articles/${article_id}`,
1082
+ payload,
1083
+ process.env.LOOPCTL_AGENT_KEY,
1084
+ );
1085
+ return toContent(result);
1086
+ }
1087
+
1068
1088
  // --- Agent Memory Tools (US-28.4) ---
1069
1089
  //
1070
1090
  // memory_* is YOUR own scoped, private, accumulated working state — recall
@@ -1159,6 +1179,17 @@ async function memoryForget({ id }) {
1159
1179
  return toContent(result);
1160
1180
  }
1161
1181
 
1182
+ async function memoryPromote({ session_id }) {
1183
+ const payload = { session_id };
1184
+ const result = await apiCall(
1185
+ "POST",
1186
+ "/api/v1/memory/promote",
1187
+ payload,
1188
+ process.env.LOOPCTL_AGENT_KEY,
1189
+ );
1190
+ return toContent(result);
1191
+ }
1192
+
1162
1193
  // --- Knowledge Management Tools (orch key) ---
1163
1194
 
1164
1195
  async function knowledgePublish({ article_id }) {
@@ -1226,22 +1257,25 @@ async function knowledgeUnpublish({ article_id }) {
1226
1257
  return toContent(result);
1227
1258
  }
1228
1259
 
1260
+ // #331: single-article archive is agent-role KB curation (reversible soft delete,
1261
+ // audited, visibility-scoped server-side).
1229
1262
  async function knowledgeArchive({ article_id }) {
1230
1263
  const result = await apiCall(
1231
1264
  "POST",
1232
1265
  `/api/v1/articles/${article_id}/archive`,
1233
1266
  null,
1234
- process.env.LOOPCTL_USER_KEY
1267
+ process.env.LOOPCTL_AGENT_KEY
1235
1268
  );
1236
1269
  return toContent(result);
1237
1270
  }
1238
1271
 
1272
+ // #331: soft-delete (archive) is agent-role KB curation, same as knowledge_archive.
1239
1273
  async function knowledgeDelete({ article_id }) {
1240
1274
  const result = await apiCall(
1241
1275
  "DELETE",
1242
1276
  `/api/v1/articles/${article_id}`,
1243
1277
  null,
1244
- process.env.LOOPCTL_USER_KEY
1278
+ process.env.LOOPCTL_AGENT_KEY
1245
1279
  );
1246
1280
  return toContent(result);
1247
1281
  }
@@ -3146,6 +3180,54 @@ const TOOLS = [
3146
3180
  required: ["title", "body"],
3147
3181
  },
3148
3182
  },
3183
+ {
3184
+ name: "knowledge_update",
3185
+ description:
3186
+ "Edit an EXISTING knowledge article IN PLACE, preserving its ID. Use this to fold a " +
3187
+ "new fact into a canonical article, tidy a hub, retag, or reclassify — WITHOUT " +
3188
+ "creating a new row and churning the article ID (IDs are load-bearing: cited in " +
3189
+ "project CLAUDE.mds and cross-links). Send only the fields you want to change; every " +
3190
+ "field is optional except article_id. `tags` REPLACES the whole array (send the full " +
3191
+ "desired set, not a delta). A changed body/tags re-triggers embedding + auto-linking. " +
3192
+ "Agent role — this is KB-content curation (reversible + audited). Visibility-scoped: " +
3193
+ "you can only edit an article you can see, so another agent's private/owner memory " +
3194
+ "returns 404. `tenant_id` is never accepted. Returns the full updated article. To " +
3195
+ "instead retire/replace an article, use knowledge_archive or knowledge_resolve_conflict.",
3196
+ inputSchema: {
3197
+ type: "object",
3198
+ properties: {
3199
+ article_id: {
3200
+ type: "string",
3201
+ description: "The UUID of the article to edit (preserved across the update).",
3202
+ },
3203
+ title: {
3204
+ type: "string",
3205
+ description: "Optional: new title.",
3206
+ },
3207
+ body: {
3208
+ type: "string",
3209
+ description: "Optional: new body (Markdown). Re-triggers embedding + linking.",
3210
+ },
3211
+ category: {
3212
+ type: "string",
3213
+ description: "Optional: new category.",
3214
+ },
3215
+ tags: {
3216
+ type: "array",
3217
+ items: { type: "string" },
3218
+ description: "Optional: REPLACES the whole tags array (send the full desired set).",
3219
+ },
3220
+ metadata: {
3221
+ type: "object",
3222
+ description:
3223
+ "Optional: extensible JSONB (merged/replaced wholesale). For an agent key, an " +
3224
+ "agent-memory `agent_id` is stamped from your verified key identity — you cannot " +
3225
+ "re-attribute a memory to another agent.",
3226
+ },
3227
+ },
3228
+ required: ["article_id"],
3229
+ },
3230
+ },
3149
3231
 
3150
3232
  // Agent Memory Tools (US-28.4)
3151
3233
  {
@@ -3298,6 +3380,29 @@ const TOOLS = [
3298
3380
  required: ["id"],
3299
3381
  },
3300
3382
  },
3383
+ {
3384
+ name: "memory_promote",
3385
+ description:
3386
+ "Call at session end to compile this session's short-term memory into durable long-term memory" +
3387
+ " — private, scoped working state, NOT the shared knowledge wiki. Unlike " +
3388
+ "memory_remember, which writes a single explicit fact, this compiles the WHOLE session's " +
3389
+ "session-tier memory in one shot — fire it once at session end, not per turn. Scope " +
3390
+ "(tenant_id/subject_id) is resolved server-side from your API key: you can only promote " +
3391
+ "your OWN sessions. Returns 202 Accepted with `{job_id, session_id, status: \"enqueued\"}` " +
3392
+ "— promotion runs asynchronously via a background worker, so the resulting long-term " +
3393
+ "memory becomes recallable via memory_recall only after that worker drains, not " +
3394
+ "immediately.",
3395
+ inputSchema: {
3396
+ type: "object",
3397
+ properties: {
3398
+ session_id: {
3399
+ type: "string",
3400
+ description: "The session whose short-term memory to compile into long-term memory.",
3401
+ },
3402
+ },
3403
+ required: ["session_id"],
3404
+ },
3405
+ },
3301
3406
 
3302
3407
  // Knowledge Management Tools (orchestrator key)
3303
3408
  {
@@ -3387,9 +3492,10 @@ const TOOLS = [
3387
3492
  name: "knowledge_archive",
3388
3493
  description:
3389
3494
  "Archive an article (soft delete). The article is hidden from search, context, " +
3390
- "and the index but the row is retained for audit/history. Works for drafts and " +
3391
- "published articles. REQUIRES LOOPCTL_USER_KEY (user role orchestrator role is " +
3392
- "NOT sufficient for this destructive operation).",
3495
+ "and the index but the row is retained for audit/history (reversible re-publish " +
3496
+ "or edit it back). Works for drafts and published articles. Agent role KB-content " +
3497
+ "curation. Visibility-scoped: you can only archive an article you can see, so " +
3498
+ "another agent's private/owner memory returns 404.",
3393
3499
  inputSchema: {
3394
3500
  type: "object",
3395
3501
  properties: {
@@ -3406,8 +3512,9 @@ const TOOLS = [
3406
3512
  description:
3407
3513
  "Delete an article. Under the hood this performs the same soft-delete (archive) " +
3408
3514
  "as knowledge_archive — use whichever name is clearer at the call site. The row " +
3409
- "is retained for audit; there is no hard delete. REQUIRES LOOPCTL_USER_KEY (user " +
3410
- "role orchestrator role is NOT sufficient for this destructive operation).",
3515
+ "is retained for audit; there is no hard delete (that is knowledge_bulk_delete " +
3516
+ "hard:true, which stays user-gated). Agent role KB-content curation, reversible + " +
3517
+ "audited, visibility-scoped (another agent's private/owner memory 404s).",
3411
3518
  inputSchema: {
3412
3519
  type: "object",
3413
3520
  properties: {
@@ -4510,6 +4617,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4510
4617
  case "knowledge_create":
4511
4618
  return await knowledgeCreate(args);
4512
4619
 
4620
+ case "knowledge_update":
4621
+ return await knowledgeUpdate(args);
4622
+
4513
4623
  // Agent Memory Tools
4514
4624
  case "memory_remember":
4515
4625
  return await memoryRemember(args);
@@ -4523,6 +4633,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4523
4633
  case "memory_forget":
4524
4634
  return await memoryForget(args);
4525
4635
 
4636
+ case "memory_promote":
4637
+ return await memoryPromote(args);
4638
+
4526
4639
  // Knowledge Management Tools
4527
4640
  case "knowledge_publish":
4528
4641
  return await knowledgePublish(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.37.0",
3
+ "version": "2.39.0",
4
4
  "description": "MCP server for loopctl — structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",