-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmcpToolServer.js
More file actions
4600 lines (4288 loc) · 207 KB
/
mcpToolServer.js
File metadata and controls
4600 lines (4288 loc) · 207 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* guIDE MCP Tools Server — Model Context Protocol tools for browser automation,
* web search, code execution, and system interaction.
* Copyright (c) 2025-2026 Brendan Gray (GitHub: FileShot)
* All Rights Reserved. See LICENSE for terms.
*
* Provides tool definitions + execution for the LLM to use autonomously.
*/
const { exec, spawn } = require('child_process');
const path = require('path');
const fs = require('fs').promises;
const https = require('https');
const http = require('http');
// Extracted tool modules
const mcpBrowserTools = require('./tools/mcpBrowserTools');
const mcpGitTools = require('./tools/mcpGitTools');
const {
parseToolCalls: standaloneParseToolCalls,
repairToolCalls,
_recoverWriteFileContent,
TOOL_NAME_ALIASES,
VALID_TOOLS,
} = require('./tools/toolParser');
const { canonicalizeToolParams } = require('./tools/canonicalizeToolParams');
/** run_command / terminal_run timing (ms) */
const COMMAND_SOFT_WARNING_MS = 180000;
const COMMAND_DEFAULT_TIMEOUT_MS = 600000;
const COMMAND_MAX_TIMEOUT_MS = 600000;
const COMMAND_MIN_TIMEOUT_MS = 5000;
const RUN_COMMAND_TIMEOUT_HINT =
'Do not launch chrome.exe or debug Playwright via run_command. Use browser_navigate, fetch_webpage, or allow browser automation setup. For long builds pass a higher timeout param or use terminal_run.';
/** Format uptime seconds into human-readable string */
function formatUptime(seconds) {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const parts = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
parts.push(`${mins}m`);
return parts.join(' ');
}
class MCPToolServer {
constructor(options = {}) {
this.webSearch = options.webSearch || null;
this.ragEngine = options.ragEngine || null;
this.terminalManager = options.terminalManager || null;
this.mcpClient = options.mcpClient || null;
this._userDataPath = options.userDataPath || null;
// Agent persistent PTY session (separate from user's terminal)
this._agentPty = null;
this._agentPtyId = null;
this._agentPtyBuffer = '';
this._agentPtyResolve = null;
this._agentPtyShell = null;
this._projectPath = options.projectPath ? path.resolve(options.projectPath) : null;
// Execution policy for command tools (run_command, terminal_run)
this._executionPolicy = options.executionPolicy || 'auto';
this._commandAllowList = new Set(options.commandAllowList || []);
this._commandDenyList = new Set(options.commandDenyList || []);
this._commandShell = (options.commandShell || 'powershell'); // Windows default for run_command
// When false (default), all tools auto-execute without frontend approval.
this._requireToolApproval = options.requireToolApproval !== undefined ? options.requireToolApproval : false;
this.browserManager = null;
this.playwrightBrowser = null;
this.gitManager = null;
this.imageGen = null;
this.toolHistory = [];
this.maxHistory = 50;
// File change backups for undo (filePath → { original, timestamp, tool, isNew })
this._fileBackups = new Map();
this._maxFileBackups = 200;
// Checkpoint turn tracking
this._turnSnapshots = [];
this._maxTurnSnapshots = 20;
this._currentTurnId = null;
this._currentTurnCapture = new Map();
// Load persisted checkpoints from disk
this._loadCheckpointsFromDisk();
// Caches
this._toolDefsCache = null;
this._toolPromptCache = null;
this._allToolDefsCache = null;
// Disabled tools (set by frontend via context.disabledTools)
this._disabledTools = new Set();
// TODO list
this._todos = [];
this._todoNextId = 1;
this.onTodoUpdate = null;
this._send = options.send || null;
// Scratchpad
this._scratchDir = this._projectPath ? path.join(this._projectPath, '.guide-scratch') : null;
// Permission gates for destructive operations
this.onPermissionRequest = null;
this._destructiveTools = new Set([
'delete_file', 'replace_in_file', 'write_file', 'terminal_run',
'git_commit', 'git_push', 'git_reset', 'git_branch_delete',
'kill_process', 'set_env_var', 'restore_checkpoint',
]);
// Rate limiting: max calls per tool type within the rate window
this._rateLimits = {
write_file: { max: 10, window: 10000 },
edit_file: { max: 10, window: 10000 },
delete_file: { max: 5, window: 10000 },
run_command: { max: 8, window: 10000 },
terminal_run: { max: 8, window: 10000 },
web_search: { max: 4, window: 30000 },
fetch_webpage: { max: 6, window: 30000 },
http_request: { max: 6, window: 30000 },
};
this._rateCounters = new Map(); // tool -> [{timestamp}]
// Active child processes from run_command, keyed by childId. Killed on
// cancelGeneration() so Stop actually stops.
this._activeChildren = new Map();
this._nextChildId = 1;
}
// ─── Child Process Management ───────────────────────────────────────────
killActiveChildren(reason) {
// Also kill agent PTY session on cancel
this._killAgentPty();
if (this._activeChildren.size === 0) return 0;
const count = this._activeChildren.size;
console.log(`[MCPToolServer] killActiveChildren: ${count} process(es), reason=${reason || 'unspecified'}`);
for (const [id, child] of this._activeChildren) {
try {
if (process.platform === 'win32') {
// Windows: SIGTERM does not cascade to shell children. Use taskkill /T /F
// to terminate the entire process tree rooted at the shell.
try { require('child_process').execSync(`taskkill /pid ${child.pid} /T /F`, { windowsHide: true, stdio: 'ignore' }); } catch (_) {}
} else {
child.kill('SIGTERM');
setTimeout(() => { try { child.kill('SIGKILL'); } catch (_) {} }, 500).unref();
}
} catch (_) {}
this._activeChildren.delete(id);
}
return count;
}
// ─── T30-Fix: projectPath getter/setter — always normalize to absolute ───
// When the frontend sends a relative projectPath (e.g. "r19-stress-test\my-blank-app"),
// _sanitizeFilePath's startsWith comparison fails (absolute resolved vs relative projNorm),
// causing false "path traversal blocked" on every file operation. Normalizing on assignment
// ensures all downstream code (path.join, path.resolve, startsWith checks) works correctly.
get projectPath() { return this._projectPath; }
set projectPath(val) {
const _prev = this._projectPath;
this._projectPath = val ? path.resolve(val) : null;
this._scratchDir = this._projectPath ? path.join(this._projectPath, '.guide-scratch') : null;
if (_prev !== this._projectPath) {
// Tool prompt embeds the project path in its header — invalidate so next
// getToolPrompt() rebuilds with the current path rather than returning a
// stale cached prompt pointing at the previous project.
this._toolPromptCache = null;
const _stack = new Error().stack.split('\n').slice(2, 4).map(l => l.trim()).join(' | ');
console.log(`[MCPToolServer] DIAG-PP: projectPath changed "${_prev}" → "${this._projectPath}" | ${_stack}`);
}
}
// ─── Tool Toggle Management ───────────────────────────────────────────────
setDisabledTools(toolNames) {
const newSet = new Set(Array.isArray(toolNames) ? toolNames : []);
const changed = newSet.size !== this._disabledTools.size ||
[...newSet].some(t => !this._disabledTools.has(t));
this._disabledTools = newSet;
if (changed) {
// Invalidate caches so prompts reflect the updated tool set
this._toolDefsCache = null;
this._toolPromptCache = null;
console.log(`[MCPToolServer] Disabled tools updated: ${newSet.size} disabled`);
}
}
// ─── Parameter Normalization ─────────────────────────────────────────────
_normalizeBrowserParams(toolName, params) {
return canonicalizeToolParams(toolName, params);
}
_normalizeFsParams(toolName, params) {
return canonicalizeToolParams(toolName, params);
}
// ─── Timeout Wrapper ─────────────────────────────────────────────────────
_withTimeout(promise, ms = 60000, label = 'operation') {
return Promise.race([
promise,
new Promise((resolve) =>
setTimeout(() => resolve({ success: false, error: `${label} timed out after ${ms / 1000}s` }), ms)
),
]);
}
// ─── Rate Limiting ──────────────────────────────────────────────────────
_checkRateLimit(toolName) {
const limit = this._rateLimits[toolName];
if (!limit) return { allowed: true };
const now = Date.now();
let timestamps = this._rateCounters.get(toolName) || [];
// Prune timestamps outside the window
timestamps = timestamps.filter(t => (now - t) < limit.window);
if (timestamps.length >= limit.max) {
return { allowed: false, count: timestamps.length, max: limit.max, window: limit.window };
}
timestamps.push(now);
this._rateCounters.set(toolName, timestamps);
return { allowed: true };
}
// ─── Path Sanitization ───────────────────────────────────────────────────
_sanitizeFilePath(filePath) {
if (!filePath) return filePath;
if (!this.projectPath) {
if (path.isAbsolute(filePath)) {
const err = new Error(`Path "${filePath}" is absolute but no project is open. Open a project before using absolute paths, or use a relative path.`);
err.code = 'ENOPROJECT';
throw err;
}
return filePath;
}
const resolved = path.resolve(this.projectPath, filePath);
const resolvedNorm = resolved.replace(/\\/g, '/').toLowerCase();
const projNorm = this.projectPath.replace(/\\/g, '/').toLowerCase();
// Accept: resolves inside the project.
if (resolvedNorm === projNorm || resolvedNorm.startsWith(projNorm + '/')) {
// Handle doubled project root — model repeated the project name inside a project-rooted path.
const projBasename = path.basename(this.projectPath).toLowerCase();
const afterProj = resolvedNorm.substring(projNorm.length);
if (afterProj === '/' + projBasename || afterProj.startsWith('/' + projBasename + '/')) {
const rest = afterProj.substring(('/' + projBasename).length);
const corrected = this.projectPath + rest.replace(/\//g, path.sep);
console.log(`[MCPToolServer] Doubled project root corrected: "${filePath}" → "${corrected}"`);
return corrected;
}
return resolved;
}
// Reject: resolves outside the project. Fail hard — the tool handler's
// try/catch converts this to {success:false, error} so the model sees
// the error and self-corrects instead of the call silently succeeding on
// the wrong path.
const err = new Error(`Path "${filePath}" is outside the current project ("${this.projectPath}"). Either reopen the target project or use a path inside the current one.`);
err.code = 'EOUTSIDEPROJECT';
throw err;
}
_sanitizeShellArg(str) {
if (!str || typeof str !== 'string') return '';
return str
.replace(/[\x00]/g, '')
.replace(/[`]/g, '')
.replace(/[\n\r]/g, ' ')
.trim();
}
// ─── Tool Definitions ────────────────────────────────────────────────────
getToolDefinitions() {
if (this._toolDefsCache) return this._toolDefsCache;
const allDefs = this._getAllToolDefs();
// Filter out disabled tools
if (this._disabledTools.size > 0) {
this._toolDefsCache = allDefs.filter(t => !this._disabledTools.has(t.name));
} else {
this._toolDefsCache = allDefs;
}
return this._toolDefsCache;
}
/** Returns ALL tool definitions regardless of disabled state (for UI display). */
getAllToolDefinitions() {
return this._getAllToolDefs();
}
_getAllToolDefs() {
if (this._allToolDefsCache) return this._allToolDefsCache;
this._allToolDefsCache = [
{
name: 'web_search',
description: 'Search the web (DuckDuckGo and fallbacks). Returns title, url, and short snippet per hit — not full page text. After every web_search, in the same continuation, you MUST call fetch_webpage on the first and second ranked result URLs (or each URL if fewer than two) before your final answer. Do not ask the user whether to fetch. Ground answers in fetched page text; do not substitute generic descriptions of sites or brands.',
parameters: {
query: { type: 'string', description: 'Search query', required: true },
maxResults: { type: 'number', description: 'Max results (default 5)', required: false },
},
},
{
name: 'fetch_webpage',
description: 'Fetch a URL and return extracted page text (HTML stripped). Required immediately after web_search in the same continuation: call for the first and second ranked result URLs from the search results (or the only URL if one hit) before answering. Do not ask the user for permission to fetch. For interactive browsing, use browser_navigate instead.',
parameters: {
url: { type: 'string', description: 'URL to fetch', required: true },
},
},
{
name: 'read_file',
description: 'Read the contents of a file from the project. Supports partial reads by specifying a line range. Read a file before using edit_file to get the exact text for replacement.',
parameters: {
filePath: { type: 'string', description: 'Relative or absolute file path', required: true },
startLine: { type: 'number', description: 'Start line (1-based, optional)', required: false },
endLine: { type: 'number', description: 'End line (inclusive, optional)', required: false },
reason: { type: 'string', description: 'One sentence explaining why you are reading this file', required: false },
},
},
{
name: 'write_file',
description: 'Create or overwrite a file with the provided content. Replaces the entire file. For large files, use write_file for the initial content, then append_to_file for subsequent sections.',
parameters: {
filePath: { type: 'string', description: 'File path', required: true },
content: { type: 'string', description: 'File content', required: true },
reason: { type: 'string', description: 'One sentence explaining why you are writing this file', required: false },
},
},
{
name: 'search_codebase',
description: 'Search the indexed codebase using semantic search (RAG). Finds functions, classes, patterns, and concepts by meaning rather than exact text match.',
parameters: {
query: { type: 'string', description: 'Search query', required: true },
maxResults: { type: 'number', description: 'Max results', required: false },
reason: { type: 'string', description: 'One sentence explaining what you are looking for in the codebase', required: false },
},
},
{
name: 'run_command',
description: 'Execute a shell command in the project directory and return the output. Default max wait 10 minutes (pass timeout in ms for long npm/build jobs, up to 600000). A UI warning appears at 3 minutes; the command is killed only at max timeout. IMPORTANT: Each call spawns a fresh shell — cd, set VAR=, export VAR= do NOT persist between calls. Do NOT use run_command to launch chrome.exe or debug Playwright — use browser_navigate or fetch_webpage instead. Default shell: Windows = PowerShell, Unix = /bin/sh. Set shell="cmd" on Windows to force cmd.exe.',
parameters: {
command: { type: 'string', description: 'Command to execute', required: true },
shell: { type: 'string', description: 'Shell to use on Windows: "powershell" (default) or "cmd". Ignored on Unix.', required: false },
cwd: { type: 'string', description: 'Working directory', required: false },
timeout: { type: 'number', description: 'Max wait in ms before kill (default 600000, max 600000)', required: false },
reason: { type: 'string', description: 'One sentence explaining why this command needs to be run', required: false },
},
},
{
name: 'terminal_run',
description: 'Execute a command in a persistent terminal session. Unlike run_command (fresh shell each call), this uses a persistent PTY — cd, environment variables, conda activate, nvm use, and other shell state persist across calls. Shell: Windows = PowerShell, Unix = $SHELL (bash/zsh). Use this when you need shell state to persist (e.g., cd into a directory then run multiple commands, activate a virtual environment, or run a dev server and check its output). Falls back to run_command if persistent terminal is unavailable.',
parameters: {
command: { type: 'string', description: 'Command to execute in the persistent terminal', required: true },
timeout: { type: 'number', description: 'Timeout in ms (default 30000)', required: false },
reset: { type: 'boolean', description: 'Reset the terminal session (start fresh) — use if the session is stuck or you need a clean environment', required: false },
},
},
{
name: 'list_directory',
description: 'List files and directories at a given path. Use "." to list the project root. Returns names, types, and sizes of entries.',
parameters: {
dirPath: { type: 'string', description: 'Directory path — use "." for project root', required: true },
recursive: { type: 'boolean', description: 'Recursive listing', required: false },
},
},
{
name: 'find_files',
description: 'Find files matching a name or glob pattern in the project. Searches recursively from the project root.',
parameters: {
pattern: { type: 'string', description: 'File name or glob pattern', required: true },
},
},
{
name: 'analyze_error',
description: 'Analyze an error message and stack trace against the codebase to locate the source of the error and suggest fixes.',
parameters: {
errorMessage: { type: 'string', description: 'Error message', required: true },
stackTrace: { type: 'string', description: 'Stack trace', required: false },
},
},
// ── Browser Tools ──
{
name: 'browser_navigate',
description: 'Navigate to a URL in a Playwright-controlled Chrome browser. Auto-launches the browser if needed.',
parameters: {
url: { type: 'string', description: 'Full URL to navigate to (must include https:// or http://)', required: true },
reason: { type: 'string', description: 'One sentence explaining why you are navigating to this URL', required: false },
},
},
{
name: 'browser_snapshot',
description: 'Get an accessibility snapshot of the current browser page with [ref=eN] element refs. Returns the accessibility tree (roles, names, interactive elements) and page text. Call before clicking or typing to discover element refs. Re-snapshot after page changes since refs are invalidated.',
parameters: {},
},
{
name: 'browser_click',
description: 'Click an element by its ref number. Handles scrolling and overlays automatically. Auto-retries with a fresh snapshot if the ref is stale.',
parameters: {
ref: { type: 'string', description: 'Element reference from snapshot [ref=eN] (e.g. "e5"), OR visible text of the element (e.g. "Sign In"). Also accepts: elementIndex, index, selector', required: true },
reason: { type: 'string', description: 'One sentence explaining why you are clicking this element', required: false },
button: { type: 'string', description: "Mouse button: 'left', 'right', or 'middle' (default 'left')", required: false },
doubleClick: { type: 'boolean', description: 'Double click instead of single click', required: false },
element: { type: 'string', description: 'Human-readable element description (used as fallback if ref fails)', required: false },
},
},
{
name: 'browser_type',
description: 'Type text into an input field by ref number. Clears the field first, then types the new text. Auto-retries with fresh snapshot if ref is stale.',
parameters: {
ref: { type: 'string', description: 'Element reference from snapshot [ref=eN] (e.g. "e3"). Also accepts: elementIndex, index, selector', required: true },
text: { type: 'string', description: 'Text to type', required: true },
reason: { type: 'string', description: 'One sentence explaining what you are typing and why', required: false },
slowly: { type: 'boolean', description: 'Type one character at a time (triggers key handlers). Default: fast fill.', required: false },
submit: { type: 'boolean', description: 'Press Enter after typing', required: false },
},
},
{
name: 'browser_fill_form',
description: 'Fill multiple form fields at once. Supports textbox, checkbox, radio, combobox, and slider field types.',
parameters: {
fields: {
type: 'array', description: 'Array of {ref, value, type} objects. type: "textbox"|"checkbox"|"radio"|"combobox"|"slider"', required: true,
items: {
type: 'object',
properties: {
ref: { type: 'string', description: 'Element ref from snapshot' },
value: { type: 'string', description: 'Value to fill. For checkbox: "true"/"false". For combobox: option text.' },
type: { type: 'string', description: 'Field type: textbox, checkbox, radio, combobox, slider' },
},
},
},
},
},
{
name: 'browser_select_option',
description: 'Select one or more options from a dropdown/select element by ref.',
parameters: {
ref: { type: 'string', description: 'Element ref from snapshot', required: true },
values: { type: 'array', description: 'Array of option labels or values to select', required: true },
},
},
{
name: 'browser_screenshot',
description: 'Take a screenshot of the current browser page. If the vision system is available, the screenshot is automatically captioned with a detailed text description of visible elements. Use this when you need visual context (canvas apps, charts, image-heavy layouts) alongside the accessibility snapshot.',
parameters: {
fullPage: { type: 'boolean', description: 'Capture full scrollable page (default false)', required: false },
ref: { type: 'string', description: 'Element ref to screenshot (optional, screenshots viewport by default)', required: false },
},
},
{
name: 'browser_get_content',
description: 'Get the text content or HTML of the current browser page.',
parameters: {
selector: { type: 'string', description: 'CSS selector (optional, gets body by default)', required: false },
html: { type: 'boolean', description: 'Return HTML instead of text', required: false },
},
},
{
name: 'browser_evaluate',
description: 'Execute JavaScript code in the browser page context. The code is evaluated as a page function. Returns the result.',
parameters: {
code: { type: 'string', description: 'JavaScript code to evaluate (e.g. "document.title" or "() => document.querySelectorAll(\'a\').length")', required: true },
ref: { type: 'string', description: 'Optional element ref — code receives the element as argument', required: false },
},
},
{
name: 'browser_scroll',
description: 'Scroll the browser page up or down.',
parameters: {
direction: { type: 'string', description: "Direction to scroll: 'up' or 'down'", required: true },
amount: { type: 'number', description: 'Pixels to scroll (default 500)', required: false },
},
},
{
name: 'browser_wait',
description: 'Wait for a specified duration in milliseconds.',
parameters: {
ms: { type: 'number', description: 'Milliseconds to wait (default 2000, max 30000)', required: false },
},
},
{
name: 'browser_wait_for',
description: 'Wait for text to appear or disappear on the page, or for a CSS selector to become visible.',
parameters: {
text: { type: 'string', description: 'Text to wait for (appears)', required: false },
textGone: { type: 'string', description: 'Text to wait to disappear', required: false },
time: { type: 'number', description: 'Seconds to wait', required: false },
selector: { type: 'string', description: 'CSS selector to wait for', required: false },
},
},
{
name: 'browser_back',
description: 'Navigate back in browser history.',
parameters: {},
},
{
name: 'browser_press_key',
description: 'Press a keyboard key in the browser. Supports Enter, Tab, Escape, arrow keys, function keys, and more.',
parameters: {
key: { type: 'string', description: 'Key name: Enter, Tab, Escape, Backspace, Delete, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Space, Home, End, PageUp, PageDown, F1-F12', required: true },
},
},
{
name: 'browser_hover',
description: 'Hover over an element on the browser page by ref.',
parameters: {
ref: { type: 'string', description: 'Element ref from browser_snapshot', required: true },
},
},
{
name: 'browser_drag',
description: 'Drag and drop from one element to another.',
parameters: {
startRef: { type: 'string', description: 'Source element ref', required: true },
endRef: { type: 'string', description: 'Target element ref', required: true },
},
},
{
name: 'browser_tabs',
description: 'Manage browser tabs: list all tabs, create new tab, close a tab, or select a tab.',
parameters: {
action: { type: 'string', description: "'list', 'new', 'close', or 'select'", required: true },
index: { type: 'number', description: 'Tab index (for close/select)', required: false },
},
},
{
name: 'browser_handle_dialog',
description: 'Handle a pending alert, confirm, or prompt dialog.',
parameters: {
accept: { type: 'boolean', description: 'Accept (true) or dismiss (false) the dialog', required: true },
promptText: { type: 'string', description: 'Text for prompt dialogs', required: false },
},
},
{
name: 'browser_console_messages',
description: 'Get console messages from the browser page.',
parameters: {
level: { type: 'string', description: "Minimum level: 'error', 'warning', 'info', 'debug' (default 'info')", required: false },
},
},
{
name: 'browser_file_upload',
description: 'Upload files to a file input element.',
parameters: {
ref: { type: 'string', description: 'File input element ref', required: true },
paths: { type: 'array', description: 'Array of absolute file paths to upload', required: true },
},
},
{
name: 'browser_resize',
description: 'Resize the browser viewport.',
parameters: {
width: { type: 'number', description: 'Width in pixels', required: true },
height: { type: 'number', description: 'Height in pixels', required: true },
},
},
{
name: 'browser_get_url',
description: 'Get the current URL and title of the browser page.',
parameters: {},
},
{
name: 'browser_get_links',
description: 'Get all links from the current browser page.',
parameters: {
selector: { type: 'string', description: 'Scope to a container CSS selector (optional)', required: false },
},
},
{
name: 'browser_close',
description: 'Close the browser and clean up all resources.',
parameters: {},
},
// ── File & Project Tools ──
{
name: 'get_project_structure',
description: 'Get an overview of the project file structure. Shows top-level files and directories with sizes.',
parameters: {},
},
{
name: 'create_directory',
description: 'Create a new directory in the project. Creates parent directories as needed.',
parameters: {
path: { type: 'string', description: 'Directory path to create', required: true },
},
},
{
name: 'delete_file',
description: 'Delete a file or directory from the project. Directories are removed recursively. This operation cannot be undone.',
parameters: {
filePath: { type: 'string', description: 'Path of the file or directory to delete', required: true },
},
},
{
name: 'rename_file',
description: 'Rename or move a file or directory to a new path within the project.',
parameters: {
oldPath: { type: 'string', description: 'Current file path', required: true },
newPath: { type: 'string', description: 'New file path', required: true },
},
},
{
name: 'edit_file',
description: 'Replace specific text in a file by finding oldText and replacing it with newText. More efficient than rewriting the entire file. Use read_file first to get the exact text for replacement.',
parameters: {
filePath: { type: 'string', description: 'File to edit', required: true },
reason: { type: 'string', description: 'One sentence explaining what this edit accomplishes', required: false },
oldText: { type: 'string', description: 'Exact text to find and replace (must match exactly)', required: true },
newText: { type: 'string', description: 'Replacement text', required: true },
},
},
{
name: 'get_file_info',
description: 'Get file metadata including size, modified date, type, and permissions.',
parameters: {
filePath: { type: 'string', description: 'Path to the file', required: true },
},
},
// ── Memory Tools ──
{
name: 'save_memory',
description: 'Save a piece of information for future reference across chat sessions. Stores project context, decisions, or facts by key.',
parameters: {
key: { type: 'string', description: 'A key/label for this memory', required: true },
value: { type: 'string', description: 'The information to remember', required: true },
},
},
{
name: 'get_memory',
description: 'Recall previously saved information by its key.',
parameters: {
key: { type: 'string', description: 'The key to look up', required: true },
},
},
{
name: 'list_memories',
description: 'List all saved memory keys and their stored values.',
parameters: {},
},
// ── Git Tools ──
{
name: 'git_status',
description: 'Get current git status: changed files, staged files, current branch, and untracked files.',
parameters: {},
},
{
name: 'git_commit',
description: 'Stage all changes and create a git commit with the given message.',
parameters: {
message: { type: 'string', description: 'Commit message', required: true },
},
},
{
name: 'git_diff',
description: 'Get the diff of a specific file or all uncommitted changes. Shows exactly what lines changed.',
parameters: {
filePath: { type: 'string', description: 'File to diff (optional, omit for all changes)', required: false },
},
},
{
name: 'git_log',
description: 'View recent git commit history with messages, dates, and authors.',
parameters: {
maxCount: { type: 'number', description: 'Max commits to show (default 20)', required: false },
filePath: { type: 'string', description: 'Filter log to a specific file (optional)', required: false },
},
},
{
name: 'git_branch',
description: 'List, create, or switch git branches.',
parameters: {
action: { type: 'string', description: "'list', 'create', or 'switch'", required: true },
name: { type: 'string', description: 'Branch name (required for create/switch)', required: false },
},
},
{
name: 'git_stash',
description: 'Stash or restore uncommitted changes. Temporarily saves work in progress.',
parameters: {
action: { type: 'string', description: "'push', 'pop', 'list', or 'drop'", required: true },
message: { type: 'string', description: 'Stash message (for push)', required: false },
},
},
{
name: 'git_reset',
description: 'Unstage files or reset changes. Hard reset discards changes permanently.',
parameters: {
filePath: { type: 'string', description: 'File to unstage (omit for all)', required: false },
hard: { type: 'boolean', description: 'Hard reset — discard changes (default false)', required: false },
},
},
{
name: 'git_push',
description: 'Push commits to a remote repository. Requires a remote to be configured.',
parameters: {
remote: { type: 'string', description: "Remote name (default: 'origin')", required: false },
branch: { type: 'string', description: 'Branch to push (default: current branch)', required: false },
force: { type: 'boolean', description: 'Force push (use with extreme caution)', required: false },
},
},
{
name: 'git_branch_delete',
description: 'Delete a local or remote branch. This is destructive and cannot be undone.',
parameters: {
branch: { type: 'string', description: 'Branch name to delete', required: true },
remote: { type: 'boolean', description: 'Delete remote branch instead of local (default: false)', required: false },
force: { type: 'boolean', description: 'Force delete unmerged branch (default: false)', required: false },
},
},
// ── Search Tools ──
{
name: 'grep_search',
description: 'Search for text or regex patterns across all project files. Returns matching lines with file paths and line numbers.',
parameters: {
pattern: { type: 'string', description: 'Text or regex pattern to search for', required: true },
filePattern: { type: 'string', description: 'Glob to filter files (e.g. "*.ts", "src/**/*.js")', required: false },
isRegex: { type: 'boolean', description: 'Treat pattern as regex (default false)', required: false },
maxResults: { type: 'number', description: 'Max results (default 50)', required: false },
},
},
{
name: 'search_in_file',
description: 'Search for text or regex patterns within a specific file. Returns all matching lines with line numbers.',
parameters: {
filePath: { type: 'string', description: 'File to search in', required: true },
pattern: { type: 'string', description: 'Text or regex to search for', required: true },
isRegex: { type: 'boolean', description: 'Treat as regex', required: false },
},
},
// ── More File Tools ──
{
name: 'copy_file',
description: 'Copy a file or directory to a new location within the project.',
parameters: {
source: { type: 'string', description: 'Source path', required: true },
destination: { type: 'string', description: 'Destination path', required: true },
},
},
{
name: 'append_to_file',
description: 'Append content to the end of an existing file without overwriting it. Creates the file if it does not exist. Use with write_file to build large files across multiple calls.',
parameters: {
filePath: { type: 'string', description: 'File path', required: true },
content: { type: 'string', description: 'Content to append', required: true },
},
},
{
name: 'diff_files',
description: 'Compare two files and show their differences line by line.',
parameters: {
fileA: { type: 'string', description: 'First file path', required: true },
fileB: { type: 'string', description: 'Second file path', required: true },
},
},
{
name: 'http_request',
description: 'Make an HTTP/HTTPS request (GET, POST, PUT, DELETE, PATCH). Returns response status, headers, and body.',
parameters: {
url: { type: 'string', description: 'Request URL', required: true },
method: { type: 'string', description: "HTTP method (default 'GET')", required: false },
headers: { type: 'object', description: 'Request headers', required: false },
body: { type: 'string', description: 'Request body (for POST/PUT/PATCH)', required: false },
},
},
{
name: 'check_port',
description: 'Check if a network port is in use and identify which process is using it.',
parameters: {
port: { type: 'number', description: 'Port number to check', required: true },
},
},
{
name: 'install_packages',
description: 'Install packages using npm, pip, yarn, or other package managers. Auto-detects the package manager from the project.',
parameters: {
packages: { type: 'string', description: 'Space-separated package names', required: true },
manager: { type: 'string', description: "'npm', 'pip', 'yarn' (default: auto-detect)", required: false },
},
},
{
name: 'undo_edit',
description: 'Undo a file change made by a previous tool call. Restores the file to its state before the modification.',
parameters: {
filePath: { type: 'string', description: 'Path of the file to undo (use list_undoable to see available files)', required: false },
all: { type: 'boolean', description: 'Undo ALL file changes at once', required: false },
},
},
{
name: 'list_undoable',
description: 'List all files that have undo backups available from previous tool modifications.',
parameters: {},
},
{
name: 'replace_in_files',
description: 'Find and replace text across multiple files in the project. Supports glob patterns to scope the search.',
parameters: {
searchText: { type: 'string', description: 'Text or regex pattern to find', required: true },
replaceText: { type: 'string', description: 'Text to replace matches with', required: true },
path: { type: 'string', description: 'Directory or file glob to search in (default: project root)', required: false },
isRegex: { type: 'boolean', description: 'Treat searchText as a regex pattern', required: false },
},
},
{
name: 'open_file_in_editor',
description: 'Open a file in the IDE editor as a new tab. Does not modify the file.',
parameters: {
filePath: { type: 'string', description: 'Path of the file to open', required: true },
},
},
{
name: 'generate_image',
description: 'Generate an image from a text prompt using AI image generation. Returns base64-encoded image data and optionally saves to a file.',
parameters: {
prompt: { type: 'string', description: 'Description of the image to generate', required: true },
width: { type: 'number', description: 'Image width in pixels (default 1024)', required: false },
height: { type: 'number', description: 'Image height in pixels (default 1024)', required: false },
savePath: { type: 'string', description: 'Optional — save the generated image to this file path in the project', required: false },
},
},
// ── Planning / TODO Tools ──
{
name: 'write_todos',
description: 'Create a checklist to plan and track multi-step tasks. After creating todos, you MUST call update_todo to mark each item as "in_progress" when you start it and "done" when you complete it. The user sees the todo list update in real time.',
parameters: {
items: { type: 'array', description: 'Array of todo strings or {text,status} objects', required: true },
},
},
{
name: 'update_todo',
description: 'Update a TODO item status. Call this EVERY time you start or finish a task step. Status values: "pending", "in-progress", "done". Can also update the item text.',
parameters: {
id: { type: 'number', description: 'Todo ID (from write_todos result)', required: true },
status: { type: 'string', description: 'New status: pending, in-progress, or done', required: true },
text: { type: 'string', description: 'New text (optional)', required: false },
},
},
// ── Scratchpad Tools ──
{
name: 'write_scratchpad',
description: 'Save intermediate data to a named scratchpad. Useful for storing large data outside the conversation context for later retrieval.',
parameters: {
name: { type: 'string', description: 'Scratchpad name (alphanumeric)', required: true },
content: { type: 'string', description: 'Content to save', required: true },
},
},
{
name: 'read_scratchpad',
description: 'Read previously saved scratchpad data by name.',
parameters: {
name: { type: 'string', description: 'Scratchpad name to read', required: true },
},
},
{
name: 'save_rule',
description: 'Save a project rule or skill that persists across sessions. Rules are injected into the system prompt on every future chat. Use this when the user says "remember this", "always do X", "update your rules", or gives you a standing instruction. For large rules (>~2KB), prefer write_file to .guide/rules/<name>.md instead of a huge save_rule JSON payload.',
parameters: {
name: { type: 'string', description: 'Short rule name (e.g. "coding-style", "project-conventions")', required: true },
content: { type: 'string', description: 'Rule content in markdown. Be specific and actionable.', required: true },
},
},
{
name: 'list_rules',
description: 'List all saved project rules and skills.',
parameters: {},
},
{
name: 'ask_question',
description: 'Ask the user a multi-part question and wait for their response. Use this when you need clarification, a decision, or user input before proceeding. The question appears in the chat input area with clickable option buttons. IMPORTANT: Options MUST be passed in the "options" array parameter as {label, description} objects to appear as clickable buttons. Options written in the question text string will NOT be clickable — they will just be plain text the user has to read.',
parameters: {
question: { type: 'string', description: 'The main question to ask the user (do NOT list options here — use the "options" array instead)', required: true },
options: { type: 'array', description: 'REQUIRED for clickable options. Array of {label, description} objects (max 4). Each option becomes a clickable button the user can press. Without this array, the user sees only plain text with no buttons. Include a free-form option like {label:"Other"} if the user should be able to type their own answer.', required: false },
allowMultiple: { type: 'boolean', description: 'If true, the user can select multiple options', required: false },
},
},
// ── Process / System Tools ──
{
name: 'list_processes',
description: 'List running processes on the system. Returns PID, command name, CPU%, and memory usage. Cross-platform: uses tasklist on Windows, ps on Unix.',
parameters: {
filter: { type: 'string', description: 'Filter processes by name (substring match, optional)', required: false },
sortBy: { type: 'string', description: "Sort by: 'cpu', 'memory', or 'pid' (default 'cpu')", required: false },
maxResults: { type: 'number', description: 'Max processes to return (default 30)', required: false },
},
},
{
name: 'kill_process',
description: 'Kill a process by its PID. Use list_processes first to find the PID. This is a destructive operation — the process will be terminated immediately.',
parameters: {
pid: { type: 'number', description: 'Process ID to kill', required: true },
force: { type: 'boolean', description: 'Force kill (SIGKILL on Unix, /F on Windows). Default: graceful termination.', required: false },
},
},
{
name: 'get_system_info',
description: 'Get system information: OS, CPU cores, total/free memory, disk usage, and uptime. Useful for understanding the environment before running resource-intensive tasks.',
parameters: {},
},
{
name: 'get_env_var',
description: 'Read the value of an environment variable. Returns the variable value or null if not set. Use this to check PATH, HOME, NODE_ENV, etc.',
parameters: {
name: { type: 'string', description: 'Environment variable name (e.g. "PATH", "HOME", "NODE_ENV")', required: true },
},
},
{
name: 'set_env_var',
description: 'Set an environment variable for the current IDE session and persistent terminal. The variable persists for the lifetime of the session (not across app restarts). Use this to configure build environments, set API keys, or adjust PATH.',
parameters: {
name: { type: 'string', description: 'Environment variable name', required: true },
value: { type: 'string', description: 'Variable value', required: true },
persistent: { type: 'boolean', description: 'Also persist to shell profile (.bashrc, PowerShell profile) for future sessions. Default: false.', required: false },
},
},
// ── Network Tools ──
{
name: 'ping_host',
description: 'Ping a host and return latency statistics. Cross-platform: uses ping on all OSes. Returns min/avg/max latency and packet loss.',
parameters: {
host: { type: 'string', description: 'Hostname or IP address to ping', required: true },
count: { type: 'number', description: 'Number of pings (default 4)', required: false },
},
},
{
name: 'dns_lookup',
description: 'Resolve DNS records for a hostname. Returns A, AAAA, CNAME, and MX records when available. Useful for debugging network issues or verifying domain configuration.',
parameters: {
hostname: { type: 'string', description: 'Hostname to resolve (e.g. "example.com")', required: true },
recordType: { type: 'string', description: "DNS record type: 'A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS' (default: all available)", required: false },
},
},
{
name: 'download_file',
description: 'Download a file from a URL to the project directory. Supports HTTP and HTTPS. Shows download progress. Use this to fetch assets, data files, or scripts.',
parameters: {
url: { type: 'string', description: 'URL to download from', required: true },
savePath: { type: 'string', description: 'Relative path in the project to save the file (default: filename from URL)', required: false },
overwrite: { type: 'boolean', description: 'Overwrite if file exists (default: false)', required: false },
},
},
// ── Code Quality Tools ──
{
name: 'run_linter',
description: 'Run a linter on a file or project. Auto-detects ESLint, Pylint, Ruff, Flake8, RuboCop, and other common linters from project config. Returns lint errors and warnings with file, line, and message.',
parameters: {
filePath: { type: 'string', description: 'File or directory to lint (default: project root)', required: false },
fix: { type: 'boolean', description: 'Auto-fix lint errors where possible (default: false)', required: false },
linter: { type: 'string', description: "Specific linter to use (e.g. 'eslint', 'pylint', 'ruff'). Auto-detected if omitted.", required: false },
},
},
{
name: 'run_tests',
description: 'Run the project test suite. Auto-detects test runners: npm test, pytest, cargo test, go test, dotnet test. Returns test results with pass/fail counts and failure details.',
parameters: {
testPath: { type: 'string', description: 'Specific test file or directory (default: all tests)', required: false },
testName: { type: 'string', description: 'Specific test name or pattern to run (e.g. "Auth.test")', required: false },
runner: { type: 'string', description: "Test runner to use (e.g. 'jest', 'pytest', 'cargo'). Auto-detected if omitted.", required: false },
coverage: { type: 'boolean', description: 'Generate coverage report (default: false)', required: false },
},
},
{
name: 'run_formatter',
description: 'Run a code formatter on a file or project. Auto-detects Prettier, Black, rustfmt, gofmt, and other formatters from project config. Returns list of formatted files.',
parameters: {
filePath: { type: 'string', description: 'File or directory to format (default: project root)', required: false },
formatter: { type: 'string', description: "Specific formatter (e.g. 'prettier', 'black', 'rustfmt'). Auto-detected if omitted.", required: false },
check: { type: 'boolean', description: 'Check only — report unformatted files without changing them (default: false)', required: false },
},
},
// ── IDE Integration Tools ──
{
name: 'open_terminal',
description: 'Open a new terminal tab in the IDE. Optionally run a command in the new terminal. The terminal persists after the command completes — the user can continue interacting with it.',
parameters: {
command: { type: 'string', description: 'Initial command to run in the terminal (optional)', required: false },
name: { type: 'string', description: 'Terminal tab name (optional)', required: false },
cwd: { type: 'string', description: 'Working directory for the terminal (default: project root)', required: false },
},
},
{
name: 'switch_file',
description: 'Open a file in the IDE editor and optionally move the cursor to a specific line. Use this to navigate the user to a relevant file or to show them where a change was made.',
parameters: {
filePath: { type: 'string', description: 'File to open', required: true },
line: { type: 'number', description: 'Line number to scroll to (1-based, optional)', required: false },
column: { type: 'number', description: 'Column number to position cursor (1-based, optional)', required: false },
},
},
{
name: 'get_diagnostics',
description: 'Get VS Code-style diagnostics (errors, warnings, hints) for a file or the entire project. Returns the list of problems from the language server or linter that the IDE is currently showing. More reliable than running a linter manually because it uses the IDE\'s built-in analysis.',