loopctl-mcp-server 2.23.0 → 2.25.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 (2) hide show
  1. package/index.js +35 -4
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -848,6 +848,7 @@ async function knowledgeCreate({
848
848
  tags,
849
849
  project_id,
850
850
  draft,
851
+ force,
851
852
  source_type,
852
853
  source_id,
853
854
  idempotency_key,
@@ -868,6 +869,10 @@ async function knowledgeCreate({
868
869
  // knowledge_publish.
869
870
  if (draft) payload.draft = true;
870
871
 
872
+ // The server-side novelty gate dedups the proposal against the corpus (verdict in
873
+ // the response `gate`). force:true bypasses it.
874
+ if (force) payload.force = true;
875
+
871
876
  const result = await apiCall(
872
877
  "POST",
873
878
  "/api/v1/articles",
@@ -1097,9 +1102,10 @@ async function knowledgeIngestionJobs({ limit, offset, since_days } = {}) {
1097
1102
 
1098
1103
  // --- Knowledge Analytics Tools (orch key) ---
1099
1104
 
1100
- async function knowledgeAnalyticsTop({ limit, since_days, access_type } = {}) {
1105
+ async function knowledgeAnalyticsTop({ limit, offset, since_days, access_type } = {}) {
1101
1106
  const params = new URLSearchParams();
1102
1107
  if (limit != null) params.set("limit", String(limit));
1108
+ if (offset != null) params.set("offset", String(offset));
1103
1109
  if (since_days != null) params.set("since_days", String(since_days));
1104
1110
  if (access_type) params.set("access_type", access_type);
1105
1111
  const qs = params.toString();
@@ -1187,10 +1193,11 @@ async function knowledgeAgentUsage({ api_key_id, agent_id, limit, since_days } =
1187
1193
  return toContent(result);
1188
1194
  }
1189
1195
 
1190
- async function knowledgeUnusedArticles({ days_unused, limit } = {}) {
1196
+ async function knowledgeUnusedArticles({ days_unused, limit, offset } = {}) {
1191
1197
  const params = new URLSearchParams();
1192
1198
  if (days_unused != null) params.set("days_unused", String(days_unused));
1193
1199
  if (limit != null) params.set("limit", String(limit));
1200
+ if (offset != null) params.set("offset", String(offset));
1194
1201
  const qs = params.toString();
1195
1202
  const path = qs
1196
1203
  ? `/api/v1/knowledge/analytics/unused-articles?${qs}`
@@ -2563,6 +2570,13 @@ const TOOLS = [
2563
2570
  "to agents (search/index/context) right away — no separate publish step is needed. Pass " +
2564
2571
  "draft: true to stage the article for later review instead; the response `note` says which " +
2565
2572
  "outcome occurred, and a draft can be published afterwards with knowledge_publish. " +
2573
+ "NOVELTY GATE (default ON): the server semantically dedups your proposal against the published " +
2574
+ "corpus and returns a `gate.verdict` — `duplicate` means a near-identical article already exists, " +
2575
+ "so NOTHING was created (HTTP 200, `deduplicated: true`); read/update the article at `data.id` " +
2576
+ "instead (its `gate.similarity` ~1.0). `gated_to_draft` means high overlap, so the article was " +
2577
+ "created as a DRAFT (not published) with the near-neighbors in metadata.proposal_novelty for you " +
2578
+ "to merge or publish. `created` means it was novel and went through normally. Pass force: true to " +
2579
+ "bypass the gate when you intentionally want an article near an existing one. " +
2566
2580
  "Concurrency-safe: if a create races/retries against an " +
2567
2581
  "existing article with the same title AND an identical body (ignoring surrounding whitespace), the " +
2568
2582
  "server returns that existing article idempotently (HTTP 200) instead of a 422. A same-title create " +
@@ -2598,6 +2612,13 @@ const TOOLS = [
2598
2612
  "Optional: stage as a draft instead of publishing on create (default false → " +
2599
2613
  "published immediately). Publish later with knowledge_publish.",
2600
2614
  },
2615
+ force: {
2616
+ type: "boolean",
2617
+ description:
2618
+ "Optional: bypass the novelty gate (default false). When true, the server skips " +
2619
+ "semantic dedup and creates on the requested path even if a near-duplicate exists. " +
2620
+ "Use only when you've already checked and intend an article close to an existing one.",
2621
+ },
2601
2622
  idempotency_key: {
2602
2623
  type: "string",
2603
2624
  description:
@@ -3102,10 +3123,15 @@ const TOOLS = [
3102
3123
  properties: {
3103
3124
  limit: {
3104
3125
  type: "integer",
3105
- description: "Max rows to return. Default 20, max 100.",
3126
+ description: "Max rows per page. Default 20, max 100.",
3106
3127
  minimum: 1,
3107
3128
  maximum: 100,
3108
3129
  },
3130
+ offset: {
3131
+ type: "integer",
3132
+ description: "Rows to skip — page the ranking past the first page. Default 0.",
3133
+ minimum: 0,
3134
+ },
3109
3135
  since_days: {
3110
3136
  type: "integer",
3111
3137
  description: "Look back this many days. Default 7.",
@@ -3188,10 +3214,15 @@ const TOOLS = [
3188
3214
  },
3189
3215
  limit: {
3190
3216
  type: "integer",
3191
- description: "Max rows to return. Default 50, max 200.",
3217
+ description: "Max rows per page. Default 50, max 200.",
3192
3218
  minimum: 1,
3193
3219
  maximum: 200,
3194
3220
  },
3221
+ offset: {
3222
+ type: "integer",
3223
+ description: "Rows to skip — page the full unused set to completeness. Default 0.",
3224
+ minimum: 0,
3225
+ },
3195
3226
  },
3196
3227
  required: [],
3197
3228
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.23.0",
3
+ "version": "2.25.0",
4
4
  "description": "MCP server for loopctl — structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",