@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,213 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/hooks/post-tool-use-failure.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/hooks/common.ts
9
- import * as fs from "node:fs";
10
- import * as path from "node:path";
11
- import { homedir } from "node:os";
12
- var DEFAULT_API_URL = "https://api.contextstream.io";
13
- var HOOK_SPECIFIC_OUTPUT_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "UserPromptSubmit", "PostToolUse"]);
14
- function readHookInput() {
15
- try {
16
- return JSON.parse(fs.readFileSync(0, "utf8"));
17
- } catch {
18
- return {};
19
- }
20
- }
21
- function writeHookOutput(output) {
22
- const eventName = output?.hookEventName || (typeof process.env.HOOK_EVENT_NAME === "string" ? process.env.HOOK_EVENT_NAME.trim() : "");
23
- const canUseHookSpecificOutput = HOOK_SPECIFIC_OUTPUT_EVENTS.has(eventName);
24
- const payload = output && (output.additionalContext || output.blocked || output.reason) ? {
25
- hookSpecificOutput: output.additionalContext && canUseHookSpecificOutput ? {
26
- hookEventName: eventName,
27
- additionalContext: output.additionalContext
28
- } : void 0,
29
- additionalContext: output.additionalContext,
30
- blocked: output.blocked,
31
- reason: output.reason
32
- } : {};
33
- console.log(JSON.stringify(payload));
34
- }
35
- function extractCwd(input) {
36
- const cwd = typeof input.cwd === "string" && input.cwd.trim() ? input.cwd.trim() : process.cwd();
37
- return cwd;
38
- }
39
- function loadHookConfig(cwd) {
40
- let apiUrl = process.env.CONTEXTSTREAM_API_URL || DEFAULT_API_URL;
41
- let apiKey = process.env.CONTEXTSTREAM_API_KEY || "";
42
- let jwt = process.env.CONTEXTSTREAM_JWT || "";
43
- let workspaceId = process.env.CONTEXTSTREAM_WORKSPACE_ID || null;
44
- let projectId = process.env.CONTEXTSTREAM_PROJECT_ID || null;
45
- let searchDir = path.resolve(cwd);
46
- for (let i = 0; i < 6; i++) {
47
- if (!apiKey && !jwt) {
48
- const mcpPath = path.join(searchDir, ".mcp.json");
49
- if (fs.existsSync(mcpPath)) {
50
- try {
51
- const config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
52
- const env = config.mcpServers?.contextstream?.env;
53
- if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
54
- if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
55
- if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
56
- if (env?.CONTEXTSTREAM_WORKSPACE_ID && !workspaceId) workspaceId = env.CONTEXTSTREAM_WORKSPACE_ID;
57
- if (env?.CONTEXTSTREAM_PROJECT_ID && !projectId) projectId = env.CONTEXTSTREAM_PROJECT_ID;
58
- } catch {
59
- }
60
- }
61
- }
62
- if (!workspaceId || !projectId) {
63
- const localConfigPath = path.join(searchDir, ".contextstream", "config.json");
64
- if (fs.existsSync(localConfigPath)) {
65
- try {
66
- const localConfig = JSON.parse(fs.readFileSync(localConfigPath, "utf8"));
67
- if (localConfig.workspace_id && !workspaceId) workspaceId = localConfig.workspace_id;
68
- if (localConfig.project_id && !projectId) projectId = localConfig.project_id;
69
- } catch {
70
- }
71
- }
72
- }
73
- const parentDir = path.dirname(searchDir);
74
- if (parentDir === searchDir) break;
75
- searchDir = parentDir;
76
- }
77
- if (!apiKey && !jwt) {
78
- const homeMcpPath = path.join(homedir(), ".mcp.json");
79
- if (fs.existsSync(homeMcpPath)) {
80
- try {
81
- const config = JSON.parse(fs.readFileSync(homeMcpPath, "utf8"));
82
- const env = config.mcpServers?.contextstream?.env;
83
- if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
84
- if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
85
- if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
86
- } catch {
87
- }
88
- }
89
- }
90
- return { apiUrl, apiKey, jwt, workspaceId, projectId };
91
- }
92
- function isConfigured(config) {
93
- return Boolean(config.apiKey || config.jwt);
94
- }
95
- function authHeaders(config) {
96
- if (config.apiKey) {
97
- return { "X-API-Key": config.apiKey };
98
- }
99
- if (config.jwt) {
100
- return { Authorization: `Bearer ${config.jwt}` };
101
- }
102
- return {};
103
- }
104
- async function apiRequest(config, apiPath, init = {}) {
105
- const response = await fetch(`${config.apiUrl}${apiPath}`, {
106
- method: init.method || "GET",
107
- headers: {
108
- "Content-Type": "application/json",
109
- ...authHeaders(config)
110
- },
111
- body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
112
- });
113
- if (!response.ok) {
114
- throw new Error(`${response.status} ${response.statusText}`);
115
- }
116
- const json = await response.json();
117
- if (json && typeof json === "object" && "data" in json) {
118
- return json.data;
119
- }
120
- return json;
121
- }
122
- async function postMemoryEvent(config, title, content, tags, eventType = "operation") {
123
- if (!isConfigured(config) || !config.workspaceId) return;
124
- await apiRequest(config, "/memory/events", {
125
- method: "POST",
126
- body: {
127
- workspace_id: config.workspaceId,
128
- project_id: config.projectId || void 0,
129
- event_type: eventType,
130
- title,
131
- content: typeof content === "string" ? content : JSON.stringify(content),
132
- metadata: {
133
- tags,
134
- source: "mcp_hook",
135
- captured_at: (/* @__PURE__ */ new Date()).toISOString()
136
- }
137
- }
138
- });
139
- }
140
-
141
- // src/hooks/post-tool-use-failure.ts
142
- var FAILURE_COUNTERS_FILE = path2.join(homedir2(), ".contextstream", "hook-failure-counts.json");
143
- function extractErrorText(input) {
144
- return typeof input.error === "string" && input.error || typeof input.tool_error === "string" && input.tool_error || typeof input.stderr === "string" && input.stderr || "Tool execution failed";
145
- }
146
- function failureFingerprint(toolName, errorText) {
147
- const compact = errorText.split(/\s+/).slice(0, 18).join(" ").toLowerCase();
148
- return `${toolName.toLowerCase()}:${compact}`;
149
- }
150
- function incrementFailureCounter(fingerprint) {
151
- let counters = {};
152
- try {
153
- counters = JSON.parse(fs2.readFileSync(FAILURE_COUNTERS_FILE, "utf8"));
154
- } catch {
155
- counters = {};
156
- }
157
- counters[fingerprint] = (counters[fingerprint] || 0) + 1;
158
- fs2.mkdirSync(path2.dirname(FAILURE_COUNTERS_FILE), { recursive: true });
159
- fs2.writeFileSync(FAILURE_COUNTERS_FILE, JSON.stringify(counters, null, 2), "utf8");
160
- return counters[fingerprint];
161
- }
162
- async function runPostToolUseFailureHook() {
163
- const input = readHookInput();
164
- const cwd = extractCwd(input);
165
- const config = loadHookConfig(cwd);
166
- const toolName = typeof input.tool_name === "string" && input.tool_name || typeof input.toolName === "string" && input.toolName || "unknown";
167
- const errorText = extractErrorText(input);
168
- const toolUseId = typeof input.tool_use_id === "string" && input.tool_use_id || typeof input.toolUseId === "string" && input.toolUseId || "";
169
- const fingerprint = failureFingerprint(toolName, errorText);
170
- const count = incrementFailureCounter(fingerprint);
171
- if (isConfigured(config)) {
172
- await postMemoryEvent(
173
- config,
174
- `Tool failure: ${toolName}`,
175
- {
176
- tool_name: toolName,
177
- tool_use_id: toolUseId || null,
178
- error: errorText,
179
- fingerprint,
180
- occurrence_count: count,
181
- tool_input: input.tool_input || {},
182
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
183
- },
184
- ["hook", "post_tool_use_failure", "tool_error"]
185
- ).catch(() => {
186
- });
187
- const autoLessonEnabled = process.env.CONTEXTSTREAM_FAILURE_AUTO_LESSON !== "false";
188
- if (autoLessonEnabled && count >= 3) {
189
- await postMemoryEvent(
190
- config,
191
- `Recurring failure lesson: ${toolName}`,
192
- {
193
- title: `Recurring failure in ${toolName}`,
194
- trigger: errorText,
195
- prevention: "Add guardrails or alternate fallback path for this failure mode.",
196
- occurrences: count,
197
- fingerprint
198
- },
199
- ["hook", "lesson", "recurring_failure"],
200
- "lesson"
201
- ).catch(() => {
202
- });
203
- }
204
- }
205
- writeHookOutput();
206
- }
207
- var isDirectRun = process.argv[1]?.includes("post-tool-use-failure") || process.argv[2] === "post-tool-use-failure";
208
- if (isDirectRun) {
209
- runPostToolUseFailureHook().catch(() => process.exit(0));
210
- }
211
- export {
212
- runPostToolUseFailureHook
213
- };