@contextstream/mcp-server 0.4.82 → 1.0.1

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.
@@ -1,321 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/hooks/on-read.ts
4
- import * as fs2 from "node:fs";
5
- import * as path2 from "node:path";
6
- import { homedir as homedir2 } from "node:os";
7
-
8
- // src/hot-paths.ts
9
- import * as fs from "node:fs";
10
- import * as path from "node:path";
11
- import { homedir } from "node:os";
12
- var STORE_VERSION = 1;
13
- var STORE_DIR = path.join(homedir(), ".contextstream");
14
- var STORE_FILE = path.join(STORE_DIR, "hot-paths.json");
15
- var MAX_PATHS_PER_SCOPE = 400;
16
- var HALF_LIFE_MS = 3 * 24 * 60 * 60 * 1e3;
17
- function clamp(value, min, max) {
18
- return Math.max(min, Math.min(max, value));
19
- }
20
- function normalizePathKey(input) {
21
- return input.replace(/\\/g, "/").trim();
22
- }
23
- function toScopeKey(input) {
24
- const workspace = input.workspace_id || "none";
25
- const project = input.project_id || "none";
26
- return `${workspace}:${project}`;
27
- }
28
- function signalWeight(signal) {
29
- switch (signal) {
30
- case "search_result":
31
- return 1.8;
32
- case "activity_edit":
33
- return 1.4;
34
- case "activity_focus":
35
- return 1.2;
36
- case "activity_read":
37
- default:
38
- return 1;
39
- }
40
- }
41
- function looksBroadQuery(query) {
42
- const q = query.trim().toLowerCase();
43
- if (!q) return true;
44
- if (q.length <= 2) return true;
45
- if (/^\*+$/.test(q) || q === "all files" || q === "everything") return true;
46
- const tokens = q.split(/\s+/).filter(Boolean);
47
- return tokens.length > 12;
48
- }
49
- var HotPathStore = class {
50
- constructor() {
51
- this.data = { version: STORE_VERSION, scopes: {} };
52
- this.load();
53
- }
54
- recordPaths(scope, paths, signal) {
55
- if (paths.length === 0) return;
56
- const now = Date.now();
57
- const scopeKey = toScopeKey(scope);
58
- const profile = this.ensureScope(scopeKey);
59
- const weight = signalWeight(signal);
60
- for (const raw of paths) {
61
- const pathKey = normalizePathKey(raw);
62
- if (!pathKey) continue;
63
- const current = profile.paths[pathKey] || { score: 0, last_seen: now, hits: 0 };
64
- const decayed = this.decayedScore(current.score, current.last_seen, now);
65
- profile.paths[pathKey] = {
66
- score: decayed + weight,
67
- last_seen: now,
68
- hits: current.hits + 1
69
- };
70
- }
71
- profile.updated_at = now;
72
- this.pruneScope(profile);
73
- this.persist();
74
- }
75
- buildHint(input) {
76
- const scopeKey = toScopeKey(input);
77
- const profile = this.data.scopes[scopeKey];
78
- if (!profile) return void 0;
79
- const now = Date.now();
80
- const baseEntries = Object.entries(profile.paths).map(([filePath, entry]) => ({
81
- path: filePath,
82
- score: this.decayedScore(entry.score, entry.last_seen, now)
83
- })).filter((entry) => entry.score > 0.05).sort((a, b) => b.score - a.score);
84
- const active = (input.active_paths || []).map(normalizePathKey).filter(Boolean);
85
- const merged = /* @__PURE__ */ new Map();
86
- for (const entry of baseEntries.slice(0, Math.max(12, input.limit || 8))) {
87
- merged.set(entry.path, {
88
- path: entry.path,
89
- score: Number(entry.score.toFixed(4)),
90
- source: "history"
91
- });
92
- }
93
- for (const activePath of active) {
94
- const existing = merged.get(activePath);
95
- if (existing) {
96
- existing.score = Number((existing.score + 0.9).toFixed(4));
97
- } else {
98
- merged.set(activePath, { path: activePath, score: 0.9, source: "active" });
99
- }
100
- }
101
- const limit = clamp(input.limit ?? 8, 1, 12);
102
- const entries = [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit);
103
- if (entries.length === 0) return void 0;
104
- const scoreSum = entries.reduce((sum, item) => sum + item.score, 0);
105
- const normalized = clamp(scoreSum / (limit * 2.5), 0, 1);
106
- const confidencePenalty = looksBroadQuery(input.query) ? 0.55 : 1;
107
- const confidence = Number((normalized * confidencePenalty).toFixed(3));
108
- return {
109
- entries,
110
- confidence,
111
- generated_at: new Date(now).toISOString(),
112
- profile_version: STORE_VERSION
113
- };
114
- }
115
- decayedScore(score, lastSeenMs, nowMs) {
116
- const elapsed = Math.max(0, nowMs - lastSeenMs);
117
- const decay = Math.pow(0.5, elapsed / HALF_LIFE_MS);
118
- return score * decay;
119
- }
120
- ensureScope(scopeKey) {
121
- if (!this.data.scopes[scopeKey]) {
122
- this.data.scopes[scopeKey] = { paths: {}, updated_at: Date.now() };
123
- }
124
- return this.data.scopes[scopeKey];
125
- }
126
- pruneScope(profile) {
127
- const entries = Object.entries(profile.paths);
128
- if (entries.length <= MAX_PATHS_PER_SCOPE) return;
129
- entries.sort((a, b) => b[1].score - a[1].score).slice(MAX_PATHS_PER_SCOPE).forEach(([key]) => delete profile.paths[key]);
130
- }
131
- load() {
132
- try {
133
- if (!fs.existsSync(STORE_FILE)) return;
134
- const parsed = JSON.parse(fs.readFileSync(STORE_FILE, "utf-8"));
135
- if (parsed?.version !== STORE_VERSION || !parsed.scopes) return;
136
- this.data = parsed;
137
- } catch {
138
- }
139
- }
140
- persist() {
141
- try {
142
- fs.mkdirSync(STORE_DIR, { recursive: true });
143
- fs.writeFileSync(STORE_FILE, JSON.stringify(this.data));
144
- } catch {
145
- }
146
- }
147
- };
148
- var globalHotPathStore = new HotPathStore();
149
-
150
- // src/hooks/on-read.ts
151
- var ENABLED = process.env.CONTEXTSTREAM_READ_HOOK_ENABLED !== "false";
152
- var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
153
- var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
154
- var WORKSPACE_ID = null;
155
- var recentCaptures = /* @__PURE__ */ new Set();
156
- var CAPTURE_WINDOW_MS = 6e4;
157
- function loadConfigFromMcpJson(cwd) {
158
- let searchDir = path2.resolve(cwd);
159
- for (let i = 0; i < 5; i++) {
160
- if (!API_KEY) {
161
- const mcpPath = path2.join(searchDir, ".mcp.json");
162
- if (fs2.existsSync(mcpPath)) {
163
- try {
164
- const content = fs2.readFileSync(mcpPath, "utf-8");
165
- const config = JSON.parse(content);
166
- const csEnv = config.mcpServers?.contextstream?.env;
167
- if (csEnv?.CONTEXTSTREAM_API_KEY) {
168
- API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
169
- }
170
- if (csEnv?.CONTEXTSTREAM_API_URL) {
171
- API_URL = csEnv.CONTEXTSTREAM_API_URL;
172
- }
173
- } catch {
174
- }
175
- }
176
- }
177
- if (!WORKSPACE_ID) {
178
- const csConfigPath = path2.join(searchDir, ".contextstream", "config.json");
179
- if (fs2.existsSync(csConfigPath)) {
180
- try {
181
- const content = fs2.readFileSync(csConfigPath, "utf-8");
182
- const csConfig = JSON.parse(content);
183
- if (csConfig.workspace_id) {
184
- WORKSPACE_ID = csConfig.workspace_id;
185
- }
186
- } catch {
187
- }
188
- }
189
- }
190
- const parentDir = path2.dirname(searchDir);
191
- if (parentDir === searchDir) break;
192
- searchDir = parentDir;
193
- }
194
- if (!API_KEY) {
195
- const homeMcpPath = path2.join(homedir2(), ".mcp.json");
196
- if (fs2.existsSync(homeMcpPath)) {
197
- try {
198
- const content = fs2.readFileSync(homeMcpPath, "utf-8");
199
- const config = JSON.parse(content);
200
- const csEnv = config.mcpServers?.contextstream?.env;
201
- if (csEnv?.CONTEXTSTREAM_API_KEY) {
202
- API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
203
- }
204
- if (csEnv?.CONTEXTSTREAM_API_URL) {
205
- API_URL = csEnv.CONTEXTSTREAM_API_URL;
206
- }
207
- } catch {
208
- }
209
- }
210
- }
211
- }
212
- async function captureExploration(toolName, target, resultSummary, sessionId) {
213
- if (!API_KEY) return;
214
- const cacheKey = `${toolName}:${target}`;
215
- if (recentCaptures.has(cacheKey)) {
216
- return;
217
- }
218
- recentCaptures.add(cacheKey);
219
- setTimeout(() => recentCaptures.delete(cacheKey), CAPTURE_WINDOW_MS);
220
- const payload = {
221
- event_type: "file_exploration",
222
- title: `${toolName}: ${target.slice(0, 50)}`,
223
- content: JSON.stringify({
224
- tool: toolName,
225
- target,
226
- result_summary: resultSummary.slice(0, 500),
227
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
228
- }),
229
- importance: "low",
230
- tags: ["exploration", toolName.toLowerCase()],
231
- source_type: "hook",
232
- session_id: sessionId
233
- };
234
- if (WORKSPACE_ID) {
235
- payload.workspace_id = WORKSPACE_ID;
236
- }
237
- try {
238
- const controller = new AbortController();
239
- const timeoutId = setTimeout(() => controller.abort(), 3e3);
240
- await fetch(`${API_URL}/api/v1/memory/events`, {
241
- method: "POST",
242
- headers: {
243
- "Content-Type": "application/json",
244
- "X-API-Key": API_KEY
245
- },
246
- body: JSON.stringify(payload),
247
- signal: controller.signal
248
- });
249
- clearTimeout(timeoutId);
250
- } catch {
251
- }
252
- }
253
- async function runOnReadHook() {
254
- if (!ENABLED) {
255
- process.exit(0);
256
- }
257
- let inputData = "";
258
- for await (const chunk of process.stdin) {
259
- inputData += chunk;
260
- }
261
- if (!inputData.trim()) {
262
- process.exit(0);
263
- }
264
- let input;
265
- try {
266
- input = JSON.parse(inputData);
267
- } catch {
268
- process.exit(0);
269
- }
270
- const toolName = input.tool_name || "";
271
- if (!["Read", "Glob", "Grep"].includes(toolName)) {
272
- process.exit(0);
273
- }
274
- const cwd = input.cwd || process.cwd();
275
- loadConfigFromMcpJson(cwd);
276
- const sessionId = input.session_id || "unknown";
277
- let target = "";
278
- let resultSummary = "";
279
- switch (toolName) {
280
- case "Read":
281
- target = input.tool_input?.file_path || "";
282
- resultSummary = `Read file: ${target}`;
283
- if (target) {
284
- globalHotPathStore.recordPaths(
285
- { workspace_id: WORKSPACE_ID || void 0, project_id: void 0 },
286
- [target],
287
- "activity_read"
288
- );
289
- }
290
- break;
291
- case "Glob":
292
- target = input.tool_input?.pattern || "";
293
- const globFiles = input.tool_result?.files || [];
294
- resultSummary = `Found ${globFiles.length} files matching ${target}`;
295
- if (globFiles.length > 0) {
296
- globalHotPathStore.recordPaths(
297
- { workspace_id: WORKSPACE_ID || void 0, project_id: void 0 },
298
- globFiles.slice(0, 30),
299
- "activity_focus"
300
- );
301
- }
302
- break;
303
- case "Grep":
304
- target = input.tool_input?.pattern || "";
305
- const matches = input.tool_result?.matches || 0;
306
- resultSummary = `Found ${matches} matches for "${target}"`;
307
- break;
308
- }
309
- if (target) {
310
- captureExploration(toolName, target, resultSummary, sessionId).catch(() => {
311
- });
312
- }
313
- process.exit(0);
314
- }
315
- var isDirectRun = process.argv[1]?.includes("on-read") || process.argv[2] === "on-read";
316
- if (isDirectRun) {
317
- runOnReadHook().catch(() => process.exit(0));
318
- }
319
- export {
320
- runOnReadHook
321
- };
@@ -1,132 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/hooks/on-save-intent.ts
4
- var ENABLED = process.env.CONTEXTSTREAM_SAVE_INTENT_ENABLED !== "false";
5
- var SAVE_PATTERNS = [
6
- // Direct save requests
7
- /\b(save|store|record|capture|log|document|write down|note down|keep track)\b.*\b(this|that|it|the)\b/i,
8
- /\b(save|store|record|capture|log)\b.*\b(to|in|for)\b.*\b(contextstream|memory|later|reference|future)\b/i,
9
- // Document creation
10
- /\b(create|make|write|draft)\b.*\b(a|the)\b.*\b(document|doc|note|summary|report|spec|design)\b/i,
11
- /\b(document|summarize|write up)\b.*\b(this|that|the|our)\b.*\b(decision|discussion|conversation|meeting|finding)\b/i,
12
- // Memory/reference requests
13
- /\b(remember|don't forget|keep in mind|note that|important to remember)\b/i,
14
- /\bfor\s+(future|later)\s+reference\b/i,
15
- /\b(add|put)\s+(this|it|that)\s+(to|in)\s+(memory|notes|docs)\b/i,
16
- // Decision tracking
17
- /\b(we\s+)?(decided|agreed|concluded|determined)\b.*\b(to|that)\b/i,
18
- /\blet('s|s)\s+document\b/i,
19
- /\bsave\s+(this|the)\s+(decision|choice|approach)\b/i,
20
- // Implementation/design docs
21
- /\b(implementation|design|architecture|spec)\s+(doc|document|plan)\b/i,
22
- /\bwrite\s+(the|a|an)\s+.*(md|markdown|readme)\b/i
23
- ];
24
- var LOCAL_FILE_PATTERNS = [
25
- /\b(save|write|create)\s+(it|this|the\s+\w+)\s+(to|in|as)\s+[./~]/i,
26
- /\b(save|write)\s+to\s+.*(\.md|\.txt|\.json|docs\/|notes\/)/i,
27
- /\bcreate\s+(a|the)\s+file\b/i
28
- ];
29
- function detectsSaveIntent(text) {
30
- const hasSaveIntent = SAVE_PATTERNS.some((p) => p.test(text));
31
- const isLocalFile = LOCAL_FILE_PATTERNS.some((p) => p.test(text));
32
- return { hasSaveIntent, isLocalFile };
33
- }
34
- var SAVE_GUIDANCE = `[CONTEXTSTREAM DOCUMENT STORAGE]
35
- The user wants to save/store content. Use ContextStream instead of local files:
36
-
37
- **For decisions/notes:**
38
- \`\`\`
39
- mcp__contextstream__session(
40
- action="capture",
41
- event_type="decision|note|insight",
42
- title="...",
43
- content="...",
44
- importance="high|medium|low"
45
- )
46
- \`\`\`
47
-
48
- **For documents/specs:**
49
- \`\`\`
50
- mcp__contextstream__docs(
51
- action="create",
52
- title="...",
53
- content="...",
54
- doc_type="implementation|design|spec|guide"
55
- )
56
- \`\`\`
57
-
58
- **For plans:**
59
- \`\`\`
60
- mcp__contextstream__session(
61
- action="capture_plan",
62
- title="...",
63
- steps=[...]
64
- )
65
- \`\`\`
66
-
67
- **Why ContextStream?**
68
- - Persists across sessions (local files don't)
69
- - Searchable and retrievable
70
- - Shows up in context automatically
71
- - Can be shared with team
72
-
73
- Only save to local files if user explicitly requests a specific file path.
74
- [END GUIDANCE]`;
75
- async function runOnSaveIntentHook() {
76
- if (!ENABLED) {
77
- process.exit(0);
78
- }
79
- let inputData = "";
80
- for await (const chunk of process.stdin) {
81
- inputData += chunk;
82
- }
83
- if (!inputData.trim()) {
84
- process.exit(0);
85
- }
86
- let input;
87
- try {
88
- input = JSON.parse(inputData);
89
- } catch {
90
- process.exit(0);
91
- }
92
- let prompt = input.prompt || "";
93
- if (!prompt && input.session?.messages) {
94
- for (const msg of [...input.session.messages].reverse()) {
95
- if (msg.role === "user") {
96
- if (typeof msg.content === "string") {
97
- prompt = msg.content;
98
- } else if (Array.isArray(msg.content)) {
99
- for (const block of msg.content) {
100
- if (block.type === "text" && block.text) {
101
- prompt = block.text;
102
- break;
103
- }
104
- }
105
- }
106
- break;
107
- }
108
- }
109
- }
110
- if (!prompt) {
111
- process.exit(0);
112
- }
113
- const { hasSaveIntent, isLocalFile } = detectsSaveIntent(prompt);
114
- if (hasSaveIntent || isLocalFile) {
115
- console.log(
116
- JSON.stringify({
117
- hookSpecificOutput: {
118
- hookEventName: "UserPromptSubmit",
119
- additionalContext: SAVE_GUIDANCE
120
- }
121
- })
122
- );
123
- }
124
- process.exit(0);
125
- }
126
- var isDirectRun = process.argv[1]?.includes("on-save-intent") || process.argv[2] === "on-save-intent";
127
- if (isDirectRun) {
128
- runOnSaveIntentHook().catch(() => process.exit(0));
129
- }
130
- export {
131
- runOnSaveIntentHook
132
- };
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/hooks/on-task.ts
4
- import * as fs from "node:fs";
5
- import * as path from "node:path";
6
- import { homedir } from "node:os";
7
- var ENABLED = process.env.CONTEXTSTREAM_TASK_HOOK_ENABLED !== "false";
8
- var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
- var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
- var WORKSPACE_ID = null;
11
- function loadConfigFromMcpJson(cwd) {
12
- let searchDir = path.resolve(cwd);
13
- for (let i = 0; i < 5; i++) {
14
- if (!API_KEY) {
15
- const mcpPath = path.join(searchDir, ".mcp.json");
16
- if (fs.existsSync(mcpPath)) {
17
- try {
18
- const content = fs.readFileSync(mcpPath, "utf-8");
19
- const config = JSON.parse(content);
20
- const csEnv = config.mcpServers?.contextstream?.env;
21
- if (csEnv?.CONTEXTSTREAM_API_KEY) {
22
- API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
23
- }
24
- if (csEnv?.CONTEXTSTREAM_API_URL) {
25
- API_URL = csEnv.CONTEXTSTREAM_API_URL;
26
- }
27
- } catch {
28
- }
29
- }
30
- }
31
- if (!WORKSPACE_ID) {
32
- const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
33
- if (fs.existsSync(csConfigPath)) {
34
- try {
35
- const content = fs.readFileSync(csConfigPath, "utf-8");
36
- const csConfig = JSON.parse(content);
37
- if (csConfig.workspace_id) {
38
- WORKSPACE_ID = csConfig.workspace_id;
39
- }
40
- } catch {
41
- }
42
- }
43
- }
44
- const parentDir = path.dirname(searchDir);
45
- if (parentDir === searchDir) break;
46
- searchDir = parentDir;
47
- }
48
- if (!API_KEY) {
49
- const homeMcpPath = path.join(homedir(), ".mcp.json");
50
- if (fs.existsSync(homeMcpPath)) {
51
- try {
52
- const content = fs.readFileSync(homeMcpPath, "utf-8");
53
- const config = JSON.parse(content);
54
- const csEnv = config.mcpServers?.contextstream?.env;
55
- if (csEnv?.CONTEXTSTREAM_API_KEY) {
56
- API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
57
- }
58
- if (csEnv?.CONTEXTSTREAM_API_URL) {
59
- API_URL = csEnv.CONTEXTSTREAM_API_URL;
60
- }
61
- } catch {
62
- }
63
- }
64
- }
65
- }
66
- async function captureTaskInvocation(description, prompt, agentType, result, sessionId) {
67
- if (!API_KEY) return;
68
- const payload = {
69
- event_type: "task_agent",
70
- title: `Agent: ${agentType} - ${description}`,
71
- content: JSON.stringify({
72
- description,
73
- prompt: prompt.slice(0, 1e3),
74
- agent_type: agentType,
75
- result: result.slice(0, 2e3),
76
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
77
- }),
78
- importance: "medium",
79
- tags: ["task", "agent", agentType.toLowerCase()],
80
- source_type: "hook",
81
- session_id: sessionId
82
- };
83
- if (WORKSPACE_ID) {
84
- payload.workspace_id = WORKSPACE_ID;
85
- }
86
- try {
87
- const controller = new AbortController();
88
- const timeoutId = setTimeout(() => controller.abort(), 3e3);
89
- await fetch(`${API_URL}/api/v1/memory/events`, {
90
- method: "POST",
91
- headers: {
92
- "Content-Type": "application/json",
93
- "X-API-Key": API_KEY
94
- },
95
- body: JSON.stringify(payload),
96
- signal: controller.signal
97
- });
98
- clearTimeout(timeoutId);
99
- } catch {
100
- }
101
- }
102
- async function runOnTaskHook() {
103
- if (!ENABLED) {
104
- process.exit(0);
105
- }
106
- let inputData = "";
107
- for await (const chunk of process.stdin) {
108
- inputData += chunk;
109
- }
110
- if (!inputData.trim()) {
111
- process.exit(0);
112
- }
113
- let input;
114
- try {
115
- input = JSON.parse(inputData);
116
- } catch {
117
- process.exit(0);
118
- }
119
- if (input.tool_name !== "Task") {
120
- process.exit(0);
121
- }
122
- const cwd = input.cwd || process.cwd();
123
- loadConfigFromMcpJson(cwd);
124
- const description = input.tool_input?.description || "Unknown task";
125
- const prompt = input.tool_input?.prompt || "";
126
- const agentType = input.tool_input?.subagent_type || "general-purpose";
127
- const result = input.tool_result?.output || "";
128
- const sessionId = input.session_id || "unknown";
129
- captureTaskInvocation(description, prompt, agentType, result, sessionId).catch(() => {
130
- });
131
- process.exit(0);
132
- }
133
- var isDirectRun = process.argv[1]?.includes("on-task") || process.argv[2] === "on-task";
134
- if (isDirectRun) {
135
- runOnTaskHook().catch(() => process.exit(0));
136
- }
137
- export {
138
- runOnTaskHook
139
- };