@contextstream/mcp-server 0.4.34 → 0.4.35
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 +111 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4297,7 +4297,7 @@ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504])
|
|
|
4297
4297
|
var MAX_RETRIES = 3;
|
|
4298
4298
|
var BASE_DELAY = 1e3;
|
|
4299
4299
|
async function sleep(ms) {
|
|
4300
|
-
return new Promise((
|
|
4300
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
4301
4301
|
}
|
|
4302
4302
|
async function request(config, path8, options = {}) {
|
|
4303
4303
|
const { apiUrl, userAgent } = config;
|
|
@@ -8925,13 +8925,21 @@ var PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
|
8925
8925
|
"""
|
|
8926
8926
|
ContextStream PreToolUse Hook for Claude Code
|
|
8927
8927
|
Blocks Grep/Glob/Search/Task(Explore)/EnterPlanMode and redirects to ContextStream.
|
|
8928
|
+
|
|
8929
|
+
Only blocks if the current project is indexed in ContextStream.
|
|
8930
|
+
If not indexed, allows local tools through with a suggestion to index.
|
|
8928
8931
|
"""
|
|
8929
8932
|
|
|
8930
8933
|
import json
|
|
8931
8934
|
import sys
|
|
8932
8935
|
import os
|
|
8936
|
+
from pathlib import Path
|
|
8937
|
+
from datetime import datetime, timedelta
|
|
8933
8938
|
|
|
8934
8939
|
ENABLED = os.environ.get("CONTEXTSTREAM_HOOK_ENABLED", "true").lower() == "true"
|
|
8940
|
+
INDEX_STATUS_FILE = Path.home() / ".contextstream" / "indexed-projects.json"
|
|
8941
|
+
# Consider index stale after 7 days
|
|
8942
|
+
STALE_THRESHOLD_DAYS = 7
|
|
8935
8943
|
|
|
8936
8944
|
DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"]
|
|
8937
8945
|
|
|
@@ -8953,6 +8961,44 @@ def is_discovery_grep(file_path):
|
|
|
8953
8961
|
return True
|
|
8954
8962
|
return False
|
|
8955
8963
|
|
|
8964
|
+
def is_project_indexed(cwd: str) -> tuple[bool, bool]:
|
|
8965
|
+
"""
|
|
8966
|
+
Check if the current directory is in an indexed project.
|
|
8967
|
+
Returns (is_indexed, is_stale).
|
|
8968
|
+
"""
|
|
8969
|
+
if not INDEX_STATUS_FILE.exists():
|
|
8970
|
+
return False, False
|
|
8971
|
+
|
|
8972
|
+
try:
|
|
8973
|
+
with open(INDEX_STATUS_FILE, "r") as f:
|
|
8974
|
+
data = json.load(f)
|
|
8975
|
+
except:
|
|
8976
|
+
return False, False
|
|
8977
|
+
|
|
8978
|
+
projects = data.get("projects", {})
|
|
8979
|
+
cwd_path = Path(cwd).resolve()
|
|
8980
|
+
|
|
8981
|
+
# Check if cwd is within any indexed project
|
|
8982
|
+
for project_path, info in projects.items():
|
|
8983
|
+
try:
|
|
8984
|
+
indexed_path = Path(project_path).resolve()
|
|
8985
|
+
# Check if cwd is the project or a subdirectory
|
|
8986
|
+
if cwd_path == indexed_path or indexed_path in cwd_path.parents:
|
|
8987
|
+
# Check if stale
|
|
8988
|
+
indexed_at = info.get("indexed_at")
|
|
8989
|
+
if indexed_at:
|
|
8990
|
+
try:
|
|
8991
|
+
indexed_time = datetime.fromisoformat(indexed_at.replace("Z", "+00:00"))
|
|
8992
|
+
if datetime.now(indexed_time.tzinfo) - indexed_time > timedelta(days=STALE_THRESHOLD_DAYS):
|
|
8993
|
+
return True, True # Indexed but stale
|
|
8994
|
+
except:
|
|
8995
|
+
pass
|
|
8996
|
+
return True, False # Indexed and fresh
|
|
8997
|
+
except:
|
|
8998
|
+
continue
|
|
8999
|
+
|
|
9000
|
+
return False, False
|
|
9001
|
+
|
|
8956
9002
|
def main():
|
|
8957
9003
|
if not ENABLED:
|
|
8958
9004
|
sys.exit(0)
|
|
@@ -8964,6 +9010,20 @@ def main():
|
|
|
8964
9010
|
|
|
8965
9011
|
tool = data.get("tool_name", "")
|
|
8966
9012
|
inp = data.get("tool_input", {})
|
|
9013
|
+
cwd = data.get("cwd", os.getcwd())
|
|
9014
|
+
|
|
9015
|
+
# Check if project is indexed
|
|
9016
|
+
is_indexed, is_stale = is_project_indexed(cwd)
|
|
9017
|
+
|
|
9018
|
+
if not is_indexed:
|
|
9019
|
+
# Project not indexed - allow local tools but suggest indexing
|
|
9020
|
+
# Don't block, just exit successfully
|
|
9021
|
+
sys.exit(0)
|
|
9022
|
+
|
|
9023
|
+
if is_stale:
|
|
9024
|
+
# Index is stale - allow with warning (printed but not blocking)
|
|
9025
|
+
# Still allow the tool but remind about re-indexing
|
|
9026
|
+
pass # Continue to blocking logic but could add warning
|
|
8967
9027
|
|
|
8968
9028
|
if tool == "Glob":
|
|
8969
9029
|
pattern = inp.get("pattern", "")
|
|
@@ -8971,18 +9031,16 @@ def main():
|
|
|
8971
9031
|
print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of Glob.", file=sys.stderr)
|
|
8972
9032
|
sys.exit(2)
|
|
8973
9033
|
|
|
8974
|
-
elif tool == "Grep":
|
|
9034
|
+
elif tool == "Grep" or tool == "Search":
|
|
9035
|
+
# Block ALL Grep/Search operations - use ContextStream search or Read for specific files
|
|
8975
9036
|
pattern = inp.get("pattern", "")
|
|
8976
9037
|
path = inp.get("path", "")
|
|
8977
|
-
if
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
query = inp.get("pattern", "") or inp.get("query", "")
|
|
8984
|
-
if query:
|
|
8985
|
-
print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{query}\\") instead of Search.", file=sys.stderr)
|
|
9038
|
+
if pattern:
|
|
9039
|
+
if path and not is_discovery_grep(path):
|
|
9040
|
+
# Specific file - suggest Read instead
|
|
9041
|
+
print(f"STOP: Use Read(\\"{path}\\") to view file content, or mcp__contextstream__search(mode=\\"keyword\\", query=\\"{pattern}\\") for codebase search.", file=sys.stderr)
|
|
9042
|
+
else:
|
|
9043
|
+
print(f"STOP: Use mcp__contextstream__search(mode=\\"hybrid\\", query=\\"{pattern}\\") instead of {tool}.", file=sys.stderr)
|
|
8986
9044
|
sys.exit(2)
|
|
8987
9045
|
|
|
8988
9046
|
elif tool == "Task":
|
|
@@ -9148,6 +9206,34 @@ async function installClaudeCodeHooks(options) {
|
|
|
9148
9206
|
}
|
|
9149
9207
|
return result;
|
|
9150
9208
|
}
|
|
9209
|
+
function getIndexStatusPath() {
|
|
9210
|
+
return path4.join(homedir2(), ".contextstream", "indexed-projects.json");
|
|
9211
|
+
}
|
|
9212
|
+
async function readIndexStatus() {
|
|
9213
|
+
const statusPath = getIndexStatusPath();
|
|
9214
|
+
try {
|
|
9215
|
+
const content = await fs3.readFile(statusPath, "utf-8");
|
|
9216
|
+
return JSON.parse(content);
|
|
9217
|
+
} catch {
|
|
9218
|
+
return { version: 1, projects: {} };
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
async function writeIndexStatus(status) {
|
|
9222
|
+
const statusPath = getIndexStatusPath();
|
|
9223
|
+
const dir = path4.dirname(statusPath);
|
|
9224
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
9225
|
+
await fs3.writeFile(statusPath, JSON.stringify(status, null, 2));
|
|
9226
|
+
}
|
|
9227
|
+
async function markProjectIndexed(projectPath, options) {
|
|
9228
|
+
const status = await readIndexStatus();
|
|
9229
|
+
const resolvedPath = path4.resolve(projectPath);
|
|
9230
|
+
status.projects[resolvedPath] = {
|
|
9231
|
+
indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9232
|
+
project_id: options?.project_id,
|
|
9233
|
+
project_name: options?.project_name
|
|
9234
|
+
};
|
|
9235
|
+
await writeIndexStatus(status);
|
|
9236
|
+
}
|
|
9151
9237
|
|
|
9152
9238
|
// src/tools.ts
|
|
9153
9239
|
var LESSON_DEDUP_WINDOW_MS = 2 * 60 * 1e3;
|
|
@@ -11262,6 +11348,12 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
11262
11348
|
console.error(
|
|
11263
11349
|
`[ContextStream] Completed background ingestion: ${totalIndexed} files in ${batchCount} batches`
|
|
11264
11350
|
);
|
|
11351
|
+
try {
|
|
11352
|
+
await markProjectIndexed(resolvedPath, { project_id: projectId });
|
|
11353
|
+
console.error(`[ContextStream] Marked project as indexed: ${resolvedPath}`);
|
|
11354
|
+
} catch (markError) {
|
|
11355
|
+
console.error(`[ContextStream] Failed to mark project as indexed:`, markError);
|
|
11356
|
+
}
|
|
11265
11357
|
} catch (error) {
|
|
11266
11358
|
console.error(`[ContextStream] Ingestion failed:`, error);
|
|
11267
11359
|
}
|
|
@@ -13007,6 +13099,12 @@ ${benefitsList}` : "",
|
|
|
13007
13099
|
noticeLines.push(
|
|
13008
13100
|
`[INGEST_STATUS] Background indexing started. Codebase will be searchable shortly.`
|
|
13009
13101
|
);
|
|
13102
|
+
} else if (folderPathForRules && !ingestRec?.recommended) {
|
|
13103
|
+
const projectId = typeof result.project_id === "string" ? result.project_id : void 0;
|
|
13104
|
+
const projectName = typeof result.project_name === "string" ? result.project_name : void 0;
|
|
13105
|
+
markProjectIndexed(folderPathForRules, { project_id: projectId, project_name: projectName }).catch(
|
|
13106
|
+
(err) => console.error("[ContextStream] Failed to mark project as indexed:", err)
|
|
13107
|
+
);
|
|
13010
13108
|
}
|
|
13011
13109
|
if (noticeLines.length > 0) {
|
|
13012
13110
|
text = `${text}
|
|
@@ -19569,10 +19667,10 @@ Code: ${device.user_code}`);
|
|
|
19569
19667
|
if (poll && poll.status === "pending") {
|
|
19570
19668
|
const intervalSeconds = typeof poll.interval === "number" ? poll.interval : 5;
|
|
19571
19669
|
const waitMs = Math.max(1, intervalSeconds) * 1e3;
|
|
19572
|
-
await new Promise((
|
|
19670
|
+
await new Promise((resolve4) => setTimeout(resolve4, waitMs));
|
|
19573
19671
|
continue;
|
|
19574
19672
|
}
|
|
19575
|
-
await new Promise((
|
|
19673
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
19576
19674
|
}
|
|
19577
19675
|
if (!accessToken) {
|
|
19578
19676
|
throw new Error(
|
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.35",
|
|
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",
|