@contextstream/mcp-server 0.4.21 → 0.4.23
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/LICENSE +21 -21
- package/README.md +403 -399
- package/dist/index.js +212 -49
- package/package.json +57 -57
package/dist/index.js
CHANGED
|
@@ -4905,6 +4905,13 @@ function normalizeGraphTier(value) {
|
|
|
4905
4905
|
}
|
|
4906
4906
|
var AI_PLAN_TIMEOUT_MS = 5e4;
|
|
4907
4907
|
var AI_PLAN_RETRIES = 0;
|
|
4908
|
+
var INGEST_BENEFITS = [
|
|
4909
|
+
"Enable semantic code search across your entire codebase",
|
|
4910
|
+
"Get AI-powered code understanding and context for your questions",
|
|
4911
|
+
"Unlock dependency analysis and impact assessment",
|
|
4912
|
+
"Allow the AI assistant to find relevant code without manual file navigation",
|
|
4913
|
+
"Build a searchable knowledge base of your codebase structure"
|
|
4914
|
+
];
|
|
4908
4915
|
var ContextStreamClient = class {
|
|
4909
4916
|
constructor(config) {
|
|
4910
4917
|
this.config = config;
|
|
@@ -5450,6 +5457,72 @@ var ContextStreamClient = class {
|
|
|
5450
5457
|
uuidSchema.parse(projectId);
|
|
5451
5458
|
return request(this.config, `/projects/${projectId}/index/status`, { method: "GET" });
|
|
5452
5459
|
}
|
|
5460
|
+
/**
|
|
5461
|
+
* Check if project ingestion is recommended and return a recommendation.
|
|
5462
|
+
* This is used by session_init to inform the AI about the project's index status.
|
|
5463
|
+
*
|
|
5464
|
+
* @param projectId - Project UUID
|
|
5465
|
+
* @param folderPath - Optional folder path for generating the ingest command
|
|
5466
|
+
* @returns IngestRecommendation with status, reason, and benefits if recommended
|
|
5467
|
+
*/
|
|
5468
|
+
async checkIngestRecommendation(projectId, folderPath) {
|
|
5469
|
+
try {
|
|
5470
|
+
const status = await this.projectIndexStatus(projectId);
|
|
5471
|
+
const data = status.data || status;
|
|
5472
|
+
const indexedFiles = data.indexed_files ?? 0;
|
|
5473
|
+
const lastUpdated = data.last_updated;
|
|
5474
|
+
const statusDetail = data.status_detail;
|
|
5475
|
+
const isStale = lastUpdated ? Date.now() - new Date(lastUpdated).getTime() > 24 * 60 * 60 * 1e3 : false;
|
|
5476
|
+
const isRecent = lastUpdated ? Date.now() - new Date(lastUpdated).getTime() < 60 * 60 * 1e3 : false;
|
|
5477
|
+
const ingestCommand = folderPath ? `project(action="ingest_local", path="${folderPath}")` : `project(action="ingest_local", path="<your_project_path>")`;
|
|
5478
|
+
if (indexedFiles === 0 || statusDetail === "no_files_indexed") {
|
|
5479
|
+
return {
|
|
5480
|
+
recommended: true,
|
|
5481
|
+
status: "not_indexed",
|
|
5482
|
+
indexed_files: 0,
|
|
5483
|
+
reason: "No files have been indexed yet. Ingesting your codebase will enable semantic search and AI-powered code understanding.",
|
|
5484
|
+
benefits: INGEST_BENEFITS,
|
|
5485
|
+
command: ingestCommand
|
|
5486
|
+
};
|
|
5487
|
+
}
|
|
5488
|
+
if (isStale) {
|
|
5489
|
+
return {
|
|
5490
|
+
recommended: true,
|
|
5491
|
+
status: "stale",
|
|
5492
|
+
indexed_files: indexedFiles,
|
|
5493
|
+
last_indexed: lastUpdated,
|
|
5494
|
+
reason: `Index is over 24 hours old (${indexedFiles} files). Re-ingesting will capture any recent changes.`,
|
|
5495
|
+
benefits: ["Capture recent code changes", "Update dependency analysis", "Improve search accuracy"],
|
|
5496
|
+
command: ingestCommand
|
|
5497
|
+
};
|
|
5498
|
+
}
|
|
5499
|
+
if (isRecent) {
|
|
5500
|
+
return {
|
|
5501
|
+
recommended: false,
|
|
5502
|
+
status: "recently_indexed",
|
|
5503
|
+
indexed_files: indexedFiles,
|
|
5504
|
+
last_indexed: lastUpdated,
|
|
5505
|
+
reason: `Project was recently indexed (${indexedFiles} files). No action needed.`
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
return {
|
|
5509
|
+
recommended: false,
|
|
5510
|
+
status: "indexed",
|
|
5511
|
+
indexed_files: indexedFiles,
|
|
5512
|
+
last_indexed: lastUpdated,
|
|
5513
|
+
reason: `Project is indexed (${indexedFiles} files). Consider re-indexing if you've made significant changes.`
|
|
5514
|
+
};
|
|
5515
|
+
} catch (error) {
|
|
5516
|
+
const ingestCommand = folderPath ? `project(action="ingest_local", path="${folderPath}")` : `project(action="ingest_local", path="<your_project_path>")`;
|
|
5517
|
+
return {
|
|
5518
|
+
recommended: true,
|
|
5519
|
+
status: "not_indexed",
|
|
5520
|
+
reason: "Unable to determine index status. Ingesting will ensure your codebase is searchable.",
|
|
5521
|
+
benefits: INGEST_BENEFITS,
|
|
5522
|
+
command: ingestCommand
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5453
5526
|
/**
|
|
5454
5527
|
* Ingest files for indexing
|
|
5455
5528
|
* This uploads files to the API for indexing
|
|
@@ -5784,20 +5857,45 @@ var ContextStreamClient = class {
|
|
|
5784
5857
|
});
|
|
5785
5858
|
}
|
|
5786
5859
|
}
|
|
5787
|
-
if (projectId
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5860
|
+
if (projectId) {
|
|
5861
|
+
const autoIndex = params.auto_index === void 0 || params.auto_index === true;
|
|
5862
|
+
if (autoIndex) {
|
|
5863
|
+
context.indexing_status = "started";
|
|
5864
|
+
context.ingest_recommendation = {
|
|
5865
|
+
recommended: false,
|
|
5866
|
+
status: "auto_started",
|
|
5867
|
+
reason: "Background ingestion started automatically. Your codebase will be searchable shortly."
|
|
5868
|
+
};
|
|
5869
|
+
const projectIdCopy = projectId;
|
|
5870
|
+
const rootPathCopy = rootPath;
|
|
5871
|
+
(async () => {
|
|
5872
|
+
try {
|
|
5873
|
+
for await (const batch of readAllFilesInBatches(rootPathCopy, { batchSize: 50 })) {
|
|
5874
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
5875
|
+
}
|
|
5876
|
+
console.error(`[ContextStream] Background indexing completed for ${rootPathCopy}`);
|
|
5877
|
+
} catch (e) {
|
|
5878
|
+
console.error(`[ContextStream] Background indexing failed:`, e);
|
|
5879
|
+
}
|
|
5880
|
+
})();
|
|
5881
|
+
} else {
|
|
5792
5882
|
try {
|
|
5793
|
-
|
|
5794
|
-
|
|
5883
|
+
const recommendation = await this.checkIngestRecommendation(projectId, rootPath);
|
|
5884
|
+
context.ingest_recommendation = recommendation;
|
|
5885
|
+
if (recommendation.recommended) {
|
|
5886
|
+
console.error(`[ContextStream] Ingest recommended for ${rootPath}: ${recommendation.status}`);
|
|
5795
5887
|
}
|
|
5796
|
-
console.error(`[ContextStream] Background indexing completed for ${rootPathCopy}`);
|
|
5797
5888
|
} catch (e) {
|
|
5798
|
-
console.error(`[ContextStream]
|
|
5889
|
+
console.error(`[ContextStream] Failed to check ingest recommendation:`, e);
|
|
5890
|
+
context.ingest_recommendation = {
|
|
5891
|
+
recommended: true,
|
|
5892
|
+
status: "not_indexed",
|
|
5893
|
+
reason: "Unable to determine index status. Consider ingesting to enable code search.",
|
|
5894
|
+
benefits: INGEST_BENEFITS,
|
|
5895
|
+
command: `project(action="ingest_local", path="${rootPath}")`
|
|
5896
|
+
};
|
|
5799
5897
|
}
|
|
5800
|
-
}
|
|
5898
|
+
}
|
|
5801
5899
|
}
|
|
5802
5900
|
} catch (e) {
|
|
5803
5901
|
context.project_error = String(e);
|
|
@@ -5877,6 +5975,22 @@ var ContextStreamClient = class {
|
|
|
5877
5975
|
await this._fetchSessionContextFallback(context, workspaceId, projectId, params);
|
|
5878
5976
|
}
|
|
5879
5977
|
}
|
|
5978
|
+
if (projectId && !context.ingest_recommendation) {
|
|
5979
|
+
try {
|
|
5980
|
+
const recommendation = await this.checkIngestRecommendation(projectId, rootPath);
|
|
5981
|
+
context.ingest_recommendation = recommendation;
|
|
5982
|
+
if (recommendation.recommended) {
|
|
5983
|
+
console.error(`[ContextStream] Ingest recommended for existing project ${projectId}: ${recommendation.status}`);
|
|
5984
|
+
}
|
|
5985
|
+
} catch (e) {
|
|
5986
|
+
console.error(`[ContextStream] Failed to check ingest recommendation for existing project:`, e);
|
|
5987
|
+
context.ingest_recommendation = {
|
|
5988
|
+
recommended: false,
|
|
5989
|
+
status: "indexed",
|
|
5990
|
+
reason: 'Unable to determine exact index status. Use project(action="index_status") to check.'
|
|
5991
|
+
};
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5880
5994
|
return context;
|
|
5881
5995
|
}
|
|
5882
5996
|
/**
|
|
@@ -6717,8 +6831,11 @@ var ContextStreamClient = class {
|
|
|
6717
6831
|
let charsUsed = 0;
|
|
6718
6832
|
const maxChars = maxTokens * 4;
|
|
6719
6833
|
let candidateContext;
|
|
6834
|
+
const CS_FIRST_RULE = "\u26A0\uFE0F STOP: Before Glob/Grep/Read \u2192 search(mode=hybrid) FIRST. Local tools ONLY if 0 results.";
|
|
6835
|
+
const CS_FIRST_RULE_MINIFIED = "R:CS-first|Idx:project.index_status->ingest|NoLocalScanUnlessCSempty";
|
|
6720
6836
|
if (format === "minified") {
|
|
6721
|
-
const parts = [];
|
|
6837
|
+
const parts = [CS_FIRST_RULE_MINIFIED];
|
|
6838
|
+
charsUsed = CS_FIRST_RULE_MINIFIED.length + 1;
|
|
6722
6839
|
for (const item of items) {
|
|
6723
6840
|
const entry = `${item.type}:${item.value}`;
|
|
6724
6841
|
if (charsUsed + entry.length + 1 > maxChars) break;
|
|
@@ -6726,9 +6843,10 @@ var ContextStreamClient = class {
|
|
|
6726
6843
|
charsUsed += entry.length + 1;
|
|
6727
6844
|
}
|
|
6728
6845
|
context = parts.join("|");
|
|
6729
|
-
candidateContext = items.map((i) => `${i.type}:${i.value}`).join("|");
|
|
6846
|
+
candidateContext = [CS_FIRST_RULE_MINIFIED, ...items.map((i) => `${i.type}:${i.value}`)].join("|");
|
|
6730
6847
|
} else if (format === "structured") {
|
|
6731
|
-
const grouped = {};
|
|
6848
|
+
const grouped = { R: [CS_FIRST_RULE] };
|
|
6849
|
+
charsUsed = CS_FIRST_RULE.length + 10;
|
|
6732
6850
|
for (const item of items) {
|
|
6733
6851
|
if (charsUsed > maxChars) break;
|
|
6734
6852
|
if (!grouped[item.type]) grouped[item.type] = [];
|
|
@@ -6736,14 +6854,15 @@ var ContextStreamClient = class {
|
|
|
6736
6854
|
charsUsed += item.value.length + 5;
|
|
6737
6855
|
}
|
|
6738
6856
|
context = JSON.stringify(grouped);
|
|
6739
|
-
const candidateGrouped = {};
|
|
6857
|
+
const candidateGrouped = { R: [CS_FIRST_RULE] };
|
|
6740
6858
|
for (const item of items) {
|
|
6741
6859
|
if (!candidateGrouped[item.type]) candidateGrouped[item.type] = [];
|
|
6742
6860
|
candidateGrouped[item.type].push(item.value);
|
|
6743
6861
|
}
|
|
6744
6862
|
candidateContext = JSON.stringify(candidateGrouped);
|
|
6745
6863
|
} else {
|
|
6746
|
-
const lines = ["[CTX]"];
|
|
6864
|
+
const lines = [CS_FIRST_RULE, "", "[CTX]"];
|
|
6865
|
+
charsUsed = CS_FIRST_RULE.length + 10;
|
|
6747
6866
|
for (const item of items) {
|
|
6748
6867
|
const line = `${item.type}:${item.value}`;
|
|
6749
6868
|
if (charsUsed + line.length + 1 > maxChars) break;
|
|
@@ -6752,7 +6871,7 @@ var ContextStreamClient = class {
|
|
|
6752
6871
|
}
|
|
6753
6872
|
lines.push("[/CTX]");
|
|
6754
6873
|
context = lines.join("\n");
|
|
6755
|
-
const candidateLines = ["[CTX]"];
|
|
6874
|
+
const candidateLines = [CS_FIRST_RULE, "", "[CTX]"];
|
|
6756
6875
|
for (const item of items) {
|
|
6757
6876
|
candidateLines.push(`${item.type}:${item.value}`);
|
|
6758
6877
|
}
|
|
@@ -6761,7 +6880,9 @@ var ContextStreamClient = class {
|
|
|
6761
6880
|
}
|
|
6762
6881
|
if (context.length === 0 && withDefaults.workspace_id) {
|
|
6763
6882
|
const wsHint = items.find((i) => i.type === "W")?.value || withDefaults.workspace_id;
|
|
6764
|
-
context = format === "minified" ?
|
|
6883
|
+
context = format === "minified" ? `${CS_FIRST_RULE_MINIFIED}|W:${wsHint}|[NO_MATCHES]` : `${CS_FIRST_RULE}
|
|
6884
|
+
|
|
6885
|
+
[CTX]
|
|
6765
6886
|
W:${wsHint}
|
|
6766
6887
|
[NO_MATCHES]
|
|
6767
6888
|
[/CTX]`;
|
|
@@ -7548,7 +7669,7 @@ v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode
|
|
|
7548
7669
|
|
|
7549
7670
|
| Domain | Actions/Modes | Example |
|
|
7550
7671
|
|--------|---------------|---------|
|
|
7551
|
-
| **\`search\`** | mode: semantic, hybrid, keyword, pattern | \`search(mode="hybrid", query="auth implementation")\` |
|
|
7672
|
+
| **\`search\`** | mode: semantic, hybrid, keyword, pattern | \`search(mode="hybrid", query="auth implementation", limit=3)\` |
|
|
7552
7673
|
| **\`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="...")\` |
|
|
7553
7674
|
| **\`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)\` |
|
|
7554
7675
|
| **\`graph\`** | action: dependencies, impact, call_path, related, path, decisions, ingest, circular_dependencies, unused_code, contradictions | \`graph(action="impact", symbol_name="AuthService")\` |
|
|
@@ -7619,14 +7740,18 @@ Only after this preflight, proceed with search/analysis below.
|
|
|
7619
7740
|
|
|
7620
7741
|
### Search & Code Intelligence (ContextStream-first)
|
|
7621
7742
|
|
|
7743
|
+
\u26A0\uFE0F **STOP: Before using Glob/Grep/Read/Explore** \u2192 Call \`search(mode="hybrid")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
|
|
7744
|
+
|
|
7622
7745
|
**Search order:**
|
|
7623
7746
|
1. \`session(action="smart_search", query="...")\` - context-enriched
|
|
7624
|
-
2. \`search(mode="hybrid", query="...")\` or \`search(mode="keyword", query="<filename>")\`
|
|
7747
|
+
2. \`search(mode="hybrid", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
|
|
7625
7748
|
3. \`project(action="files")\` - file tree/list (only when needed)
|
|
7626
7749
|
4. \`graph(action="dependencies", ...)\` - code structure
|
|
7627
|
-
5. Local repo scans (rg/ls/find) -
|
|
7750
|
+
5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
|
|
7628
7751
|
|
|
7629
|
-
|
|
7752
|
+
**Search defaults:** \`search\` returns the top 3 results with compact snippets. Use \`limit\` + \`offset\` for pagination, and \`content_max_chars\` to expand snippets when needed.
|
|
7753
|
+
|
|
7754
|
+
If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits or ContextStream returned 0 results.
|
|
7630
7755
|
|
|
7631
7756
|
**Code Analysis:**
|
|
7632
7757
|
- Dependencies: \`graph(action="dependencies", file_path="...")\`
|
|
@@ -7726,7 +7851,7 @@ Rules Version: ${RULES_VERSION}
|
|
|
7726
7851
|
|
|
7727
7852
|
| Tool | Common Usage |
|
|
7728
7853
|
|------|--------------|
|
|
7729
|
-
| \`search\` | \`search(mode="semantic", query="...")\` \u2014 modes: semantic, hybrid, keyword, pattern |
|
|
7854
|
+
| \`search\` | \`search(mode="semantic", query="...", limit=3)\` \u2014 modes: semantic, hybrid, keyword, pattern |
|
|
7730
7855
|
| \`session\` | \`session(action="capture", ...)\` \u2014 actions: capture, capture_lesson, get_lessons, recall, remember, user_context, summary, compress, delta, smart_search |
|
|
7731
7856
|
| \`memory\` | \`memory(action="list_events", ...)\` \u2014 CRUD for events/nodes, search, decisions, timeline, summary |
|
|
7732
7857
|
| \`graph\` | \`graph(action="dependencies", ...)\` \u2014 dependencies, impact, call_path, related, ingest |
|
|
@@ -7737,10 +7862,13 @@ Rules Version: ${RULES_VERSION}
|
|
|
7737
7862
|
|
|
7738
7863
|
### Behavior Rules
|
|
7739
7864
|
|
|
7865
|
+
\u26A0\uFE0F **STOP: Before using Glob/Grep/Read/Explore** \u2192 Call \`search(mode="hybrid")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
|
|
7866
|
+
|
|
7740
7867
|
- **First message**: Call \`session_init\` with context_hint, then call \`context_smart\` before any other tool or response
|
|
7868
|
+
- **On [INGEST_RECOMMENDED]**: Ask the user if they want to enable semantic code search. Explain: "Indexing your codebase enables AI-powered code search, dependency analysis, and better context. This takes a few minutes." If user agrees, run the provided \`project(action="ingest_local")\` command.
|
|
7741
7869
|
- **Every message after**: Always call \`context_smart\` BEFORE responding (semantic search for relevant context)
|
|
7742
7870
|
- **Before searching files/code**: Check \`project(action="index_status")\`; if missing/stale run \`project(action="ingest_local", path="<cwd>")\` or \`project(action="index")\`, and use \`graph(action="ingest")\` if needed
|
|
7743
|
-
- **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\`
|
|
7871
|
+
- **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` \u2014 NEVER use local Glob/Grep/Read first
|
|
7744
7872
|
- **For file/function/config lookups**: Use \`search\`/\`graph\` first; only fall back to rg/ls/find if ContextStream returns no results
|
|
7745
7873
|
- **If ContextStream returns results**: Do NOT use local Search/Explore/Read; only open specific files when needed for exact edits
|
|
7746
7874
|
- **For code analysis**: Use \`graph(action="dependencies")\` or \`graph(action="impact")\` for call/dependency analysis
|
|
@@ -9154,8 +9282,14 @@ function getOperationSchema(name) {
|
|
|
9154
9282
|
};
|
|
9155
9283
|
}
|
|
9156
9284
|
}
|
|
9285
|
+
function parsePositiveInt(raw, fallback) {
|
|
9286
|
+
const parsed = Number.parseInt(raw ?? "", 10);
|
|
9287
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
9288
|
+
}
|
|
9157
9289
|
var OUTPUT_FORMAT = process.env.CONTEXTSTREAM_OUTPUT_FORMAT || "compact";
|
|
9158
9290
|
var COMPACT_OUTPUT = OUTPUT_FORMAT === "compact";
|
|
9291
|
+
var DEFAULT_SEARCH_LIMIT = parsePositiveInt(process.env.CONTEXTSTREAM_SEARCH_LIMIT, 3);
|
|
9292
|
+
var DEFAULT_SEARCH_CONTENT_MAX_CHARS = parsePositiveInt(process.env.CONTEXTSTREAM_SEARCH_MAX_CHARS, 400);
|
|
9159
9293
|
var CONSOLIDATED_MODE = process.env.CONTEXTSTREAM_CONSOLIDATED !== "false";
|
|
9160
9294
|
var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
|
|
9161
9295
|
"session_init",
|
|
@@ -9735,6 +9869,9 @@ Access: ${accessLabel}${showUpgrade ? ` (upgrade: ${upgradeUrl2})` : ""}`;
|
|
|
9735
9869
|
const isPlanLimit = String(errorCode).toUpperCase() === "FORBIDDEN" && String(errorMessage).toLowerCase().includes("plan limit reached");
|
|
9736
9870
|
const upgradeHint = isPlanLimit ? `
|
|
9737
9871
|
Upgrade: ${upgradeUrl2}` : "";
|
|
9872
|
+
const isUnauthorized = String(errorCode).toUpperCase() === "UNAUTHORIZED";
|
|
9873
|
+
const sessionHint = isUnauthorized ? `
|
|
9874
|
+
Hint: Run session_init(folder_path="<your_project_path>") first to establish a session, or check that your API key is valid.` : "";
|
|
9738
9875
|
const errorPayload = {
|
|
9739
9876
|
success: false,
|
|
9740
9877
|
error: {
|
|
@@ -9743,7 +9880,7 @@ Upgrade: ${upgradeUrl2}` : "";
|
|
|
9743
9880
|
details: errorDetails
|
|
9744
9881
|
}
|
|
9745
9882
|
};
|
|
9746
|
-
const errorText = `[${errorCode}] ${errorMessage}${upgradeHint}${errorDetails ? `: ${JSON.stringify(errorDetails)}` : ""}`;
|
|
9883
|
+
const errorText = `[${errorCode}] ${errorMessage}${upgradeHint}${sessionHint}${errorDetails ? `: ${JSON.stringify(errorDetails)}` : ""}`;
|
|
9747
9884
|
return {
|
|
9748
9885
|
content: [{ type: "text", text: errorText }],
|
|
9749
9886
|
structuredContent: errorPayload,
|
|
@@ -10303,13 +10440,28 @@ Access: Free`,
|
|
|
10303
10440
|
query: external_exports.string(),
|
|
10304
10441
|
workspace_id: external_exports.string().uuid().optional(),
|
|
10305
10442
|
project_id: external_exports.string().uuid().optional(),
|
|
10306
|
-
limit: external_exports.number().optional()
|
|
10443
|
+
limit: external_exports.number().optional().describe("Max results to return (default: 3)"),
|
|
10444
|
+
offset: external_exports.number().optional().describe("Offset for pagination"),
|
|
10445
|
+
content_max_chars: external_exports.number().optional().describe("Max chars per result content (default: 400)")
|
|
10307
10446
|
});
|
|
10447
|
+
function normalizeSearchParams(input) {
|
|
10448
|
+
const limit = typeof input.limit === "number" && input.limit > 0 ? Math.min(Math.floor(input.limit), 100) : DEFAULT_SEARCH_LIMIT;
|
|
10449
|
+
const offset = typeof input.offset === "number" && input.offset > 0 ? Math.floor(input.offset) : void 0;
|
|
10450
|
+
const contentMax = typeof input.content_max_chars === "number" && input.content_max_chars > 0 ? Math.max(50, Math.min(Math.floor(input.content_max_chars), 1e4)) : DEFAULT_SEARCH_CONTENT_MAX_CHARS;
|
|
10451
|
+
return {
|
|
10452
|
+
query: input.query,
|
|
10453
|
+
workspace_id: resolveWorkspaceId(input.workspace_id),
|
|
10454
|
+
project_id: resolveProjectId(input.project_id),
|
|
10455
|
+
limit,
|
|
10456
|
+
offset,
|
|
10457
|
+
content_max_chars: contentMax
|
|
10458
|
+
};
|
|
10459
|
+
}
|
|
10308
10460
|
registerTool(
|
|
10309
10461
|
"search_semantic",
|
|
10310
10462
|
{ title: "Semantic search", description: "Semantic vector search", inputSchema: searchSchema },
|
|
10311
10463
|
async (input) => {
|
|
10312
|
-
const result = await client.searchSemantic(input);
|
|
10464
|
+
const result = await client.searchSemantic(normalizeSearchParams(input));
|
|
10313
10465
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
10314
10466
|
}
|
|
10315
10467
|
);
|
|
@@ -10317,7 +10469,7 @@ Access: Free`,
|
|
|
10317
10469
|
"search_hybrid",
|
|
10318
10470
|
{ title: "Hybrid search", description: "Hybrid search (semantic + keyword)", inputSchema: searchSchema },
|
|
10319
10471
|
async (input) => {
|
|
10320
|
-
const result = await client.searchHybrid(input);
|
|
10472
|
+
const result = await client.searchHybrid(normalizeSearchParams(input));
|
|
10321
10473
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
10322
10474
|
}
|
|
10323
10475
|
);
|
|
@@ -10325,7 +10477,7 @@ Access: Free`,
|
|
|
10325
10477
|
"search_keyword",
|
|
10326
10478
|
{ title: "Keyword search", description: "Keyword search", inputSchema: searchSchema },
|
|
10327
10479
|
async (input) => {
|
|
10328
|
-
const result = await client.searchKeyword(input);
|
|
10480
|
+
const result = await client.searchKeyword(normalizeSearchParams(input));
|
|
10329
10481
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
10330
10482
|
}
|
|
10331
10483
|
);
|
|
@@ -10333,7 +10485,7 @@ Access: Free`,
|
|
|
10333
10485
|
"search_pattern",
|
|
10334
10486
|
{ title: "Pattern search", description: "Pattern/regex search", inputSchema: searchSchema },
|
|
10335
10487
|
async (input) => {
|
|
10336
|
-
const result = await client.searchPattern(input);
|
|
10488
|
+
const result = await client.searchPattern(normalizeSearchParams(input));
|
|
10337
10489
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
10338
10490
|
}
|
|
10339
10491
|
);
|
|
@@ -11149,8 +11301,12 @@ Runs in the background and returns immediately; use 'projects_index_status' to m
|
|
|
11149
11301
|
title: "Initialize conversation session",
|
|
11150
11302
|
description: `Initialize a new conversation session and automatically retrieve relevant context.
|
|
11151
11303
|
This is the FIRST tool AI assistants should call when starting a conversation.
|
|
11152
|
-
Returns: workspace info, project info, recent memory, recent decisions, relevant context,
|
|
11153
|
-
|
|
11304
|
+
Returns: workspace info, project info, recent memory, recent decisions, relevant context, high-priority lessons, and ingest_recommendation.
|
|
11305
|
+
|
|
11306
|
+
The ingest_recommendation field indicates if the project needs indexing for code search:
|
|
11307
|
+
- If [INGEST_RECOMMENDED] appears, ask the user if they want to enable semantic code search
|
|
11308
|
+
- Benefits: AI-powered code understanding, dependency analysis, better context retrieval
|
|
11309
|
+
- If user agrees, run: project(action="ingest_local", path="<project_path>")
|
|
11154
11310
|
|
|
11155
11311
|
IMPORTANT: Pass the user's FIRST MESSAGE as context_hint to get semantically relevant context!
|
|
11156
11312
|
Example: session_init(folder_path="/path/to/project", context_hint="how do I implement auth?")
|
|
@@ -11283,10 +11439,24 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
11283
11439
|
if (versionNotice?.behind) {
|
|
11284
11440
|
noticeLines.push(`[VERSION_NOTICE] current=${versionNotice.current} latest=${versionNotice.latest} upgrade="${versionNotice.upgrade_command}"`);
|
|
11285
11441
|
}
|
|
11442
|
+
const ingestRec = result.ingest_recommendation;
|
|
11443
|
+
if (ingestRec?.recommended) {
|
|
11444
|
+
const benefitsList = ingestRec.benefits?.slice(0, 3).map((b) => ` \u2022 ${b}`).join("\n") || "";
|
|
11445
|
+
noticeLines.push(
|
|
11446
|
+
`[INGEST_RECOMMENDED] status=${ingestRec.status}`,
|
|
11447
|
+
`Reason: ${ingestRec.reason}`,
|
|
11448
|
+
ingestRec.benefits ? `Benefits:
|
|
11449
|
+
${benefitsList}` : "",
|
|
11450
|
+
`Action: Ask the user if they want to enable code search by running:`,
|
|
11451
|
+
` ${ingestRec.command || 'project(action="ingest_local", path="<project_path>")'}`
|
|
11452
|
+
);
|
|
11453
|
+
} else if (ingestRec?.status === "auto_started") {
|
|
11454
|
+
noticeLines.push(`[INGEST_STATUS] Background indexing started. Codebase will be searchable shortly.`);
|
|
11455
|
+
}
|
|
11286
11456
|
if (noticeLines.length > 0) {
|
|
11287
11457
|
text = `${text}
|
|
11288
11458
|
|
|
11289
|
-
${noticeLines.join("\n")}`;
|
|
11459
|
+
${noticeLines.filter(Boolean).join("\n")}`;
|
|
11290
11460
|
}
|
|
11291
11461
|
return { content: [{ type: "text", text }], structuredContent: toStructured(result) };
|
|
11292
11462
|
}
|
|
@@ -13118,16 +13288,13 @@ Use this to remove a reminder that is no longer relevant.`,
|
|
|
13118
13288
|
query: external_exports.string().describe("Search query"),
|
|
13119
13289
|
workspace_id: external_exports.string().uuid().optional(),
|
|
13120
13290
|
project_id: external_exports.string().uuid().optional(),
|
|
13121
|
-
limit: external_exports.number().optional()
|
|
13291
|
+
limit: external_exports.number().optional().describe("Max results to return (default: 3)"),
|
|
13292
|
+
offset: external_exports.number().optional().describe("Offset for pagination"),
|
|
13293
|
+
content_max_chars: external_exports.number().optional().describe("Max chars per result content (default: 400)")
|
|
13122
13294
|
})
|
|
13123
13295
|
},
|
|
13124
13296
|
async (input) => {
|
|
13125
|
-
const params =
|
|
13126
|
-
query: input.query,
|
|
13127
|
-
workspace_id: resolveWorkspaceId(input.workspace_id),
|
|
13128
|
-
project_id: resolveProjectId(input.project_id),
|
|
13129
|
-
limit: input.limit
|
|
13130
|
-
};
|
|
13297
|
+
const params = normalizeSearchParams(input);
|
|
13131
13298
|
let result;
|
|
13132
13299
|
switch (input.mode) {
|
|
13133
13300
|
case "semantic":
|
|
@@ -16739,10 +16906,7 @@ Code: ${device.user_code}`);
|
|
|
16739
16906
|
}
|
|
16740
16907
|
apiKey = createdKey.secret_key.trim();
|
|
16741
16908
|
apiKeySource = "browser";
|
|
16742
|
-
|
|
16743
|
-
console.log(`
|
|
16744
|
-
Created API key: ${maskedNewKey}
|
|
16745
|
-
`);
|
|
16909
|
+
console.log("\nCreated API key\n");
|
|
16746
16910
|
}
|
|
16747
16911
|
}
|
|
16748
16912
|
const client = new ContextStreamClient(buildClientConfig({ apiUrl, apiKey }));
|
|
@@ -16754,14 +16918,11 @@ Created API key: ${maskedNewKey}
|
|
|
16754
16918
|
throw new Error(`Authentication failed. Check your API key. (${message})`);
|
|
16755
16919
|
}
|
|
16756
16920
|
const email = typeof me?.data?.email === "string" ? me.data.email : typeof me?.email === "string" ? me.email : void 0;
|
|
16757
|
-
|
|
16758
|
-
console.log(`Authenticated as: ${email || "unknown user"} (${maskedKey})
|
|
16759
|
-
`);
|
|
16921
|
+
console.log("Authenticated\n");
|
|
16760
16922
|
if (!dryRun && (apiKeySource === "browser" || apiKeySource === "paste")) {
|
|
16761
16923
|
try {
|
|
16762
|
-
|
|
16763
|
-
console.log(
|
|
16764
|
-
`);
|
|
16924
|
+
await writeSavedCredentials({ apiUrl, apiKey, email });
|
|
16925
|
+
console.log("Saved API key for future runs\n");
|
|
16765
16926
|
} catch (err) {
|
|
16766
16927
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16767
16928
|
console.log(`Warning: failed to save API key for future runs (${credentialsFilePath()}): ${msg}
|
|
@@ -17248,6 +17409,8 @@ Environment variables:
|
|
|
17248
17409
|
CONTEXTSTREAM_PROGRESSIVE_MODE Progressive disclosure: true|false (default: false, starts with ~13 core tools)
|
|
17249
17410
|
CONTEXTSTREAM_ROUTER_MODE Router pattern: true|false (default: false, exposes only 2 meta-tools)
|
|
17250
17411
|
CONTEXTSTREAM_OUTPUT_FORMAT Output verbosity: compact|pretty (default: compact, ~30% fewer tokens)
|
|
17412
|
+
CONTEXTSTREAM_SEARCH_LIMIT Default MCP search limit (default: 3)
|
|
17413
|
+
CONTEXTSTREAM_SEARCH_MAX_CHARS Max chars per search result content (default: 400)
|
|
17251
17414
|
CONTEXTSTREAM_CONSOLIDATED Consolidated domain tools: true|false (default: true in v0.4.x, ~75% token reduction)
|
|
17252
17415
|
CONTEXTSTREAM_CONTEXT_PACK Enable Context Pack in context_smart: true|false (default: true)
|
|
17253
17416
|
CONTEXTSTREAM_PRO_TOOLS Optional comma-separated PRO tool names (default: AI tools)
|
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@contextstream/mcp-server",
|
|
3
|
-
"mcpName": "io.github.contextstreamio/mcp-server",
|
|
4
|
-
"version": "0.4.
|
|
5
|
-
"description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"main": "dist/index.js",
|
|
9
|
-
"files": [
|
|
10
|
-
"dist",
|
|
11
|
-
"README.md"
|
|
12
|
-
],
|
|
13
|
-
"bin": {
|
|
14
|
-
"contextstream-mcp": "dist/index.js"
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"dev": "tsx src/index.ts",
|
|
18
|
-
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=esm --external:@modelcontextprotocol/sdk --banner:js=\"#!/usr/bin/env node\" && esbuild src/test-server.ts --bundle --platform=node --target=node18 --outfile=dist/test-server.js --format=esm --external:@modelcontextprotocol/sdk",
|
|
19
|
-
"start": "node dist/index.js",
|
|
20
|
-
"test-server": "tsx src/test-server.ts",
|
|
21
|
-
"test-server:start": "node dist/test-server.js",
|
|
22
|
-
"typecheck": "tsc --noEmit"
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
26
|
-
"zod": "^3.23.8"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"@types/node": "^20.10.0",
|
|
30
|
-
"esbuild": "^0.27.0",
|
|
31
|
-
"tsx": "^4.15.4",
|
|
32
|
-
"typescript": "^5.6.3"
|
|
33
|
-
},
|
|
34
|
-
"engines": {
|
|
35
|
-
"node": ">=18"
|
|
36
|
-
},
|
|
37
|
-
"keywords": [
|
|
38
|
-
"mcp",
|
|
39
|
-
"model-context-protocol",
|
|
40
|
-
"contextstream",
|
|
41
|
-
"ai",
|
|
42
|
-
"code-context",
|
|
43
|
-
"memory",
|
|
44
|
-
"knowledge-graph"
|
|
45
|
-
],
|
|
46
|
-
"repository": {
|
|
47
|
-
"type": "git",
|
|
48
|
-
"url": "git+https://github.com/contextstream/mcp-server.git"
|
|
49
|
-
},
|
|
50
|
-
"homepage": "https://contextstream.io/docs/mcp",
|
|
51
|
-
"bugs": {
|
|
52
|
-
"url": "https://github.com/contextstream/mcp-server/issues"
|
|
53
|
-
},
|
|
54
|
-
"overrides": {
|
|
55
|
-
"qs": "6.14.1"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@contextstream/mcp-server",
|
|
3
|
+
"mcpName": "io.github.contextstreamio/mcp-server",
|
|
4
|
+
"version": "0.4.23",
|
|
5
|
+
"description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"contextstream-mcp": "dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=esm --external:@modelcontextprotocol/sdk --banner:js=\"#!/usr/bin/env node\" && esbuild src/test-server.ts --bundle --platform=node --target=node18 --outfile=dist/test-server.js --format=esm --external:@modelcontextprotocol/sdk",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"test-server": "tsx src/test-server.ts",
|
|
21
|
+
"test-server:start": "node dist/test-server.js",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
26
|
+
"zod": "^3.23.8"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.10.0",
|
|
30
|
+
"esbuild": "^0.27.0",
|
|
31
|
+
"tsx": "^4.15.4",
|
|
32
|
+
"typescript": "^5.6.3"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"mcp",
|
|
39
|
+
"model-context-protocol",
|
|
40
|
+
"contextstream",
|
|
41
|
+
"ai",
|
|
42
|
+
"code-context",
|
|
43
|
+
"memory",
|
|
44
|
+
"knowledge-graph"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/contextstream/mcp-server.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://contextstream.io/docs/mcp",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/contextstream/mcp-server/issues"
|
|
53
|
+
},
|
|
54
|
+
"overrides": {
|
|
55
|
+
"qs": "6.14.1"
|
|
56
|
+
}
|
|
57
|
+
}
|