@contextstream/mcp-server 0.4.58 → 0.4.60

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/dist/index.js CHANGED
@@ -958,7 +958,7 @@ var init_rules_templates = __esm({
958
958
  "generate_rules",
959
959
  // Consolidated domain tools (v0.4.x default)
960
960
  "search",
961
- // Modes: semantic, hybrid, keyword, pattern
961
+ // Modes: auto (recommended), semantic, hybrid (legacy alias), keyword, pattern
962
962
  "session",
963
963
  // Actions: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search, decision_trace
964
964
  "memory",
@@ -982,7 +982,7 @@ var init_rules_templates = __esm({
982
982
  |---------|----------|
983
983
  | **1st message** | \`init()\` \u2192 \`context(user_message="...")\` |
984
984
  | **Every message** | \`context(user_message="...")\` FIRST |
985
- | **Before file search** | \`search(mode="hybrid")\` BEFORE Glob/Grep/Read |
985
+ | **Before file search** | \`search(mode="auto")\` BEFORE Glob/Grep/Read |
986
986
  </contextstream_rules>
987
987
 
988
988
  **Why?** \`context()\` delivers task-specific rules, lessons from past mistakes, and relevant decisions. Skip it = fly blind.
@@ -1005,7 +1005,7 @@ v${RULES_VERSION}
1005
1005
  | 1st message | \`init()\` \u2192 \`context(user_message="<msg>")\` |
1006
1006
  | **EVERY message after** | \`context(user_message="<msg>")\` **FIRST** |
1007
1007
 
1008
- **BEFORE Glob/Grep/Read/Search:** \u2192 \`search(mode="hybrid")\` FIRST
1008
+ **BEFORE Glob/Grep/Read/Search:** \u2192 \`search(mode="auto")\` FIRST
1009
1009
  </contextstream_rules>
1010
1010
 
1011
1011
  **This block MUST appear at the start of EVERY response.** Failing to call \`context()\` means missing rules, lessons, and relevant context.
@@ -1057,7 +1057,7 @@ v${RULES_VERSION}
1057
1057
  - Example: \`[CONTEXTSTREAM RULES] 1. BEFORE Glob/Grep... [END RULES]\`
1058
1058
 
1059
1059
  2. **PreToolUse blocking** - If you try to use Glob/Grep/Search/Explore:
1060
- - Hook returns error: \`STOP: Use mcp__contextstream__search(mode="hybrid") instead\`
1060
+ - Hook returns error: \`STOP: Use mcp__contextstream__search(mode="auto") instead\`
1061
1061
  - **You MUST use the suggested ContextStream tool instead**
1062
1062
  - Local tools are only allowed if project is not indexed or ContextStream returns 0 results
1063
1063
 
@@ -1081,7 +1081,7 @@ Set environment variable: \`CONTEXTSTREAM_HOOK_ENABLED=false\`
1081
1081
 
1082
1082
  **BEFORE using Glob, Grep, Search, Read (for discovery), Explore, or ANY local file scanning:**
1083
1083
  \`\`\`
1084
- STOP \u2192 Call search(mode="hybrid", query="...") FIRST
1084
+ STOP \u2192 Call search(mode="auto", query="...") FIRST
1085
1085
  \`\`\`
1086
1086
 
1087
1087
  **Note:** PreToolUse hooks block these tools when ContextStream is available.
@@ -1090,11 +1090,11 @@ STOP \u2192 Call search(mode="hybrid", query="...") FIRST
1090
1090
  \u274C **NEVER DO THIS:**
1091
1091
  - \`Glob("**/*.ts")\` \u2192 Use \`search(mode="pattern", query="*.ts")\` instead
1092
1092
  - \`Grep("functionName")\` \u2192 Use \`search(mode="keyword", query="functionName")\` instead
1093
- - \`Read(file)\` for discovery \u2192 Use \`search(mode="hybrid", query="...")\` instead
1094
- - \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="hybrid")\` instead
1093
+ - \`Read(file)\` for discovery \u2192 Use \`search(mode="auto", query="...")\` instead
1094
+ - \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="auto")\` instead
1095
1095
 
1096
1096
  \u2705 **ALWAYS DO THIS:**
1097
- 1. \`search(mode="hybrid", query="what you're looking for")\`
1097
+ 1. \`search(mode="auto", query="what you're looking for")\`
1098
1098
  2. Only use local tools (Glob/Grep/Read) if ContextStream returns **0 results**
1099
1099
  3. Use Read ONLY for exact file edits after you know the file path
1100
1100
 
@@ -1165,7 +1165,7 @@ Rules Version: ${RULES_VERSION}
1165
1165
  |---------|----------|
1166
1166
  | **1st message** | \`init()\` \u2192 \`context(user_message="<msg>")\` |
1167
1167
  | **EVERY message after** | \`context(user_message="<msg>")\` **FIRST** |
1168
- | **Before file search** | \`search(mode="hybrid")\` FIRST |
1168
+ | **Before file search** | \`search(mode="auto")\` FIRST |
1169
1169
  | **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
1170
1170
  | **User correction** | \`session(action="capture_lesson", ...)\` |
1171
1171
 
@@ -1185,7 +1185,7 @@ Rules Version: ${RULES_VERSION}
1185
1185
  |------|--------------|
1186
1186
  | **1st message** | \`init(folder_path="...", context_hint="<msg>")\`, then \`context(...)\` |
1187
1187
  | **2nd+ messages** | \`context(user_message="<msg>", format="minified", max_tokens=400)\` |
1188
- | **Code search** | \`search(mode="hybrid", query="...")\` \u2014 BEFORE Glob/Grep/Read |
1188
+ | **Code search** | \`search(mode="auto", query="...")\` \u2014 BEFORE Glob/Grep/Read |
1189
1189
  | **After significant work** | \`session(action="capture", event_type="decision", ...)\` |
1190
1190
  | **User correction** | \`session(action="capture_lesson", ...)\` |
1191
1191
  | **\u26A0\uFE0F When warnings received** | **STOP**, acknowledge, explain mitigation, then proceed |
@@ -1212,7 +1212,7 @@ v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode
1212
1212
 
1213
1213
  | Domain | Actions/Modes | Example |
1214
1214
  |--------|---------------|---------|
1215
- | **\`search\`** | mode: semantic, hybrid, keyword, pattern | \`search(mode="hybrid", query="auth implementation", limit=3)\` |
1215
+ | **\`search\`** | mode: auto (recommended), semantic, hybrid (legacy alias), keyword, pattern | \`search(mode="auto", query="auth implementation", limit=3)\` |
1216
1216
  | **\`session\`** | action: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search, decision_trace | \`session(action="capture", event_type="decision", title="Use JWT", content="...")\` |
1217
1217
  | **\`memory\`** | action: create_event, get_event, update_event, delete_event, list_events, distill_event, create_node, get_node, update_node, delete_node, list_nodes, supersede_node, search, decisions, timeline, summary | \`memory(action="list_events", limit=10)\` |
1218
1218
  | **\`graph\`** | action: dependencies, impact, call_path, related, path, decisions, ingest, circular_dependencies, unused_code, contradictions | \`graph(action="impact", symbol_name="AuthService")\` |
@@ -1358,7 +1358,7 @@ session(action="capture", event_type="session_snapshot", title="Pre-compaction s
1358
1358
 
1359
1359
  ### Search & Code Intelligence (ContextStream-first)
1360
1360
 
1361
- \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="hybrid")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
1361
+ \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="auto")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
1362
1362
 
1363
1363
  **\u274C WRONG workflow (wastes tokens, slow):**
1364
1364
  \`\`\`
@@ -1367,14 +1367,14 @@ Grep "function" \u2192 Read file1.ts \u2192 Read file2.ts \u2192 Read file3.ts \
1367
1367
 
1368
1368
  **\u2705 CORRECT workflow (fast, complete):**
1369
1369
  \`\`\`
1370
- search(mode="hybrid", query="function implementation") \u2192 done (results include context)
1370
+ search(mode="auto", query="function implementation") \u2192 done (results include context)
1371
1371
  \`\`\`
1372
1372
 
1373
1373
  **Why?** ContextStream search returns semantic matches + context + file locations in ONE call. Local tools require multiple round-trips.
1374
1374
 
1375
1375
  **Search order:**
1376
1376
  1. \`session(action="smart_search", query="...")\` - context-enriched
1377
- 2. \`search(mode="hybrid", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
1377
+ 2. \`search(mode="auto", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
1378
1378
  3. \`project(action="files")\` - file tree/list (only when needed)
1379
1379
  4. \`graph(action="dependencies", ...)\` - code structure
1380
1380
  5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
@@ -1383,7 +1383,7 @@ search(mode="hybrid", query="function implementation") \u2192 done (results incl
1383
1383
 
1384
1384
  | Need | Mode | Example |
1385
1385
  |------|------|---------|
1386
- | Find code by meaning | \`hybrid\` | "authentication logic", "error handling" |
1386
+ | Find code by meaning | \`auto\` | "authentication logic", "error handling" |
1387
1387
  | Exact string/symbol | \`keyword\` | "UserAuthService", "API_KEY" |
1388
1388
  | File patterns | \`pattern\` | "*.sql", "test_*.py" |
1389
1389
  | ALL matches (grep-like) | \`exhaustive\` | "TODO", "FIXME" (find all occurrences) |
@@ -1534,7 +1534,7 @@ See full documentation: https://contextstream.io/docs/mcp/tools
1534
1534
  2. \`context(user_message="<msg>")\` (EVERY subsequent message)
1535
1535
 
1536
1536
  **BEFORE Glob/Grep/Read/Search/Explore:**
1537
- \u2192 \`search(mode="hybrid", query="...")\` FIRST \u2014 local tools ONLY if 0 results
1537
+ \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 local tools ONLY if 0 results
1538
1538
 
1539
1539
  **HOOKS: \`<system-reminder>\` tags contain instructions \u2014 FOLLOW THEM**
1540
1540
  </contextstream_protocol>
@@ -1547,7 +1547,7 @@ Rules Version: ${RULES_VERSION}
1547
1547
 
1548
1548
  | \u274C WRONG | \u2705 CORRECT |
1549
1549
  |----------|-----------|
1550
- | \`Grep \u2192 Read \u2192 Read \u2192 Read\` (4+ calls) | \`search(mode="hybrid")\` (1 call) |
1550
+ | \`Grep \u2192 Read \u2192 Read \u2192 Read\` (4+ calls) | \`search(mode="auto")\` (1 call) |
1551
1551
  | Missing past decisions & lessons | \`context()\` = rules + lessons + memory |
1552
1552
  | Ignoring \`<system-reminder>\` hooks | Hooks enforce ContextStream-first |
1553
1553
 
@@ -1563,7 +1563,7 @@ Rules Version: ${RULES_VERSION}
1563
1563
  |------|------|
1564
1564
  | 1st message | \`init()\` \u2192 \`context(user_message="...")\` |
1565
1565
  | Every message after | \`context(user_message="...")\` |
1566
- | Before ANY file discovery | \`search(mode="hybrid", query="...")\` |
1566
+ | Before ANY file discovery | \`search(mode="auto", query="...")\` |
1567
1567
  | On \`<system-reminder>\` | **Follow instructions inside** |
1568
1568
  | Save important decisions | \`session(action="capture", event_type="decision", ...)\` |
1569
1569
  | Check past mistakes | \`session(action="get_lessons", query="...")\` |
@@ -1572,7 +1572,8 @@ Rules Version: ${RULES_VERSION}
1572
1572
 
1573
1573
  | Mode | When |
1574
1574
  |------|------|
1575
- | \`hybrid\` | Default \u2014 semantic + keyword |
1575
+ | \`auto\` | Default \u2014 semantic + keyword |
1576
+ | \`hybrid\` | Legacy alias for auto |
1576
1577
  | \`keyword\` | Exact symbol match |
1577
1578
  | \`exhaustive\` | Find ALL occurrences |
1578
1579
  | \`semantic\` | Conceptual questions |
@@ -1752,7 +1753,7 @@ This tells you:
1752
1753
 
1753
1754
  **IF project is indexed and fresh:**
1754
1755
  \`\`\`
1755
- search(mode="hybrid", query="what you're looking for")
1756
+ search(mode="auto", query="what you're looking for")
1756
1757
  \`\`\`
1757
1758
 
1758
1759
  **IF project is NOT indexed or very stale (>7 days):**
@@ -2829,7 +2830,7 @@ def main():
2829
2830
  if tool == "Glob":
2830
2831
  pattern = inp.get("pattern", "")
2831
2832
  if is_discovery_glob(pattern):
2832
- print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of Glob.", file=sys.stderr)
2833
+ print(f"STOP: Use mcp__contextstream__search(mode=\\"auto\\", query=\\"{pattern}\\") instead of Glob.", file=sys.stderr)
2833
2834
  sys.exit(2)
2834
2835
 
2835
2836
  elif tool == "Grep" or tool == "Search":
@@ -2841,12 +2842,12 @@ def main():
2841
2842
  # Specific file - suggest Read instead
2842
2843
  print(f"STOP: Use Read(\\"{path}\\") to view file content, or mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") for codebase search.", file=sys.stderr)
2843
2844
  else:
2844
- print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}.", file=sys.stderr)
2845
+ print(f"STOP: Use mcp__contextstream__search(mode=\\"auto\\", query=\\"{pattern}\\") instead of {tool}.", file=sys.stderr)
2845
2846
  sys.exit(2)
2846
2847
 
2847
2848
  elif tool == "Task":
2848
2849
  if inp.get("subagent_type", "").lower() == "explore":
2849
- print("STOP: Use mcp__contextstream__search(mode=\\"hybrid\\") instead of Task(Explore).", file=sys.stderr)
2850
+ print("STOP: Use mcp__contextstream__search(mode=\\"auto\\") instead of Task(Explore).", file=sys.stderr)
2850
2851
  sys.exit(2)
2851
2852
  if inp.get("subagent_type", "").lower() == "plan":
2852
2853
  print("STOP: Use mcp__contextstream__session(action=\\"capture_plan\\") for planning. ContextStream plans persist across sessions.", file=sys.stderr)
@@ -2873,7 +2874,7 @@ import os
2873
2874
  ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
2874
2875
 
2875
2876
  REMINDER = """[CONTEXTSTREAM RULES]
2876
- 1. BEFORE Glob/Grep/Read/Search: mcp__contextstream__search(mode="hybrid") FIRST
2877
+ 1. BEFORE Glob/Grep/Read/Search: mcp__contextstream__search(mode="auto") FIRST
2877
2878
  2. Call context_smart at start of EVERY response
2878
2879
  3. Local tools ONLY if ContextStream returns 0 results
2879
2880
  [END RULES]"""
@@ -3309,7 +3310,7 @@ def main():
3309
3310
  pattern = params.get("path", "") or params.get("regex", "")
3310
3311
  if is_discovery_glob(pattern) or is_discovery_grep(pattern):
3311
3312
  output_block(
3312
- f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
3313
+ f"Use mcp__contextstream__search(mode=\\"auto\\", query=\\"{pattern}\\") instead of {tool}. "
3313
3314
  "ContextStream search is indexed and faster. Only use local tools if ContextStream returns 0 results.",
3314
3315
  "[CONTEXTSTREAM] Use ContextStream search for code discovery."
3315
3316
  )
@@ -3336,7 +3337,7 @@ import os
3336
3337
  ENABLED = os.environ.get("CONTEXTSTREAM_REMINDER_ENABLED", "true").lower() == "true"
3337
3338
 
3338
3339
  REMINDER = """[CONTEXTSTREAM RULES]
3339
- 1. BEFORE list_files/search_files/read_file: mcp__contextstream__search(mode="hybrid") FIRST
3340
+ 1. BEFORE list_files/search_files/read_file: mcp__contextstream__search(mode="auto") FIRST
3340
3341
  2. Call context_smart at start of EVERY response
3341
3342
  3. Local tools ONLY if ContextStream returns 0 results
3342
3343
  [END RULES]"""
@@ -3487,7 +3488,7 @@ def main():
3487
3488
  pattern = params.get("pattern", "") or params.get("path", "")
3488
3489
  if is_discovery_glob(pattern):
3489
3490
  output_deny(
3490
- f"Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}. "
3491
+ f"Use mcp__contextstream__search(mode=\\"auto\\", query=\\"{pattern}\\") instead of {tool}. "
3491
3492
  "ContextStream search is indexed and faster."
3492
3493
  )
3493
3494
 
@@ -4059,7 +4060,7 @@ async function runPreToolUseHook() {
4059
4060
  fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] Glob pattern=${pattern}, isDiscovery=${isDiscoveryGlob(pattern)}
4060
4061
  `);
4061
4062
  if (isDiscoveryGlob(pattern)) {
4062
- const msg = `STOP: Use mcp__contextstream__search(mode="hybrid", query="${pattern}") instead of Glob.`;
4063
+ const msg = `STOP: Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of Glob.`;
4063
4064
  fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] Intercepting discovery glob: ${msg}
4064
4065
  `);
4065
4066
  if (editorFormat === "cline") {
@@ -4082,7 +4083,7 @@ async function runPreToolUseHook() {
4082
4083
  }
4083
4084
  blockClaudeCode(msg);
4084
4085
  } else {
4085
- const msg = `STOP: Use mcp__contextstream__search(mode="hybrid", query="${pattern}") instead of ${tool}.`;
4086
+ const msg = `STOP: Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool}.`;
4086
4087
  if (editorFormat === "cline") {
4087
4088
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
4088
4089
  } else if (editorFormat === "cursor") {
@@ -4094,7 +4095,7 @@ async function runPreToolUseHook() {
4094
4095
  } else if (tool === "Task") {
4095
4096
  const subagentType = toolInput?.subagent_type?.toLowerCase() || "";
4096
4097
  if (subagentType === "explore") {
4097
- const msg = 'STOP: Use mcp__contextstream__search(mode="hybrid") instead of Task(Explore).';
4098
+ const msg = 'STOP: Use mcp__contextstream__search(mode="auto") instead of Task(Explore).';
4098
4099
  if (editorFormat === "cline") {
4099
4100
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
4100
4101
  } else if (editorFormat === "cursor") {
@@ -4123,7 +4124,7 @@ async function runPreToolUseHook() {
4123
4124
  if (tool === "list_files" || tool === "search_files") {
4124
4125
  const pattern = toolInput?.path || toolInput?.regex || "";
4125
4126
  if (isDiscoveryGlob(pattern) || isDiscoveryGrep(pattern)) {
4126
- const msg = `Use mcp__contextstream__search(mode="hybrid", query="${pattern}") instead of ${tool}. ContextStream search is indexed and faster.`;
4127
+ const msg = `Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool}. ContextStream search is indexed and faster.`;
4127
4128
  if (editorFormat === "cline") {
4128
4129
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
4129
4130
  } else if (editorFormat === "cursor") {
@@ -4381,6 +4382,34 @@ async function saveLastExchange(exchange, cwd, clientName) {
4381
4382
  } catch {
4382
4383
  }
4383
4384
  }
4385
+ async function fetchHookContext() {
4386
+ if (!API_KEY2) return null;
4387
+ try {
4388
+ const controller = new AbortController();
4389
+ const timeoutId = setTimeout(() => controller.abort(), 2e3);
4390
+ const url = `${API_URL2}/api/v1/context/hook`;
4391
+ const body = {};
4392
+ if (WORKSPACE_ID) body.workspace_id = WORKSPACE_ID;
4393
+ if (PROJECT_ID) body.project_id = PROJECT_ID;
4394
+ const response = await fetch(url, {
4395
+ method: "POST",
4396
+ headers: {
4397
+ "X-API-Key": API_KEY2,
4398
+ "Content-Type": "application/json"
4399
+ },
4400
+ body: JSON.stringify(body),
4401
+ signal: controller.signal
4402
+ });
4403
+ clearTimeout(timeoutId);
4404
+ if (response.ok) {
4405
+ const data = await response.json();
4406
+ return data?.data?.context || null;
4407
+ }
4408
+ return null;
4409
+ } catch {
4410
+ return null;
4411
+ }
4412
+ }
4384
4413
  async function fetchSessionContext() {
4385
4414
  if (!API_KEY2) return null;
4386
4415
  try {
@@ -4459,26 +4488,6 @@ function transformSmartContextResponse(data) {
4459
4488
  return null;
4460
4489
  }
4461
4490
  }
4462
- function buildClaudeReminder(ctx, versionNotice) {
4463
- const parts = [];
4464
- if (versionNotice?.behind) {
4465
- const versionInfo = getVersionNoticeForHook(versionNotice);
4466
- if (versionInfo) {
4467
- parts.push(versionInfo);
4468
- parts.push("");
4469
- }
4470
- }
4471
- const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
4472
- if (highImportancePrefs.length > 0) {
4473
- parts.push(`[USER PREFERENCES - Always respect these]`);
4474
- for (const pref of highImportancePrefs.slice(0, 5)) {
4475
- parts.push(`\u2022 ${pref.title}: ${pref.content}`);
4476
- }
4477
- parts.push("");
4478
- }
4479
- parts.push(REMINDER);
4480
- return parts.join("\n");
4481
- }
4482
4491
  function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
4483
4492
  const parts = [ENHANCED_REMINDER_HEADER];
4484
4493
  if (versionNotice?.behind) {
@@ -4496,7 +4505,7 @@ function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
4496
4505
  2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
4497
4506
  3. Generate a unique session_id (e.g., "session-" + timestamp or UUID) - use this for ALL context() calls
4498
4507
  4. Call \`context(user_message="...", save_exchange=true, session_id="<your-session-id>")\` for task-specific context
4499
- 5. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
4508
+ 5. Use \`search(mode="auto")\` for code discovery (not Glob/Grep/Read)
4500
4509
 
4501
4510
  `);
4502
4511
  }
@@ -4537,7 +4546,7 @@ function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
4537
4546
  parts.push("");
4538
4547
  }
4539
4548
  parts.push("---\n");
4540
- parts.push(REMINDER);
4549
+ parts.push(FULL_REMINDER);
4541
4550
  parts.push(`
4542
4551
 
4543
4552
  ---
@@ -4554,7 +4563,7 @@ Returns: \`indexed\` (true/false), \`last_indexed_at\`, \`file_count\`
4554
4563
  ### \u{1F50D} Search Decision Tree:
4555
4564
 
4556
4565
  **IF indexed=true AND last_indexed_at is recent:**
4557
- \u2192 Use \`search(mode="hybrid", query="...")\`
4566
+ \u2192 Use \`search(mode="auto", query="...")\`
4558
4567
 
4559
4568
  **IF indexed=false OR last_indexed_at is stale (>7 days):**
4560
4569
  \u2192 Use local tools (Glob/Grep/Read) directly
@@ -4625,61 +4634,58 @@ async function runUserPromptSubmitHook() {
4625
4634
  }
4626
4635
  const editorFormat = detectEditorFormat2(input);
4627
4636
  const cwd = input.cwd || process.cwd();
4628
- loadConfigFromMcpJson(cwd);
4629
- const versionNoticePromise = getUpdateNotice();
4630
- const lastExchange = extractLastExchange(input, editorFormat);
4631
- const clientName = editorFormat === "claude" ? "claude-code" : editorFormat;
4632
- const saveExchangePromise = lastExchange ? saveLastExchange(lastExchange, cwd, clientName) : Promise.resolve();
4633
4637
  if (editorFormat === "claude") {
4634
- saveExchangePromise.catch(() => {
4635
- });
4636
- const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise]);
4637
- const claudeReminder = buildClaudeReminder(ctx, versionNotice);
4638
+ loadConfigFromMcpJson(cwd);
4639
+ let context = REMINDER;
4640
+ if (API_KEY2) {
4641
+ try {
4642
+ const hookContext = await fetchHookContext();
4643
+ if (hookContext) {
4644
+ context = hookContext;
4645
+ }
4646
+ } catch {
4647
+ }
4648
+ }
4638
4649
  console.log(
4639
4650
  JSON.stringify({
4640
4651
  hookSpecificOutput: {
4641
4652
  hookEventName: "UserPromptSubmit",
4642
- additionalContext: claudeReminder
4653
+ additionalContext: context
4643
4654
  }
4644
4655
  })
4645
4656
  );
4646
- } else if (editorFormat === "cline") {
4647
- const newSession = isNewSession(input, editorFormat);
4648
- const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
4649
- const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
4650
- console.log(
4651
- JSON.stringify({
4652
- cancel: false,
4653
- contextModification: enhancedReminder
4654
- })
4655
- );
4656
- } else if (editorFormat === "cursor") {
4657
+ } else {
4658
+ loadConfigFromMcpJson(cwd);
4659
+ const versionNoticePromise = getUpdateNotice();
4660
+ const lastExchange = extractLastExchange(input, editorFormat);
4661
+ const clientName = editorFormat;
4662
+ const saveExchangePromise = lastExchange ? saveLastExchange(lastExchange, cwd, clientName) : Promise.resolve();
4657
4663
  const newSession = isNewSession(input, editorFormat);
4658
4664
  const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
4659
- let cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="hybrid") before Glob/Grep. After file edits: project(action="index").` : `[CONTEXTSTREAM] Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="hybrid") before Glob/Grep/Read. After file edits: project(action="index").`;
4660
- if (versionNotice?.behind) {
4661
- cursorReminder += ` [UPDATE v${versionNotice.current}\u2192${versionNotice.latest}]`;
4665
+ if (editorFormat === "cursor") {
4666
+ let cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="auto") before Glob/Grep. After file edits: project(action="index").` : `[CONTEXTSTREAM] Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="auto") before Glob/Grep/Read. After file edits: project(action="index").`;
4667
+ if (versionNotice?.behind) {
4668
+ cursorReminder += ` [UPDATE v${versionNotice.current}\u2192${versionNotice.latest}]`;
4669
+ }
4670
+ console.log(
4671
+ JSON.stringify({
4672
+ continue: true,
4673
+ user_message: cursorReminder
4674
+ })
4675
+ );
4676
+ } else {
4677
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
4678
+ console.log(
4679
+ JSON.stringify({
4680
+ cancel: false,
4681
+ contextModification: enhancedReminder
4682
+ })
4683
+ );
4662
4684
  }
4663
- console.log(
4664
- JSON.stringify({
4665
- continue: true,
4666
- user_message: cursorReminder
4667
- })
4668
- );
4669
- } else if (editorFormat === "antigravity") {
4670
- const newSession = isNewSession(input, editorFormat);
4671
- const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
4672
- const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
4673
- console.log(
4674
- JSON.stringify({
4675
- cancel: false,
4676
- contextModification: enhancedReminder
4677
- })
4678
- );
4679
4685
  }
4680
4686
  process.exit(0);
4681
4687
  }
4682
- var ENABLED3, API_URL2, API_KEY2, WORKSPACE_ID, PROJECT_ID, REMINDER, ENHANCED_REMINDER_HEADER, isDirectRun3;
4688
+ var ENABLED3, API_URL2, API_KEY2, WORKSPACE_ID, PROJECT_ID, REMINDER, FULL_REMINDER, ENHANCED_REMINDER_HEADER, isDirectRun3;
4683
4689
  var init_user_prompt_submit = __esm({
4684
4690
  "src/hooks/user-prompt-submit.ts"() {
4685
4691
  "use strict";
@@ -4689,7 +4695,9 @@ var init_user_prompt_submit = __esm({
4689
4695
  API_KEY2 = process.env.CONTEXTSTREAM_API_KEY || "";
4690
4696
  WORKSPACE_ID = null;
4691
4697
  PROJECT_ID = null;
4692
- REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
4698
+ REMINDER = `[CONTEXTSTREAM] Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") FIRST before any other tool. Response contains dynamic rules, lessons, preferences. For search: use search(mode="auto") if indexed, else local tools.
4699
+ [END]`;
4700
+ FULL_REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
4693
4701
 
4694
4702
  1. FIRST: Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") before ANY other tool
4695
4703
  - Returns: dynamic rules, lessons from past mistakes, relevant context
@@ -4699,7 +4707,7 @@ var init_user_prompt_submit = __esm({
4699
4707
 
4700
4708
  2. FOR CODE SEARCH: Check index status, then search appropriately
4701
4709
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
4702
- \u2705 IF indexed & fresh: Use mcp__contextstream__search(mode="hybrid", query="...")
4710
+ \u2705 IF indexed & fresh: Use mcp__contextstream__search(mode="auto", query="...")
4703
4711
  \u2705 IF NOT indexed OR stale: Use local tools (Glob/Grep/Read) directly
4704
4712
  \u2705 IF search returns 0 results: Fallback to local tools (Glob/Grep/Read)
4705
4713
 
@@ -11311,7 +11319,16 @@ async function request(config, path22, options = {}) {
11311
11319
  const authOverride = getAuthOverride();
11312
11320
  const apiKey = authOverride?.apiKey ?? config.apiKey;
11313
11321
  const jwt = authOverride?.jwt ?? config.jwt;
11314
- const apiPath = path22.startsWith("/api/") ? path22 : `/api/v1${path22}`;
11322
+ const rawPath = path22.startsWith("/") ? path22 : `/${path22}`;
11323
+ const apiPath = rawPath.startsWith("/api/") ? rawPath : `/api/v1${rawPath}`;
11324
+ const unauthenticatedEndpoints = ["/api/v1/auth/device/start", "/api/v1/auth/device/token"];
11325
+ const isUnauthenticatedEndpoint = unauthenticatedEndpoints.some(
11326
+ (endpoint) => apiPath === endpoint || apiPath.startsWith(`${endpoint}?`)
11327
+ );
11328
+ if (!isUnauthenticatedEndpoint && !apiKey && !jwt) {
11329
+ const message = config.allowHeaderAuth ? "Missing authentication: CONTEXTSTREAM_ALLOW_HEADER_AUTH is enabled, but this request did not include auth credentials." : "Missing authentication: set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT.";
11330
+ throw new HttpError(401, message);
11331
+ }
11315
11332
  const url = `${apiUrl.replace(/\/$/, "")}${apiPath}`;
11316
11333
  const maxRetries = options.retries ?? MAX_RETRIES;
11317
11334
  const baseDelay = options.retryDelay ?? BASE_DELAY;
@@ -12294,6 +12311,12 @@ var ContextStreamClient = class {
12294
12311
  this.indexRefreshInProgress = false;
12295
12312
  this.hasTriggeredAutoIndex = false;
12296
12313
  }
12314
+ hasEffectiveAuth() {
12315
+ const authOverride = getAuthOverride();
12316
+ const apiKey = authOverride?.apiKey ?? this.config.apiKey;
12317
+ const jwt = authOverride?.jwt ?? this.config.jwt;
12318
+ return Boolean(apiKey || jwt);
12319
+ }
12297
12320
  /**
12298
12321
  * Update the client's default workspace/project IDs at runtime.
12299
12322
  *
@@ -14731,7 +14754,7 @@ ${context2}`;
14731
14754
  let candidateContext;
14732
14755
  const CS_HEADER = "\u2B21 ContextStream \u2014 Smart Context & Memory";
14733
14756
  const CS_HEADER_MINIFIED = "CS:active";
14734
- const CS_FIRST_RULE = "\u26A0\uFE0F STOP: Before Glob/Grep/Read \u2192 search(mode=hybrid) FIRST. Local tools ONLY if 0 results.";
14757
+ const CS_FIRST_RULE = "\u26A0\uFE0F STOP: Before Glob/Grep/Read \u2192 search(mode=auto) FIRST. Local tools ONLY if 0 results.";
14735
14758
  const CS_FIRST_RULE_MINIFIED = "R:CS-first|Idx:project.index_status->ingest|NoLocalScanUnlessCSempty";
14736
14759
  if (format === "minified") {
14737
14760
  const parts = [CS_HEADER_MINIFIED, CS_FIRST_RULE_MINIFIED];
@@ -16537,10 +16560,22 @@ var TASK_HINTS = {
16537
16560
  created: "Task created. Use update_task() to change status.",
16538
16561
  completed: "Task completed. Well done!",
16539
16562
  blocked: "Task blocked. Add blocked_reason for context.",
16563
+ cancelled: "Task cancelled.",
16540
16564
  linked_to_plan: "Task linked to plan. Progress will be tracked."
16541
16565
  };
16542
16566
 
16543
16567
  // src/tools.ts
16568
+ function parseBoolEnvDefault(raw, fallback) {
16569
+ if (raw === void 0) return fallback;
16570
+ const normalized = raw.trim().toLowerCase();
16571
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
16572
+ return true;
16573
+ }
16574
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
16575
+ return false;
16576
+ }
16577
+ return fallback;
16578
+ }
16544
16579
  var LOG_LEVEL = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
16545
16580
  var LOG_QUIET = LOG_LEVEL === "quiet";
16546
16581
  var LOG_VERBOSE = LOG_LEVEL === "verbose";
@@ -16604,7 +16639,7 @@ var lastAutoSaveTime = 0;
16604
16639
  var lastAutoSavePressureLevel = "";
16605
16640
  var contextCallsSinceLastSave = 0;
16606
16641
  var SEARCH_RULES_REMINDER_ENABLED = process.env.CONTEXTSTREAM_SEARCH_REMINDER?.toLowerCase() !== "false";
16607
- var SEARCH_RULES_REMINDER = `[SEARCH] Use search(mode="hybrid") before Glob/Grep/Read. Local tools only if 0 results.`;
16642
+ var SEARCH_RULES_REMINDER = `[SEARCH] Use search(mode="auto") before Glob/Grep/Read. Local tools only if 0 results.`;
16608
16643
  var CONTEXT_CALL_REMINDER = `[CONTEXT] Call context(user_message="...") at start of EVERY response. This is MANDATORY.`;
16609
16644
  var LESSONS_REMINDER_PREFIX = `
16610
16645
  \u{1F6A8} [LESSONS_WARNING] Past Mistakes Found - READ BEFORE PROCEEDING!
@@ -17834,7 +17869,7 @@ function getBundleInfo() {
17834
17869
  core: "Essential session tools (always enabled)",
17835
17870
  session: "Extended session management and utilities",
17836
17871
  memory: "Full memory CRUD operations",
17837
- search: "Semantic, hybrid, and keyword search",
17872
+ search: "Auto, semantic, hybrid (legacy alias), and keyword search",
17838
17873
  graph: "Code graph analysis and dependencies",
17839
17874
  workspace: "Workspace management",
17840
17875
  project: "Project management and indexing",
@@ -17937,11 +17972,22 @@ function parsePositiveInt(raw, fallback) {
17937
17972
  var OUTPUT_FORMAT = process.env.CONTEXTSTREAM_OUTPUT_FORMAT || "compact";
17938
17973
  var COMPACT_OUTPUT = OUTPUT_FORMAT === "compact";
17939
17974
  var SHOW_TIMING = process.env.CONTEXTSTREAM_SHOW_TIMING === "true" || process.env.CONTEXTSTREAM_SHOW_TIMING === "1";
17975
+ var INCLUDE_STRUCTURED_CONTENT = parseBoolEnvDefault(
17976
+ process.env.CONTEXTSTREAM_INCLUDE_STRUCTURED_CONTENT,
17977
+ true
17978
+ );
17940
17979
  var DEFAULT_SEARCH_LIMIT = parsePositiveInt(process.env.CONTEXTSTREAM_SEARCH_LIMIT, 3);
17941
17980
  var DEFAULT_SEARCH_CONTENT_MAX_CHARS = parsePositiveInt(
17942
17981
  process.env.CONTEXTSTREAM_SEARCH_MAX_CHARS,
17943
17982
  400
17944
17983
  );
17984
+ function maybeStripStructuredContent(result) {
17985
+ if (INCLUDE_STRUCTURED_CONTENT || !Object.prototype.hasOwnProperty.call(result, "structuredContent")) {
17986
+ return result;
17987
+ }
17988
+ const { structuredContent: _structuredContent, ...rest } = result;
17989
+ return rest;
17990
+ }
17945
17991
  var CONSOLIDATED_MODE = process.env.CONTEXTSTREAM_CONSOLIDATED !== "false";
17946
17992
  var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
17947
17993
  "init",
@@ -17971,6 +18017,38 @@ var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
17971
18017
  "help"
17972
18018
  // Consolidates session_tools, auth_me, mcp_server_version, etc.
17973
18019
  ]);
18020
+ function mapToolToConsolidatedDomain(toolName) {
18021
+ if (CONSOLIDATED_TOOLS.has(toolName)) return toolName;
18022
+ if (toolName === "session_init") return "init";
18023
+ if (toolName === "context_smart") return "context";
18024
+ if (toolName === "generate_rules" || toolName === "generate_editor_rules") return "generate_rules";
18025
+ if (toolName.startsWith("search_")) return "search";
18026
+ if (toolName.startsWith("session_") || toolName === "context_feedback") return "session";
18027
+ if (toolName.startsWith("memory_") || toolName === "decision_trace") return "memory";
18028
+ if (toolName.startsWith("graph_")) return "graph";
18029
+ if (toolName.startsWith("projects_")) return "project";
18030
+ if (toolName.startsWith("workspaces_") || toolName.startsWith("workspace_")) return "workspace";
18031
+ if (toolName.startsWith("reminders_")) return "reminder";
18032
+ if (toolName.startsWith("slack_") || toolName.startsWith("github_") || toolName.startsWith("integrations_") || toolName.startsWith("notion_")) {
18033
+ return "integration";
18034
+ }
18035
+ if (toolName.startsWith("media_")) return "media";
18036
+ if (toolName === "session_tools" || toolName === "auth_me" || toolName === "mcp_server_version" || toolName === "tools_enable_bundle") {
18037
+ return "help";
18038
+ }
18039
+ return null;
18040
+ }
18041
+ function resolveConsolidatedAllowlist(allowlist) {
18042
+ if (!allowlist) return null;
18043
+ const mapped = /* @__PURE__ */ new Set();
18044
+ for (const toolName of allowlist) {
18045
+ const consolidated = mapToolToConsolidatedDomain(toolName);
18046
+ if (consolidated) {
18047
+ mapped.add(consolidated);
18048
+ }
18049
+ }
18050
+ return mapped;
18051
+ }
17974
18052
  var TOOLSET_ALIASES = {
17975
18053
  // Light mode - minimal, fastest
17976
18054
  light: LIGHT_TOOLSET,
@@ -18141,10 +18219,15 @@ function setupClientDetection(server) {
18141
18219
  function registerTools(server, client, sessionManager) {
18142
18220
  const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
18143
18221
  const toolFilter = resolveToolFilter();
18144
- const toolAllowlist = toolFilter.allowlist;
18222
+ const rawToolAllowlist = toolFilter.allowlist;
18223
+ const toolAllowlist = CONSOLIDATED_MODE ? resolveConsolidatedAllowlist(rawToolAllowlist) : rawToolAllowlist;
18145
18224
  if (toolAllowlist) {
18146
18225
  const source = toolFilter.source;
18147
- logDebug(`Toolset: ${source} (${toolAllowlist.size} tools)`);
18226
+ if (CONSOLIDATED_MODE) {
18227
+ logDebug(`Toolset: ${source} (${toolAllowlist.size} consolidated domain tools)`);
18228
+ } else {
18229
+ logDebug(`Toolset: ${source} (${toolAllowlist.size} tools)`);
18230
+ }
18148
18231
  } else {
18149
18232
  logDebug("Toolset: complete (all tools)");
18150
18233
  }
@@ -18541,7 +18624,8 @@ function registerTools(server, client, sessionManager) {
18541
18624
  if (proGated) return proGated;
18542
18625
  const integrationGated = await gateIfIntegrationTool(name);
18543
18626
  if (integrationGated) return integrationGated;
18544
- return await handler(input, extra);
18627
+ const result = await handler(input, extra);
18628
+ return maybeStripStructuredContent(result);
18545
18629
  } catch (error) {
18546
18630
  const errorMessage = error?.message || String(error);
18547
18631
  const errorDetails = error?.body || error?.details || null;
@@ -18561,11 +18645,11 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
18561
18645
  }
18562
18646
  };
18563
18647
  const errorText = `[${errorCode}] ${errorMessage}${upgradeHint}${sessionHint}${errorDetails ? `: ${JSON.stringify(errorDetails)}` : ""}`;
18564
- return {
18648
+ return maybeStripStructuredContent({
18565
18649
  content: [{ type: "text", text: errorText }],
18566
18650
  structuredContent: errorPayload,
18567
18651
  isError: true
18568
- };
18652
+ });
18569
18653
  }
18570
18654
  };
18571
18655
  serverRef.registerTool(name, annotatedConfig, wrapWithAutoContext(name, safeHandler));
@@ -18622,7 +18706,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
18622
18706
  });
18623
18707
  return;
18624
18708
  }
18625
- if (!CONSOLIDATED_MODE && toolAllowlist && !toolAllowlist.has(name)) {
18709
+ if (toolAllowlist && !toolAllowlist.has(name)) {
18626
18710
  if (ROUTER_MODE && !ROUTER_DIRECT_TOOLS.has(name)) {
18627
18711
  operationsRegistry.set(name, {
18628
18712
  name,
@@ -18771,7 +18855,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
18771
18855
  Available bundles:
18772
18856
  - session: Extended session management (~6 tools)
18773
18857
  - memory: Full memory CRUD operations (~16 tools)
18774
- - search: Semantic, hybrid, and keyword search (~3 tools)
18858
+ - search: Auto, semantic, hybrid (legacy alias), and keyword search (~3 tools)
18775
18859
  - graph: Code graph analysis and dependencies (~9 tools)
18776
18860
  - workspace: Workspace management (~4 tools)
18777
18861
  - project: Project management and indexing (~10 tools)
@@ -19226,10 +19310,18 @@ Access: Free`,
19226
19310
  output_format: input.output_format
19227
19311
  };
19228
19312
  }
19313
+ function getSearchAuthError() {
19314
+ if (client.hasEffectiveAuth()) return null;
19315
+ return errorResult(
19316
+ "Authentication required for search. Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT, or pass auth credentials from your MCP client when CONTEXTSTREAM_ALLOW_HEADER_AUTH=true."
19317
+ );
19318
+ }
19229
19319
  registerTool(
19230
19320
  "search_semantic",
19231
19321
  { title: "Semantic search", description: "Semantic vector search", inputSchema: searchSchema },
19232
19322
  async (input) => {
19323
+ const authError = getSearchAuthError();
19324
+ if (authError) return authError;
19233
19325
  const result = await client.searchSemantic(normalizeSearchParams(input));
19234
19326
  return {
19235
19327
  content: [{ type: "text", text: formatContent(result) }]
@@ -19244,6 +19336,8 @@ Access: Free`,
19244
19336
  inputSchema: searchSchema
19245
19337
  },
19246
19338
  async (input) => {
19339
+ const authError = getSearchAuthError();
19340
+ if (authError) return authError;
19247
19341
  const result = await client.searchHybrid(normalizeSearchParams(input));
19248
19342
  return {
19249
19343
  content: [{ type: "text", text: formatContent(result) }]
@@ -19254,6 +19348,8 @@ Access: Free`,
19254
19348
  "search_keyword",
19255
19349
  { title: "Keyword search", description: "Keyword search", inputSchema: searchSchema },
19256
19350
  async (input) => {
19351
+ const authError = getSearchAuthError();
19352
+ if (authError) return authError;
19257
19353
  const result = await client.searchKeyword(normalizeSearchParams(input));
19258
19354
  return {
19259
19355
  content: [{ type: "text", text: formatContent(result) }]
@@ -19264,6 +19360,8 @@ Access: Free`,
19264
19360
  "search_pattern",
19265
19361
  { title: "Pattern search", description: "Pattern/regex search", inputSchema: searchSchema },
19266
19362
  async (input) => {
19363
+ const authError = getSearchAuthError();
19364
+ if (authError) return authError;
19267
19365
  const result = await client.searchPattern(normalizeSearchParams(input));
19268
19366
  return {
19269
19367
  content: [{ type: "text", text: formatContent(result) }]
@@ -20291,7 +20389,7 @@ This does semantic search on the first message. You only need context on subsequ
20291
20389
  );
20292
20390
  result.tools_hint = getCoreToolsHint();
20293
20391
  const sessionId = typeof result.session_id === "string" ? result.session_id : void 0;
20294
- result.educational_tip = getSessionInitTip(sessionId);
20392
+ result.educational_tip = getSessionInitTip(sessionId ?? "");
20295
20393
  const shouldRestoreContext = input.is_post_compact ?? RESTORE_CONTEXT_DEFAULT;
20296
20394
  if (shouldRestoreContext) {
20297
20395
  result.is_post_compact = true;
@@ -20557,7 +20655,7 @@ Format options:
20557
20655
 
20558
20656
  Example output (grouped):
20559
20657
  Session: init(start-conv) smart(each-msg) capture(save) recall(find) remember(quick)
20560
- Search: semantic(meaning) hybrid(combo) keyword(exact)
20658
+ Search: auto(default) semantic(meaning) hybrid(alias) keyword(exact)
20561
20659
  Memory: events(crud) nodes(knowledge) search(find) decisions(choices)`,
20562
20660
  inputSchema: external_exports.object({
20563
20661
  format: external_exports.enum(["grouped", "minimal", "full"]).optional().default("grouped").describe(
@@ -21004,7 +21102,7 @@ Use this in combination with session_init(is_post_compact=true) for seamless con
21004
21102
  }
21005
21103
  try {
21006
21104
  if (input.snapshot_id) {
21007
- const eventResult = await client.getEvent(input.snapshot_id);
21105
+ const eventResult = await client.getMemoryEvent(input.snapshot_id);
21008
21106
  const event = eventResult?.data || eventResult;
21009
21107
  if (!event || !event.content) {
21010
21108
  return errorResult(
@@ -22095,7 +22193,7 @@ Action: ${cp.suggested_action === "prepare_save" ? "Consider saving important de
22095
22193
  contextCallsSinceLastSave++;
22096
22194
  const now = Date.now();
22097
22195
  const timeSinceLastSave = now - lastAutoSaveTime;
22098
- const pressureLevelIncreased = lastAutoSavePressureLevel === "" && cp.level !== "low" || lastAutoSavePressureLevel === "medium" && (cp.level === "high" || cp.level === "critical") || lastAutoSavePressureLevel === "high" && cp.level === "critical";
22196
+ const pressureLevelIncreased = lastAutoSavePressureLevel === "" || lastAutoSavePressureLevel === "medium" && (cp.level === "high" || cp.level === "critical") || lastAutoSavePressureLevel === "high" && cp.level === "critical";
22099
22197
  const shouldSave = pressureLevelIncreased && timeSinceLastSave > AUTO_SAVE_MIN_INTERVAL_MS || contextCallsSinceLastSave >= AUTO_SAVE_CALL_INTERVAL || timeSinceLastSave > AUTO_SAVE_MIN_INTERVAL_MS * 2.5;
22100
22198
  if (shouldSave) {
22101
22199
  const trigger = pressureLevelIncreased ? "pressure_increase" : contextCallsSinceLastSave >= AUTO_SAVE_CALL_INTERVAL ? "call_count" : "time_interval";
@@ -23160,11 +23258,11 @@ Use this to remove a reminder that is no longer relevant.`,
23160
23258
  "search",
23161
23259
  {
23162
23260
  title: "Search",
23163
- description: `Search workspace memory and knowledge. Modes: semantic (meaning-based), hybrid (semantic + keyword), keyword (exact match), pattern (regex), exhaustive (all matches like grep), refactor (word-boundary matching for symbol renaming), team (cross-project team search - team plans only).
23261
+ description: `Search workspace memory and knowledge. Modes: auto (recommended), semantic (meaning-based), hybrid (legacy alias for auto), keyword (exact match), pattern (regex), exhaustive (all matches like grep), refactor (word-boundary matching for symbol renaming), team (cross-project team search - team plans only).
23164
23262
 
23165
23263
  Output formats: full (default, includes content), paths (file paths only - 80% token savings), minimal (compact - 60% savings), count (match counts only - 90% savings).`,
23166
23264
  inputSchema: external_exports.object({
23167
- mode: external_exports.enum(["semantic", "hybrid", "keyword", "pattern", "exhaustive", "refactor", "team"]).describe("Search mode"),
23265
+ mode: external_exports.enum(["auto", "semantic", "hybrid", "keyword", "pattern", "exhaustive", "refactor", "team"]).optional().default("auto").describe("Search mode (auto recommended; hybrid is a backward-compatible alias)"),
23168
23266
  query: external_exports.string().describe("Search query"),
23169
23267
  workspace_id: external_exports.string().uuid().optional(),
23170
23268
  project_id: external_exports.string().uuid().optional(),
@@ -23179,21 +23277,25 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23179
23277
  })
23180
23278
  },
23181
23279
  async (input) => {
23280
+ const authError = getSearchAuthError();
23281
+ if (authError) return authError;
23182
23282
  client.checkAndIndexChangedFiles().catch(() => {
23183
23283
  });
23184
23284
  const params = normalizeSearchParams(input);
23185
23285
  const startTime = Date.now();
23286
+ const requestedMode = input.mode || "auto";
23186
23287
  let result;
23187
23288
  let toolType;
23188
- switch (input.mode) {
23189
- case "semantic":
23190
- result = await client.searchSemantic(params);
23191
- toolType = "search_semantic";
23192
- break;
23289
+ switch (requestedMode) {
23290
+ case "auto":
23193
23291
  case "hybrid":
23194
23292
  result = await client.searchHybrid(params);
23195
23293
  toolType = "search_hybrid";
23196
23294
  break;
23295
+ case "semantic":
23296
+ result = await client.searchSemantic(params);
23297
+ toolType = "search_semantic";
23298
+ break;
23197
23299
  case "keyword":
23198
23300
  result = await client.searchKeyword(params);
23199
23301
  toolType = "search_keyword";
@@ -23257,6 +23359,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23257
23359
  break;
23258
23360
  }
23259
23361
  default:
23362
+ result = await client.searchHybrid(params);
23260
23363
  toolType = "search_hybrid";
23261
23364
  }
23262
23365
  const roundTripMs = Date.now() - startTime;
@@ -23728,7 +23831,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23728
23831
  );
23729
23832
  }
23730
23833
  if (input.snapshot_id) {
23731
- const eventResult = await client.getEvent(input.snapshot_id);
23834
+ const eventResult = await client.getMemoryEvent(input.snapshot_id);
23732
23835
  const event = eventResult?.data || eventResult;
23733
23836
  if (!event || !event.content) {
23734
23837
  return errorResult(
@@ -24896,7 +24999,7 @@ ${formatContent(result)}`
24896
24999
  workspace_id: ws.id,
24897
25000
  status: input.todo_status,
24898
25001
  priority: input.todo_priority,
24899
- limit: input.limit ? Math.ceil(input.limit / workspacesForTodos.length) : 10
25002
+ per_page: input.limit ? Math.ceil(input.limit / workspacesForTodos.length) : 10
24900
25003
  });
24901
25004
  const items = todos?.data?.items || todos?.items || [];
24902
25005
  allTodos.push(...items.map((t) => ({ ...t, workspace_name: ws.name })));
@@ -24946,7 +25049,7 @@ ${formatContent(result)}`
24946
25049
  const diagrams = await client.diagramsList({
24947
25050
  workspace_id: ws.id,
24948
25051
  diagram_type: input.diagram_type,
24949
- limit: input.limit ? Math.ceil(input.limit / workspacesForDiagrams.length) : 10
25052
+ per_page: input.limit ? Math.ceil(input.limit / workspacesForDiagrams.length) : 10
24950
25053
  });
24951
25054
  const items = diagrams?.data?.items || diagrams?.items || [];
24952
25055
  allDiagrams.push(...items.map((d) => ({ ...d, workspace_name: ws.name })));
@@ -24988,7 +25091,7 @@ ${formatContent(result)}`
24988
25091
  const docs = await client.docsList({
24989
25092
  workspace_id: ws.id,
24990
25093
  doc_type: input.doc_type,
24991
- limit: input.limit ? Math.ceil(input.limit / workspacesForDocs.length) : 10
25094
+ per_page: input.limit ? Math.ceil(input.limit / workspacesForDocs.length) : 10
24992
25095
  });
24993
25096
  const items = docs?.data?.items || docs?.items || [];
24994
25097
  allDocs.push(...items.map((d) => ({ ...d, workspace_name: ws.name })));
@@ -26257,7 +26360,6 @@ Last edited: ${updatedPage.last_edited_time}`
26257
26360
  try {
26258
26361
  const notionActivity = await client.notionActivity({
26259
26362
  workspace_id: ws.id,
26260
- days,
26261
26363
  limit: perSourceLimit
26262
26364
  });
26263
26365
  const notionItems = notionActivity?.data || notionActivity || [];
@@ -26281,7 +26383,6 @@ Last edited: ${updatedPage.last_edited_time}`
26281
26383
  try {
26282
26384
  const slackActivity = await client.slackActivity({
26283
26385
  workspace_id: ws.id,
26284
- days,
26285
26386
  limit: perSourceLimit
26286
26387
  });
26287
26388
  const slackItems = slackActivity?.data || slackActivity || [];
@@ -26305,7 +26406,6 @@ Last edited: ${updatedPage.last_edited_time}`
26305
26406
  try {
26306
26407
  const githubActivity = await client.githubActivity({
26307
26408
  workspace_id: ws.id,
26308
- days,
26309
26409
  limit: perSourceLimit
26310
26410
  });
26311
26411
  const githubItems = githubActivity?.data || githubActivity || [];
@@ -29098,21 +29198,22 @@ async function isEditorInstalled(editor) {
29098
29198
  }
29099
29199
  }
29100
29200
  var IS_WINDOWS = process.platform === "win32";
29101
- function buildContextStreamMcpServer(params) {
29102
- const env = {
29201
+ function escapeTomlString(value) {
29202
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
29203
+ }
29204
+ function formatTomlEnvLines(env) {
29205
+ return Object.entries(env).map(([key, value]) => `${key} = "${escapeTomlString(value)}"`).join("\n");
29206
+ }
29207
+ function buildSetupEnv(params) {
29208
+ const contextPack = params.contextPackEnabled === false ? "false" : "true";
29209
+ return {
29103
29210
  CONTEXTSTREAM_API_URL: params.apiUrl,
29104
- CONTEXTSTREAM_API_KEY: params.apiKey
29211
+ CONTEXTSTREAM_API_KEY: params.apiKey,
29212
+ CONTEXTSTREAM_CONTEXT_PACK: contextPack
29105
29213
  };
29106
- if (params.toolset === "router") {
29107
- env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
29108
- }
29109
- env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
29110
- if (params.restoreContextEnabled === false) {
29111
- env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
29112
- }
29113
- if (params.showTiming) {
29114
- env.CONTEXTSTREAM_SHOW_TIMING = "true";
29115
- }
29214
+ }
29215
+ function buildContextStreamMcpServer(params) {
29216
+ const env = buildSetupEnv(params);
29116
29217
  if (IS_WINDOWS) {
29117
29218
  return {
29118
29219
  command: "cmd",
@@ -29127,20 +29228,7 @@ function buildContextStreamMcpServer(params) {
29127
29228
  };
29128
29229
  }
29129
29230
  function buildContextStreamVsCodeServer(params) {
29130
- const env = {
29131
- CONTEXTSTREAM_API_URL: params.apiUrl,
29132
- CONTEXTSTREAM_API_KEY: params.apiKey
29133
- };
29134
- if (params.toolset === "router") {
29135
- env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
29136
- }
29137
- env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
29138
- if (params.restoreContextEnabled === false) {
29139
- env.CONTEXTSTREAM_RESTORE_CONTEXT = "false";
29140
- }
29141
- if (params.showTiming) {
29142
- env.CONTEXTSTREAM_SHOW_TIMING = "true";
29143
- }
29231
+ const env = buildSetupEnv(params);
29144
29232
  if (IS_WINDOWS) {
29145
29233
  return {
29146
29234
  type: "stdio",
@@ -29238,16 +29326,10 @@ async function upsertCodexTomlConfig(filePath, params) {
29238
29326
  await fs7.mkdir(path8.dirname(filePath), { recursive: true });
29239
29327
  const exists = await fileExists(filePath);
29240
29328
  const existing = exists ? await fs7.readFile(filePath, "utf8").catch(() => "") : "";
29329
+ const env = buildSetupEnv(params);
29330
+ const envLines = formatTomlEnvLines(env);
29241
29331
  const marker = "[mcp_servers.contextstream]";
29242
29332
  const envMarker = "[mcp_servers.contextstream.env]";
29243
- const toolsetLine = params.toolset === "router" ? `CONTEXTSTREAM_PROGRESSIVE_MODE = "true"
29244
- ` : "";
29245
- const contextPackLine = `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"
29246
- `;
29247
- const restoreContextLine = params.restoreContextEnabled === false ? `CONTEXTSTREAM_RESTORE_CONTEXT = "false"
29248
- ` : "";
29249
- const showTimingLine = params.showTiming ? `CONTEXTSTREAM_SHOW_TIMING = "true"
29250
- ` : "";
29251
29333
  const commandLine = IS_WINDOWS ? `command = "cmd"
29252
29334
  args = ["/c", "npx", "--prefer-online", "-y", "@contextstream/mcp-server@latest"]
29253
29335
  ` : `command = "npx"
@@ -29259,9 +29341,7 @@ args = ["--prefer-online", "-y", "@contextstream/mcp-server@latest"]
29259
29341
  [mcp_servers.contextstream]
29260
29342
  ` + commandLine + `
29261
29343
  [mcp_servers.contextstream.env]
29262
- CONTEXTSTREAM_API_URL = "${params.apiUrl}"
29263
- CONTEXTSTREAM_API_KEY = "${params.apiKey}"
29264
- ` + toolsetLine + contextPackLine + restoreContextLine + showTimingLine;
29344
+ ` + envLines + "\n";
29265
29345
  if (!exists) {
29266
29346
  await fs7.writeFile(filePath, block.trimStart(), "utf8");
29267
29347
  return "created";
@@ -29273,10 +29353,7 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
29273
29353
  if (!existing.includes(envMarker)) {
29274
29354
  await fs7.writeFile(
29275
29355
  filePath,
29276
- existing.trimEnd() + "\n\n" + envMarker + `
29277
- CONTEXTSTREAM_API_URL = "${params.apiUrl}"
29278
- CONTEXTSTREAM_API_KEY = "${params.apiKey}"
29279
- ` + toolsetLine + contextPackLine,
29356
+ existing.trimEnd() + "\n\n" + envMarker + "\n" + envLines + "\n",
29280
29357
  "utf8"
29281
29358
  );
29282
29359
  return "updated";
@@ -29284,51 +29361,43 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
29284
29361
  const lines = existing.split(/\r?\n/);
29285
29362
  const out = [];
29286
29363
  let inEnv = false;
29287
- let sawUrl = false;
29288
- let sawKey = false;
29289
- let sawContextPack = false;
29364
+ const seen = /* @__PURE__ */ new Set();
29365
+ const managedKeys = new Set(Object.keys(env));
29290
29366
  for (const line of lines) {
29291
29367
  const trimmed = line.trim();
29292
29368
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
29293
29369
  if (inEnv && trimmed !== envMarker) {
29294
- if (!sawUrl) out.push(`CONTEXTSTREAM_API_URL = "${params.apiUrl}"`);
29295
- if (!sawKey) out.push(`CONTEXTSTREAM_API_KEY = "${params.apiKey}"`);
29296
- if (!sawContextPack)
29297
- out.push(
29298
- `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"`
29299
- );
29370
+ for (const [key, value] of Object.entries(env)) {
29371
+ if (!seen.has(key)) {
29372
+ out.push(`${key} = "${escapeTomlString(value)}"`);
29373
+ }
29374
+ }
29300
29375
  inEnv = false;
29301
29376
  }
29302
- if (trimmed === envMarker) inEnv = true;
29377
+ if (trimmed === envMarker) {
29378
+ inEnv = true;
29379
+ seen.clear();
29380
+ }
29303
29381
  out.push(line);
29304
29382
  continue;
29305
29383
  }
29306
- if (inEnv && /^\s*CONTEXTSTREAM_API_URL\s*=/.test(line)) {
29307
- out.push(`CONTEXTSTREAM_API_URL = "${params.apiUrl}"`);
29308
- sawUrl = true;
29309
- continue;
29310
- }
29311
- if (inEnv && /^\s*CONTEXTSTREAM_API_KEY\s*=/.test(line)) {
29312
- out.push(`CONTEXTSTREAM_API_KEY = "${params.apiKey}"`);
29313
- sawKey = true;
29314
- continue;
29315
- }
29316
- if (inEnv && /^\s*CONTEXTSTREAM_CONTEXT_PACK\s*=/.test(line)) {
29317
- out.push(
29318
- `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"`
29319
- );
29320
- sawContextPack = true;
29321
- continue;
29384
+ if (inEnv) {
29385
+ const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=/);
29386
+ if (match && managedKeys.has(match[1])) {
29387
+ const key = match[1];
29388
+ out.push(`${key} = "${escapeTomlString(env[key])}"`);
29389
+ seen.add(key);
29390
+ continue;
29391
+ }
29322
29392
  }
29323
29393
  out.push(line);
29324
29394
  }
29325
29395
  if (inEnv) {
29326
- if (!sawUrl) out.push(`CONTEXTSTREAM_API_URL = "${params.apiUrl}"`);
29327
- if (!sawKey) out.push(`CONTEXTSTREAM_API_KEY = "${params.apiKey}"`);
29328
- if (!sawContextPack)
29329
- out.push(
29330
- `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"`
29331
- );
29396
+ for (const [key, value] of Object.entries(env)) {
29397
+ if (!seen.has(key)) {
29398
+ out.push(`${key} = "${escapeTomlString(value)}"`);
29399
+ }
29400
+ }
29332
29401
  }
29333
29402
  const updated = out.join("\n");
29334
29403
  if (updated === existing) return "skipped";
@@ -29764,11 +29833,7 @@ Code: ${device.user_code}`);
29764
29833
  const planLabel = detectedPlanName ?? "unknown";
29765
29834
  console.log(`
29766
29835
  Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
29767
- const toolset = "consolidated";
29768
- const isPro = detectedPlanName && ["pro", "team", "enterprise"].some((p) => detectedPlanName.toLowerCase().includes(p));
29769
- const contextPackEnabled = isPro;
29770
- const showTiming = false;
29771
- const restoreContextEnabled = true;
29836
+ const contextPackEnabled = !!detectedPlanName && ["pro", "team", "enterprise"].some((p) => detectedPlanName.toLowerCase().includes(p));
29772
29837
  console.log("\nAuto-Update:");
29773
29838
  console.log(" When enabled, ContextStream will automatically update to the latest version");
29774
29839
  console.log(" on new sessions (checks daily). You can disable this if you prefer manual updates.");
@@ -29856,23 +29921,9 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
29856
29921
  )
29857
29922
  ) || mcpChoiceDefault;
29858
29923
  const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
29859
- const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming, restoreContextEnabled });
29860
- const mcpServerClaude = buildContextStreamMcpServer({
29861
- apiUrl,
29862
- apiKey,
29863
- toolset,
29864
- contextPackEnabled,
29865
- showTiming,
29866
- restoreContextEnabled
29867
- });
29868
- const vsCodeServer = buildContextStreamVsCodeServer({
29869
- apiUrl,
29870
- apiKey,
29871
- toolset,
29872
- contextPackEnabled,
29873
- showTiming,
29874
- restoreContextEnabled
29875
- });
29924
+ const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
29925
+ const mcpServerClaude = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
29926
+ const vsCodeServer = buildContextStreamVsCodeServer({ apiUrl, apiKey, contextPackEnabled });
29876
29927
  const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
29877
29928
  if (needsGlobalMcpConfig) {
29878
29929
  console.log("\nInstalling global MCP config...");
@@ -29889,10 +29940,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
29889
29940
  const status = await upsertCodexTomlConfig(filePath, {
29890
29941
  apiUrl,
29891
29942
  apiKey,
29892
- toolset,
29893
- contextPackEnabled,
29894
- showTiming,
29895
- restoreContextEnabled
29943
+ contextPackEnabled
29896
29944
  });
29897
29945
  writeActions.push({ kind: "mcp-config", target: filePath, status });
29898
29946
  console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
@@ -29918,10 +29966,9 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
29918
29966
  console.log(
29919
29967
  "- Claude Code: global MCP config is best done via `claude mcp add --transport stdio ...` (see docs)."
29920
29968
  );
29921
- const envHint = toolset === "router" ? " --env CONTEXTSTREAM_PROGRESSIVE_MODE=true" : "";
29922
29969
  const packHint = contextPackEnabled === false ? " --env CONTEXTSTREAM_CONTEXT_PACK=false" : " --env CONTEXTSTREAM_CONTEXT_PACK=true";
29923
29970
  console.log(
29924
- ` macOS/Linux: claude mcp add --transport stdio contextstream --scope user --env CONTEXTSTREAM_API_URL=... --env CONTEXTSTREAM_API_KEY=...${envHint}${packHint} -- npx --prefer-online -y @contextstream/mcp-server@latest`
29971
+ ` macOS/Linux: claude mcp add --transport stdio contextstream --scope user --env CONTEXTSTREAM_API_URL=... --env CONTEXTSTREAM_API_KEY=...${packHint} -- npx --prefer-online -y @contextstream/mcp-server@latest`
29925
29972
  );
29926
29973
  console.log(
29927
29974
  " Windows (native): use `cmd /c npx --prefer-online -y @contextstream/mcp-server@latest` after `--` if `npx` is not found."
@@ -30371,6 +30418,7 @@ Environment variables:
30371
30418
  CONTEXTSTREAM_PROGRESSIVE_MODE Progressive disclosure: true|false (default: false, starts with ~13 core tools)
30372
30419
  CONTEXTSTREAM_ROUTER_MODE Router pattern: true|false (default: false, exposes only 2 meta-tools)
30373
30420
  CONTEXTSTREAM_OUTPUT_FORMAT Output verbosity: compact|pretty (default: compact, ~30% fewer tokens)
30421
+ CONTEXTSTREAM_INCLUDE_STRUCTURED_CONTENT Include structured JSON payloads in tool results: true|false (default: true)
30374
30422
  CONTEXTSTREAM_SEARCH_LIMIT Default MCP search limit (default: 3)
30375
30423
  CONTEXTSTREAM_SEARCH_MAX_CHARS Max chars per search result content (default: 400)
30376
30424
  CONTEXTSTREAM_CONSOLIDATED Consolidated domain tools: true|false (default: true in v0.4.x, ~75% token reduction)