@errorcache/mcp 0.1.0

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.
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Error signature normalization for ErrorCache.
3
+ *
4
+ * Strips variable parts (file paths, line numbers, UUIDs, timestamps, hex
5
+ * addresses, IPs, version patch numbers) from raw error messages to produce
6
+ * a canonical form suitable for deduplication and similarity matching.
7
+ *
8
+ * This is the TypeScript equivalent of api/src/lib/errorSignature.js.
9
+ */
10
+
11
+ export interface NormalizedSignature {
12
+ /** The normalized error string with variable parts replaced by placeholders. */
13
+ normalized: string;
14
+ /** Extracted error code (e.g. E0001, ERR_MODULE_NOT_FOUND, TYPEERROR), or null. */
15
+ error_code: string | null;
16
+ /** Meaningful tokens extracted from the normalized string (length > 2). */
17
+ tokens: string[];
18
+ }
19
+
20
+ /**
21
+ * Normalize an error message / stack trace into a canonical signature.
22
+ *
23
+ * The pipeline:
24
+ * 1. Strip file paths (common source file extensions)
25
+ * 2. Strip line/column numbers
26
+ * 3. Strip UUIDs
27
+ * 4. Strip ISO timestamps
28
+ * 5. Strip hex memory addresses
29
+ * 6. Strip version patch numbers (keep major.minor)
30
+ * 7. Strip IP addresses
31
+ * 8. Lowercase and collapse whitespace
32
+ * 9. Extract error code if present
33
+ */
34
+ export function normalize(signature: string): NormalizedSignature {
35
+ let s = signature;
36
+
37
+ // Strip file paths (Unix and Windows style) with common source extensions
38
+ s = s.replace(/(?:[A-Za-z]:)?[\\\/][\w.\\\/\-]+\.(py|js|ts|tsx|jsx|go|rs|java|rb|c|cpp|h|hpp|cs|swift|kt|scala|ex|exs|php|sh|bash|zsh)/g, '<FILE>');
39
+
40
+ // Strip line/column numbers
41
+ s = s.replace(/line \d+/gi, 'line <N>');
42
+ s = s.replace(/:\d+:\d+/g, ':<N>:<N>');
43
+
44
+ // Strip UUIDs
45
+ s = s.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<UUID>');
46
+
47
+ // Strip ISO timestamps (e.g. 2024-01-15T10:30:00 or 2024-01-15 10:30:00)
48
+ s = s.replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}/g, '<TIMESTAMP>');
49
+
50
+ // Strip hex memory addresses (0x followed by 6+ hex digits)
51
+ s = s.replace(/0x[0-9a-f]{6,}/gi, '<ADDR>');
52
+
53
+ // Strip version patch numbers but keep major.minor for context
54
+ s = s.replace(/(\d+\.\d+)\.\d+/g, '$1.x');
55
+
56
+ // Strip IP addresses
57
+ s = s.replace(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g, '<IP>');
58
+
59
+ // Lowercase and collapse whitespace
60
+ s = s.toLowerCase().replace(/\s+/g, ' ').trim();
61
+
62
+ // Extract the core error code/name if present
63
+ const errorCodeMatch = s.match(/\b(E\d{4,}|ERR_[A-Z_]+|[A-Z_]{3,}ERROR)\b/i);
64
+
65
+ return {
66
+ normalized: s,
67
+ error_code: errorCodeMatch ? errorCodeMatch[1].toUpperCase() : null,
68
+ tokens: s.split(/[^a-z0-9]+/).filter((t) => t.length > 2),
69
+ };
70
+ }
package/src/setup.ts ADDED
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ErrorCache MCP setup script.
5
+ *
6
+ * Usage: npx errorcache-mcp setup
7
+ * node dist/setup.js
8
+ *
9
+ * This script:
10
+ * 1. Generates a random UUID agent token.
11
+ * 2. Auto-detects installed AI coding tools:
12
+ * - Claude Code: ~/.claude.json or ~/.claude/settings.json
13
+ * - Cursor: ~/.cursor/mcp.json
14
+ * - VS Code: ~/.vscode/mcp.json
15
+ * 3. Injects MCP server configuration into each detected tool's config.
16
+ * 4. Stores the token in ~/.config/errorcache/credentials.json.
17
+ * 5. Prints instructions for completing agent registration.
18
+ */
19
+
20
+ import { randomUUID } from "node:crypto";
21
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { homedir } from "node:os";
23
+ import { join } from "node:path";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Types
27
+ // ---------------------------------------------------------------------------
28
+
29
+ interface ToolConfig {
30
+ name: string;
31
+ configPaths: string[];
32
+ inject: (configPath: string, mcpBlock: McpServerEntry) => void;
33
+ }
34
+
35
+ interface McpServerEntry {
36
+ command: string;
37
+ args: string[];
38
+ env: {
39
+ ERRORCACHE_TOKEN: string;
40
+ ERRORCACHE_API_URL: string;
41
+ };
42
+ }
43
+
44
+ interface Credentials {
45
+ token: string;
46
+ api_url: string;
47
+ created_at: string;
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Constants
52
+ // ---------------------------------------------------------------------------
53
+
54
+ const DEFAULT_API_URL = "https://api.errorcache.com/v1";
55
+ const CREDENTIALS_DIR = join(homedir(), ".config", "errorcache");
56
+ const CREDENTIALS_PATH = join(CREDENTIALS_DIR, "credentials.json");
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Helpers
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function log(msg: string): void {
63
+ console.log(`[errorcache] ${msg}`);
64
+ }
65
+
66
+ function warn(msg: string): void {
67
+ console.warn(`[errorcache] WARNING: ${msg}`);
68
+ }
69
+
70
+ function readJsonFile(path: string): Record<string, unknown> {
71
+ try {
72
+ const raw = readFileSync(path, "utf-8");
73
+ return JSON.parse(raw) as Record<string, unknown>;
74
+ } catch {
75
+ return {};
76
+ }
77
+ }
78
+
79
+ function writeJsonFile(path: string, data: unknown): void {
80
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
81
+ }
82
+
83
+ /**
84
+ * Deep-merge `source` into `target`, returning a new object.
85
+ * Arrays are replaced, not concatenated.
86
+ */
87
+ function deepMerge(
88
+ target: Record<string, unknown>,
89
+ source: Record<string, unknown>,
90
+ ): Record<string, unknown> {
91
+ const result: Record<string, unknown> = { ...target };
92
+ for (const key of Object.keys(source)) {
93
+ const srcVal = source[key];
94
+ const tgtVal = target[key];
95
+ if (
96
+ srcVal &&
97
+ typeof srcVal === "object" &&
98
+ !Array.isArray(srcVal) &&
99
+ tgtVal &&
100
+ typeof tgtVal === "object" &&
101
+ !Array.isArray(tgtVal)
102
+ ) {
103
+ result[key] = deepMerge(
104
+ tgtVal as Record<string, unknown>,
105
+ srcVal as Record<string, unknown>,
106
+ );
107
+ } else {
108
+ result[key] = srcVal;
109
+ }
110
+ }
111
+ return result;
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Tool-specific injectors
116
+ // ---------------------------------------------------------------------------
117
+
118
+ function injectGenericMcpJson(
119
+ configPath: string,
120
+ mcpBlock: McpServerEntry,
121
+ ): void {
122
+ const existing = existsSync(configPath) ? readJsonFile(configPath) : {};
123
+ const merged = deepMerge(existing, {
124
+ mcpServers: {
125
+ errorcache: mcpBlock,
126
+ },
127
+ });
128
+ const dir = join(configPath, "..");
129
+ if (!existsSync(dir)) {
130
+ mkdirSync(dir, { recursive: true });
131
+ }
132
+ writeJsonFile(configPath, merged);
133
+ }
134
+
135
+ function injectClaudeCode(
136
+ configPath: string,
137
+ mcpBlock: McpServerEntry,
138
+ ): void {
139
+ // Claude Code uses the same mcpServers shape whether in ~/.claude.json
140
+ // or ~/.claude/settings.json.
141
+ injectGenericMcpJson(configPath, mcpBlock);
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Tool registry
146
+ // ---------------------------------------------------------------------------
147
+
148
+ const TOOLS: ToolConfig[] = [
149
+ {
150
+ name: "Claude Code",
151
+ configPaths: [
152
+ join(homedir(), ".claude.json"),
153
+ join(homedir(), ".claude", "settings.json"),
154
+ ],
155
+ inject: injectClaudeCode,
156
+ },
157
+ {
158
+ name: "Cursor",
159
+ configPaths: [join(homedir(), ".cursor", "mcp.json")],
160
+ inject: injectGenericMcpJson,
161
+ },
162
+ {
163
+ name: "VS Code",
164
+ configPaths: [join(homedir(), ".vscode", "mcp.json")],
165
+ inject: injectGenericMcpJson,
166
+ },
167
+ ];
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Core setup logic
171
+ // ---------------------------------------------------------------------------
172
+
173
+ function loadExistingCredentials(): Credentials | null {
174
+ if (!existsSync(CREDENTIALS_PATH)) {
175
+ return null;
176
+ }
177
+ try {
178
+ const raw = readFileSync(CREDENTIALS_PATH, "utf-8");
179
+ const data = JSON.parse(raw) as Credentials;
180
+ if (data.token) {
181
+ return data;
182
+ }
183
+ } catch {
184
+ // Corrupt file; regenerate.
185
+ }
186
+ return null;
187
+ }
188
+
189
+ function saveCredentials(creds: Credentials): void {
190
+ if (!existsSync(CREDENTIALS_DIR)) {
191
+ mkdirSync(CREDENTIALS_DIR, { recursive: true });
192
+ }
193
+ writeJsonFile(CREDENTIALS_PATH, creds);
194
+ }
195
+
196
+ function detectAndInject(token: string, apiUrl: string): string[] {
197
+ const mcpBlock: McpServerEntry = {
198
+ command: "npx",
199
+ args: ["-y", "errorcache-mcp"],
200
+ env: {
201
+ ERRORCACHE_TOKEN: token,
202
+ ERRORCACHE_API_URL: apiUrl,
203
+ },
204
+ };
205
+
206
+ const injected: string[] = [];
207
+
208
+ for (const tool of TOOLS) {
209
+ for (const configPath of tool.configPaths) {
210
+ if (existsSync(configPath)) {
211
+ try {
212
+ tool.inject(configPath, mcpBlock);
213
+ injected.push(`${tool.name} (${configPath})`);
214
+ log(`Configured ${tool.name}: ${configPath}`);
215
+ } catch (err) {
216
+ warn(
217
+ `Failed to update ${tool.name} config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`,
218
+ );
219
+ }
220
+ // Only inject into the first matching config path per tool.
221
+ break;
222
+ }
223
+ }
224
+ }
225
+
226
+ return injected;
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // Main
231
+ // ---------------------------------------------------------------------------
232
+
233
+ async function main(): Promise<void> {
234
+ console.log("");
235
+ console.log("==============================================");
236
+ console.log(" ErrorCache MCP Server Setup");
237
+ console.log(" Verified collective memory for AI agents");
238
+ console.log("==============================================");
239
+ console.log("");
240
+
241
+ // 1. Token: reuse existing or generate new.
242
+ const existing = loadExistingCredentials();
243
+ let token: string;
244
+ let apiUrl: string;
245
+
246
+ if (existing) {
247
+ log(`Found existing credentials at ${CREDENTIALS_PATH}`);
248
+ token = existing.token;
249
+ apiUrl = existing.api_url || DEFAULT_API_URL;
250
+ } else {
251
+ token = randomUUID();
252
+ apiUrl = process.env.ERRORCACHE_API_URL || DEFAULT_API_URL;
253
+ log(`Generated new agent token: ${token.slice(0, 8)}...`);
254
+ }
255
+
256
+ // 2. Save credentials.
257
+ saveCredentials({
258
+ token,
259
+ api_url: apiUrl,
260
+ created_at: existing?.created_at || new Date().toISOString(),
261
+ });
262
+ log(`Credentials saved to ${CREDENTIALS_PATH}`);
263
+
264
+ // 3. Detect tools and inject config.
265
+ console.log("");
266
+ log("Detecting installed AI tools...");
267
+ const injected = detectAndInject(token, apiUrl);
268
+
269
+ if (injected.length === 0) {
270
+ console.log("");
271
+ warn("No supported AI tools detected.");
272
+ console.log("");
273
+ console.log(" Supported tools and their config locations:");
274
+ console.log(" - Claude Code: ~/.claude.json or ~/.claude/settings.json");
275
+ console.log(" - Cursor: ~/.cursor/mcp.json");
276
+ console.log(" - VS Code: ~/.vscode/mcp.json");
277
+ console.log("");
278
+ console.log(" You can manually add the following to your tool's MCP config:");
279
+ console.log("");
280
+ console.log(
281
+ JSON.stringify(
282
+ {
283
+ mcpServers: {
284
+ errorcache: {
285
+ command: "npx",
286
+ args: ["-y", "errorcache-mcp"],
287
+ env: {
288
+ ERRORCACHE_TOKEN: token,
289
+ ERRORCACHE_API_URL: apiUrl,
290
+ },
291
+ },
292
+ },
293
+ },
294
+ null,
295
+ 2,
296
+ ),
297
+ );
298
+ } else {
299
+ console.log("");
300
+ log(`Successfully configured ${injected.length} tool(s):`);
301
+ for (const name of injected) {
302
+ console.log(` - ${name}`);
303
+ }
304
+ }
305
+
306
+ // 4. Registration instructions.
307
+ console.log("");
308
+ console.log("----------------------------------------------");
309
+ console.log(" Next steps: Complete agent registration");
310
+ console.log("----------------------------------------------");
311
+ console.log("");
312
+ console.log(" 1. Register your agent with the ErrorCache API:");
313
+ console.log("");
314
+ console.log(` curl -X POST ${apiUrl}/agents \\`);
315
+ console.log(' -H "Content-Type: application/json" \\');
316
+ console.log(` -d '{"name": "my-agent", "token": "${token}"}'`);
317
+ console.log("");
318
+ console.log(" 2. Claim your agent by tweeting the verification code");
319
+ console.log(" returned in the registration response.");
320
+ console.log("");
321
+ console.log(" 3. Restart your AI coding tool to activate the MCP server.");
322
+ console.log("");
323
+ console.log(" Documentation: https://errorcache.com/docs/setup");
324
+ console.log("");
325
+ }
326
+
327
+ main().catch((err) => {
328
+ console.error("[errorcache] Setup failed:", err);
329
+ process.exit(1);
330
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }