@contextstream/mcp-server 0.4.48 → 0.4.50
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/README.md +27 -0
- package/dist/hooks/auto-rules.js +5 -6
- package/dist/hooks/post-write.js +0 -0
- package/dist/hooks/pre-compact.js +100 -11
- package/dist/hooks/user-prompt-submit.js +10 -5
- package/dist/index.js +675 -126
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,6 +157,33 @@ claude mcp update contextstream -e CONTEXTSTREAM_API_KEY=your_key
|
|
|
157
157
|
|
|
158
158
|
</details>
|
|
159
159
|
|
|
160
|
+
<details>
|
|
161
|
+
<summary><b>GitHub Copilot CLI</b></summary>
|
|
162
|
+
|
|
163
|
+
Use the Copilot CLI to interactively add the MCP server:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
/mcp add
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Or add to `~/.copilot/mcp-config.json`:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"mcpServers": {
|
|
174
|
+
"contextstream": {
|
|
175
|
+
"command": "npx",
|
|
176
|
+
"args": ["-y", "@contextstream/mcp-server"],
|
|
177
|
+
"env": { "CONTEXTSTREAM_API_KEY": "your_key" }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
For more information, see the [GitHub Copilot CLI documentation](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli).
|
|
184
|
+
|
|
185
|
+
</details>
|
|
186
|
+
|
|
160
187
|
---
|
|
161
188
|
|
|
162
189
|
## Links
|
package/dist/hooks/auto-rules.js
CHANGED
|
@@ -105,7 +105,7 @@ function buildHooksConfig(options) {
|
|
|
105
105
|
],
|
|
106
106
|
UserPromptSubmit: userPromptHooks
|
|
107
107
|
};
|
|
108
|
-
if (options?.includePreCompact) {
|
|
108
|
+
if (options?.includePreCompact !== false) {
|
|
109
109
|
config.PreCompact = [
|
|
110
110
|
{
|
|
111
111
|
// Match both manual (/compact) and automatic compaction
|
|
@@ -157,7 +157,7 @@ async function installHookScripts(options) {
|
|
|
157
157
|
preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
|
|
158
158
|
userPrompt: "npx @contextstream/mcp-server hook user-prompt-submit"
|
|
159
159
|
};
|
|
160
|
-
if (options?.includePreCompact) {
|
|
160
|
+
if (options?.includePreCompact !== false) {
|
|
161
161
|
result.preCompact = "npx @contextstream/mcp-server hook pre-compact";
|
|
162
162
|
}
|
|
163
163
|
if (options?.includeMediaAware !== false) {
|
|
@@ -203,7 +203,7 @@ async function installClaudeCodeHooks(options) {
|
|
|
203
203
|
"npx @contextstream/mcp-server hook pre-tool-use",
|
|
204
204
|
"npx @contextstream/mcp-server hook user-prompt-submit"
|
|
205
205
|
);
|
|
206
|
-
if (options.includePreCompact) {
|
|
206
|
+
if (options.includePreCompact !== false) {
|
|
207
207
|
result.scripts.push("npx @contextstream/mcp-server hook pre-compact");
|
|
208
208
|
}
|
|
209
209
|
if (options.includeMediaAware !== false) {
|
|
@@ -270,12 +270,11 @@ When Media-Aware hook detects media patterns, it injects context about:
|
|
|
270
270
|
- How to get clips for Remotion (with frame-based props)
|
|
271
271
|
- How to index new media files
|
|
272
272
|
|
|
273
|
-
### PreCompact Hook
|
|
273
|
+
### PreCompact Hook
|
|
274
274
|
- **Command:** \`npx @contextstream/mcp-server hook pre-compact\`
|
|
275
275
|
- **Purpose:** Saves conversation state before context compaction
|
|
276
276
|
- **Triggers:** Both manual (/compact) and automatic compaction
|
|
277
|
-
- **
|
|
278
|
-
- **Note:** Enable with \`generate_rules(include_pre_compact=true)\` to activate
|
|
277
|
+
- **Installed:** By default (disable with \`CONTEXTSTREAM_HOOK_ENABLED=false\`)
|
|
279
278
|
|
|
280
279
|
When PreCompact runs, it:
|
|
281
280
|
1. Parses the transcript for active files and tool calls
|
package/dist/hooks/post-write.js
CHANGED
|
File without changes
|
|
@@ -68,6 +68,9 @@ function parseTranscript(transcriptPath) {
|
|
|
68
68
|
const activeFiles = /* @__PURE__ */ new Set();
|
|
69
69
|
const recentMessages = [];
|
|
70
70
|
const toolCalls = [];
|
|
71
|
+
const messages = [];
|
|
72
|
+
let startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
73
|
+
let firstTimestamp = true;
|
|
71
74
|
try {
|
|
72
75
|
const content = fs.readFileSync(transcriptPath, "utf-8");
|
|
73
76
|
const lines = content.split("\n");
|
|
@@ -76,6 +79,11 @@ function parseTranscript(transcriptPath) {
|
|
|
76
79
|
try {
|
|
77
80
|
const entry = JSON.parse(line);
|
|
78
81
|
const msgType = entry.type || "";
|
|
82
|
+
const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
83
|
+
if (firstTimestamp && entry.timestamp) {
|
|
84
|
+
startedAt = entry.timestamp;
|
|
85
|
+
firstTimestamp = false;
|
|
86
|
+
}
|
|
79
87
|
if (msgType === "tool_use") {
|
|
80
88
|
const toolName = entry.name || "";
|
|
81
89
|
const toolInput = entry.input || {};
|
|
@@ -91,11 +99,40 @@ function parseTranscript(transcriptPath) {
|
|
|
91
99
|
activeFiles.add(`[glob:${pattern}]`);
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
messages.push({
|
|
103
|
+
role: "assistant",
|
|
104
|
+
content: `[Tool: ${toolName}]`,
|
|
105
|
+
timestamp,
|
|
106
|
+
tool_calls: { name: toolName, input: toolInput }
|
|
107
|
+
});
|
|
108
|
+
} else if (msgType === "tool_result") {
|
|
109
|
+
const resultContent = typeof entry.content === "string" ? entry.content.slice(0, 2e3) : JSON.stringify(entry.content || {}).slice(0, 2e3);
|
|
110
|
+
messages.push({
|
|
111
|
+
role: "tool",
|
|
112
|
+
content: resultContent,
|
|
113
|
+
timestamp,
|
|
114
|
+
tool_results: { name: entry.name }
|
|
115
|
+
});
|
|
116
|
+
} else if (msgType === "user" || entry.role === "user") {
|
|
117
|
+
const userContent = typeof entry.content === "string" ? entry.content : "";
|
|
118
|
+
if (userContent) {
|
|
119
|
+
messages.push({
|
|
120
|
+
role: "user",
|
|
121
|
+
content: userContent,
|
|
122
|
+
timestamp
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} else if (msgType === "assistant" || entry.role === "assistant") {
|
|
126
|
+
const assistantContent = typeof entry.content === "string" ? entry.content : "";
|
|
127
|
+
if (assistantContent) {
|
|
128
|
+
messages.push({
|
|
129
|
+
role: "assistant",
|
|
130
|
+
content: assistantContent,
|
|
131
|
+
timestamp
|
|
132
|
+
});
|
|
133
|
+
if (assistantContent.length > 50) {
|
|
134
|
+
recentMessages.push(assistantContent.slice(0, 500));
|
|
135
|
+
}
|
|
99
136
|
}
|
|
100
137
|
}
|
|
101
138
|
} catch {
|
|
@@ -108,11 +145,57 @@ function parseTranscript(transcriptPath) {
|
|
|
108
145
|
activeFiles: Array.from(activeFiles).slice(-20),
|
|
109
146
|
// Last 20 files
|
|
110
147
|
toolCallCount: toolCalls.length,
|
|
111
|
-
messageCount:
|
|
112
|
-
lastTools: toolCalls.slice(-10).map((t) => t.name)
|
|
148
|
+
messageCount: messages.length,
|
|
149
|
+
lastTools: toolCalls.slice(-10).map((t) => t.name),
|
|
113
150
|
// Last 10 tool names
|
|
151
|
+
messages,
|
|
152
|
+
startedAt
|
|
114
153
|
};
|
|
115
154
|
}
|
|
155
|
+
async function saveFullTranscript(sessionId, transcriptData, trigger) {
|
|
156
|
+
if (!API_KEY) {
|
|
157
|
+
return { success: false, message: "No API key configured" };
|
|
158
|
+
}
|
|
159
|
+
if (transcriptData.messages.length === 0) {
|
|
160
|
+
return { success: false, message: "No messages to save" };
|
|
161
|
+
}
|
|
162
|
+
const payload = {
|
|
163
|
+
session_id: sessionId,
|
|
164
|
+
messages: transcriptData.messages,
|
|
165
|
+
started_at: transcriptData.startedAt,
|
|
166
|
+
source_type: "pre_compact",
|
|
167
|
+
title: `Pre-compaction save (${trigger})`,
|
|
168
|
+
metadata: {
|
|
169
|
+
trigger,
|
|
170
|
+
active_files: transcriptData.activeFiles,
|
|
171
|
+
tool_call_count: transcriptData.toolCallCount
|
|
172
|
+
},
|
|
173
|
+
tags: ["pre_compaction", trigger]
|
|
174
|
+
};
|
|
175
|
+
if (WORKSPACE_ID) {
|
|
176
|
+
payload.workspace_id = WORKSPACE_ID;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const controller = new AbortController();
|
|
180
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
181
|
+
const response = await fetch(`${API_URL}/api/v1/transcripts`, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers: {
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
"X-API-Key": API_KEY
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify(payload),
|
|
188
|
+
signal: controller.signal
|
|
189
|
+
});
|
|
190
|
+
clearTimeout(timeoutId);
|
|
191
|
+
if (response.ok) {
|
|
192
|
+
return { success: true, message: `Transcript saved (${transcriptData.messages.length} messages)` };
|
|
193
|
+
}
|
|
194
|
+
return { success: false, message: `API error: ${response.status}` };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return { success: false, message: String(error) };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
116
199
|
async function saveSnapshot(sessionId, transcriptData, trigger) {
|
|
117
200
|
if (!API_KEY) {
|
|
118
201
|
return { success: false, message: "No API key configured" };
|
|
@@ -193,13 +276,19 @@ async function runPreCompactHook() {
|
|
|
193
276
|
}
|
|
194
277
|
let autoSaveStatus = "";
|
|
195
278
|
if (AUTO_SAVE && API_KEY) {
|
|
196
|
-
const
|
|
197
|
-
if (success) {
|
|
279
|
+
const transcriptResult = await saveFullTranscript(sessionId, transcriptData, trigger);
|
|
280
|
+
if (transcriptResult.success) {
|
|
198
281
|
autoSaveStatus = `
|
|
199
|
-
[ContextStream:
|
|
282
|
+
[ContextStream: ${transcriptResult.message}]`;
|
|
200
283
|
} else {
|
|
201
|
-
|
|
284
|
+
const { success, message } = await saveSnapshot(sessionId, transcriptData, trigger);
|
|
285
|
+
if (success) {
|
|
286
|
+
autoSaveStatus = `
|
|
287
|
+
[ContextStream: Auto-saved snapshot with ${transcriptData.activeFiles.length} active files (transcript save failed: ${transcriptResult.message})]`;
|
|
288
|
+
} else {
|
|
289
|
+
autoSaveStatus = `
|
|
202
290
|
[ContextStream: Auto-save failed - ${message}]`;
|
|
291
|
+
}
|
|
203
292
|
}
|
|
204
293
|
}
|
|
205
294
|
const filesList = transcriptData.activeFiles.slice(0, 5).join(", ") || "none detected";
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// src/hooks/user-prompt-submit.ts
|
|
4
4
|
var ENABLED = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
|
|
5
|
-
var REMINDER = `[CONTEXTSTREAM
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
var REMINDER = `[CONTEXTSTREAM - DO THIS FIRST]
|
|
6
|
+
mcp__contextstream__context(user_message="<user's message>") - CALL THIS BEFORE ANY OTHER TOOL
|
|
7
|
+
|
|
8
|
+
Why: Delivers task-specific rules, lessons from past mistakes, and relevant context.
|
|
9
|
+
Skip it = repeat past mistakes, miss relevant decisions, fly blind.
|
|
10
|
+
|
|
11
|
+
BEFORE Glob/Grep/Read/Search/Explore:
|
|
12
|
+
mcp__contextstream__search(mode="hybrid", query="...") FIRST
|
|
13
|
+
Local tools ONLY if search returns 0 results.
|
|
14
|
+
[END]`;
|
|
10
15
|
function detectEditorFormat(input) {
|
|
11
16
|
if (input.hookName !== void 0) {
|
|
12
17
|
return "cline";
|