loopctl-mcp-server 2.73.0 → 2.74.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.
package/index.js CHANGED
@@ -1497,9 +1497,16 @@ async function knowledgeFacets({
1497
1497
  return toContent(result);
1498
1498
  }
1499
1499
 
1500
- async function knowledgeSearch({ q, project_id, story_id, category, tags, match, mode, limit, offset }) {
1500
+ async function knowledgeSearch({ q, project_id, story_id, category, tags, match, mode, format, limit, offset }) {
1501
1501
  const params = new URLSearchParams();
1502
1502
  if (q != null && q !== "") params.set("q", q);
1503
+ // `format` is the SHAPE of the response, not a different search (#678). The server
1504
+ // dispatches `stubs` to the same progressive_index/3 and `bodies` to the same
1505
+ // get_context/3 that knowledge_progressive_index and knowledge_context call, so those
1506
+ // tools remain and are not retired — they are now siblings on one path rather than
1507
+ // separate doors an agent has to choose between. That choice was unobservable and
1508
+ // therefore confounded any measurement of the ranking behind it.
1509
+ if (format) params.set("format", format);
1503
1510
  if (project_id) params.set("project_id", project_id);
1504
1511
  if (story_id) params.set("story_id", story_id);
1505
1512
  if (category) params.set("category", category);
@@ -4616,6 +4623,21 @@ const TOOLS = [
4616
4623
  enum: ["keyword", "semantic", "combined"],
4617
4624
  description: "Optional: search mode (keyword, semantic, or combined).",
4618
4625
  },
4626
+ format: {
4627
+ type: "string",
4628
+ enum: ["results", "stubs", "bodies"],
4629
+ description:
4630
+ "Optional: the SHAPE of the response, not a different search. 'results' " +
4631
+ "(default) is ranked results plus snippets and is the only shape that " +
4632
+ "supports cursor pagination. 'stubs' returns capped stubs with one hop of hub " +
4633
+ "enrichment — use it to survey a broad topic without pulling bodies into " +
4634
+ "context, then knowledge_progressive_drill into a chosen stub. 'bodies' " +
4635
+ "returns full article bodies plus linked references for one deep read. " +
4636
+ "'stubs' and 'bodies' REQUIRE a query; sending either without one is a 400, " +
4637
+ "as is an unknown value (it is never silently downgraded to 'results'). These " +
4638
+ "dispatch to exactly the same code knowledge_progressive_index and " +
4639
+ "knowledge_context call, which both remain available.",
4640
+ },
4619
4641
  limit: {
4620
4642
  type: "integer",
4621
4643
  description: "Optional: maximum number of results to return.",
@@ -4839,8 +4861,10 @@ const TOOLS = [
4839
4861
  properties: {
4840
4862
  article_id: {
4841
4863
  type: "string",
4842
- format: "uuid",
4843
- description: "The UUID of the article.",
4864
+ description:
4865
+ "The UUID of the article. A unique ID PREFIX (>= 8 hex characters) also " +
4866
+ "resolves, so copy what you have rather than reconstructing 36 characters " +
4867
+ "from memory; an ambiguous prefix is a 404, never a guess.",
4844
4868
  },
4845
4869
  links: {
4846
4870
  type: "string",
@@ -12,18 +12,34 @@
12
12
  * remains the sole authority. The server stores these under a `client_` prefix so no later
13
13
  * reader mistakes them for server-derived facts.
14
14
  *
15
- * WHAT IS ACTUALLY AVAILABLE (verified on a live session, 2026-08-12):
15
+ * WHAT IS ACTUALLY AVAILABLE re-measured 2026-08-12 by reading /proc/<mcp-pid>/environ on
16
+ * a live session, which corrected two entries an earlier pass got wrong:
16
17
  *
17
- * CLAUDE_EFFORT=high -> effort, available
18
- * CLAUDE_SESSION_ID=<uuid> -> session id, available
19
- * CLAUDE_CODE_CHILD_SESSION=1 -> subagent vs main session, available
18
+ * CLAUDE_CODE_SESSION_ID=<uuid> -> session id, available
20
19
  * CLAUDE_CODE_ENTRYPOINT=cli -> entrypoint, available
20
+ * CLAUDE_SESSION_ID -> ABSENT (the CODE_ spelling is the one that is set)
21
+ * CLAUDE_EFFORT -> ABSENT from THIS process. It is set for Bash-tool
22
+ * invocations, which is where the earlier claim that
23
+ * it was "available" came from; the MCP server does
24
+ * not get it, so `effort` is enriched offline.
25
+ * CLAUDE_CODE_CHILD_SESSION -> ABSENT, and it would not mean what it looks like:
26
+ * it is set to 1 for Bash-tool invocations of a MAIN
27
+ * session, so it marks "a child PROCESS", not "a
28
+ * dispatched agent".
21
29
  * (no model variable) -> MODEL IS NOT AVAILABLE
22
30
  *
23
- * The model is deliberately still sent when a variable for it appears, because the session
24
- * TRANSCRIPT does record it (`message.model`, e.g. `claude-opus-5`) keyed by session id
25
- * so `client_session_id` is the join key that enriches model offline today, and the field
26
- * fills itself in the day the runtime exposes one.
31
+ * THE KIND REPORTED HERE IS THE SESSION'S, NOT THE CALLER'S. One MCP server process is
32
+ * spawned per session and serves the main session AND every agent it dispatches, and the
33
+ * environment above is read once and cached for the life of that process. So `kind` is a
34
+ * property of the session, and every search it labels comes back `main`. The three-way
35
+ * main/subagent/workflow split a measurement actually needs is recoverable only from the
36
+ * transcript, where `isSidechain` plus the file's path give it unambiguously.
37
+ *
38
+ * Two fields are therefore sent as JOIN KEYS rather than as answers: `session_id` is what
39
+ * lets `mix loopctl.enrich_search_events` find the transcript that records the model, the
40
+ * effort and the real kind. Note that a RESUMED session breaks even that — the new process
41
+ * reports a fresh session id while the transcript keeps appending under the original — so
42
+ * the enrichment carries a query-only fallback for exactly that case.
27
43
  */
28
44
 
29
45
  import { execFileSync } from "node:child_process";
@@ -87,15 +103,17 @@ function clientContext({ version } = {}) {
87
103
  version,
88
104
  };
89
105
 
90
- // main vs child. A dispatched agent sets CLAUDE_CODE_CHILD_SESSION=1.
106
+ // The SESSION's kind — see the header. This process is shared by the main session and
107
+ // every agent it dispatches, so in practice this resolves to "main" for all of them and
108
+ // is NOT caller-level evidence. It is still worth sending: it is the only thing available
109
+ // before the offline enrichment runs, and the enrichment treats it as an assertion to be
110
+ // corrected rather than as a value to be preserved.
91
111
  //
92
- // Only TWO values, on purpose. A workflow agent cannot be distinguished from an ordinary
93
- // subagent here: CLAUDE_CODE_WORKFLOWS is a feature flag that is present in main sessions
94
- // too, and nothing else marks one. Since an audit measured materially different failure
95
- // rates across main/workflow/subagent, the third value matters but it is recoverable
96
- // only OFFLINE, by joining session_id to the transcript path (wf_* vs subagents/). Report
97
- // what is observable and let the join supply the rest; do not guess a value that would
98
- // then be analysed as fact.
112
+ // Do not try to widen it to three values from the environment. CLAUDE_CODE_WORKFLOWS is a
113
+ // feature flag present in main sessions too, and CLAUDE_CODE_CHILD_SESSION marks a child
114
+ // PROCESS rather than a dispatched agent. Guessing here would be worse than the null it
115
+ // replaces, because the number this column exists to support is a comparison BETWEEN
116
+ // kinds.
99
117
  const child = env("CLAUDE_CODE_CHILD_SESSION");
100
118
  if (child !== undefined) {
101
119
  ctx.kind = child === "1" || child.toLowerCase() === "true" ? "child" : "main";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.73.0",
3
+ "version": "2.74.0",
4
4
  "description": "MCP server for loopctl \u2014 structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",