@contextstream/mcp-server 0.4.24 → 0.4.26
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 +231 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4610,6 +4610,55 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
|
4610
4610
|
yield batch;
|
|
4611
4611
|
}
|
|
4612
4612
|
}
|
|
4613
|
+
async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}) {
|
|
4614
|
+
const batchSize = options.batchSize ?? 50;
|
|
4615
|
+
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
4616
|
+
const sinceMs = sinceTimestamp.getTime();
|
|
4617
|
+
let batch = [];
|
|
4618
|
+
let filesScanned = 0;
|
|
4619
|
+
let filesChanged = 0;
|
|
4620
|
+
async function* walkDir(dir, relativePath = "") {
|
|
4621
|
+
let entries;
|
|
4622
|
+
try {
|
|
4623
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
4624
|
+
} catch {
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
for (const entry of entries) {
|
|
4628
|
+
const fullPath = path.join(dir, entry.name);
|
|
4629
|
+
const relPath = path.join(relativePath, entry.name);
|
|
4630
|
+
if (entry.isDirectory()) {
|
|
4631
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
4632
|
+
yield* walkDir(fullPath, relPath);
|
|
4633
|
+
} else if (entry.isFile()) {
|
|
4634
|
+
if (IGNORE_FILES.has(entry.name)) continue;
|
|
4635
|
+
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
4636
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
4637
|
+
try {
|
|
4638
|
+
const stat2 = await fs.promises.stat(fullPath);
|
|
4639
|
+
filesScanned++;
|
|
4640
|
+
if (stat2.mtimeMs <= sinceMs) continue;
|
|
4641
|
+
if (stat2.size > maxFileSize) continue;
|
|
4642
|
+
const content = await fs.promises.readFile(fullPath, "utf-8");
|
|
4643
|
+
filesChanged++;
|
|
4644
|
+
yield { path: relPath, content };
|
|
4645
|
+
} catch {
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
for await (const file of walkDir(rootPath)) {
|
|
4651
|
+
batch.push(file);
|
|
4652
|
+
if (batch.length >= batchSize) {
|
|
4653
|
+
yield batch;
|
|
4654
|
+
batch = [];
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
if (batch.length > 0) {
|
|
4658
|
+
yield batch;
|
|
4659
|
+
}
|
|
4660
|
+
console.error(`[ContextStream] Incremental scan: ${filesChanged} changed files out of ${filesScanned} scanned (since ${sinceTimestamp.toISOString()})`);
|
|
4661
|
+
}
|
|
4613
4662
|
async function countIndexableFiles(rootPath, options = {}) {
|
|
4614
4663
|
const maxFiles = options.maxFiles ?? 1;
|
|
4615
4664
|
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
@@ -4915,6 +4964,7 @@ var INGEST_BENEFITS = [
|
|
|
4915
4964
|
var ContextStreamClient = class {
|
|
4916
4965
|
constructor(config) {
|
|
4917
4966
|
this.config = config;
|
|
4967
|
+
this.indexRefreshInProgress = false;
|
|
4918
4968
|
}
|
|
4919
4969
|
/**
|
|
4920
4970
|
* Update the client's default workspace/project IDs at runtime.
|
|
@@ -5085,11 +5135,13 @@ var ContextStreamClient = class {
|
|
|
5085
5135
|
return request(this.config, `/projects/${projectId}/index`, { body: {} });
|
|
5086
5136
|
}
|
|
5087
5137
|
// Search - each method adds required search_type and filters fields
|
|
5138
|
+
// Optional params: context_lines (like grep -C), exact_match_boost (boost for exact matches)
|
|
5088
5139
|
searchSemantic(body) {
|
|
5089
5140
|
return request(this.config, "/search/semantic", {
|
|
5090
5141
|
body: {
|
|
5091
5142
|
...this.withDefaults(body),
|
|
5092
5143
|
search_type: "semantic",
|
|
5144
|
+
output_format: body.output_format,
|
|
5093
5145
|
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
5094
5146
|
}
|
|
5095
5147
|
});
|
|
@@ -5099,6 +5151,7 @@ var ContextStreamClient = class {
|
|
|
5099
5151
|
body: {
|
|
5100
5152
|
...this.withDefaults(body),
|
|
5101
5153
|
search_type: "hybrid",
|
|
5154
|
+
output_format: body.output_format,
|
|
5102
5155
|
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
5103
5156
|
}
|
|
5104
5157
|
});
|
|
@@ -5108,6 +5161,7 @@ var ContextStreamClient = class {
|
|
|
5108
5161
|
body: {
|
|
5109
5162
|
...this.withDefaults(body),
|
|
5110
5163
|
search_type: "keyword",
|
|
5164
|
+
output_format: body.output_format,
|
|
5111
5165
|
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
5112
5166
|
}
|
|
5113
5167
|
});
|
|
@@ -5117,6 +5171,22 @@ var ContextStreamClient = class {
|
|
|
5117
5171
|
body: {
|
|
5118
5172
|
...this.withDefaults(body),
|
|
5119
5173
|
search_type: "pattern",
|
|
5174
|
+
output_format: body.output_format,
|
|
5175
|
+
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
5176
|
+
}
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
/**
|
|
5180
|
+
* Exhaustive search returns ALL matches from the index.
|
|
5181
|
+
* Use this when you need complete coverage like grep.
|
|
5182
|
+
* Includes index_freshness to indicate result trustworthiness.
|
|
5183
|
+
*/
|
|
5184
|
+
searchExhaustive(body) {
|
|
5185
|
+
return request(this.config, "/search/exhaustive", {
|
|
5186
|
+
body: {
|
|
5187
|
+
...this.withDefaults(body),
|
|
5188
|
+
search_type: "exhaustive",
|
|
5189
|
+
output_format: body.output_format,
|
|
5120
5190
|
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
5121
5191
|
}
|
|
5122
5192
|
});
|
|
@@ -5682,6 +5752,7 @@ var ContextStreamClient = class {
|
|
|
5682
5752
|
session_id: params.session_id || randomUUID(),
|
|
5683
5753
|
initialized_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5684
5754
|
};
|
|
5755
|
+
this.sessionStartTime = Date.now();
|
|
5685
5756
|
const rootPath = ideRoots.length > 0 ? ideRoots[0] : void 0;
|
|
5686
5757
|
if (!workspaceId && rootPath) {
|
|
5687
5758
|
const resolved = resolveWorkspace(rootPath);
|
|
@@ -5906,6 +5977,8 @@ var ContextStreamClient = class {
|
|
|
5906
5977
|
context.workspace_name = workspaceName;
|
|
5907
5978
|
context.project_id = projectId;
|
|
5908
5979
|
context.ide_roots = ideRoots;
|
|
5980
|
+
this.sessionProjectId = projectId;
|
|
5981
|
+
this.sessionRootPath = rootPath;
|
|
5909
5982
|
if (!workspaceId) {
|
|
5910
5983
|
context.workspace_warning = "No workspace was resolved for this session. Workspace-level tools (memory/search/graph) may not work until you associate this folder with a workspace.";
|
|
5911
5984
|
}
|
|
@@ -5978,9 +6051,45 @@ var ContextStreamClient = class {
|
|
|
5978
6051
|
if (projectId && !context.ingest_recommendation) {
|
|
5979
6052
|
try {
|
|
5980
6053
|
const recommendation = await this.checkIngestRecommendation(projectId, rootPath);
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
6054
|
+
const autoIndex = params.auto_index !== false;
|
|
6055
|
+
const needsRefresh = recommendation.status === "not_indexed" || recommendation.status === "stale" || recommendation.status === "indexed";
|
|
6056
|
+
if (autoIndex && rootPath && needsRefresh && recommendation.status !== "recently_indexed") {
|
|
6057
|
+
const useIncremental = recommendation.last_indexed && recommendation.status !== "not_indexed";
|
|
6058
|
+
console.error(`[ContextStream] Auto-refreshing stale index for project ${projectId}: ${recommendation.status} (${useIncremental ? "incremental" : "full"})`);
|
|
6059
|
+
context.indexing_status = "refreshing";
|
|
6060
|
+
context.ingest_recommendation = {
|
|
6061
|
+
recommended: false,
|
|
6062
|
+
status: "auto_refreshing",
|
|
6063
|
+
indexed_files: recommendation.indexed_files,
|
|
6064
|
+
last_indexed: recommendation.last_indexed,
|
|
6065
|
+
reason: useIncremental ? "Incremental index refresh started automatically (only changed files)." : "Background index refresh started automatically to capture recent changes."
|
|
6066
|
+
};
|
|
6067
|
+
const projectIdCopy = projectId;
|
|
6068
|
+
const rootPathCopy = rootPath;
|
|
6069
|
+
const lastIndexedCopy = recommendation.last_indexed;
|
|
6070
|
+
(async () => {
|
|
6071
|
+
try {
|
|
6072
|
+
if (useIncremental && lastIndexedCopy) {
|
|
6073
|
+
const sinceDate = new Date(lastIndexedCopy);
|
|
6074
|
+
for await (const batch of readChangedFilesInBatches(rootPathCopy, sinceDate, { batchSize: 50 })) {
|
|
6075
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6076
|
+
}
|
|
6077
|
+
console.error(`[ContextStream] Incremental index refresh completed for ${rootPathCopy}`);
|
|
6078
|
+
} else {
|
|
6079
|
+
for await (const batch of readAllFilesInBatches(rootPathCopy, { batchSize: 50 })) {
|
|
6080
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6081
|
+
}
|
|
6082
|
+
console.error(`[ContextStream] Full index refresh completed for ${rootPathCopy}`);
|
|
6083
|
+
}
|
|
6084
|
+
} catch (e) {
|
|
6085
|
+
console.error(`[ContextStream] Background index refresh failed:`, e);
|
|
6086
|
+
}
|
|
6087
|
+
})();
|
|
6088
|
+
} else {
|
|
6089
|
+
context.ingest_recommendation = recommendation;
|
|
6090
|
+
if (recommendation.recommended) {
|
|
6091
|
+
console.error(`[ContextStream] Ingest recommended for existing project ${projectId}: ${recommendation.status}`);
|
|
6092
|
+
}
|
|
5984
6093
|
}
|
|
5985
6094
|
} catch (e) {
|
|
5986
6095
|
console.error(`[ContextStream] Failed to check ingest recommendation for existing project:`, e);
|
|
@@ -6692,6 +6801,46 @@ var ContextStreamClient = class {
|
|
|
6692
6801
|
sources_used: 0
|
|
6693
6802
|
};
|
|
6694
6803
|
}
|
|
6804
|
+
const THIRTY_MINUTES_MS = 30 * 60 * 1e3;
|
|
6805
|
+
const TEN_MINUTES_MS = 10 * 60 * 1e3;
|
|
6806
|
+
const now = Date.now();
|
|
6807
|
+
const sessionAge = this.sessionStartTime ? now - this.sessionStartTime : 0;
|
|
6808
|
+
const timeSinceLastCheck = this.lastRefreshCheckTime ? now - this.lastRefreshCheckTime : Infinity;
|
|
6809
|
+
if (sessionAge > THIRTY_MINUTES_MS && timeSinceLastCheck > TEN_MINUTES_MS && this.sessionProjectId && this.sessionRootPath && !this.indexRefreshInProgress) {
|
|
6810
|
+
this.lastRefreshCheckTime = now;
|
|
6811
|
+
const projectIdCopy = this.sessionProjectId;
|
|
6812
|
+
const rootPathCopy = this.sessionRootPath;
|
|
6813
|
+
(async () => {
|
|
6814
|
+
try {
|
|
6815
|
+
const recommendation = await this.checkIngestRecommendation(projectIdCopy, rootPathCopy);
|
|
6816
|
+
const needsRefresh = recommendation.status === "not_indexed" || recommendation.status === "stale" || recommendation.status === "indexed";
|
|
6817
|
+
if (needsRefresh && recommendation.status !== "recently_indexed") {
|
|
6818
|
+
this.indexRefreshInProgress = true;
|
|
6819
|
+
const useIncremental = recommendation.last_indexed && recommendation.status !== "not_indexed";
|
|
6820
|
+
console.error(`[ContextStream] Long session re-index: refreshing stale index for project ${projectIdCopy} (session age: ${Math.round(sessionAge / 6e4)} mins, ${useIncremental ? "incremental" : "full"})`);
|
|
6821
|
+
try {
|
|
6822
|
+
if (useIncremental && recommendation.last_indexed) {
|
|
6823
|
+
const sinceDate = new Date(recommendation.last_indexed);
|
|
6824
|
+
for await (const batch of readChangedFilesInBatches(rootPathCopy, sinceDate, { batchSize: 50 })) {
|
|
6825
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6826
|
+
}
|
|
6827
|
+
console.error(`[ContextStream] Long session incremental re-index completed for ${rootPathCopy}`);
|
|
6828
|
+
} else {
|
|
6829
|
+
for await (const batch of readAllFilesInBatches(rootPathCopy, { batchSize: 50 })) {
|
|
6830
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6831
|
+
}
|
|
6832
|
+
console.error(`[ContextStream] Long session full re-index completed for ${rootPathCopy}`);
|
|
6833
|
+
}
|
|
6834
|
+
} finally {
|
|
6835
|
+
this.indexRefreshInProgress = false;
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
} catch (e) {
|
|
6839
|
+
console.error(`[ContextStream] Long session re-index check failed:`, e);
|
|
6840
|
+
this.indexRefreshInProgress = false;
|
|
6841
|
+
}
|
|
6842
|
+
})();
|
|
6843
|
+
}
|
|
6695
6844
|
try {
|
|
6696
6845
|
const apiResult = await request(this.config, "/context/smart", {
|
|
6697
6846
|
body: {
|
|
@@ -6721,7 +6870,8 @@ var ContextStreamClient = class {
|
|
|
6721
6870
|
workspace_id: withDefaults.workspace_id,
|
|
6722
6871
|
project_id: withDefaults.project_id,
|
|
6723
6872
|
...versionNotice2 ? { version_notice: versionNotice2 } : {},
|
|
6724
|
-
...Array.isArray(data?.errors) ? { errors: data.errors } : {}
|
|
6873
|
+
...Array.isArray(data?.errors) ? { errors: data.errors } : {},
|
|
6874
|
+
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
|
|
6725
6875
|
};
|
|
6726
6876
|
} catch (err) {
|
|
6727
6877
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
@@ -6916,8 +7066,9 @@ W:${wsHint}
|
|
|
6916
7066
|
workspace_id: withDefaults.workspace_id,
|
|
6917
7067
|
project_id: withDefaults.project_id,
|
|
6918
7068
|
...versionNotice ? { version_notice: versionNotice } : {},
|
|
6919
|
-
...errors.length > 0 && { errors }
|
|
7069
|
+
...errors.length > 0 && { errors },
|
|
6920
7070
|
// Include errors for debugging
|
|
7071
|
+
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
|
|
6921
7072
|
};
|
|
6922
7073
|
}
|
|
6923
7074
|
/**
|
|
@@ -7742,6 +7893,18 @@ Only after this preflight, proceed with search/analysis below.
|
|
|
7742
7893
|
|
|
7743
7894
|
\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.
|
|
7744
7895
|
|
|
7896
|
+
**\u274C WRONG workflow (wastes tokens, slow):**
|
|
7897
|
+
\`\`\`
|
|
7898
|
+
Grep "function" \u2192 Read file1.ts \u2192 Read file2.ts \u2192 Read file3.ts \u2192 finally understand
|
|
7899
|
+
\`\`\`
|
|
7900
|
+
|
|
7901
|
+
**\u2705 CORRECT workflow (fast, complete):**
|
|
7902
|
+
\`\`\`
|
|
7903
|
+
search(mode="hybrid", query="function implementation") \u2192 done (results include context)
|
|
7904
|
+
\`\`\`
|
|
7905
|
+
|
|
7906
|
+
**Why?** ContextStream search returns semantic matches + context + file locations in ONE call. Local tools require multiple round-trips.
|
|
7907
|
+
|
|
7745
7908
|
**Search order:**
|
|
7746
7909
|
1. \`session(action="smart_search", query="...")\` - context-enriched
|
|
7747
7910
|
2. \`search(mode="hybrid", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
|
|
@@ -7749,6 +7912,22 @@ Only after this preflight, proceed with search/analysis below.
|
|
|
7749
7912
|
4. \`graph(action="dependencies", ...)\` - code structure
|
|
7750
7913
|
5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
|
|
7751
7914
|
|
|
7915
|
+
**Search Mode Selection:**
|
|
7916
|
+
|
|
7917
|
+
| Need | Mode | Example |
|
|
7918
|
+
|------|------|---------|
|
|
7919
|
+
| Find code by meaning | \`hybrid\` | "authentication logic", "error handling" |
|
|
7920
|
+
| Exact string/symbol | \`keyword\` | "UserAuthService", "API_KEY" |
|
|
7921
|
+
| File patterns | \`pattern\` | "*.sql", "test_*.py" |
|
|
7922
|
+
| ALL matches (refactoring) | \`exhaustive\` | "oldFunctionName" (rename across codebase) |
|
|
7923
|
+
| Conceptual search | \`semantic\` | "how does caching work" |
|
|
7924
|
+
|
|
7925
|
+
**Token Efficiency:** Use \`output_format\` to reduce response size:
|
|
7926
|
+
- \`full\` (default): Full content for understanding code
|
|
7927
|
+
- \`paths\`: File paths only (80% token savings) - use for file listings
|
|
7928
|
+
- \`minimal\`: Compact format (60% savings) - use for refactoring
|
|
7929
|
+
- \`count\`: Match counts only (90% savings) - use for quick checks
|
|
7930
|
+
|
|
7752
7931
|
**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
7932
|
|
|
7754
7933
|
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.
|
|
@@ -7864,6 +8043,18 @@ Rules Version: ${RULES_VERSION}
|
|
|
7864
8043
|
|
|
7865
8044
|
\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.
|
|
7866
8045
|
|
|
8046
|
+
**\u274C WRONG workflow (wastes tokens, slow):**
|
|
8047
|
+
\`\`\`
|
|
8048
|
+
Grep "function" \u2192 Read file1.ts \u2192 Read file2.ts \u2192 Read file3.ts \u2192 finally understand
|
|
8049
|
+
\`\`\`
|
|
8050
|
+
|
|
8051
|
+
**\u2705 CORRECT workflow (fast, complete):**
|
|
8052
|
+
\`\`\`
|
|
8053
|
+
search(mode="hybrid", query="function implementation") \u2192 done (results include context)
|
|
8054
|
+
\`\`\`
|
|
8055
|
+
|
|
8056
|
+
**Why?** ContextStream search returns semantic matches + context + file locations in ONE call. Local tools require multiple round-trips.
|
|
8057
|
+
|
|
7867
8058
|
- **First message**: Call \`session_init\` with context_hint, then call \`context_smart\` before any other tool or response
|
|
7868
8059
|
- **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.
|
|
7869
8060
|
- **Every message after**: Always call \`context_smart\` BEFORE responding (semantic search for relevant context)
|
|
@@ -7876,6 +8067,24 @@ Rules Version: ${RULES_VERSION}
|
|
|
7876
8067
|
- **After completing work**: Always capture decisions/insights with \`session(action="capture")\`
|
|
7877
8068
|
- **On mistakes/corrections**: Immediately capture lessons with \`session(action="capture_lesson")\`
|
|
7878
8069
|
|
|
8070
|
+
### Search Mode Selection
|
|
8071
|
+
|
|
8072
|
+
| Need | Mode | Example |
|
|
8073
|
+
|------|------|---------|
|
|
8074
|
+
| Find code by meaning | \`hybrid\` | "authentication logic", "error handling" |
|
|
8075
|
+
| Exact string/symbol | \`keyword\` | "UserAuthService", "API_KEY" |
|
|
8076
|
+
| File patterns | \`pattern\` | "*.sql", "test_*.py" |
|
|
8077
|
+
| ALL matches (refactoring) | \`exhaustive\` | "oldFunctionName" (rename across codebase) |
|
|
8078
|
+
| Conceptual search | \`semantic\` | "how does caching work" |
|
|
8079
|
+
|
|
8080
|
+
### Token Efficiency
|
|
8081
|
+
|
|
8082
|
+
Use \`output_format\` to reduce response size:
|
|
8083
|
+
- \`full\` (default): Full content for understanding code
|
|
8084
|
+
- \`paths\`: File paths only (80% token savings) - use for file listings
|
|
8085
|
+
- \`minimal\`: Compact format (60% savings) - use for refactoring
|
|
8086
|
+
- \`count\`: Match counts only (90% savings) - use for quick checks
|
|
8087
|
+
|
|
7879
8088
|
### Plans & Tasks
|
|
7880
8089
|
|
|
7881
8090
|
When user asks to create a plan or implementation roadmap:
|
|
@@ -10448,13 +10657,18 @@ Access: Free`,
|
|
|
10448
10657
|
const limit = typeof input.limit === "number" && input.limit > 0 ? Math.min(Math.floor(input.limit), 100) : DEFAULT_SEARCH_LIMIT;
|
|
10449
10658
|
const offset = typeof input.offset === "number" && input.offset > 0 ? Math.floor(input.offset) : void 0;
|
|
10450
10659
|
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;
|
|
10660
|
+
const contextLines = typeof input.context_lines === "number" && input.context_lines >= 0 ? Math.min(Math.floor(input.context_lines), 10) : void 0;
|
|
10661
|
+
const exactMatchBoost = typeof input.exact_match_boost === "number" && input.exact_match_boost >= 1 ? Math.min(input.exact_match_boost, 10) : void 0;
|
|
10451
10662
|
return {
|
|
10452
10663
|
query: input.query,
|
|
10453
10664
|
workspace_id: resolveWorkspaceId(input.workspace_id),
|
|
10454
10665
|
project_id: resolveProjectId(input.project_id),
|
|
10455
10666
|
limit,
|
|
10456
10667
|
offset,
|
|
10457
|
-
content_max_chars: contentMax
|
|
10668
|
+
content_max_chars: contentMax,
|
|
10669
|
+
context_lines: contextLines,
|
|
10670
|
+
exact_match_boost: exactMatchBoost,
|
|
10671
|
+
output_format: input.output_format
|
|
10458
10672
|
};
|
|
10459
10673
|
}
|
|
10460
10674
|
registerTool(
|
|
@@ -13282,15 +13496,20 @@ Use this to remove a reminder that is no longer relevant.`,
|
|
|
13282
13496
|
"search",
|
|
13283
13497
|
{
|
|
13284
13498
|
title: "Search",
|
|
13285
|
-
description: `Search workspace memory and knowledge. Modes: semantic (meaning-based), hybrid (semantic + keyword), keyword (exact match), pattern (regex)
|
|
13499
|
+
description: `Search workspace memory and knowledge. Modes: semantic (meaning-based), hybrid (semantic + keyword), keyword (exact match), pattern (regex), exhaustive (all matches like grep).
|
|
13500
|
+
|
|
13501
|
+
Output formats: full (default, includes content), paths (file paths only - 80% token savings), minimal (compact - 60% savings), count (match counts only - 90% savings).`,
|
|
13286
13502
|
inputSchema: external_exports.object({
|
|
13287
|
-
mode: external_exports.enum(["semantic", "hybrid", "keyword", "pattern"]).describe("Search mode"),
|
|
13503
|
+
mode: external_exports.enum(["semantic", "hybrid", "keyword", "pattern", "exhaustive"]).describe("Search mode"),
|
|
13288
13504
|
query: external_exports.string().describe("Search query"),
|
|
13289
13505
|
workspace_id: external_exports.string().uuid().optional(),
|
|
13290
13506
|
project_id: external_exports.string().uuid().optional(),
|
|
13291
13507
|
limit: external_exports.number().optional().describe("Max results to return (default: 3)"),
|
|
13292
13508
|
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)")
|
|
13509
|
+
content_max_chars: external_exports.number().optional().describe("Max chars per result content (default: 400)"),
|
|
13510
|
+
context_lines: external_exports.number().min(0).max(10).optional().describe("Lines of context around matches (like grep -C)"),
|
|
13511
|
+
exact_match_boost: external_exports.number().min(1).max(10).optional().describe("Boost factor for exact matches (default: 2.0)"),
|
|
13512
|
+
output_format: external_exports.enum(["full", "paths", "minimal", "count"]).optional().describe("Response format: full (default), paths (80% savings), minimal (60% savings), count (90% savings)")
|
|
13294
13513
|
})
|
|
13295
13514
|
},
|
|
13296
13515
|
async (input) => {
|
|
@@ -13309,6 +13528,9 @@ Use this to remove a reminder that is no longer relevant.`,
|
|
|
13309
13528
|
case "pattern":
|
|
13310
13529
|
result = await client.searchPattern(params);
|
|
13311
13530
|
break;
|
|
13531
|
+
case "exhaustive":
|
|
13532
|
+
result = await client.searchExhaustive(params);
|
|
13533
|
+
break;
|
|
13312
13534
|
}
|
|
13313
13535
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
13314
13536
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextstream/mcp-server",
|
|
3
3
|
"mcpName": "io.github.contextstreamio/mcp-server",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.26",
|
|
5
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
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|