@azerate/claudette-mcp 1.0.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.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Claudette MCP Server
2
+
3
+ Provides Claude with direct access to:
4
+ - **get_errors** - Check TypeScript/ESLint errors
5
+ - **get_changes** - See pending git changes
6
+ - **get_memory** - Read project memory notes
7
+ - **add_memory** - Add notes to project memory
8
+
9
+ ## Setup
10
+
11
+ ### Option 1: Add via Claude Code CLI
12
+ ```bash
13
+ claude mcp add claudette node C:/repo/AzApp/mcp-server/dist/index.js
14
+ ```
15
+
16
+ ### Option 2: Add to .mcp.json (project-level)
17
+ Create `.mcp.json` in your project root:
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "claudette": {
22
+ "type": "stdio",
23
+ "command": "node",
24
+ "args": ["C:/repo/AzApp/mcp-server/dist/index.js"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Option 3: Add to Claude Code settings
31
+ Add to `~/.claude.json` under your project's `mcpServers`:
32
+ ```json
33
+ "mcpServers": {
34
+ "claudette": {
35
+ "type": "stdio",
36
+ "command": "node",
37
+ "args": ["C:/repo/AzApp/mcp-server/dist/index.js"]
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Once configured, Claude will have access to these tools:
45
+ - `get_errors` - Pass workspace_path to check for TypeScript errors
46
+ - `get_changes` - Pass workspace_path to see git status
47
+ - `get_memory` - Pass workspace_path to read project notes
48
+ - `add_memory` - Pass workspace_path and note to save a note
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,890 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { execSync } from "child_process";
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
9
+ // Claudette server API base URL
10
+ const CLAUDETTE_API = "http://localhost:52001";
11
+ // Claudette data directory
12
+ const DATA_DIR = join(homedir(), ".claudette");
13
+ const WORKSPACE_CONFIGS_DIR = join(DATA_DIR, "workspace-configs");
14
+ // Strip ANSI escape codes
15
+ function stripAnsi(str) {
16
+ return str
17
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
18
+ .replace(/\x1b\][^\x07]*\x07/g, '')
19
+ .replace(/\x1b[()][AB012]/g, '')
20
+ .replace(/\x1b[>=]/g, '')
21
+ .replace(/\x07/g, '')
22
+ .replace(/\r/g, '')
23
+ .replace(/\x1b\[\?[0-9;]*[a-zA-Z]/g, '');
24
+ }
25
+ // Get list of scripts from workspace
26
+ async function getScripts(workspacePath) {
27
+ try {
28
+ const response = await fetch(`${CLAUDETTE_API}/api/scripts?path=${encodeURIComponent(workspacePath)}`);
29
+ if (!response.ok)
30
+ return { scripts: [], hasPackageJson: false };
31
+ return await response.json();
32
+ }
33
+ catch {
34
+ return { scripts: [], hasPackageJson: false };
35
+ }
36
+ }
37
+ // Get script output
38
+ async function getScriptOutput(workspacePath, scriptName) {
39
+ try {
40
+ const response = await fetch(`${CLAUDETTE_API}/api/scripts/status?path=${encodeURIComponent(workspacePath)}&scriptName=${encodeURIComponent(scriptName)}`);
41
+ if (!response.ok)
42
+ return null;
43
+ const data = await response.json();
44
+ if (!data.found || !data.script)
45
+ return null;
46
+ // Join output lines and strip ANSI codes
47
+ const output = data.script.output.join('');
48
+ return stripAnsi(output);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ // Start a script
55
+ async function startScript(workspacePath, scriptName) {
56
+ try {
57
+ const response = await fetch(`${CLAUDETTE_API}/api/scripts/run`, {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify({ path: workspacePath, scriptName, autoLaunchBrowser: true }),
61
+ });
62
+ return await response.json();
63
+ }
64
+ catch (err) {
65
+ return { success: false, error: err.message };
66
+ }
67
+ }
68
+ // Stop a script
69
+ async function stopScript(workspacePath, scriptName) {
70
+ try {
71
+ const response = await fetch(`${CLAUDETTE_API}/api/scripts/stop`, {
72
+ method: 'POST',
73
+ headers: { 'Content-Type': 'application/json' },
74
+ body: JSON.stringify({ path: workspacePath, scriptName }),
75
+ });
76
+ return await response.json();
77
+ }
78
+ catch (err) {
79
+ return { success: false, error: err.message };
80
+ }
81
+ }
82
+ // Read pending action from file (file-based approach for reliability)
83
+ function getPendingAction(workspacePath) {
84
+ const filePath = join(workspacePath, '.claudette', 'pending-action.json');
85
+ if (!existsSync(filePath)) {
86
+ return null;
87
+ }
88
+ try {
89
+ const content = readFileSync(filePath, 'utf-8');
90
+ const action = JSON.parse(content);
91
+ // Check if action is too old (5 minutes)
92
+ if (Date.now() - action.timestamp > 5 * 60 * 1000) {
93
+ unlinkSync(filePath);
94
+ return null;
95
+ }
96
+ // Delete the file after reading (consume it)
97
+ unlinkSync(filePath);
98
+ return action;
99
+ }
100
+ catch {
101
+ // Try to clean up corrupted file
102
+ try {
103
+ unlinkSync(filePath);
104
+ }
105
+ catch { }
106
+ return null;
107
+ }
108
+ }
109
+ // Get workspace config path
110
+ function getWorkspaceConfigPath(workspacePath) {
111
+ const safeName = workspacePath.replace(/[\\/:*?"<>|]/g, "-").replace(/--+/g, "-");
112
+ return join(WORKSPACE_CONFIGS_DIR, `${safeName}.json`);
113
+ }
114
+ // Read workspace config
115
+ function getWorkspaceConfig(workspacePath) {
116
+ const configPath = getWorkspaceConfigPath(workspacePath);
117
+ if (!existsSync(configPath))
118
+ return null;
119
+ try {
120
+ return JSON.parse(readFileSync(configPath, "utf-8"));
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ // Save workspace config
127
+ function saveWorkspaceConfig(config) {
128
+ if (!existsSync(WORKSPACE_CONFIGS_DIR)) {
129
+ mkdirSync(WORKSPACE_CONFIGS_DIR, { recursive: true });
130
+ }
131
+ const configPath = getWorkspaceConfigPath(config.path);
132
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
133
+ }
134
+ // Check TypeScript errors
135
+ function getTypeScriptErrors(workspacePath) {
136
+ const errors = [];
137
+ try {
138
+ // Check if tsconfig exists
139
+ if (!existsSync(join(workspacePath, "tsconfig.json"))) {
140
+ return [];
141
+ }
142
+ // Run TypeScript compiler
143
+ execSync("npx tsc --noEmit 2>&1", {
144
+ cwd: workspacePath,
145
+ encoding: "utf-8",
146
+ maxBuffer: 10 * 1024 * 1024,
147
+ });
148
+ }
149
+ catch (err) {
150
+ const output = err.stdout || err.message || "";
151
+ // Parse TypeScript errors: src/file.ts(10,5): error TS2322: ...
152
+ const errorRegex = /^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s*(TS\d+):\s*(.+)$/gm;
153
+ let match;
154
+ while ((match = errorRegex.exec(output)) !== null) {
155
+ errors.push({
156
+ file: match[1],
157
+ line: parseInt(match[2], 10),
158
+ column: parseInt(match[3], 10),
159
+ severity: match[4],
160
+ code: match[5],
161
+ message: match[6],
162
+ });
163
+ }
164
+ }
165
+ return errors;
166
+ }
167
+ // Get checkpoints (git stashes)
168
+ function getCheckpoints(workspacePath) {
169
+ const checkpoints = [];
170
+ try {
171
+ const output = execSync("git stash list", {
172
+ cwd: workspacePath,
173
+ encoding: "utf-8",
174
+ });
175
+ if (!output.trim())
176
+ return [];
177
+ const lines = output.trim().split("\n");
178
+ for (const line of lines) {
179
+ const match = line.match(/^(stash@\{(\d+)\}):\s*(?:On\s+\w+:\s*)?(?:WIP on \w+:\s*\w+\s*)?(.+)$/);
180
+ if (match) {
181
+ const id = match[1];
182
+ let message = match[3].trim().replace(/^[a-f0-9]+\s+/, "");
183
+ try {
184
+ const dateOutput = execSync(`git log -1 --format="%ci" ${id}`, {
185
+ cwd: workspacePath,
186
+ encoding: "utf-8",
187
+ }).trim();
188
+ let filesChanged = 0;
189
+ try {
190
+ const diffOutput = execSync(`git stash show ${id} --stat`, {
191
+ cwd: workspacePath,
192
+ encoding: "utf-8",
193
+ });
194
+ const filesMatch = diffOutput.match(/(\d+)\s+files?\s+changed/);
195
+ if (filesMatch)
196
+ filesChanged = parseInt(filesMatch[1]);
197
+ }
198
+ catch { }
199
+ checkpoints.push({ id, message, timestamp: dateOutput, filesChanged });
200
+ }
201
+ catch { }
202
+ }
203
+ }
204
+ }
205
+ catch { }
206
+ return checkpoints;
207
+ }
208
+ // Create a checkpoint
209
+ function createCheckpoint(workspacePath, message) {
210
+ try {
211
+ const status = execSync("git status --porcelain", {
212
+ cwd: workspacePath,
213
+ encoding: "utf-8",
214
+ }).trim();
215
+ if (!status) {
216
+ return { success: false, error: "No changes to checkpoint" };
217
+ }
218
+ // Stage tracked files
219
+ try {
220
+ execSync("git add -u", { cwd: workspacePath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
221
+ }
222
+ catch { }
223
+ // Create stash without removing changes
224
+ const stashHash = execSync("git stash create", {
225
+ cwd: workspacePath,
226
+ encoding: "utf-8",
227
+ stdio: ["pipe", "pipe", "pipe"],
228
+ }).trim();
229
+ if (!stashHash) {
230
+ return { success: false, error: "Failed to create checkpoint" };
231
+ }
232
+ // Generate name from changes if not provided
233
+ const stashMessage = message || generateCheckpointName(status);
234
+ execSync(`git stash store -m "${stashMessage.replace(/"/g, '\\"')}" ${stashHash}`, {
235
+ cwd: workspacePath,
236
+ encoding: "utf-8",
237
+ });
238
+ return { success: true };
239
+ }
240
+ catch (err) {
241
+ return { success: false, error: err.message };
242
+ }
243
+ }
244
+ // Generate checkpoint name from git status
245
+ function generateCheckpointName(status) {
246
+ const lines = status.trim().split("\n").filter(Boolean);
247
+ const changes = {
248
+ modified: [], added: [], deleted: [],
249
+ };
250
+ for (const line of lines) {
251
+ const code = line.substring(0, 2);
252
+ const file = line.substring(3).split("/").pop() || line.substring(3);
253
+ if (code.includes("M"))
254
+ changes.modified.push(file);
255
+ else if (code.includes("A") || code.includes("?"))
256
+ changes.added.push(file);
257
+ else if (code.includes("D"))
258
+ changes.deleted.push(file);
259
+ }
260
+ const parts = [];
261
+ if (changes.modified.length > 0) {
262
+ parts.push(changes.modified.length <= 2 ? `Edit ${changes.modified.join(", ")}` : `Edit ${changes.modified.length} files`);
263
+ }
264
+ if (changes.added.length > 0) {
265
+ parts.push(changes.added.length <= 2 ? `Add ${changes.added.join(", ")}` : `Add ${changes.added.length} files`);
266
+ }
267
+ if (changes.deleted.length > 0) {
268
+ parts.push(changes.deleted.length <= 2 ? `Delete ${changes.deleted.join(", ")}` : `Delete ${changes.deleted.length} files`);
269
+ }
270
+ if (parts.length === 0)
271
+ return `Checkpoint ${new Date().toLocaleTimeString()}`;
272
+ let result = parts.join(", ");
273
+ return result.length > 60 ? result.substring(0, 57) + "..." : result;
274
+ }
275
+ // Restore a checkpoint
276
+ function restoreCheckpoint(workspacePath, id) {
277
+ try {
278
+ // Parse original stash index to calculate new index after auto-save
279
+ const stashMatch = id.match(/stash@\{(\d+)\}/);
280
+ if (!stashMatch) {
281
+ return { success: false, error: "Invalid checkpoint ID" };
282
+ }
283
+ let stashIndex = parseInt(stashMatch[1], 10);
284
+ let autoSaveCreated = false;
285
+ // Auto-save current changes first
286
+ const status = execSync("git status --porcelain", {
287
+ cwd: workspacePath,
288
+ encoding: "utf-8",
289
+ }).trim();
290
+ if (status) {
291
+ try {
292
+ execSync("git add -u", { cwd: workspacePath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
293
+ }
294
+ catch { }
295
+ const stashHash = execSync("git stash create", {
296
+ cwd: workspacePath,
297
+ encoding: "utf-8",
298
+ stdio: ["pipe", "pipe", "pipe"],
299
+ }).trim();
300
+ if (stashHash) {
301
+ execSync(`git stash store -m "Auto-save before restore" ${stashHash}`, {
302
+ cwd: workspacePath,
303
+ encoding: "utf-8",
304
+ });
305
+ // Stash indices shifted up by 1
306
+ stashIndex += 1;
307
+ autoSaveCreated = true;
308
+ }
309
+ }
310
+ // Reset working directory completely
311
+ execSync("git reset --hard HEAD", {
312
+ cwd: workspacePath,
313
+ encoding: "utf-8",
314
+ stdio: ["pipe", "pipe", "pipe"],
315
+ });
316
+ // Clean untracked files to prevent conflicts
317
+ execSync("git clean -fd", {
318
+ cwd: workspacePath,
319
+ encoding: "utf-8",
320
+ stdio: ["pipe", "pipe", "pipe"],
321
+ });
322
+ // Apply the checkpoint with adjusted index
323
+ try {
324
+ execSync(`git stash apply stash@{${stashIndex}}`, {
325
+ cwd: workspacePath,
326
+ encoding: "utf-8",
327
+ stdio: ["pipe", "pipe", "pipe"],
328
+ });
329
+ }
330
+ catch (applyErr) {
331
+ // Check if there are conflict markers in the working directory
332
+ try {
333
+ const conflictCheck = execSync("git diff --check", {
334
+ cwd: workspacePath,
335
+ encoding: "utf-8",
336
+ stdio: ["pipe", "pipe", "pipe"],
337
+ });
338
+ }
339
+ catch (diffErr) {
340
+ const diffOutput = diffErr.stdout || diffErr.message || "";
341
+ if (diffOutput.includes("conflict") || diffOutput.includes("leftover")) {
342
+ // Conflicts detected - abort and reset to clean state
343
+ execSync("git reset --hard HEAD", {
344
+ cwd: workspacePath,
345
+ encoding: "utf-8",
346
+ stdio: ["pipe", "pipe", "pipe"],
347
+ });
348
+ execSync("git clean -fd", {
349
+ cwd: workspacePath,
350
+ encoding: "utf-8",
351
+ stdio: ["pipe", "pipe", "pipe"],
352
+ });
353
+ return {
354
+ success: false,
355
+ error: "Checkpoint restore failed due to conflicts. Working directory reset to clean state." +
356
+ (autoSaveCreated ? " Your changes were saved as stash@{0}." : "")
357
+ };
358
+ }
359
+ }
360
+ // Also check for conflict markers in files directly
361
+ try {
362
+ const grepConflict = execSync("git grep -l \"^<<<<<<<\" || true", {
363
+ cwd: workspacePath,
364
+ encoding: "utf-8",
365
+ stdio: ["pipe", "pipe", "pipe"],
366
+ }).trim();
367
+ if (grepConflict) {
368
+ // Conflict markers found - abort
369
+ execSync("git reset --hard HEAD", {
370
+ cwd: workspacePath,
371
+ encoding: "utf-8",
372
+ stdio: ["pipe", "pipe", "pipe"],
373
+ });
374
+ execSync("git clean -fd", {
375
+ cwd: workspacePath,
376
+ encoding: "utf-8",
377
+ stdio: ["pipe", "pipe", "pipe"],
378
+ });
379
+ return {
380
+ success: false,
381
+ error: "Checkpoint restore created merge conflicts. Aborted and reset to clean state." +
382
+ (autoSaveCreated ? " Your changes were saved as stash@{0}." : "")
383
+ };
384
+ }
385
+ }
386
+ catch { }
387
+ // If we got here, the apply error wasn't about conflicts - re-throw
388
+ throw applyErr;
389
+ }
390
+ return {
391
+ success: true,
392
+ autoSaveId: autoSaveCreated ? "stash@{0}" : undefined
393
+ };
394
+ }
395
+ catch (err) {
396
+ return { success: false, error: err.message };
397
+ }
398
+ }
399
+ // Delete a checkpoint
400
+ function deleteCheckpoint(workspacePath, id) {
401
+ try {
402
+ execSync(`git stash drop ${id}`, {
403
+ cwd: workspacePath,
404
+ encoding: "utf-8",
405
+ });
406
+ return { success: true };
407
+ }
408
+ catch (err) {
409
+ return { success: false, error: err.message };
410
+ }
411
+ }
412
+ // Get git changes
413
+ function getGitChanges(workspacePath) {
414
+ const changes = [];
415
+ try {
416
+ const output = execSync("git status --porcelain", {
417
+ cwd: workspacePath,
418
+ encoding: "utf-8",
419
+ });
420
+ const lines = output.trim().split("\n").filter(Boolean);
421
+ for (const line of lines) {
422
+ const staged = line[0] !== " " && line[0] !== "?";
423
+ const statusCode = line.substring(0, 2).trim();
424
+ const file = line.substring(3);
425
+ let status = "unknown";
426
+ if (statusCode.includes("M"))
427
+ status = "modified";
428
+ else if (statusCode.includes("A"))
429
+ status = "added";
430
+ else if (statusCode.includes("D"))
431
+ status = "deleted";
432
+ else if (statusCode.includes("R"))
433
+ status = "renamed";
434
+ else if (statusCode.includes("?"))
435
+ status = "untracked";
436
+ changes.push({ file, status, staged });
437
+ }
438
+ }
439
+ catch {
440
+ // Not a git repo or git not available
441
+ }
442
+ return changes;
443
+ }
444
+ // Create the MCP server
445
+ const server = new Server({
446
+ name: "claudette-mcp",
447
+ version: "1.0.0",
448
+ }, {
449
+ capabilities: {
450
+ tools: {},
451
+ },
452
+ });
453
+ // List available tools
454
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
455
+ return {
456
+ tools: [
457
+ {
458
+ name: "get_errors",
459
+ description: "Get TypeScript compilation errors for the current workspace. Returns a list of errors with file, line, column, and message.",
460
+ inputSchema: {
461
+ type: "object",
462
+ properties: {
463
+ workspace_path: {
464
+ type: "string",
465
+ description: "Path to the workspace directory",
466
+ },
467
+ },
468
+ required: ["workspace_path"],
469
+ },
470
+ },
471
+ {
472
+ name: "get_changes",
473
+ description: "Get pending git changes (modified, added, deleted files) for the workspace. Shows both staged and unstaged changes.",
474
+ inputSchema: {
475
+ type: "object",
476
+ properties: {
477
+ workspace_path: {
478
+ type: "string",
479
+ description: "Path to the workspace directory",
480
+ },
481
+ },
482
+ required: ["workspace_path"],
483
+ },
484
+ },
485
+ {
486
+ name: "get_memory",
487
+ description: "Get project memory notes for the workspace. These are persistent notes about the project.",
488
+ inputSchema: {
489
+ type: "object",
490
+ properties: {
491
+ workspace_path: {
492
+ type: "string",
493
+ description: "Path to the workspace directory",
494
+ },
495
+ },
496
+ required: ["workspace_path"],
497
+ },
498
+ },
499
+ {
500
+ name: "add_memory",
501
+ description: "Add a note to project memory. Use this to remember important things about the project.",
502
+ inputSchema: {
503
+ type: "object",
504
+ properties: {
505
+ workspace_path: {
506
+ type: "string",
507
+ description: "Path to the workspace directory",
508
+ },
509
+ note: {
510
+ type: "string",
511
+ description: "The note to add to project memory",
512
+ },
513
+ },
514
+ required: ["workspace_path", "note"],
515
+ },
516
+ },
517
+ {
518
+ name: "get_checkpoints",
519
+ description: "Get all saved checkpoints (snapshots) for the workspace. Checkpoints allow reverting to previous states.",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: {
523
+ workspace_path: {
524
+ type: "string",
525
+ description: "Path to the workspace directory",
526
+ },
527
+ },
528
+ required: ["workspace_path"],
529
+ },
530
+ },
531
+ {
532
+ name: "create_checkpoint",
533
+ description: "Create a checkpoint (snapshot) of current changes. Use before making risky changes.",
534
+ inputSchema: {
535
+ type: "object",
536
+ properties: {
537
+ workspace_path: {
538
+ type: "string",
539
+ description: "Path to the workspace directory",
540
+ },
541
+ message: {
542
+ type: "string",
543
+ description: "Optional name for the checkpoint. Auto-generates from changes if not provided.",
544
+ },
545
+ },
546
+ required: ["workspace_path"],
547
+ },
548
+ },
549
+ {
550
+ name: "restore_checkpoint",
551
+ description: "Restore workspace to a previous checkpoint. Current changes are auto-saved first.",
552
+ inputSchema: {
553
+ type: "object",
554
+ properties: {
555
+ workspace_path: {
556
+ type: "string",
557
+ description: "Path to the workspace directory",
558
+ },
559
+ checkpoint_id: {
560
+ type: "string",
561
+ description: "The checkpoint ID to restore (e.g., 'stash@{0}')",
562
+ },
563
+ },
564
+ required: ["workspace_path", "checkpoint_id"],
565
+ },
566
+ },
567
+ {
568
+ name: "delete_checkpoint",
569
+ description: "Delete a checkpoint permanently.",
570
+ inputSchema: {
571
+ type: "object",
572
+ properties: {
573
+ workspace_path: {
574
+ type: "string",
575
+ description: "Path to the workspace directory",
576
+ },
577
+ checkpoint_id: {
578
+ type: "string",
579
+ description: "The checkpoint ID to delete (e.g., 'stash@{0}')",
580
+ },
581
+ },
582
+ required: ["workspace_path", "checkpoint_id"],
583
+ },
584
+ },
585
+ {
586
+ name: "list_scripts",
587
+ description: "List all npm scripts available in the workspace and their current status (running/stopped).",
588
+ inputSchema: {
589
+ type: "object",
590
+ properties: {
591
+ workspace_path: {
592
+ type: "string",
593
+ description: "Path to the workspace directory",
594
+ },
595
+ },
596
+ required: ["workspace_path"],
597
+ },
598
+ },
599
+ {
600
+ name: "get_console_output",
601
+ description: "Get the console output from a running or recently run script. Useful for checking dev server logs, build output, or error messages.",
602
+ inputSchema: {
603
+ type: "object",
604
+ properties: {
605
+ workspace_path: {
606
+ type: "string",
607
+ description: "Path to the workspace directory",
608
+ },
609
+ script_name: {
610
+ type: "string",
611
+ description: "Name of the script (e.g., 'dev', 'build', 'test')",
612
+ },
613
+ },
614
+ required: ["workspace_path", "script_name"],
615
+ },
616
+ },
617
+ {
618
+ name: "run_script",
619
+ description: "Start an npm script in the workspace. For dev scripts, this will auto-launch Chrome with DevTools.",
620
+ inputSchema: {
621
+ type: "object",
622
+ properties: {
623
+ workspace_path: {
624
+ type: "string",
625
+ description: "Path to the workspace directory",
626
+ },
627
+ script_name: {
628
+ type: "string",
629
+ description: "Name of the script to run (e.g., 'dev', 'build', 'test')",
630
+ },
631
+ },
632
+ required: ["workspace_path", "script_name"],
633
+ },
634
+ },
635
+ {
636
+ name: "stop_script",
637
+ description: "Stop a running npm script in the workspace.",
638
+ inputSchema: {
639
+ type: "object",
640
+ properties: {
641
+ workspace_path: {
642
+ type: "string",
643
+ description: "Path to the workspace directory",
644
+ },
645
+ script_name: {
646
+ type: "string",
647
+ description: "Name of the script to stop",
648
+ },
649
+ },
650
+ required: ["workspace_path", "script_name"],
651
+ },
652
+ },
653
+ {
654
+ name: "check_quick_actions",
655
+ description: "Check for pending quick actions from Claudette IDE. Call this when the user asks you to check for actions or when you want to see if there are any UI-triggered tasks.",
656
+ inputSchema: {
657
+ type: "object",
658
+ properties: {
659
+ workspace_path: {
660
+ type: "string",
661
+ description: "Path to the workspace directory",
662
+ },
663
+ },
664
+ required: ["workspace_path"],
665
+ },
666
+ },
667
+ ],
668
+ };
669
+ });
670
+ // Handle tool calls
671
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
672
+ const { name, arguments: args } = request.params;
673
+ switch (name) {
674
+ case "get_errors": {
675
+ const workspacePath = args?.workspace_path;
676
+ if (!workspacePath) {
677
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
678
+ }
679
+ const errors = getTypeScriptErrors(workspacePath);
680
+ if (errors.length === 0) {
681
+ return { content: [{ type: "text", text: "No TypeScript errors found." }] };
682
+ }
683
+ const summary = `Found ${errors.length} error(s):\n\n` +
684
+ errors.map(e => `${e.file}:${e.line}:${e.column} - ${e.severity} ${e.code}: ${e.message}`).join("\n");
685
+ return { content: [{ type: "text", text: summary }] };
686
+ }
687
+ case "get_changes": {
688
+ const workspacePath = args?.workspace_path;
689
+ if (!workspacePath) {
690
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
691
+ }
692
+ const changes = getGitChanges(workspacePath);
693
+ if (changes.length === 0) {
694
+ return { content: [{ type: "text", text: "No pending git changes." }] };
695
+ }
696
+ const staged = changes.filter(c => c.staged);
697
+ const unstaged = changes.filter(c => !c.staged);
698
+ let summary = `Git Changes:\n\n`;
699
+ if (staged.length > 0) {
700
+ summary += `Staged (${staged.length}):\n`;
701
+ summary += staged.map(c => ` [${c.status}] ${c.file}`).join("\n");
702
+ summary += "\n\n";
703
+ }
704
+ if (unstaged.length > 0) {
705
+ summary += `Unstaged (${unstaged.length}):\n`;
706
+ summary += unstaged.map(c => ` [${c.status}] ${c.file}`).join("\n");
707
+ }
708
+ return { content: [{ type: "text", text: summary }] };
709
+ }
710
+ case "get_memory": {
711
+ const workspacePath = args?.workspace_path;
712
+ if (!workspacePath) {
713
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
714
+ }
715
+ const config = getWorkspaceConfig(workspacePath);
716
+ const memory = config?.memory || [];
717
+ if (memory.length === 0) {
718
+ return { content: [{ type: "text", text: "No project memory notes yet." }] };
719
+ }
720
+ return { content: [{ type: "text", text: `Project Memory:\n\n${memory.join("\n\n")}` }] };
721
+ }
722
+ case "add_memory": {
723
+ const workspacePath = args?.workspace_path;
724
+ const note = args?.note;
725
+ if (!workspacePath || !note) {
726
+ return { content: [{ type: "text", text: "Error: workspace_path and note are required" }] };
727
+ }
728
+ let config = getWorkspaceConfig(workspacePath);
729
+ if (!config) {
730
+ config = {
731
+ name: workspacePath.split(/[/\\]/).pop() || "workspace",
732
+ path: workspacePath,
733
+ configured: false,
734
+ memory: [],
735
+ };
736
+ }
737
+ const timestamp = new Date().toLocaleString();
738
+ config.memory.push(`[${timestamp}] ${note}`);
739
+ saveWorkspaceConfig(config);
740
+ return { content: [{ type: "text", text: `Added to project memory: "${note}"` }] };
741
+ }
742
+ case "get_checkpoints": {
743
+ const workspacePath = args?.workspace_path;
744
+ if (!workspacePath) {
745
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
746
+ }
747
+ const checkpoints = getCheckpoints(workspacePath);
748
+ if (checkpoints.length === 0) {
749
+ return { content: [{ type: "text", text: "No checkpoints saved." }] };
750
+ }
751
+ const summary = `Found ${checkpoints.length} checkpoint(s):\n\n` +
752
+ checkpoints.map((c, i) => `${i + 1}. [${c.id}] ${c.message} (${c.filesChanged} files, ${c.timestamp})`).join("\n");
753
+ return { content: [{ type: "text", text: summary }] };
754
+ }
755
+ case "create_checkpoint": {
756
+ const workspacePath = args?.workspace_path;
757
+ const message = args?.message;
758
+ if (!workspacePath) {
759
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
760
+ }
761
+ const result = createCheckpoint(workspacePath, message);
762
+ if (result.success) {
763
+ return { content: [{ type: "text", text: "Checkpoint created successfully." }] };
764
+ }
765
+ else {
766
+ return { content: [{ type: "text", text: `Failed to create checkpoint: ${result.error}` }] };
767
+ }
768
+ }
769
+ case "restore_checkpoint": {
770
+ const workspacePath = args?.workspace_path;
771
+ const checkpointId = args?.checkpoint_id;
772
+ if (!workspacePath || !checkpointId) {
773
+ return { content: [{ type: "text", text: "Error: workspace_path and checkpoint_id are required" }] };
774
+ }
775
+ const result = restoreCheckpoint(workspacePath, checkpointId);
776
+ if (result.success) {
777
+ return { content: [{ type: "text", text: `Restored to checkpoint ${checkpointId}. Your previous changes were auto-saved.` }] };
778
+ }
779
+ else {
780
+ return { content: [{ type: "text", text: `Failed to restore checkpoint: ${result.error}` }] };
781
+ }
782
+ }
783
+ case "delete_checkpoint": {
784
+ const workspacePath = args?.workspace_path;
785
+ const checkpointId = args?.checkpoint_id;
786
+ if (!workspacePath || !checkpointId) {
787
+ return { content: [{ type: "text", text: "Error: workspace_path and checkpoint_id are required" }] };
788
+ }
789
+ const result = deleteCheckpoint(workspacePath, checkpointId);
790
+ if (result.success) {
791
+ return { content: [{ type: "text", text: `Deleted checkpoint ${checkpointId}.` }] };
792
+ }
793
+ else {
794
+ return { content: [{ type: "text", text: `Failed to delete checkpoint: ${result.error}` }] };
795
+ }
796
+ }
797
+ case "list_scripts": {
798
+ const workspacePath = args?.workspace_path;
799
+ if (!workspacePath) {
800
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
801
+ }
802
+ const data = await getScripts(workspacePath);
803
+ if (!data.hasPackageJson) {
804
+ return { content: [{ type: "text", text: "No package.json found in workspace." }] };
805
+ }
806
+ if (data.scripts.length === 0) {
807
+ return { content: [{ type: "text", text: "No scripts defined in package.json." }] };
808
+ }
809
+ const scriptList = data.scripts.map(s => {
810
+ const statusIcon = s.status === 'running' ? '🟢' : s.status === 'error' ? '🔴' : '⚪';
811
+ return `${statusIcon} ${s.name}: ${s.command}${s.status === 'running' ? ' (RUNNING)' : ''}`;
812
+ }).join('\n');
813
+ return { content: [{ type: "text", text: `Scripts in workspace:\n\n${scriptList}` }] };
814
+ }
815
+ case "get_console_output": {
816
+ const workspacePath = args?.workspace_path;
817
+ const scriptName = args?.script_name;
818
+ if (!workspacePath || !scriptName) {
819
+ return { content: [{ type: "text", text: "Error: workspace_path and script_name are required" }] };
820
+ }
821
+ const output = await getScriptOutput(workspacePath, scriptName);
822
+ if (output === null) {
823
+ return { content: [{ type: "text", text: `No output found for script "${scriptName}". The script may not have been run yet.` }] };
824
+ }
825
+ if (!output.trim()) {
826
+ return { content: [{ type: "text", text: `Script "${scriptName}" has no output yet.` }] };
827
+ }
828
+ // Limit output to last 200 lines to avoid overwhelming Claude
829
+ const lines = output.split('\n');
830
+ const limitedOutput = lines.slice(-200).join('\n');
831
+ const truncated = lines.length > 200 ? `(Showing last 200 of ${lines.length} lines)\n\n` : '';
832
+ return { content: [{ type: "text", text: `Console output for "${scriptName}":\n\n${truncated}${limitedOutput}` }] };
833
+ }
834
+ case "run_script": {
835
+ const workspacePath = args?.workspace_path;
836
+ const scriptName = args?.script_name;
837
+ if (!workspacePath || !scriptName) {
838
+ return { content: [{ type: "text", text: "Error: workspace_path and script_name are required" }] };
839
+ }
840
+ const result = await startScript(workspacePath, scriptName);
841
+ if (result.success) {
842
+ return { content: [{ type: "text", text: `Started script "${scriptName}". Use get_console_output to check the output.` }] };
843
+ }
844
+ else {
845
+ return { content: [{ type: "text", text: `Failed to start script: ${result.error}` }] };
846
+ }
847
+ }
848
+ case "stop_script": {
849
+ const workspacePath = args?.workspace_path;
850
+ const scriptName = args?.script_name;
851
+ if (!workspacePath || !scriptName) {
852
+ return { content: [{ type: "text", text: "Error: workspace_path and script_name are required" }] };
853
+ }
854
+ const result = await stopScript(workspacePath, scriptName);
855
+ if (result.success) {
856
+ return { content: [{ type: "text", text: `Stopped script "${scriptName}".` }] };
857
+ }
858
+ else {
859
+ return { content: [{ type: "text", text: `Failed to stop script: ${result.error}` }] };
860
+ }
861
+ }
862
+ case "check_quick_actions": {
863
+ const workspacePath = args?.workspace_path;
864
+ if (!workspacePath) {
865
+ return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
866
+ }
867
+ const pending = getPendingAction(workspacePath);
868
+ if (pending) {
869
+ return {
870
+ content: [{
871
+ type: "text",
872
+ text: `[CLAUDETTE QUICK ACTION]\n\nThe user clicked a quick action button in Claudette IDE. Please execute the following request:\n\n${pending.prompt}\n\n(Action type: ${pending.action || 'custom'})`
873
+ }]
874
+ };
875
+ }
876
+ else {
877
+ return { content: [{ type: "text", text: "No pending quick actions from Claudette IDE." }] };
878
+ }
879
+ }
880
+ default:
881
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
882
+ }
883
+ });
884
+ // Start the server
885
+ async function main() {
886
+ const transport = new StdioServerTransport();
887
+ await server.connect(transport);
888
+ console.error("Claudette MCP server running on stdio");
889
+ }
890
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@azerate/claudette-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Claudette IDE - TypeScript errors, git changes, checkpoints, memory, and script management",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "claudette-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "claude",
21
+ "claudette",
22
+ "typescript",
23
+ "git",
24
+ "ide"
25
+ ],
26
+ "author": "azerate",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/azerate/claudette"
31
+ },
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "typescript": "^5.7.0"
38
+ }
39
+ }