@contextstream/mcp-server 0.4.58 → 0.4.59
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/hooks/auto-rules.js +7 -7
- package/dist/hooks/pre-tool-use.js +4 -4
- package/dist/hooks/runner.js +84 -77
- package/dist/hooks/session-init.js +21 -20
- package/dist/hooks/user-prompt-submit.js +72 -65
- package/dist/index.js +289 -190
- package/package.json +1 -1
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
1094
|
-
- \`Task(subagent_type="Explore")\` \u2192 Use \`search(mode="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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 | \`
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
| \`
|
|
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="
|
|
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=\\"
|
|
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=\\"
|
|
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=\\"
|
|
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="
|
|
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=\\"
|
|
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="
|
|
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=\\"
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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(
|
|
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="
|
|
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
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
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:
|
|
4653
|
+
additionalContext: context
|
|
4643
4654
|
}
|
|
4644
4655
|
})
|
|
4645
4656
|
);
|
|
4646
|
-
} else
|
|
4647
|
-
|
|
4648
|
-
const
|
|
4649
|
-
const
|
|
4650
|
-
|
|
4651
|
-
|
|
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
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
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
|
|
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="
|
|
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
|
|
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=
|
|
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];
|
|
@@ -16541,6 +16564,17 @@ var TASK_HINTS = {
|
|
|
16541
16564
|
};
|
|
16542
16565
|
|
|
16543
16566
|
// src/tools.ts
|
|
16567
|
+
function parseBoolEnvDefault(raw, fallback) {
|
|
16568
|
+
if (raw === void 0) return fallback;
|
|
16569
|
+
const normalized = raw.trim().toLowerCase();
|
|
16570
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
16571
|
+
return true;
|
|
16572
|
+
}
|
|
16573
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
16574
|
+
return false;
|
|
16575
|
+
}
|
|
16576
|
+
return fallback;
|
|
16577
|
+
}
|
|
16544
16578
|
var LOG_LEVEL = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
|
|
16545
16579
|
var LOG_QUIET = LOG_LEVEL === "quiet";
|
|
16546
16580
|
var LOG_VERBOSE = LOG_LEVEL === "verbose";
|
|
@@ -16604,7 +16638,7 @@ var lastAutoSaveTime = 0;
|
|
|
16604
16638
|
var lastAutoSavePressureLevel = "";
|
|
16605
16639
|
var contextCallsSinceLastSave = 0;
|
|
16606
16640
|
var SEARCH_RULES_REMINDER_ENABLED = process.env.CONTEXTSTREAM_SEARCH_REMINDER?.toLowerCase() !== "false";
|
|
16607
|
-
var SEARCH_RULES_REMINDER = `[SEARCH] Use search(mode="
|
|
16641
|
+
var SEARCH_RULES_REMINDER = `[SEARCH] Use search(mode="auto") before Glob/Grep/Read. Local tools only if 0 results.`;
|
|
16608
16642
|
var CONTEXT_CALL_REMINDER = `[CONTEXT] Call context(user_message="...") at start of EVERY response. This is MANDATORY.`;
|
|
16609
16643
|
var LESSONS_REMINDER_PREFIX = `
|
|
16610
16644
|
\u{1F6A8} [LESSONS_WARNING] Past Mistakes Found - READ BEFORE PROCEEDING!
|
|
@@ -17834,7 +17868,7 @@ function getBundleInfo() {
|
|
|
17834
17868
|
core: "Essential session tools (always enabled)",
|
|
17835
17869
|
session: "Extended session management and utilities",
|
|
17836
17870
|
memory: "Full memory CRUD operations",
|
|
17837
|
-
search: "
|
|
17871
|
+
search: "Auto, semantic, hybrid (legacy alias), and keyword search",
|
|
17838
17872
|
graph: "Code graph analysis and dependencies",
|
|
17839
17873
|
workspace: "Workspace management",
|
|
17840
17874
|
project: "Project management and indexing",
|
|
@@ -17937,11 +17971,22 @@ function parsePositiveInt(raw, fallback) {
|
|
|
17937
17971
|
var OUTPUT_FORMAT = process.env.CONTEXTSTREAM_OUTPUT_FORMAT || "compact";
|
|
17938
17972
|
var COMPACT_OUTPUT = OUTPUT_FORMAT === "compact";
|
|
17939
17973
|
var SHOW_TIMING = process.env.CONTEXTSTREAM_SHOW_TIMING === "true" || process.env.CONTEXTSTREAM_SHOW_TIMING === "1";
|
|
17974
|
+
var INCLUDE_STRUCTURED_CONTENT = parseBoolEnvDefault(
|
|
17975
|
+
process.env.CONTEXTSTREAM_INCLUDE_STRUCTURED_CONTENT,
|
|
17976
|
+
true
|
|
17977
|
+
);
|
|
17940
17978
|
var DEFAULT_SEARCH_LIMIT = parsePositiveInt(process.env.CONTEXTSTREAM_SEARCH_LIMIT, 3);
|
|
17941
17979
|
var DEFAULT_SEARCH_CONTENT_MAX_CHARS = parsePositiveInt(
|
|
17942
17980
|
process.env.CONTEXTSTREAM_SEARCH_MAX_CHARS,
|
|
17943
17981
|
400
|
|
17944
17982
|
);
|
|
17983
|
+
function maybeStripStructuredContent(result) {
|
|
17984
|
+
if (INCLUDE_STRUCTURED_CONTENT || !Object.prototype.hasOwnProperty.call(result, "structuredContent")) {
|
|
17985
|
+
return result;
|
|
17986
|
+
}
|
|
17987
|
+
const { structuredContent: _structuredContent, ...rest } = result;
|
|
17988
|
+
return rest;
|
|
17989
|
+
}
|
|
17945
17990
|
var CONSOLIDATED_MODE = process.env.CONTEXTSTREAM_CONSOLIDATED !== "false";
|
|
17946
17991
|
var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
|
|
17947
17992
|
"init",
|
|
@@ -17971,6 +18016,38 @@ var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
17971
18016
|
"help"
|
|
17972
18017
|
// Consolidates session_tools, auth_me, mcp_server_version, etc.
|
|
17973
18018
|
]);
|
|
18019
|
+
function mapToolToConsolidatedDomain(toolName) {
|
|
18020
|
+
if (CONSOLIDATED_TOOLS.has(toolName)) return toolName;
|
|
18021
|
+
if (toolName === "session_init") return "init";
|
|
18022
|
+
if (toolName === "context_smart") return "context";
|
|
18023
|
+
if (toolName === "generate_rules" || toolName === "generate_editor_rules") return "generate_rules";
|
|
18024
|
+
if (toolName.startsWith("search_")) return "search";
|
|
18025
|
+
if (toolName.startsWith("session_") || toolName === "context_feedback") return "session";
|
|
18026
|
+
if (toolName.startsWith("memory_") || toolName === "decision_trace") return "memory";
|
|
18027
|
+
if (toolName.startsWith("graph_")) return "graph";
|
|
18028
|
+
if (toolName.startsWith("projects_")) return "project";
|
|
18029
|
+
if (toolName.startsWith("workspaces_") || toolName.startsWith("workspace_")) return "workspace";
|
|
18030
|
+
if (toolName.startsWith("reminders_")) return "reminder";
|
|
18031
|
+
if (toolName.startsWith("slack_") || toolName.startsWith("github_") || toolName.startsWith("integrations_") || toolName.startsWith("notion_")) {
|
|
18032
|
+
return "integration";
|
|
18033
|
+
}
|
|
18034
|
+
if (toolName.startsWith("media_")) return "media";
|
|
18035
|
+
if (toolName === "session_tools" || toolName === "auth_me" || toolName === "mcp_server_version" || toolName === "tools_enable_bundle") {
|
|
18036
|
+
return "help";
|
|
18037
|
+
}
|
|
18038
|
+
return null;
|
|
18039
|
+
}
|
|
18040
|
+
function resolveConsolidatedAllowlist(allowlist) {
|
|
18041
|
+
if (!allowlist) return null;
|
|
18042
|
+
const mapped = /* @__PURE__ */ new Set();
|
|
18043
|
+
for (const toolName of allowlist) {
|
|
18044
|
+
const consolidated = mapToolToConsolidatedDomain(toolName);
|
|
18045
|
+
if (consolidated) {
|
|
18046
|
+
mapped.add(consolidated);
|
|
18047
|
+
}
|
|
18048
|
+
}
|
|
18049
|
+
return mapped;
|
|
18050
|
+
}
|
|
17974
18051
|
var TOOLSET_ALIASES = {
|
|
17975
18052
|
// Light mode - minimal, fastest
|
|
17976
18053
|
light: LIGHT_TOOLSET,
|
|
@@ -18141,10 +18218,15 @@ function setupClientDetection(server) {
|
|
|
18141
18218
|
function registerTools(server, client, sessionManager) {
|
|
18142
18219
|
const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
|
|
18143
18220
|
const toolFilter = resolveToolFilter();
|
|
18144
|
-
const
|
|
18221
|
+
const rawToolAllowlist = toolFilter.allowlist;
|
|
18222
|
+
const toolAllowlist = CONSOLIDATED_MODE ? resolveConsolidatedAllowlist(rawToolAllowlist) : rawToolAllowlist;
|
|
18145
18223
|
if (toolAllowlist) {
|
|
18146
18224
|
const source = toolFilter.source;
|
|
18147
|
-
|
|
18225
|
+
if (CONSOLIDATED_MODE) {
|
|
18226
|
+
logDebug(`Toolset: ${source} (${toolAllowlist.size} consolidated domain tools)`);
|
|
18227
|
+
} else {
|
|
18228
|
+
logDebug(`Toolset: ${source} (${toolAllowlist.size} tools)`);
|
|
18229
|
+
}
|
|
18148
18230
|
} else {
|
|
18149
18231
|
logDebug("Toolset: complete (all tools)");
|
|
18150
18232
|
}
|
|
@@ -18541,7 +18623,8 @@ function registerTools(server, client, sessionManager) {
|
|
|
18541
18623
|
if (proGated) return proGated;
|
|
18542
18624
|
const integrationGated = await gateIfIntegrationTool(name);
|
|
18543
18625
|
if (integrationGated) return integrationGated;
|
|
18544
|
-
|
|
18626
|
+
const result = await handler(input, extra);
|
|
18627
|
+
return maybeStripStructuredContent(result);
|
|
18545
18628
|
} catch (error) {
|
|
18546
18629
|
const errorMessage = error?.message || String(error);
|
|
18547
18630
|
const errorDetails = error?.body || error?.details || null;
|
|
@@ -18561,11 +18644,11 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
18561
18644
|
}
|
|
18562
18645
|
};
|
|
18563
18646
|
const errorText = `[${errorCode}] ${errorMessage}${upgradeHint}${sessionHint}${errorDetails ? `: ${JSON.stringify(errorDetails)}` : ""}`;
|
|
18564
|
-
return {
|
|
18647
|
+
return maybeStripStructuredContent({
|
|
18565
18648
|
content: [{ type: "text", text: errorText }],
|
|
18566
18649
|
structuredContent: errorPayload,
|
|
18567
18650
|
isError: true
|
|
18568
|
-
};
|
|
18651
|
+
});
|
|
18569
18652
|
}
|
|
18570
18653
|
};
|
|
18571
18654
|
serverRef.registerTool(name, annotatedConfig, wrapWithAutoContext(name, safeHandler));
|
|
@@ -18622,7 +18705,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
18622
18705
|
});
|
|
18623
18706
|
return;
|
|
18624
18707
|
}
|
|
18625
|
-
if (
|
|
18708
|
+
if (toolAllowlist && !toolAllowlist.has(name)) {
|
|
18626
18709
|
if (ROUTER_MODE && !ROUTER_DIRECT_TOOLS.has(name)) {
|
|
18627
18710
|
operationsRegistry.set(name, {
|
|
18628
18711
|
name,
|
|
@@ -18771,7 +18854,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
18771
18854
|
Available bundles:
|
|
18772
18855
|
- session: Extended session management (~6 tools)
|
|
18773
18856
|
- memory: Full memory CRUD operations (~16 tools)
|
|
18774
|
-
- search:
|
|
18857
|
+
- search: Auto, semantic, hybrid (legacy alias), and keyword search (~3 tools)
|
|
18775
18858
|
- graph: Code graph analysis and dependencies (~9 tools)
|
|
18776
18859
|
- workspace: Workspace management (~4 tools)
|
|
18777
18860
|
- project: Project management and indexing (~10 tools)
|
|
@@ -19226,10 +19309,18 @@ Access: Free`,
|
|
|
19226
19309
|
output_format: input.output_format
|
|
19227
19310
|
};
|
|
19228
19311
|
}
|
|
19312
|
+
function getSearchAuthError() {
|
|
19313
|
+
if (client.hasEffectiveAuth()) return null;
|
|
19314
|
+
return errorResult(
|
|
19315
|
+
"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."
|
|
19316
|
+
);
|
|
19317
|
+
}
|
|
19229
19318
|
registerTool(
|
|
19230
19319
|
"search_semantic",
|
|
19231
19320
|
{ title: "Semantic search", description: "Semantic vector search", inputSchema: searchSchema },
|
|
19232
19321
|
async (input) => {
|
|
19322
|
+
const authError = getSearchAuthError();
|
|
19323
|
+
if (authError) return authError;
|
|
19233
19324
|
const result = await client.searchSemantic(normalizeSearchParams(input));
|
|
19234
19325
|
return {
|
|
19235
19326
|
content: [{ type: "text", text: formatContent(result) }]
|
|
@@ -19244,6 +19335,8 @@ Access: Free`,
|
|
|
19244
19335
|
inputSchema: searchSchema
|
|
19245
19336
|
},
|
|
19246
19337
|
async (input) => {
|
|
19338
|
+
const authError = getSearchAuthError();
|
|
19339
|
+
if (authError) return authError;
|
|
19247
19340
|
const result = await client.searchHybrid(normalizeSearchParams(input));
|
|
19248
19341
|
return {
|
|
19249
19342
|
content: [{ type: "text", text: formatContent(result) }]
|
|
@@ -19254,6 +19347,8 @@ Access: Free`,
|
|
|
19254
19347
|
"search_keyword",
|
|
19255
19348
|
{ title: "Keyword search", description: "Keyword search", inputSchema: searchSchema },
|
|
19256
19349
|
async (input) => {
|
|
19350
|
+
const authError = getSearchAuthError();
|
|
19351
|
+
if (authError) return authError;
|
|
19257
19352
|
const result = await client.searchKeyword(normalizeSearchParams(input));
|
|
19258
19353
|
return {
|
|
19259
19354
|
content: [{ type: "text", text: formatContent(result) }]
|
|
@@ -19264,6 +19359,8 @@ Access: Free`,
|
|
|
19264
19359
|
"search_pattern",
|
|
19265
19360
|
{ title: "Pattern search", description: "Pattern/regex search", inputSchema: searchSchema },
|
|
19266
19361
|
async (input) => {
|
|
19362
|
+
const authError = getSearchAuthError();
|
|
19363
|
+
if (authError) return authError;
|
|
19267
19364
|
const result = await client.searchPattern(normalizeSearchParams(input));
|
|
19268
19365
|
return {
|
|
19269
19366
|
content: [{ type: "text", text: formatContent(result) }]
|
|
@@ -20557,7 +20654,7 @@ Format options:
|
|
|
20557
20654
|
|
|
20558
20655
|
Example output (grouped):
|
|
20559
20656
|
Session: init(start-conv) smart(each-msg) capture(save) recall(find) remember(quick)
|
|
20560
|
-
Search: semantic(meaning) hybrid(
|
|
20657
|
+
Search: auto(default) semantic(meaning) hybrid(alias) keyword(exact)
|
|
20561
20658
|
Memory: events(crud) nodes(knowledge) search(find) decisions(choices)`,
|
|
20562
20659
|
inputSchema: external_exports.object({
|
|
20563
20660
|
format: external_exports.enum(["grouped", "minimal", "full"]).optional().default("grouped").describe(
|
|
@@ -23160,11 +23257,11 @@ Use this to remove a reminder that is no longer relevant.`,
|
|
|
23160
23257
|
"search",
|
|
23161
23258
|
{
|
|
23162
23259
|
title: "Search",
|
|
23163
|
-
description: `Search workspace memory and knowledge. Modes: semantic (meaning-based), hybrid (
|
|
23260
|
+
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
23261
|
|
|
23165
23262
|
Output formats: full (default, includes content), paths (file paths only - 80% token savings), minimal (compact - 60% savings), count (match counts only - 90% savings).`,
|
|
23166
23263
|
inputSchema: external_exports.object({
|
|
23167
|
-
mode: external_exports.enum(["semantic", "hybrid", "keyword", "pattern", "exhaustive", "refactor", "team"]).describe("Search mode"),
|
|
23264
|
+
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
23265
|
query: external_exports.string().describe("Search query"),
|
|
23169
23266
|
workspace_id: external_exports.string().uuid().optional(),
|
|
23170
23267
|
project_id: external_exports.string().uuid().optional(),
|
|
@@ -23179,21 +23276,25 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
23179
23276
|
})
|
|
23180
23277
|
},
|
|
23181
23278
|
async (input) => {
|
|
23279
|
+
const authError = getSearchAuthError();
|
|
23280
|
+
if (authError) return authError;
|
|
23182
23281
|
client.checkAndIndexChangedFiles().catch(() => {
|
|
23183
23282
|
});
|
|
23184
23283
|
const params = normalizeSearchParams(input);
|
|
23185
23284
|
const startTime = Date.now();
|
|
23285
|
+
const requestedMode = input.mode || "auto";
|
|
23186
23286
|
let result;
|
|
23187
23287
|
let toolType;
|
|
23188
|
-
switch (
|
|
23189
|
-
case "
|
|
23190
|
-
result = await client.searchSemantic(params);
|
|
23191
|
-
toolType = "search_semantic";
|
|
23192
|
-
break;
|
|
23288
|
+
switch (requestedMode) {
|
|
23289
|
+
case "auto":
|
|
23193
23290
|
case "hybrid":
|
|
23194
23291
|
result = await client.searchHybrid(params);
|
|
23195
23292
|
toolType = "search_hybrid";
|
|
23196
23293
|
break;
|
|
23294
|
+
case "semantic":
|
|
23295
|
+
result = await client.searchSemantic(params);
|
|
23296
|
+
toolType = "search_semantic";
|
|
23297
|
+
break;
|
|
23197
23298
|
case "keyword":
|
|
23198
23299
|
result = await client.searchKeyword(params);
|
|
23199
23300
|
toolType = "search_keyword";
|
|
@@ -23257,6 +23358,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
23257
23358
|
break;
|
|
23258
23359
|
}
|
|
23259
23360
|
default:
|
|
23361
|
+
result = await client.searchHybrid(params);
|
|
23260
23362
|
toolType = "search_hybrid";
|
|
23261
23363
|
}
|
|
23262
23364
|
const roundTripMs = Date.now() - startTime;
|
|
@@ -29098,21 +29200,49 @@ async function isEditorInstalled(editor) {
|
|
|
29098
29200
|
}
|
|
29099
29201
|
}
|
|
29100
29202
|
var IS_WINDOWS = process.platform === "win32";
|
|
29101
|
-
function
|
|
29102
|
-
|
|
29203
|
+
function escapeTomlString(value) {
|
|
29204
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
29205
|
+
}
|
|
29206
|
+
function formatTomlEnvLines(env) {
|
|
29207
|
+
return Object.entries(env).map(([key, value]) => `${key} = "${escapeTomlString(value)}"`).join("\n");
|
|
29208
|
+
}
|
|
29209
|
+
function buildSetupEnv(params) {
|
|
29210
|
+
const contextPack = params.contextPackEnabled === false ? "false" : "true";
|
|
29211
|
+
const progressive = params.toolset === "router" ? "true" : "false";
|
|
29212
|
+
const restoreContext = params.restoreContextEnabled === false ? "false" : "true";
|
|
29213
|
+
const showTiming = params.showTiming ? "true" : "false";
|
|
29214
|
+
return {
|
|
29103
29215
|
CONTEXTSTREAM_API_URL: params.apiUrl,
|
|
29104
|
-
CONTEXTSTREAM_API_KEY: params.apiKey
|
|
29216
|
+
CONTEXTSTREAM_API_KEY: params.apiKey,
|
|
29217
|
+
CONTEXTSTREAM_JWT: "",
|
|
29218
|
+
CONTEXTSTREAM_ALLOW_HEADER_AUTH: "false",
|
|
29219
|
+
CONTEXTSTREAM_WORKSPACE_ID: "",
|
|
29220
|
+
CONTEXTSTREAM_PROJECT_ID: "",
|
|
29221
|
+
CONTEXTSTREAM_USER_AGENT: `contextstream-mcp/${VERSION}`,
|
|
29222
|
+
CONTEXTSTREAM_TOOLSET: "standard",
|
|
29223
|
+
CONTEXTSTREAM_TOOL_ALLOWLIST: "",
|
|
29224
|
+
CONTEXTSTREAM_AUTO_TOOLSET: "false",
|
|
29225
|
+
CONTEXTSTREAM_AUTO_HIDE_INTEGRATIONS: "true",
|
|
29226
|
+
CONTEXTSTREAM_SCHEMA_MODE: "full",
|
|
29227
|
+
CONTEXTSTREAM_PROGRESSIVE_MODE: progressive,
|
|
29228
|
+
CONTEXTSTREAM_ROUTER_MODE: "false",
|
|
29229
|
+
CONTEXTSTREAM_OUTPUT_FORMAT: "compact",
|
|
29230
|
+
CONTEXTSTREAM_INCLUDE_STRUCTURED_CONTENT: "true",
|
|
29231
|
+
CONTEXTSTREAM_SEARCH_LIMIT: "3",
|
|
29232
|
+
CONTEXTSTREAM_SEARCH_MAX_CHARS: "400",
|
|
29233
|
+
CONTEXTSTREAM_CONSOLIDATED: "true",
|
|
29234
|
+
CONTEXTSTREAM_CONTEXT_PACK: contextPack,
|
|
29235
|
+
CONTEXTSTREAM_CONTEXT_PACK_ENABLED: contextPack,
|
|
29236
|
+
CONTEXTSTREAM_RESTORE_CONTEXT: restoreContext,
|
|
29237
|
+
CONTEXTSTREAM_SHOW_TIMING: showTiming,
|
|
29238
|
+
CONTEXTSTREAM_PRO_TOOLS: "",
|
|
29239
|
+
CONTEXTSTREAM_UPGRADE_URL: "https://contextstream.io/pricing",
|
|
29240
|
+
CONTEXTSTREAM_ENABLE_PROMPTS: "true",
|
|
29241
|
+
CONTEXTSTREAM_LOG_LEVEL: "normal"
|
|
29105
29242
|
};
|
|
29106
|
-
|
|
29107
|
-
|
|
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
|
-
}
|
|
29243
|
+
}
|
|
29244
|
+
function buildContextStreamMcpServer(params) {
|
|
29245
|
+
const env = buildSetupEnv(params);
|
|
29116
29246
|
if (IS_WINDOWS) {
|
|
29117
29247
|
return {
|
|
29118
29248
|
command: "cmd",
|
|
@@ -29127,20 +29257,7 @@ function buildContextStreamMcpServer(params) {
|
|
|
29127
29257
|
};
|
|
29128
29258
|
}
|
|
29129
29259
|
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
|
-
}
|
|
29260
|
+
const env = buildSetupEnv(params);
|
|
29144
29261
|
if (IS_WINDOWS) {
|
|
29145
29262
|
return {
|
|
29146
29263
|
type: "stdio",
|
|
@@ -29238,16 +29355,10 @@ async function upsertCodexTomlConfig(filePath, params) {
|
|
|
29238
29355
|
await fs7.mkdir(path8.dirname(filePath), { recursive: true });
|
|
29239
29356
|
const exists = await fileExists(filePath);
|
|
29240
29357
|
const existing = exists ? await fs7.readFile(filePath, "utf8").catch(() => "") : "";
|
|
29358
|
+
const env = buildSetupEnv(params);
|
|
29359
|
+
const envLines = formatTomlEnvLines(env);
|
|
29241
29360
|
const marker = "[mcp_servers.contextstream]";
|
|
29242
29361
|
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
29362
|
const commandLine = IS_WINDOWS ? `command = "cmd"
|
|
29252
29363
|
args = ["/c", "npx", "--prefer-online", "-y", "@contextstream/mcp-server@latest"]
|
|
29253
29364
|
` : `command = "npx"
|
|
@@ -29259,9 +29370,7 @@ args = ["--prefer-online", "-y", "@contextstream/mcp-server@latest"]
|
|
|
29259
29370
|
[mcp_servers.contextstream]
|
|
29260
29371
|
` + commandLine + `
|
|
29261
29372
|
[mcp_servers.contextstream.env]
|
|
29262
|
-
|
|
29263
|
-
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
29264
|
-
` + toolsetLine + contextPackLine + restoreContextLine + showTimingLine;
|
|
29373
|
+
` + envLines + "\n";
|
|
29265
29374
|
if (!exists) {
|
|
29266
29375
|
await fs7.writeFile(filePath, block.trimStart(), "utf8");
|
|
29267
29376
|
return "created";
|
|
@@ -29273,10 +29382,7 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
|
29273
29382
|
if (!existing.includes(envMarker)) {
|
|
29274
29383
|
await fs7.writeFile(
|
|
29275
29384
|
filePath,
|
|
29276
|
-
existing.trimEnd() + "\n\n" + envMarker +
|
|
29277
|
-
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
29278
|
-
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
29279
|
-
` + toolsetLine + contextPackLine,
|
|
29385
|
+
existing.trimEnd() + "\n\n" + envMarker + "\n" + envLines + "\n",
|
|
29280
29386
|
"utf8"
|
|
29281
29387
|
);
|
|
29282
29388
|
return "updated";
|
|
@@ -29284,51 +29390,43 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
|
29284
29390
|
const lines = existing.split(/\r?\n/);
|
|
29285
29391
|
const out = [];
|
|
29286
29392
|
let inEnv = false;
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
let sawContextPack = false;
|
|
29393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29394
|
+
const managedKeys = new Set(Object.keys(env));
|
|
29290
29395
|
for (const line of lines) {
|
|
29291
29396
|
const trimmed = line.trim();
|
|
29292
29397
|
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
29293
29398
|
if (inEnv && trimmed !== envMarker) {
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
|
|
29297
|
-
|
|
29298
|
-
|
|
29299
|
-
);
|
|
29399
|
+
for (const [key, value] of Object.entries(env)) {
|
|
29400
|
+
if (!seen.has(key)) {
|
|
29401
|
+
out.push(`${key} = "${escapeTomlString(value)}"`);
|
|
29402
|
+
}
|
|
29403
|
+
}
|
|
29300
29404
|
inEnv = false;
|
|
29301
29405
|
}
|
|
29302
|
-
if (trimmed === envMarker)
|
|
29406
|
+
if (trimmed === envMarker) {
|
|
29407
|
+
inEnv = true;
|
|
29408
|
+
seen.clear();
|
|
29409
|
+
}
|
|
29303
29410
|
out.push(line);
|
|
29304
29411
|
continue;
|
|
29305
29412
|
}
|
|
29306
|
-
if (inEnv
|
|
29307
|
-
|
|
29308
|
-
|
|
29309
|
-
|
|
29310
|
-
|
|
29311
|
-
|
|
29312
|
-
|
|
29313
|
-
|
|
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;
|
|
29413
|
+
if (inEnv) {
|
|
29414
|
+
const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=/);
|
|
29415
|
+
if (match && managedKeys.has(match[1])) {
|
|
29416
|
+
const key = match[1];
|
|
29417
|
+
out.push(`${key} = "${escapeTomlString(env[key])}"`);
|
|
29418
|
+
seen.add(key);
|
|
29419
|
+
continue;
|
|
29420
|
+
}
|
|
29322
29421
|
}
|
|
29323
29422
|
out.push(line);
|
|
29324
29423
|
}
|
|
29325
29424
|
if (inEnv) {
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
);
|
|
29425
|
+
for (const [key, value] of Object.entries(env)) {
|
|
29426
|
+
if (!seen.has(key)) {
|
|
29427
|
+
out.push(`${key} = "${escapeTomlString(value)}"`);
|
|
29428
|
+
}
|
|
29429
|
+
}
|
|
29332
29430
|
}
|
|
29333
29431
|
const updated = out.join("\n");
|
|
29334
29432
|
if (updated === existing) return "skipped";
|
|
@@ -30371,6 +30469,7 @@ Environment variables:
|
|
|
30371
30469
|
CONTEXTSTREAM_PROGRESSIVE_MODE Progressive disclosure: true|false (default: false, starts with ~13 core tools)
|
|
30372
30470
|
CONTEXTSTREAM_ROUTER_MODE Router pattern: true|false (default: false, exposes only 2 meta-tools)
|
|
30373
30471
|
CONTEXTSTREAM_OUTPUT_FORMAT Output verbosity: compact|pretty (default: compact, ~30% fewer tokens)
|
|
30472
|
+
CONTEXTSTREAM_INCLUDE_STRUCTURED_CONTENT Include structured JSON payloads in tool results: true|false (default: true)
|
|
30374
30473
|
CONTEXTSTREAM_SEARCH_LIMIT Default MCP search limit (default: 3)
|
|
30375
30474
|
CONTEXTSTREAM_SEARCH_MAX_CHARS Max chars per search result content (default: 400)
|
|
30376
30475
|
CONTEXTSTREAM_CONSOLIDATED Consolidated domain tools: true|false (default: true in v0.4.x, ~75% token reduction)
|