@contextstream/mcp-server 0.4.24 → 0.4.25
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 +164 -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,6 +5135,7 @@ 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: {
|
|
@@ -5121,6 +5172,20 @@ var ContextStreamClient = class {
|
|
|
5121
5172
|
}
|
|
5122
5173
|
});
|
|
5123
5174
|
}
|
|
5175
|
+
/**
|
|
5176
|
+
* Exhaustive search returns ALL matches from the index.
|
|
5177
|
+
* Use this when you need complete coverage like grep.
|
|
5178
|
+
* Includes index_freshness to indicate result trustworthiness.
|
|
5179
|
+
*/
|
|
5180
|
+
searchExhaustive(body) {
|
|
5181
|
+
return request(this.config, "/search/exhaustive", {
|
|
5182
|
+
body: {
|
|
5183
|
+
...this.withDefaults(body),
|
|
5184
|
+
search_type: "exhaustive",
|
|
5185
|
+
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
5186
|
+
}
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5124
5189
|
// Memory / Knowledge
|
|
5125
5190
|
createMemoryEvent(body) {
|
|
5126
5191
|
const withDefaults = this.withDefaults(body);
|
|
@@ -5682,6 +5747,7 @@ var ContextStreamClient = class {
|
|
|
5682
5747
|
session_id: params.session_id || randomUUID(),
|
|
5683
5748
|
initialized_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5684
5749
|
};
|
|
5750
|
+
this.sessionStartTime = Date.now();
|
|
5685
5751
|
const rootPath = ideRoots.length > 0 ? ideRoots[0] : void 0;
|
|
5686
5752
|
if (!workspaceId && rootPath) {
|
|
5687
5753
|
const resolved = resolveWorkspace(rootPath);
|
|
@@ -5906,6 +5972,8 @@ var ContextStreamClient = class {
|
|
|
5906
5972
|
context.workspace_name = workspaceName;
|
|
5907
5973
|
context.project_id = projectId;
|
|
5908
5974
|
context.ide_roots = ideRoots;
|
|
5975
|
+
this.sessionProjectId = projectId;
|
|
5976
|
+
this.sessionRootPath = rootPath;
|
|
5909
5977
|
if (!workspaceId) {
|
|
5910
5978
|
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
5979
|
}
|
|
@@ -5978,9 +6046,45 @@ var ContextStreamClient = class {
|
|
|
5978
6046
|
if (projectId && !context.ingest_recommendation) {
|
|
5979
6047
|
try {
|
|
5980
6048
|
const recommendation = await this.checkIngestRecommendation(projectId, rootPath);
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
6049
|
+
const autoIndex = params.auto_index !== false;
|
|
6050
|
+
const needsRefresh = recommendation.status === "not_indexed" || recommendation.status === "stale" || recommendation.status === "indexed";
|
|
6051
|
+
if (autoIndex && rootPath && needsRefresh && recommendation.status !== "recently_indexed") {
|
|
6052
|
+
const useIncremental = recommendation.last_indexed && recommendation.status !== "not_indexed";
|
|
6053
|
+
console.error(`[ContextStream] Auto-refreshing stale index for project ${projectId}: ${recommendation.status} (${useIncremental ? "incremental" : "full"})`);
|
|
6054
|
+
context.indexing_status = "refreshing";
|
|
6055
|
+
context.ingest_recommendation = {
|
|
6056
|
+
recommended: false,
|
|
6057
|
+
status: "auto_refreshing",
|
|
6058
|
+
indexed_files: recommendation.indexed_files,
|
|
6059
|
+
last_indexed: recommendation.last_indexed,
|
|
6060
|
+
reason: useIncremental ? "Incremental index refresh started automatically (only changed files)." : "Background index refresh started automatically to capture recent changes."
|
|
6061
|
+
};
|
|
6062
|
+
const projectIdCopy = projectId;
|
|
6063
|
+
const rootPathCopy = rootPath;
|
|
6064
|
+
const lastIndexedCopy = recommendation.last_indexed;
|
|
6065
|
+
(async () => {
|
|
6066
|
+
try {
|
|
6067
|
+
if (useIncremental && lastIndexedCopy) {
|
|
6068
|
+
const sinceDate = new Date(lastIndexedCopy);
|
|
6069
|
+
for await (const batch of readChangedFilesInBatches(rootPathCopy, sinceDate, { batchSize: 50 })) {
|
|
6070
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6071
|
+
}
|
|
6072
|
+
console.error(`[ContextStream] Incremental index refresh completed for ${rootPathCopy}`);
|
|
6073
|
+
} else {
|
|
6074
|
+
for await (const batch of readAllFilesInBatches(rootPathCopy, { batchSize: 50 })) {
|
|
6075
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6076
|
+
}
|
|
6077
|
+
console.error(`[ContextStream] Full index refresh completed for ${rootPathCopy}`);
|
|
6078
|
+
}
|
|
6079
|
+
} catch (e) {
|
|
6080
|
+
console.error(`[ContextStream] Background index refresh failed:`, e);
|
|
6081
|
+
}
|
|
6082
|
+
})();
|
|
6083
|
+
} else {
|
|
6084
|
+
context.ingest_recommendation = recommendation;
|
|
6085
|
+
if (recommendation.recommended) {
|
|
6086
|
+
console.error(`[ContextStream] Ingest recommended for existing project ${projectId}: ${recommendation.status}`);
|
|
6087
|
+
}
|
|
5984
6088
|
}
|
|
5985
6089
|
} catch (e) {
|
|
5986
6090
|
console.error(`[ContextStream] Failed to check ingest recommendation for existing project:`, e);
|
|
@@ -6692,6 +6796,46 @@ var ContextStreamClient = class {
|
|
|
6692
6796
|
sources_used: 0
|
|
6693
6797
|
};
|
|
6694
6798
|
}
|
|
6799
|
+
const THIRTY_MINUTES_MS = 30 * 60 * 1e3;
|
|
6800
|
+
const TEN_MINUTES_MS = 10 * 60 * 1e3;
|
|
6801
|
+
const now = Date.now();
|
|
6802
|
+
const sessionAge = this.sessionStartTime ? now - this.sessionStartTime : 0;
|
|
6803
|
+
const timeSinceLastCheck = this.lastRefreshCheckTime ? now - this.lastRefreshCheckTime : Infinity;
|
|
6804
|
+
if (sessionAge > THIRTY_MINUTES_MS && timeSinceLastCheck > TEN_MINUTES_MS && this.sessionProjectId && this.sessionRootPath && !this.indexRefreshInProgress) {
|
|
6805
|
+
this.lastRefreshCheckTime = now;
|
|
6806
|
+
const projectIdCopy = this.sessionProjectId;
|
|
6807
|
+
const rootPathCopy = this.sessionRootPath;
|
|
6808
|
+
(async () => {
|
|
6809
|
+
try {
|
|
6810
|
+
const recommendation = await this.checkIngestRecommendation(projectIdCopy, rootPathCopy);
|
|
6811
|
+
const needsRefresh = recommendation.status === "not_indexed" || recommendation.status === "stale" || recommendation.status === "indexed";
|
|
6812
|
+
if (needsRefresh && recommendation.status !== "recently_indexed") {
|
|
6813
|
+
this.indexRefreshInProgress = true;
|
|
6814
|
+
const useIncremental = recommendation.last_indexed && recommendation.status !== "not_indexed";
|
|
6815
|
+
console.error(`[ContextStream] Long session re-index: refreshing stale index for project ${projectIdCopy} (session age: ${Math.round(sessionAge / 6e4)} mins, ${useIncremental ? "incremental" : "full"})`);
|
|
6816
|
+
try {
|
|
6817
|
+
if (useIncremental && recommendation.last_indexed) {
|
|
6818
|
+
const sinceDate = new Date(recommendation.last_indexed);
|
|
6819
|
+
for await (const batch of readChangedFilesInBatches(rootPathCopy, sinceDate, { batchSize: 50 })) {
|
|
6820
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6821
|
+
}
|
|
6822
|
+
console.error(`[ContextStream] Long session incremental re-index completed for ${rootPathCopy}`);
|
|
6823
|
+
} else {
|
|
6824
|
+
for await (const batch of readAllFilesInBatches(rootPathCopy, { batchSize: 50 })) {
|
|
6825
|
+
await this.ingestFiles(projectIdCopy, batch);
|
|
6826
|
+
}
|
|
6827
|
+
console.error(`[ContextStream] Long session full re-index completed for ${rootPathCopy}`);
|
|
6828
|
+
}
|
|
6829
|
+
} finally {
|
|
6830
|
+
this.indexRefreshInProgress = false;
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
} catch (e) {
|
|
6834
|
+
console.error(`[ContextStream] Long session re-index check failed:`, e);
|
|
6835
|
+
this.indexRefreshInProgress = false;
|
|
6836
|
+
}
|
|
6837
|
+
})();
|
|
6838
|
+
}
|
|
6695
6839
|
try {
|
|
6696
6840
|
const apiResult = await request(this.config, "/context/smart", {
|
|
6697
6841
|
body: {
|
|
@@ -6721,7 +6865,8 @@ var ContextStreamClient = class {
|
|
|
6721
6865
|
workspace_id: withDefaults.workspace_id,
|
|
6722
6866
|
project_id: withDefaults.project_id,
|
|
6723
6867
|
...versionNotice2 ? { version_notice: versionNotice2 } : {},
|
|
6724
|
-
...Array.isArray(data?.errors) ? { errors: data.errors } : {}
|
|
6868
|
+
...Array.isArray(data?.errors) ? { errors: data.errors } : {},
|
|
6869
|
+
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
|
|
6725
6870
|
};
|
|
6726
6871
|
} catch (err) {
|
|
6727
6872
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
@@ -6916,8 +7061,9 @@ W:${wsHint}
|
|
|
6916
7061
|
workspace_id: withDefaults.workspace_id,
|
|
6917
7062
|
project_id: withDefaults.project_id,
|
|
6918
7063
|
...versionNotice ? { version_notice: versionNotice } : {},
|
|
6919
|
-
...errors.length > 0 && { errors }
|
|
7064
|
+
...errors.length > 0 && { errors },
|
|
6920
7065
|
// Include errors for debugging
|
|
7066
|
+
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
|
|
6921
7067
|
};
|
|
6922
7068
|
}
|
|
6923
7069
|
/**
|
|
@@ -10448,13 +10594,17 @@ Access: Free`,
|
|
|
10448
10594
|
const limit = typeof input.limit === "number" && input.limit > 0 ? Math.min(Math.floor(input.limit), 100) : DEFAULT_SEARCH_LIMIT;
|
|
10449
10595
|
const offset = typeof input.offset === "number" && input.offset > 0 ? Math.floor(input.offset) : void 0;
|
|
10450
10596
|
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;
|
|
10597
|
+
const contextLines = typeof input.context_lines === "number" && input.context_lines >= 0 ? Math.min(Math.floor(input.context_lines), 10) : void 0;
|
|
10598
|
+
const exactMatchBoost = typeof input.exact_match_boost === "number" && input.exact_match_boost >= 1 ? Math.min(input.exact_match_boost, 10) : void 0;
|
|
10451
10599
|
return {
|
|
10452
10600
|
query: input.query,
|
|
10453
10601
|
workspace_id: resolveWorkspaceId(input.workspace_id),
|
|
10454
10602
|
project_id: resolveProjectId(input.project_id),
|
|
10455
10603
|
limit,
|
|
10456
10604
|
offset,
|
|
10457
|
-
content_max_chars: contentMax
|
|
10605
|
+
content_max_chars: contentMax,
|
|
10606
|
+
context_lines: contextLines,
|
|
10607
|
+
exact_match_boost: exactMatchBoost
|
|
10458
10608
|
};
|
|
10459
10609
|
}
|
|
10460
10610
|
registerTool(
|
|
@@ -13282,15 +13432,17 @@ Use this to remove a reminder that is no longer relevant.`,
|
|
|
13282
13432
|
"search",
|
|
13283
13433
|
{
|
|
13284
13434
|
title: "Search",
|
|
13285
|
-
description: `Search workspace memory and knowledge. Modes: semantic (meaning-based), hybrid (semantic + keyword), keyword (exact match), pattern (regex).`,
|
|
13435
|
+
description: `Search workspace memory and knowledge. Modes: semantic (meaning-based), hybrid (semantic + keyword), keyword (exact match), pattern (regex), exhaustive (all matches like grep).`,
|
|
13286
13436
|
inputSchema: external_exports.object({
|
|
13287
|
-
mode: external_exports.enum(["semantic", "hybrid", "keyword", "pattern"]).describe("Search mode"),
|
|
13437
|
+
mode: external_exports.enum(["semantic", "hybrid", "keyword", "pattern", "exhaustive"]).describe("Search mode"),
|
|
13288
13438
|
query: external_exports.string().describe("Search query"),
|
|
13289
13439
|
workspace_id: external_exports.string().uuid().optional(),
|
|
13290
13440
|
project_id: external_exports.string().uuid().optional(),
|
|
13291
13441
|
limit: external_exports.number().optional().describe("Max results to return (default: 3)"),
|
|
13292
13442
|
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)")
|
|
13443
|
+
content_max_chars: external_exports.number().optional().describe("Max chars per result content (default: 400)"),
|
|
13444
|
+
context_lines: external_exports.number().min(0).max(10).optional().describe("Lines of context around matches (like grep -C)"),
|
|
13445
|
+
exact_match_boost: external_exports.number().min(1).max(10).optional().describe("Boost factor for exact matches (default: 2.0)")
|
|
13294
13446
|
})
|
|
13295
13447
|
},
|
|
13296
13448
|
async (input) => {
|
|
@@ -13309,6 +13461,9 @@ Use this to remove a reminder that is no longer relevant.`,
|
|
|
13309
13461
|
case "pattern":
|
|
13310
13462
|
result = await client.searchPattern(params);
|
|
13311
13463
|
break;
|
|
13464
|
+
case "exhaustive":
|
|
13465
|
+
result = await client.searchExhaustive(params);
|
|
13466
|
+
break;
|
|
13312
13467
|
}
|
|
13313
13468
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
13314
13469
|
}
|
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.25",
|
|
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",
|