junis 0.3.1 → 0.3.2
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/dist/cli/index.js +351 -155
- package/dist/server/mcp.js +330 -152
- package/dist/server/stdio.js +228 -126
- package/package.json +1 -1
package/dist/server/mcp.js
CHANGED
|
@@ -104,11 +104,26 @@ var FilesystemTools = class {
|
|
|
104
104
|
register(server) {
|
|
105
105
|
server.tool(
|
|
106
106
|
"execute_command",
|
|
107
|
-
|
|
107
|
+
[
|
|
108
|
+
"Execute a shell command on the user's local device.",
|
|
109
|
+
"",
|
|
110
|
+
"ROUTING:",
|
|
111
|
+
"- Use for system commands, package managers (npm, pip, brew), git, build tools, and scripting.",
|
|
112
|
+
"- For reading files prefer read_file, for editing prefer edit_block, for searching prefer search_code.",
|
|
113
|
+
"",
|
|
114
|
+
"BEHAVIOR:",
|
|
115
|
+
"- Safe, routine commands (ls, pwd, git status, echo): execute immediately without explanation.",
|
|
116
|
+
"- Destructive or irreversible commands (rm -rf, sudo, shutdown, mkfs): explain what will happen and get user confirmation first.",
|
|
117
|
+
"- If a command fails, analyze the error and suggest an alternative. Do not retry the identical command more than twice.",
|
|
118
|
+
"",
|
|
119
|
+
"SAFETY:",
|
|
120
|
+
"- Commands run with the user's full permissions. Never execute commands that could damage the system, expose credentials, or modify security settings without explicit user request.",
|
|
121
|
+
"- Avoid piping untrusted input into shells. Use absolute paths when possible. Quote paths containing spaces."
|
|
122
|
+
].join("\n"),
|
|
108
123
|
{
|
|
109
|
-
command: import_zod.z.string().describe("
|
|
110
|
-
timeout_ms: import_zod.z.number().optional().default(3e4).describe("
|
|
111
|
-
background: import_zod.z.boolean().optional().default(false).describe("Run in background")
|
|
124
|
+
command: import_zod.z.string().describe("The shell command to execute. Use absolute paths when possible. Quote paths containing spaces."),
|
|
125
|
+
timeout_ms: import_zod.z.number().optional().default(3e4).describe("Maximum execution time in milliseconds (default: 30000). Increase for long-running builds or downloads."),
|
|
126
|
+
background: import_zod.z.boolean().optional().default(false).describe("Run in background without waiting for completion. Use for servers or long-running processes.")
|
|
112
127
|
},
|
|
113
128
|
async ({ command, timeout_ms, background }) => {
|
|
114
129
|
checkPermission("execute_command");
|
|
@@ -140,10 +155,15 @@ ${error.stderr ?? ""}`
|
|
|
140
155
|
);
|
|
141
156
|
server.tool(
|
|
142
157
|
"read_file",
|
|
143
|
-
|
|
158
|
+
[
|
|
159
|
+
"Read the contents of a file from the local filesystem.",
|
|
160
|
+
"",
|
|
161
|
+
"Returns file content as text (utf-8) or base64 for binary files. Supports any file type.",
|
|
162
|
+
"For searching within files, prefer search_code instead. For listing directory contents, use list_directory."
|
|
163
|
+
].join("\n"),
|
|
144
164
|
{
|
|
145
|
-
path: import_zod.z.string().describe("
|
|
146
|
-
encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("
|
|
165
|
+
path: import_zod.z.string().describe("Absolute or relative file path to read"),
|
|
166
|
+
encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("'utf-8' for text files (default), 'base64' for binary files (images, PDFs, archives)")
|
|
147
167
|
},
|
|
148
168
|
async ({ path: filePath, encoding }) => {
|
|
149
169
|
try {
|
|
@@ -160,10 +180,15 @@ ${error.stderr ?? ""}`
|
|
|
160
180
|
);
|
|
161
181
|
server.tool(
|
|
162
182
|
"write_file",
|
|
163
|
-
|
|
183
|
+
[
|
|
184
|
+
"Create a new file or completely overwrite an existing file. Parent directories are created automatically.",
|
|
185
|
+
"",
|
|
186
|
+
"WARNING: This replaces the entire file content. For partial modifications, use edit_block instead.",
|
|
187
|
+
"Prefer edit_block over write_file for existing files \u2014 it's safer and preserves unmodified content."
|
|
188
|
+
].join("\n"),
|
|
164
189
|
{
|
|
165
|
-
path: import_zod.z.string().describe("File path"),
|
|
166
|
-
content: import_zod.z.string().describe("
|
|
190
|
+
path: import_zod.z.string().describe("File path to create or overwrite. Parent directories are auto-created."),
|
|
191
|
+
content: import_zod.z.string().describe("Complete file content. This replaces the entire file.")
|
|
167
192
|
},
|
|
168
193
|
async ({ path: filePath, content }) => {
|
|
169
194
|
checkPermission("write_file");
|
|
@@ -174,9 +199,12 @@ ${error.stderr ?? ""}`
|
|
|
174
199
|
);
|
|
175
200
|
server.tool(
|
|
176
201
|
"list_directory",
|
|
177
|
-
|
|
202
|
+
[
|
|
203
|
+
"List files and subdirectories in the specified path. Returns entries with type indicators (\u{1F4C1} directory, \u{1F4C4} file).",
|
|
204
|
+
"Use this to explore project structure before reading or modifying files."
|
|
205
|
+
].join("\n"),
|
|
178
206
|
{
|
|
179
|
-
path: import_zod.z.string().describe("Directory path")
|
|
207
|
+
path: import_zod.z.string().describe("Directory path to list")
|
|
180
208
|
},
|
|
181
209
|
async ({ path: dirPath }) => {
|
|
182
210
|
try {
|
|
@@ -194,11 +222,16 @@ ${error.stderr ?? ""}`
|
|
|
194
222
|
);
|
|
195
223
|
server.tool(
|
|
196
224
|
"search_code",
|
|
197
|
-
|
|
225
|
+
[
|
|
226
|
+
"Search for text patterns across files using regex. Uses ripgrep for speed with glob fallback.",
|
|
227
|
+
"",
|
|
228
|
+
"Use this to find code definitions, function references, configuration values, or any text pattern.",
|
|
229
|
+
"Returns matching lines with file paths and line numbers for precise navigation."
|
|
230
|
+
].join("\n"),
|
|
198
231
|
{
|
|
199
|
-
pattern: import_zod.z.string().describe("Search pattern
|
|
200
|
-
directory: import_zod.z.string().optional().default(".").describe("
|
|
201
|
-
file_pattern: import_zod.z.string().optional().default("**/*").describe("
|
|
232
|
+
pattern: import_zod.z.string().describe("Search pattern with full regex support (e.g. 'function\\s+\\w+', 'import.*from', 'TODO')"),
|
|
233
|
+
directory: import_zod.z.string().optional().default(".").describe("Root directory to search from (default: current working directory)"),
|
|
234
|
+
file_pattern: import_zod.z.string().optional().default("**/*").describe("Glob pattern to filter files (e.g. '**/*.ts', '*.py', 'src/**/*.js')")
|
|
202
235
|
},
|
|
203
236
|
async ({ pattern, directory, file_pattern }) => {
|
|
204
237
|
try {
|
|
@@ -236,7 +269,7 @@ ${error.stderr ?? ""}`
|
|
|
236
269
|
);
|
|
237
270
|
server.tool(
|
|
238
271
|
"list_processes",
|
|
239
|
-
"List running processes",
|
|
272
|
+
"List the top 30 running processes sorted by CPU usage. Use this to identify resource-heavy processes, find PIDs for kill_process, or diagnose performance issues.",
|
|
240
273
|
{},
|
|
241
274
|
async () => {
|
|
242
275
|
const cmd = process.platform === "win32" ? "tasklist" : process.platform === "darwin" ? "ps aux | sort -rk 3 | head -30" : "ps aux --sort=-%cpu | head -30";
|
|
@@ -246,10 +279,14 @@ ${error.stderr ?? ""}`
|
|
|
246
279
|
);
|
|
247
280
|
server.tool(
|
|
248
281
|
"kill_process",
|
|
249
|
-
|
|
282
|
+
[
|
|
283
|
+
"Terminate a process by PID. Default: sends SIGTERM (graceful shutdown), waits 3 seconds, then auto-applies SIGKILL if still alive.",
|
|
284
|
+
"",
|
|
285
|
+
"SAFETY: Only kill processes the user explicitly identifies. Never kill system-critical processes (init, systemd, loginwindow, WindowServer) without explicit instruction."
|
|
286
|
+
].join("\n"),
|
|
250
287
|
{
|
|
251
|
-
pid: import_zod.z.number().describe("PID of the process to
|
|
252
|
-
signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("
|
|
288
|
+
pid: import_zod.z.number().describe("PID of the process to terminate (use list_processes to find PIDs)"),
|
|
289
|
+
signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("SIGTERM (default): graceful shutdown with 3s auto-SIGKILL fallback. SIGKILL: immediate force kill.")
|
|
253
290
|
},
|
|
254
291
|
async ({ pid, signal }) => {
|
|
255
292
|
const isWindows = process.platform === "win32";
|
|
@@ -295,12 +332,20 @@ ${error.stderr ?? ""}`
|
|
|
295
332
|
);
|
|
296
333
|
server.tool(
|
|
297
334
|
"edit_block",
|
|
298
|
-
|
|
335
|
+
[
|
|
336
|
+
"Replace a specific text block in a file with new text (diff-based partial edit).",
|
|
337
|
+
"",
|
|
338
|
+
"WORKFLOW: Always use read_file first to see current content, then use edit_block with the exact text to replace.",
|
|
339
|
+
"The old_string must match character-for-character including whitespace, indentation, and line breaks.",
|
|
340
|
+
"If multiple matches exist, include more surrounding context to make it unique, or set replace_all=true.",
|
|
341
|
+
"",
|
|
342
|
+
"Prefer this over write_file for modifying existing files \u2014 it only changes what you specify and preserves the rest."
|
|
343
|
+
].join("\n"),
|
|
299
344
|
{
|
|
300
|
-
path: import_zod.z.string().describe("
|
|
301
|
-
old_string: import_zod.z.string().describe("
|
|
302
|
-
new_string: import_zod.z.string().describe("
|
|
303
|
-
replace_all: import_zod.z.boolean().optional().default(false).describe("If true, replace
|
|
345
|
+
path: import_zod.z.string().describe("Path to the file to edit. The file must already exist."),
|
|
346
|
+
old_string: import_zod.z.string().describe("The exact text to find and replace. Must match character-for-character including whitespace and newlines. Include enough context for uniqueness."),
|
|
347
|
+
new_string: import_zod.z.string().describe("The replacement text. Use empty string to delete the matched text."),
|
|
348
|
+
replace_all: import_zod.z.boolean().optional().default(false).describe("If true, replace ALL matches. If false (default), require exactly one match (errors on ambiguous multiple matches).")
|
|
304
349
|
},
|
|
305
350
|
async ({ path: filePath, old_string, new_string, replace_all }) => {
|
|
306
351
|
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
@@ -335,11 +380,16 @@ ${error.stderr ?? ""}`
|
|
|
335
380
|
);
|
|
336
381
|
server.tool(
|
|
337
382
|
"cron_create",
|
|
338
|
-
|
|
383
|
+
[
|
|
384
|
+
"Create a recurring scheduled task (cron job) using standard cron syntax.",
|
|
385
|
+
"",
|
|
386
|
+
"Common schedules: '*/5 * * * *' (every 5 min), '0 9 * * 1-5' (weekdays 9am), '0 0 * * *' (daily midnight), '0 */2 * * *' (every 2 hours).",
|
|
387
|
+
"Duplicate commands are automatically detected and rejected. Use cron_list to see existing jobs."
|
|
388
|
+
].join("\n"),
|
|
339
389
|
{
|
|
340
|
-
schedule: import_zod.z.string().describe("Cron schedule expression (
|
|
341
|
-
command: import_zod.z.string().describe("Shell command to execute"),
|
|
342
|
-
label: import_zod.z.string().optional().describe("
|
|
390
|
+
schedule: import_zod.z.string().describe("Cron schedule expression (5 fields: minute hour day month weekday). Examples: '*/5 * * * *' (every 5 min), '0 9 * * 1-5' (weekdays 9am)"),
|
|
391
|
+
command: import_zod.z.string().describe("Shell command to execute on schedule"),
|
|
392
|
+
label: import_zod.z.string().optional().describe("Human-readable label for identification (e.g. 'daily-backup', 'log-cleanup')")
|
|
343
393
|
},
|
|
344
394
|
async ({ schedule, command, label }) => {
|
|
345
395
|
try {
|
|
@@ -381,7 +431,7 @@ ${error.stderr ?? ""}`
|
|
|
381
431
|
);
|
|
382
432
|
server.tool(
|
|
383
433
|
"cron_list",
|
|
384
|
-
"List all cron jobs
|
|
434
|
+
"List all scheduled cron jobs with their IDs, labels, schedules, and commands. Use the returned ID numbers with cron_delete to remove specific jobs.",
|
|
385
435
|
{},
|
|
386
436
|
async () => {
|
|
387
437
|
try {
|
|
@@ -428,10 +478,10 @@ ${error.stderr ?? ""}`
|
|
|
428
478
|
);
|
|
429
479
|
server.tool(
|
|
430
480
|
"cron_delete",
|
|
431
|
-
"Delete a cron job by its ID (from cron_list) or by matching command string",
|
|
481
|
+
"Delete a scheduled cron job by its ID (from cron_list output) or by matching command string. Associated comment labels are automatically cleaned up.",
|
|
432
482
|
{
|
|
433
|
-
id: import_zod.z.number().optional().describe("Cron job ID from cron_list output"),
|
|
434
|
-
command: import_zod.z.string().optional().describe("Delete
|
|
483
|
+
id: import_zod.z.number().optional().describe("Cron job ID from cron_list output (e.g. 1, 2, 3)"),
|
|
484
|
+
command: import_zod.z.string().optional().describe("Delete all jobs matching this command string")
|
|
435
485
|
},
|
|
436
486
|
async ({ id, command }) => {
|
|
437
487
|
if (!id && !command) {
|
|
@@ -544,13 +594,22 @@ var BrowserTools = class {
|
|
|
544
594
|
};
|
|
545
595
|
server.tool(
|
|
546
596
|
"browser_start",
|
|
547
|
-
|
|
597
|
+
[
|
|
598
|
+
"Launch or connect to a web browser for automation.",
|
|
599
|
+
"",
|
|
600
|
+
"MODES:",
|
|
601
|
+
"- 'managed' (default): Launches a new Chromium instance. Use 'headless' for background operation, 'profile' for persistent sessions (cookies, logins preserved).",
|
|
602
|
+
"- 'remote-cdp': Connects to an already-running Chrome via CDP URL (e.g. from chrome://inspect). Use this to automate an existing browser session.",
|
|
603
|
+
"",
|
|
604
|
+
"WORKFLOW: browser_start \u2192 browser_navigate \u2192 browser_snapshot \u2192 interact (click/type/fill) \u2192 browser_stop.",
|
|
605
|
+
"Always call browser_stop when done to release system resources."
|
|
606
|
+
].join("\n"),
|
|
548
607
|
{
|
|
549
|
-
mode: import_zod2.z.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome"),
|
|
550
|
-
headless: import_zod2.z.boolean().optional().default(false).describe("Run
|
|
551
|
-
cdpUrl: import_zod2.z.string().optional().describe("
|
|
552
|
-
profile: import_zod2.z.string().optional().describe("
|
|
553
|
-
allowInternal: import_zod2.z.boolean().optional().default(false).describe("Allow localhost
|
|
608
|
+
mode: import_zod2.z.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome via CDP"),
|
|
609
|
+
headless: import_zod2.z.boolean().optional().default(false).describe("Run without visible window (managed mode only). Use for background tasks."),
|
|
610
|
+
cdpUrl: import_zod2.z.string().optional().describe("Chrome DevTools Protocol URL for remote-cdp mode (e.g. http://localhost:9222)"),
|
|
611
|
+
profile: import_zod2.z.string().optional().describe("Browser profile name for persistent sessions \u2014 preserves cookies, logins, and history across restarts (managed mode only)"),
|
|
612
|
+
allowInternal: import_zod2.z.boolean().optional().default(false).describe("Allow navigation to localhost and internal network URLs")
|
|
554
613
|
},
|
|
555
614
|
({ mode, headless, cdpUrl, profile, allowInternal }) => this.withLock(async () => {
|
|
556
615
|
if (this.browser) {
|
|
@@ -571,7 +630,7 @@ var BrowserTools = class {
|
|
|
571
630
|
);
|
|
572
631
|
server.tool(
|
|
573
632
|
"browser_stop",
|
|
574
|
-
"Stop browser and release resources",
|
|
633
|
+
"Stop the browser and release all associated resources (memory, connections, processes). Always call this when browser automation is complete.",
|
|
575
634
|
{},
|
|
576
635
|
() => this.withLock(async () => {
|
|
577
636
|
await this.cleanup();
|
|
@@ -580,9 +639,9 @@ var BrowserTools = class {
|
|
|
580
639
|
);
|
|
581
640
|
server.tool(
|
|
582
641
|
"browser_navigate",
|
|
583
|
-
"Navigate to URL.
|
|
642
|
+
"Navigate the browser to a URL. Automatically opens a new tab if the browser is started but no page exists yet. Waits for the page to load before returning.",
|
|
584
643
|
{
|
|
585
|
-
url: import_zod2.z.string().describe("URL to navigate to")
|
|
644
|
+
url: import_zod2.z.string().describe("Full URL to navigate to (include https://)")
|
|
586
645
|
},
|
|
587
646
|
({ url }) => this.withLock(async () => {
|
|
588
647
|
if (!this.browser) throw new Error("Browser not started. Call browser_start first.");
|
|
@@ -597,10 +656,17 @@ var BrowserTools = class {
|
|
|
597
656
|
);
|
|
598
657
|
server.tool(
|
|
599
658
|
"browser_snapshot",
|
|
600
|
-
|
|
659
|
+
[
|
|
660
|
+
"Capture the page's Accessibility Tree with numbered ref IDs for each element. This is the primary way to 'see' and understand page content.",
|
|
661
|
+
"",
|
|
662
|
+
"WORKFLOW: Call browser_snapshot \u2192 find the target element's ref (e.g. 'e1', 'e5') \u2192 use that ref in browser_click, browser_type, or other interaction tools.",
|
|
663
|
+
"Refs change after page updates \u2014 always call browser_snapshot again after navigation or clicks that modify the page.",
|
|
664
|
+
"",
|
|
665
|
+
"Prefer this over browser_screenshot for understanding page structure \u2014 it's faster, structured, and machine-readable."
|
|
666
|
+
].join("\n"),
|
|
601
667
|
{
|
|
602
|
-
interactive: import_zod2.z.boolean().optional().default(true).describe("
|
|
603
|
-
compact: import_zod2.z.boolean().optional().default(true).describe("
|
|
668
|
+
interactive: import_zod2.z.boolean().optional().default(true).describe("true (default): only show clickable/typeable elements. false: show all elements including static text."),
|
|
669
|
+
compact: import_zod2.z.boolean().optional().default(true).describe("true (default): hide empty containers for cleaner output")
|
|
604
670
|
},
|
|
605
671
|
({ interactive, compact }) => this.withLock(async () => {
|
|
606
672
|
const result = await requirePage().snapshot({ interactive, compact });
|
|
@@ -620,11 +686,11 @@ ${refList}`
|
|
|
620
686
|
);
|
|
621
687
|
server.tool(
|
|
622
688
|
"browser_click",
|
|
623
|
-
"Click element by ref number from browser_snapshot",
|
|
689
|
+
"Click an element by its ref number from browser_snapshot. Always call browser_snapshot first to get current refs \u2014 they change after page updates.",
|
|
624
690
|
{
|
|
625
|
-
ref: import_zod2.z.string().describe("
|
|
626
|
-
doubleClick: import_zod2.z.boolean().optional().default(false),
|
|
627
|
-
button: import_zod2.z.enum(["left", "right", "middle"]).optional().default("left")
|
|
691
|
+
ref: import_zod2.z.string().describe("Element ref from browser_snapshot (e.g. 'e1', 'e15'). Call browser_snapshot first to get current refs."),
|
|
692
|
+
doubleClick: import_zod2.z.boolean().optional().default(false).describe("Double-click instead of single click"),
|
|
693
|
+
button: import_zod2.z.enum(["left", "right", "middle"]).optional().default("left").describe("Mouse button to use")
|
|
628
694
|
},
|
|
629
695
|
({ ref, doubleClick, button }) => this.withLock(async () => {
|
|
630
696
|
await requirePage().click(ref, { doubleClick, button });
|
|
@@ -633,12 +699,12 @@ ${refList}`
|
|
|
633
699
|
);
|
|
634
700
|
server.tool(
|
|
635
701
|
"browser_type",
|
|
636
|
-
"Type text into element by ref number",
|
|
702
|
+
"Type text into an input element by ref number. Use 'submit=true' to press Enter after typing (e.g. for search forms). Use 'slowly=true' for sites requiring keystroke-by-keystroke input.",
|
|
637
703
|
{
|
|
638
|
-
ref: import_zod2.z.string().describe("
|
|
639
|
-
text: import_zod2.z.string().describe("Text to type"),
|
|
640
|
-
submit: import_zod2.z.boolean().optional().default(false).describe("Press Enter after typing"),
|
|
641
|
-
slowly: import_zod2.z.boolean().optional().default(false).describe("Type slowly (75ms per char)")
|
|
704
|
+
ref: import_zod2.z.string().describe("Element ref from browser_snapshot (e.g. 'e3')"),
|
|
705
|
+
text: import_zod2.z.string().describe("Text to type into the element"),
|
|
706
|
+
submit: import_zod2.z.boolean().optional().default(false).describe("Press Enter after typing (useful for search boxes and forms)"),
|
|
707
|
+
slowly: import_zod2.z.boolean().optional().default(false).describe("Type slowly (75ms per char) for sites that process each keystroke")
|
|
642
708
|
},
|
|
643
709
|
({ ref, text, submit, slowly }) => this.withLock(async () => {
|
|
644
710
|
await requirePage().type(ref, text, { submit, slowly });
|
|
@@ -647,13 +713,13 @@ ${refList}`
|
|
|
647
713
|
);
|
|
648
714
|
server.tool(
|
|
649
715
|
"browser_fill",
|
|
650
|
-
"Fill multiple form fields at once",
|
|
716
|
+
"Fill multiple form fields at once \u2014 more efficient than calling browser_type repeatedly. Each field needs a ref from browser_snapshot.",
|
|
651
717
|
{
|
|
652
718
|
fields: import_zod2.z.array(import_zod2.z.object({
|
|
653
719
|
ref: import_zod2.z.string(),
|
|
654
720
|
type: import_zod2.z.enum(["text", "checkbox", "radio"]),
|
|
655
721
|
value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.boolean()])
|
|
656
|
-
})).describe("Array of {ref, type, value}")
|
|
722
|
+
})).describe("Array of {ref, type, value}. type='text': value is string. type='checkbox'/'radio': value is boolean.")
|
|
657
723
|
},
|
|
658
724
|
({ fields }) => this.withLock(async () => {
|
|
659
725
|
await requirePage().fill(fields);
|
|
@@ -662,9 +728,9 @@ ${refList}`
|
|
|
662
728
|
);
|
|
663
729
|
server.tool(
|
|
664
730
|
"browser_select",
|
|
665
|
-
"Select dropdown option
|
|
731
|
+
"Select one or more options from a dropdown/select element. Values should match the option value attributes, not display text.",
|
|
666
732
|
{
|
|
667
|
-
ref: import_zod2.z.string().describe("Ref
|
|
733
|
+
ref: import_zod2.z.string().describe("Ref of the <select> element from browser_snapshot"),
|
|
668
734
|
values: import_zod2.z.array(import_zod2.z.string()).describe("Option value(s) to select")
|
|
669
735
|
},
|
|
670
736
|
({ ref, values }) => this.withLock(async () => {
|
|
@@ -674,9 +740,9 @@ ${refList}`
|
|
|
674
740
|
);
|
|
675
741
|
server.tool(
|
|
676
742
|
"browser_press",
|
|
677
|
-
"Press keyboard key or combination (e.g. '
|
|
743
|
+
"Press a keyboard key or key combination. Use for shortcuts (e.g. 'Control+a', 'Escape'), form submission ('Enter'), or navigation ('Tab'). Does not require a specific element ref.",
|
|
678
744
|
{
|
|
679
|
-
key: import_zod2.z.string().describe("Key combination
|
|
745
|
+
key: import_zod2.z.string().describe("Key or combination: 'Enter', 'Escape', 'Tab', 'Control+a', 'Meta+c', 'ArrowDown', 'Backspace'")
|
|
680
746
|
},
|
|
681
747
|
({ key }) => this.withLock(async () => {
|
|
682
748
|
await requirePage().press(key);
|
|
@@ -685,9 +751,9 @@ ${refList}`
|
|
|
685
751
|
);
|
|
686
752
|
server.tool(
|
|
687
753
|
"browser_hover",
|
|
688
|
-
"
|
|
754
|
+
"Move the mouse cursor over an element by ref. Use to trigger hover menus, tooltips, or dropdown previews before clicking.",
|
|
689
755
|
{
|
|
690
|
-
ref: import_zod2.z.string().describe("
|
|
756
|
+
ref: import_zod2.z.string().describe("Element ref from browser_snapshot")
|
|
691
757
|
},
|
|
692
758
|
({ ref }) => this.withLock(async () => {
|
|
693
759
|
await requirePage().hover(ref);
|
|
@@ -696,10 +762,10 @@ ${refList}`
|
|
|
696
762
|
);
|
|
697
763
|
server.tool(
|
|
698
764
|
"browser_drag",
|
|
699
|
-
"Drag element from startRef to endRef",
|
|
765
|
+
"Drag an element from startRef to endRef. Both refs must come from a recent browser_snapshot. Use for drag-and-drop interfaces, sliders, or reorderable lists.",
|
|
700
766
|
{
|
|
701
|
-
startRef: import_zod2.z.string().describe("Source element ref"),
|
|
702
|
-
endRef: import_zod2.z.string().describe("Target element ref")
|
|
767
|
+
startRef: import_zod2.z.string().describe("Source element ref to drag from"),
|
|
768
|
+
endRef: import_zod2.z.string().describe("Target element ref to drag to")
|
|
703
769
|
},
|
|
704
770
|
({ startRef, endRef }) => this.withLock(async () => {
|
|
705
771
|
await requirePage().drag(startRef, endRef);
|
|
@@ -708,10 +774,10 @@ ${refList}`
|
|
|
708
774
|
);
|
|
709
775
|
server.tool(
|
|
710
776
|
"browser_upload",
|
|
711
|
-
"Upload file(
|
|
777
|
+
"Upload local files to a file input element (<input type='file'>). The ref must point to a file input from browser_snapshot.",
|
|
712
778
|
{
|
|
713
|
-
ref: import_zod2.z.string().describe("Ref
|
|
714
|
-
paths: import_zod2.z.array(import_zod2.z.string()).describe("Absolute file path(s) to upload")
|
|
779
|
+
ref: import_zod2.z.string().describe("Ref of the file input element from browser_snapshot"),
|
|
780
|
+
paths: import_zod2.z.array(import_zod2.z.string()).describe("Absolute file path(s) on the local device to upload")
|
|
715
781
|
},
|
|
716
782
|
({ ref, paths }) => this.withLock(async () => {
|
|
717
783
|
await requirePage().uploadFile(ref, paths);
|
|
@@ -720,11 +786,16 @@ ${refList}`
|
|
|
720
786
|
);
|
|
721
787
|
server.tool(
|
|
722
788
|
"browser_screenshot",
|
|
723
|
-
|
|
789
|
+
[
|
|
790
|
+
"Capture a screenshot of the current page. Returns base64 image data (viewable by AI) or saves to a file.",
|
|
791
|
+
"",
|
|
792
|
+
"Prefer browser_snapshot (Accessibility Tree) for understanding page structure \u2014 it's faster and machine-readable.",
|
|
793
|
+
"Use browser_screenshot only when visual layout matters (charts, images, styling, visual verification)."
|
|
794
|
+
].join("\n"),
|
|
724
795
|
{
|
|
725
|
-
path: import_zod2.z.string().optional().describe("Save path
|
|
726
|
-
fullPage: import_zod2.z.boolean().optional().default(false),
|
|
727
|
-
ref: import_zod2.z.string().optional().describe("Capture specific element by ref")
|
|
796
|
+
path: import_zod2.z.string().optional().describe("Save path for the screenshot. If omitted, returns base64 image data directly."),
|
|
797
|
+
fullPage: import_zod2.z.boolean().optional().default(false).describe("Capture the full scrollable page, not just the visible viewport"),
|
|
798
|
+
ref: import_zod2.z.string().optional().describe("Capture only a specific element by its ref from browser_snapshot")
|
|
728
799
|
},
|
|
729
800
|
({ path: path2, fullPage, ref }) => this.withLock(async () => {
|
|
730
801
|
const buffer = await requirePage().screenshot({ fullPage, ref });
|
|
@@ -743,9 +814,9 @@ ${refList}`
|
|
|
743
814
|
);
|
|
744
815
|
server.tool(
|
|
745
816
|
"browser_pdf",
|
|
746
|
-
"Save current page as PDF",
|
|
817
|
+
"Save the current page as a PDF file. Renders the full page including below-the-fold content. Useful for archiving, sharing, or offline reading.",
|
|
747
818
|
{
|
|
748
|
-
path: import_zod2.z.string().describe("
|
|
819
|
+
path: import_zod2.z.string().describe("Output file path (.pdf)")
|
|
749
820
|
},
|
|
750
821
|
({ path: path2 }) => this.withLock(async () => {
|
|
751
822
|
const buffer = await requirePage().pdf();
|
|
@@ -755,9 +826,14 @@ ${refList}`
|
|
|
755
826
|
);
|
|
756
827
|
server.tool(
|
|
757
828
|
"browser_evaluate",
|
|
758
|
-
|
|
829
|
+
[
|
|
830
|
+
"Execute JavaScript code directly in the browser page context and return the result.",
|
|
831
|
+
"",
|
|
832
|
+
"Use for: extracting data not available in the Accessibility Tree, DOM manipulation, interacting with page APIs, or debugging.",
|
|
833
|
+
"Wrap complex logic in an IIFE: (function(){ ... })()"
|
|
834
|
+
].join("\n"),
|
|
759
835
|
{
|
|
760
|
-
code: import_zod2.z.string().describe("JavaScript code to execute
|
|
836
|
+
code: import_zod2.z.string().describe("JavaScript code to execute in the page context. Return values are automatically serialized.")
|
|
761
837
|
},
|
|
762
838
|
({ code }) => this.withLock(async () => {
|
|
763
839
|
try {
|
|
@@ -778,13 +854,17 @@ ${refList}`
|
|
|
778
854
|
);
|
|
779
855
|
server.tool(
|
|
780
856
|
"browser_wait",
|
|
781
|
-
|
|
857
|
+
[
|
|
858
|
+
"Wait for a specific condition before proceeding. Use between actions when the page needs time to update.",
|
|
859
|
+
"",
|
|
860
|
+
"OPTIONS (use one): 'text' (wait for text to appear), 'textGone' (wait for text to disappear), 'url' (URL matches glob), 'loadState' (page load state), 'timeMs' (fixed delay as last resort)."
|
|
861
|
+
].join("\n"),
|
|
782
862
|
{
|
|
783
|
-
text: import_zod2.z.string().optional().describe("Wait until this text appears"),
|
|
784
|
-
textGone: import_zod2.z.string().optional().describe("Wait until this text disappears"),
|
|
785
|
-
url: import_zod2.z.string().optional().describe("Wait until URL matches
|
|
786
|
-
loadState: import_zod2.z.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for load state"),
|
|
787
|
-
timeMs: import_zod2.z.number().optional().describe("
|
|
863
|
+
text: import_zod2.z.string().optional().describe("Wait until this text appears on the page"),
|
|
864
|
+
textGone: import_zod2.z.string().optional().describe("Wait until this text disappears from the page"),
|
|
865
|
+
url: import_zod2.z.string().optional().describe("Wait until URL matches this glob pattern (e.g. '**/dashboard', '**/success')"),
|
|
866
|
+
loadState: import_zod2.z.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for page load state: 'load' (full), 'domcontentloaded' (DOM ready), 'networkidle' (no pending requests)"),
|
|
867
|
+
timeMs: import_zod2.z.number().optional().describe("Fixed wait in milliseconds \u2014 use as last resort when other conditions don't apply")
|
|
788
868
|
},
|
|
789
869
|
({ text, textGone, url, loadState, timeMs }) => this.withLock(async () => {
|
|
790
870
|
const condition = {};
|
|
@@ -799,9 +879,9 @@ ${refList}`
|
|
|
799
879
|
);
|
|
800
880
|
server.tool(
|
|
801
881
|
"browser_cookies",
|
|
802
|
-
"
|
|
882
|
+
"Manage browser cookies: get all cookies, set a specific cookie, or clear all cookies. Useful for authentication state, session management, or testing.",
|
|
803
883
|
{
|
|
804
|
-
action: import_zod2.z.enum(["get", "set", "clear"]).describe("
|
|
884
|
+
action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': retrieve all cookies, 'set': add/update a cookie, 'clear': remove all cookies"),
|
|
805
885
|
cookie: import_zod2.z.object({
|
|
806
886
|
name: import_zod2.z.string(),
|
|
807
887
|
value: import_zod2.z.string(),
|
|
@@ -809,7 +889,7 @@ ${refList}`
|
|
|
809
889
|
path: import_zod2.z.string().optional(),
|
|
810
890
|
httpOnly: import_zod2.z.boolean().optional(),
|
|
811
891
|
secure: import_zod2.z.boolean().optional()
|
|
812
|
-
}).optional().describe("Cookie data (required for set action)")
|
|
892
|
+
}).optional().describe("Cookie data (required for 'set' action)")
|
|
813
893
|
},
|
|
814
894
|
({ action, cookie }) => this.withLock(async () => {
|
|
815
895
|
const page = requirePage();
|
|
@@ -828,12 +908,12 @@ ${refList}`
|
|
|
828
908
|
);
|
|
829
909
|
server.tool(
|
|
830
910
|
"browser_storage",
|
|
831
|
-
"Read
|
|
911
|
+
"Read, write, or clear browser localStorage/sessionStorage. Useful for managing client-side state, authentication tokens, or application preferences.",
|
|
832
912
|
{
|
|
833
|
-
action: import_zod2.z.enum(["get", "set", "clear"]).describe("
|
|
834
|
-
kind: import_zod2.z.enum(["local", "session"]).optional().default("local").describe("
|
|
835
|
-
key: import_zod2.z.string().optional().describe("Storage key
|
|
836
|
-
value: import_zod2.z.string().optional().describe("Value to
|
|
913
|
+
action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': read value(s), 'set': write a key-value pair, 'clear': remove all entries"),
|
|
914
|
+
kind: import_zod2.z.enum(["local", "session"]).optional().default("local").describe("'local' (persistent) or 'session' (cleared on tab close)"),
|
|
915
|
+
key: import_zod2.z.string().optional().describe("Storage key to get or set. Omit key with 'get' to retrieve all entries."),
|
|
916
|
+
value: import_zod2.z.string().optional().describe("Value to store (required for 'set' action)")
|
|
837
917
|
},
|
|
838
918
|
({ action, kind, key, value }) => this.withLock(async () => {
|
|
839
919
|
const page = requirePage();
|
|
@@ -853,13 +933,13 @@ ${refList}`
|
|
|
853
933
|
server.tool(
|
|
854
934
|
"browser_dialog",
|
|
855
935
|
[
|
|
856
|
-
"Handle JavaScript dialogs (alert
|
|
857
|
-
"
|
|
858
|
-
" 1. action='arm' \u2014 register a one-shot handler (returns immediately, does NOT block).",
|
|
936
|
+
"Handle JavaScript dialogs (alert, confirm, prompt). Two-step pattern:",
|
|
937
|
+
" 1. action='arm' \u2014 register a one-shot handler (returns immediately, does NOT block).",
|
|
859
938
|
" 2. Trigger the dialog (e.g. browser_click on the button that calls confirm()).",
|
|
860
939
|
" 3. action='wait' \u2014 await the handler to confirm the dialog was handled.",
|
|
940
|
+
"",
|
|
861
941
|
"The 'accept' and 'promptText' params are only used with action='arm'."
|
|
862
|
-
].join("
|
|
942
|
+
].join("\n"),
|
|
863
943
|
{
|
|
864
944
|
action: import_zod2.z.enum(["arm", "wait"]).describe(
|
|
865
945
|
"'arm' = register handler and return immediately; 'wait' = await the previously armed handler"
|
|
@@ -871,7 +951,7 @@ ${refList}`
|
|
|
871
951
|
"Text to enter if the dialog is a prompt. Only used with action='arm'."
|
|
872
952
|
),
|
|
873
953
|
timeoutMs: import_zod2.z.number().optional().describe(
|
|
874
|
-
"Timeout in ms for 'wait' action
|
|
954
|
+
"Timeout in ms for 'wait' action (default: 30000). Increase for slow-loading dialogs."
|
|
875
955
|
)
|
|
876
956
|
},
|
|
877
957
|
({ action, accept, promptText, timeoutMs }) => this.withLock(async () => {
|
|
@@ -922,8 +1002,8 @@ var NotebookTools = class {
|
|
|
922
1002
|
register(server) {
|
|
923
1003
|
server.tool(
|
|
924
1004
|
"notebook_read",
|
|
925
|
-
"Read .ipynb notebook",
|
|
926
|
-
{ path: import_zod3.z.string().describe("
|
|
1005
|
+
"Read a Jupyter notebook (.ipynb) and return all cells with their types (code/markdown), source content, and output counts. Use this to understand notebook structure before making edits.",
|
|
1006
|
+
{ path: import_zod3.z.string().describe("Path to the .ipynb notebook file") },
|
|
927
1007
|
async ({ path: filePath }) => {
|
|
928
1008
|
const nb = await readNotebook(filePath);
|
|
929
1009
|
const cells = nb.cells.map((cell, i) => ({
|
|
@@ -939,11 +1019,11 @@ var NotebookTools = class {
|
|
|
939
1019
|
);
|
|
940
1020
|
server.tool(
|
|
941
1021
|
"notebook_edit_cell",
|
|
942
|
-
"
|
|
1022
|
+
"Replace the source code of a specific cell in a Jupyter notebook. Use notebook_read first to identify the correct cell index (0-based). Existing outputs for the cell are preserved \u2014 use notebook_execute to re-run.",
|
|
943
1023
|
{
|
|
944
|
-
path: import_zod3.z.string(),
|
|
945
|
-
cell_index: import_zod3.z.number().describe("Cell index (0-based)"),
|
|
946
|
-
source: import_zod3.z.string().describe("New source code")
|
|
1024
|
+
path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
|
|
1025
|
+
cell_index: import_zod3.z.number().describe("Cell index to edit (0-based). Use notebook_read to find the right index."),
|
|
1026
|
+
source: import_zod3.z.string().describe("New source code/content for the cell (replaces entire cell content)")
|
|
947
1027
|
},
|
|
948
1028
|
async ({ path: filePath, cell_index, source }) => {
|
|
949
1029
|
const nb = await readNotebook(filePath);
|
|
@@ -959,10 +1039,15 @@ var NotebookTools = class {
|
|
|
959
1039
|
);
|
|
960
1040
|
server.tool(
|
|
961
1041
|
"notebook_execute",
|
|
962
|
-
|
|
1042
|
+
[
|
|
1043
|
+
"Execute all cells in a Jupyter notebook using nbconvert. Results are saved in-place \u2014 the notebook file is updated with execution outputs.",
|
|
1044
|
+
"",
|
|
1045
|
+
"Requires Jupyter to be installed (pip install jupyter). The timeout applies per cell, not for the entire notebook.",
|
|
1046
|
+
"If execution fails on a cell, the error is captured in the cell output and subsequent cells may not execute."
|
|
1047
|
+
].join("\n"),
|
|
963
1048
|
{
|
|
964
|
-
path: import_zod3.z.string().describe("
|
|
965
|
-
timeout: import_zod3.z.number().optional().default(300).describe("
|
|
1049
|
+
path: import_zod3.z.string().describe("Path to the .ipynb notebook file to execute"),
|
|
1050
|
+
timeout: import_zod3.z.number().optional().default(300).describe("Maximum execution time per cell in seconds (default: 300). Increase for cells with heavy computation.")
|
|
966
1051
|
},
|
|
967
1052
|
async ({ path: filePath, timeout }) => {
|
|
968
1053
|
const nbconvertArgs = `nbconvert --to notebook --execute --inplace "${filePath}" --ExecutePreprocessor.timeout=${timeout}`;
|
|
@@ -991,12 +1076,12 @@ var NotebookTools = class {
|
|
|
991
1076
|
);
|
|
992
1077
|
server.tool(
|
|
993
1078
|
"notebook_add_cell",
|
|
994
|
-
"
|
|
1079
|
+
"Insert a new cell into a Jupyter notebook. If position is omitted, the cell is appended at the end. Use cell_type='code' for executable Python cells, 'markdown' for documentation/text cells.",
|
|
995
1080
|
{
|
|
996
|
-
path: import_zod3.z.string().describe(".ipynb file
|
|
997
|
-
cell_type: import_zod3.z.enum(["code", "markdown"]).describe("
|
|
998
|
-
source: import_zod3.z.string().describe("Cell source content"),
|
|
999
|
-
position: import_zod3.z.number().optional().describe("Insert position (0-based).
|
|
1081
|
+
path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
|
|
1082
|
+
cell_type: import_zod3.z.enum(["code", "markdown"]).describe("'code' for executable cells, 'markdown' for text/documentation cells"),
|
|
1083
|
+
source: import_zod3.z.string().describe("Cell source content (Python code or Markdown text)"),
|
|
1084
|
+
position: import_zod3.z.number().optional().describe("Insert position (0-based index). Omit to append at the end. If position exceeds cell count, appends at end with a warning.")
|
|
1000
1085
|
},
|
|
1001
1086
|
async ({ path: filePath, cell_type: cellType, source, position }) => {
|
|
1002
1087
|
const nb = await readNotebook(filePath);
|
|
@@ -1027,10 +1112,10 @@ var NotebookTools = class {
|
|
|
1027
1112
|
);
|
|
1028
1113
|
server.tool(
|
|
1029
1114
|
"notebook_delete_cell",
|
|
1030
|
-
"Delete a
|
|
1115
|
+
"Delete a cell from a Jupyter notebook by its 0-based index. Use notebook_read first to verify the cell content before deletion. This action cannot be undone.",
|
|
1031
1116
|
{
|
|
1032
|
-
path: import_zod3.z.string().describe(".ipynb file
|
|
1033
|
-
cell_index: import_zod3.z.number().describe("Cell index to delete (0-based)")
|
|
1117
|
+
path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
|
|
1118
|
+
cell_index: import_zod3.z.number().describe("Cell index to delete (0-based). Use notebook_read first to verify content.")
|
|
1034
1119
|
},
|
|
1035
1120
|
async ({ path: filePath, cell_index }) => {
|
|
1036
1121
|
const nb = await readNotebook(filePath);
|
|
@@ -1061,9 +1146,14 @@ var DeviceTools = class {
|
|
|
1061
1146
|
register(server) {
|
|
1062
1147
|
server.tool(
|
|
1063
1148
|
"camera_capture",
|
|
1064
|
-
|
|
1149
|
+
[
|
|
1150
|
+
"Capture a photo from the device's camera and return it as base64 image data.",
|
|
1151
|
+
"",
|
|
1152
|
+
"Platform-specific: macOS (imagesnap), Windows (ffmpeg/dshow), Linux (fswebcam).",
|
|
1153
|
+
"Requires a connected camera with OS permissions granted. If output_path is provided, the file is also saved to disk."
|
|
1154
|
+
].join("\n"),
|
|
1065
1155
|
{
|
|
1066
|
-
output_path: import_zod4.z.string().optional()
|
|
1156
|
+
output_path: import_zod4.z.string().optional().describe("File path to save the captured photo. If omitted, returns image data only (temp file auto-cleaned).")
|
|
1067
1157
|
},
|
|
1068
1158
|
async ({ output_path }) => {
|
|
1069
1159
|
const p = platform();
|
|
@@ -1099,10 +1189,10 @@ Please check if a camera is connected.` }],
|
|
|
1099
1189
|
);
|
|
1100
1190
|
server.tool(
|
|
1101
1191
|
"notification_send",
|
|
1102
|
-
"Send OS notification",
|
|
1192
|
+
"Send a native OS notification (banner/toast) to the user's desktop. Use for task completion alerts, reminders, or important status updates. The notification appears even when the terminal is not focused.",
|
|
1103
1193
|
{
|
|
1104
|
-
title: import_zod4.z.string().describe("Notification title"),
|
|
1105
|
-
message: import_zod4.z.string().describe("Notification body")
|
|
1194
|
+
title: import_zod4.z.string().describe("Notification title (displayed prominently)"),
|
|
1195
|
+
message: import_zod4.z.string().describe("Notification body text")
|
|
1106
1196
|
},
|
|
1107
1197
|
async ({ title, message }) => {
|
|
1108
1198
|
try {
|
|
@@ -1126,7 +1216,7 @@ Please check if a camera is connected.` }],
|
|
|
1126
1216
|
);
|
|
1127
1217
|
server.tool(
|
|
1128
1218
|
"clipboard_read",
|
|
1129
|
-
"Read clipboard",
|
|
1219
|
+
"Read the current contents of the system clipboard (text). Use to access content the user has copied. Platform-specific: macOS (pbpaste), Windows (PowerShell), Linux (xclip).",
|
|
1130
1220
|
{},
|
|
1131
1221
|
async () => {
|
|
1132
1222
|
const p = platform();
|
|
@@ -1137,8 +1227,10 @@ Please check if a camera is connected.` }],
|
|
|
1137
1227
|
);
|
|
1138
1228
|
server.tool(
|
|
1139
1229
|
"clipboard_write",
|
|
1140
|
-
"Write to clipboard",
|
|
1141
|
-
{
|
|
1230
|
+
"Write text to the system clipboard, replacing its current contents. Use to prepare content for the user to paste elsewhere.",
|
|
1231
|
+
{
|
|
1232
|
+
text: import_zod4.z.string().describe("Text to copy to the clipboard")
|
|
1233
|
+
},
|
|
1142
1234
|
async ({ text }) => {
|
|
1143
1235
|
const p = platform();
|
|
1144
1236
|
const cmd = {
|
|
@@ -1152,10 +1244,15 @@ Please check if a camera is connected.` }],
|
|
|
1152
1244
|
);
|
|
1153
1245
|
server.tool(
|
|
1154
1246
|
"screen_record",
|
|
1155
|
-
|
|
1247
|
+
[
|
|
1248
|
+
"Start or stop screen recording. Captures the full screen as MP4 video.",
|
|
1249
|
+
"",
|
|
1250
|
+
"Use action='start' to begin, action='stop' to end and save. Only one recording can be active at a time.",
|
|
1251
|
+
"Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
|
|
1252
|
+
].join("\n"),
|
|
1156
1253
|
{
|
|
1157
|
-
action: import_zod4.z.enum(["start", "stop"]).describe("start: begin recording, stop: end recording"),
|
|
1158
|
-
output_path: import_zod4.z.string().optional().describe("Output path (used
|
|
1254
|
+
action: import_zod4.z.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
|
|
1255
|
+
output_path: import_zod4.z.string().optional().describe("Output file path (used with 'start'). Default: /tmp/junis_record_<timestamp>.mp4")
|
|
1159
1256
|
},
|
|
1160
1257
|
async ({ action, output_path }) => {
|
|
1161
1258
|
const p = platform();
|
|
@@ -1186,7 +1283,12 @@ Please check if a camera is connected.` }],
|
|
|
1186
1283
|
);
|
|
1187
1284
|
server.tool(
|
|
1188
1285
|
"location_get",
|
|
1189
|
-
|
|
1286
|
+
[
|
|
1287
|
+
"Get the device's current geographic location.",
|
|
1288
|
+
"",
|
|
1289
|
+
"macOS: Uses CoreLocation (GPS-accurate) with IP-based fallback. Other platforms: IP-based geolocation (city-level accuracy only).",
|
|
1290
|
+
"Returns latitude, longitude, and (when available) city and country."
|
|
1291
|
+
].join("\n"),
|
|
1190
1292
|
{},
|
|
1191
1293
|
async () => {
|
|
1192
1294
|
const p = platform();
|
|
@@ -1213,9 +1315,9 @@ Please check if a camera is connected.` }],
|
|
|
1213
1315
|
);
|
|
1214
1316
|
server.tool(
|
|
1215
1317
|
"audio_play",
|
|
1216
|
-
"Play audio file
|
|
1318
|
+
"Play an audio file through the device's speakers. Supports MP3, WAV, AAC, and other common formats. Playback is synchronous \u2014 the tool returns after playback completes. Platform-specific: macOS (afplay), Windows/Linux (ffplay).",
|
|
1217
1319
|
{
|
|
1218
|
-
file_path: import_zod4.z.string().describe("
|
|
1320
|
+
file_path: import_zod4.z.string().describe("Absolute path to the audio file to play")
|
|
1219
1321
|
},
|
|
1220
1322
|
async ({ file_path }) => {
|
|
1221
1323
|
const p = platform();
|
|
@@ -1293,9 +1395,16 @@ var DesktopTools = class {
|
|
|
1293
1395
|
register(server) {
|
|
1294
1396
|
server.tool(
|
|
1295
1397
|
"desktop_see",
|
|
1296
|
-
|
|
1398
|
+
[
|
|
1399
|
+
"Capture the macOS Accessibility Tree snapshot for a running application. Returns structured element list with IDs, roles, labels, and positions.",
|
|
1400
|
+
"",
|
|
1401
|
+
"WORKFLOW: Call desktop_see \u2192 find target element \u2192 use its ID in desktop_click or desktop_type.",
|
|
1402
|
+
"Pass the returned snapshotId to subsequent calls for 240x speed improvement (cached lookup vs. full re-scan).",
|
|
1403
|
+
"",
|
|
1404
|
+
"SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger an automatic safety stop."
|
|
1405
|
+
].join("\n"),
|
|
1297
1406
|
{
|
|
1298
|
-
app: import_zod5.z.string().optional().describe("App name to target (e.g. 'Safari', '
|
|
1407
|
+
app: import_zod5.z.string().optional().describe("App name to target (e.g. 'Safari', 'Notes', 'Google Chrome'). Omit for the frontmost app.")
|
|
1299
1408
|
},
|
|
1300
1409
|
async ({ app }) => {
|
|
1301
1410
|
checkBlacklist(app);
|
|
@@ -1320,12 +1429,19 @@ var DesktopTools = class {
|
|
|
1320
1429
|
);
|
|
1321
1430
|
server.tool(
|
|
1322
1431
|
"desktop_click",
|
|
1323
|
-
|
|
1432
|
+
[
|
|
1433
|
+
"Click a macOS UI element by its accessibility label, ID, or x,y coordinates.",
|
|
1434
|
+
"",
|
|
1435
|
+
"The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from desktop_see, or coordinates as 'x,y' string.",
|
|
1436
|
+
"For faster interaction, pass the snapshotId from a recent desktop_see call.",
|
|
1437
|
+
"",
|
|
1438
|
+
"SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
|
|
1439
|
+
].join("\n"),
|
|
1324
1440
|
{
|
|
1325
|
-
on: import_zod5.z.string().describe("Element label, ID, or 'x,y' coordinates to click"),
|
|
1326
|
-
app: import_zod5.z.string().optional().describe("App name to target"),
|
|
1327
|
-
snapshot: import_zod5.z.string().optional().describe("snapshotId from desktop_see for cached interaction (faster)"),
|
|
1328
|
-
doubleClick: import_zod5.z.boolean().optional().default(false).describe("Double-click")
|
|
1441
|
+
on: import_zod5.z.string().describe("Element label, accessibility ID, or 'x,y' coordinates to click"),
|
|
1442
|
+
app: import_zod5.z.string().optional().describe("App name to target (e.g. 'Safari')"),
|
|
1443
|
+
snapshot: import_zod5.z.string().optional().describe("snapshotId from desktop_see for cached interaction (240x faster)"),
|
|
1444
|
+
doubleClick: import_zod5.z.boolean().optional().default(false).describe("Double-click instead of single click")
|
|
1329
1445
|
},
|
|
1330
1446
|
async ({ on, app, snapshot, doubleClick }) => {
|
|
1331
1447
|
checkBlacklist(app);
|
|
@@ -1341,10 +1457,14 @@ var DesktopTools = class {
|
|
|
1341
1457
|
);
|
|
1342
1458
|
server.tool(
|
|
1343
1459
|
"desktop_type",
|
|
1344
|
-
|
|
1460
|
+
[
|
|
1461
|
+
"Type text into the currently focused UI element on macOS. The text is sent as keyboard input character-by-character.",
|
|
1462
|
+
"",
|
|
1463
|
+
"SAFETY: Terminal, iTerm, and Finder are blocked. Use desktop_see first to verify the correct element is focused."
|
|
1464
|
+
].join("\n"),
|
|
1345
1465
|
{
|
|
1346
|
-
text: import_zod5.z.string().describe("Text to type"),
|
|
1347
|
-
app: import_zod5.z.string().optional().describe("App name to
|
|
1466
|
+
text: import_zod5.z.string().describe("Text to type into the focused element"),
|
|
1467
|
+
app: import_zod5.z.string().optional().describe("App name to focus before typing")
|
|
1348
1468
|
},
|
|
1349
1469
|
async ({ text, app }) => {
|
|
1350
1470
|
checkBlacklist(app);
|
|
@@ -1358,9 +1478,15 @@ var DesktopTools = class {
|
|
|
1358
1478
|
);
|
|
1359
1479
|
server.tool(
|
|
1360
1480
|
"desktop_hotkey",
|
|
1361
|
-
|
|
1481
|
+
[
|
|
1482
|
+
"Press a keyboard shortcut on macOS. Keys are comma-separated.",
|
|
1483
|
+
"",
|
|
1484
|
+
"Common shortcuts: 'cmd,c' (copy), 'cmd,v' (paste), 'cmd,z' (undo), 'cmd,s' (save), 'cmd,w' (close tab), 'cmd,q' (quit), 'cmd,shift,t' (reopen tab), 'cmd,tab' (switch app).",
|
|
1485
|
+
"",
|
|
1486
|
+
"SAFETY: Terminal, iTerm, and Finder are blocked."
|
|
1487
|
+
].join("\n"),
|
|
1362
1488
|
{
|
|
1363
|
-
keys: import_zod5.z.string().describe("Comma-separated key combination (e.g. 'cmd,c', 'cmd,shift,t', 'escape')"),
|
|
1489
|
+
keys: import_zod5.z.string().describe("Comma-separated key combination (e.g. 'cmd,c', 'cmd,shift,t', 'escape', 'cmd,option,i')"),
|
|
1364
1490
|
app: import_zod5.z.string().optional().describe("App name to target")
|
|
1365
1491
|
},
|
|
1366
1492
|
async ({ keys, app }) => {
|
|
@@ -1375,11 +1501,11 @@ var DesktopTools = class {
|
|
|
1375
1501
|
);
|
|
1376
1502
|
server.tool(
|
|
1377
1503
|
"desktop_scroll",
|
|
1378
|
-
"Scroll
|
|
1504
|
+
"Scroll within a macOS application or specific UI element. Use 'ticks' to control scroll distance (default: 3). Can target a specific element by label or ID with the 'on' parameter.",
|
|
1379
1505
|
{
|
|
1380
1506
|
direction: import_zod5.z.enum(["up", "down", "left", "right"]).describe("Scroll direction"),
|
|
1381
|
-
ticks: import_zod5.z.number().optional().default(3).describe("Number of scroll ticks"),
|
|
1382
|
-
on: import_zod5.z.string().optional().describe("Element label or ID to scroll within"),
|
|
1507
|
+
ticks: import_zod5.z.number().optional().default(3).describe("Number of scroll ticks (default: 3). Higher = more scrolling."),
|
|
1508
|
+
on: import_zod5.z.string().optional().describe("Element label or ID to scroll within (from desktop_see). Omit to scroll the active area."),
|
|
1383
1509
|
app: import_zod5.z.string().optional().describe("App name to target")
|
|
1384
1510
|
},
|
|
1385
1511
|
async ({ direction, ticks, on, app }) => {
|
|
@@ -1395,7 +1521,7 @@ var DesktopTools = class {
|
|
|
1395
1521
|
);
|
|
1396
1522
|
server.tool(
|
|
1397
1523
|
"desktop_list_apps",
|
|
1398
|
-
"List all running applications on macOS",
|
|
1524
|
+
"List all currently running applications on macOS. Returns app names that can be used as the 'app' parameter in other desktop tools (desktop_see, desktop_click, desktop_type, etc.).",
|
|
1399
1525
|
{},
|
|
1400
1526
|
async () => {
|
|
1401
1527
|
try {
|
|
@@ -1411,9 +1537,9 @@ var DesktopTools = class {
|
|
|
1411
1537
|
);
|
|
1412
1538
|
server.tool(
|
|
1413
1539
|
"desktop_list_windows",
|
|
1414
|
-
"List all open windows on macOS",
|
|
1540
|
+
"List all open windows on macOS, optionally filtered by app name. If no app is specified, lists windows for the frontmost application. Useful for identifying which windows are available for automation.",
|
|
1415
1541
|
{
|
|
1416
|
-
app: import_zod5.z.string().optional().describe("Filter by app name
|
|
1542
|
+
app: import_zod5.z.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
|
|
1417
1543
|
},
|
|
1418
1544
|
async ({ app }) => {
|
|
1419
1545
|
checkBlacklist(app);
|
|
@@ -1439,10 +1565,15 @@ var DesktopTools = class {
|
|
|
1439
1565
|
);
|
|
1440
1566
|
server.tool(
|
|
1441
1567
|
"desktop_screenshot",
|
|
1442
|
-
|
|
1568
|
+
[
|
|
1569
|
+
"Take a high-quality macOS screenshot using Peekaboo (Retina display support). Returns base64 image data.",
|
|
1570
|
+
"",
|
|
1571
|
+
"MODES: 'screen' captures the full display, 'window' captures a specific app window.",
|
|
1572
|
+
"Prefer desktop_see (Accessibility Tree) for understanding UI structure \u2014 use screenshot only when visual appearance matters (layouts, images, colors)."
|
|
1573
|
+
].join("\n"),
|
|
1443
1574
|
{
|
|
1444
|
-
app: import_zod5.z.string().optional().describe("Capture specific app window"),
|
|
1445
|
-
mode: import_zod5.z.enum(["screen", "window"]).optional().default("screen").describe("
|
|
1575
|
+
app: import_zod5.z.string().optional().describe("Capture a specific app's window (by name)"),
|
|
1576
|
+
mode: import_zod5.z.enum(["screen", "window"]).optional().default("screen").describe("'screen': full display capture, 'window': specific app window only")
|
|
1446
1577
|
},
|
|
1447
1578
|
async ({ app, mode }) => {
|
|
1448
1579
|
checkBlacklist(app);
|
|
@@ -1469,10 +1600,15 @@ var DesktopTools = class {
|
|
|
1469
1600
|
);
|
|
1470
1601
|
server.tool(
|
|
1471
1602
|
"desktop_menu",
|
|
1472
|
-
|
|
1603
|
+
[
|
|
1604
|
+
"Click a menu bar item in a macOS application. Navigate nested menus by adding path segments.",
|
|
1605
|
+
"",
|
|
1606
|
+
"Examples: ['File', 'New Tab'], ['Edit', 'Find', 'Find...'], ['View', 'Enter Full Screen'].",
|
|
1607
|
+
"The target app must be running and accessible."
|
|
1608
|
+
].join("\n"),
|
|
1473
1609
|
{
|
|
1474
|
-
path: import_zod5.z.array(import_zod5.z.string()).describe("Menu path as array (e.g. ['File', '
|
|
1475
|
-
app: import_zod5.z.string().optional().describe("App name to target")
|
|
1610
|
+
path: import_zod5.z.array(import_zod5.z.string()).describe("Menu path as array (e.g. ['File', 'Save'], ['Edit', 'Find', 'Find...'])"),
|
|
1611
|
+
app: import_zod5.z.string().optional().describe("App name to target. Omit for the frontmost app.")
|
|
1476
1612
|
},
|
|
1477
1613
|
async ({ path: path2, app }) => {
|
|
1478
1614
|
checkBlacklist(app);
|
|
@@ -1736,16 +1872,58 @@ async function handleMCPRequest(id, payload) {
|
|
|
1736
1872
|
if (!res.ok) {
|
|
1737
1873
|
throw new Error(`MCP request failed: ${res.status} ${res.statusText}`);
|
|
1738
1874
|
}
|
|
1875
|
+
if (res.status === 202) {
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1879
|
+
if (contentType.includes("application/json")) {
|
|
1880
|
+
return res.json();
|
|
1881
|
+
}
|
|
1739
1882
|
const text = await res.text();
|
|
1740
1883
|
const lines = text.split("\n");
|
|
1884
|
+
let currentEventType = null;
|
|
1885
|
+
const collectedResults = [];
|
|
1886
|
+
let lastError = null;
|
|
1741
1887
|
for (const line of lines) {
|
|
1742
|
-
if (line.startsWith("
|
|
1888
|
+
if (line.startsWith("event: ")) {
|
|
1889
|
+
currentEventType = line.slice(7).trim();
|
|
1890
|
+
} else if (line.startsWith("data: ")) {
|
|
1891
|
+
const rawData = line.slice(6).trim();
|
|
1892
|
+
if (rawData === "") {
|
|
1893
|
+
currentEventType = null;
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1743
1896
|
try {
|
|
1744
|
-
|
|
1897
|
+
const parsed = JSON.parse(rawData);
|
|
1898
|
+
if (currentEventType === "error") {
|
|
1899
|
+
lastError = parsed;
|
|
1900
|
+
} else if (currentEventType === "message" || currentEventType === null) {
|
|
1901
|
+
collectedResults.push(parsed);
|
|
1902
|
+
}
|
|
1745
1903
|
} catch {
|
|
1746
1904
|
}
|
|
1905
|
+
currentEventType = null;
|
|
1906
|
+
} else if (line === "") {
|
|
1907
|
+
currentEventType = null;
|
|
1747
1908
|
}
|
|
1748
1909
|
}
|
|
1910
|
+
if (collectedResults.length === 0 && lastError !== null) {
|
|
1911
|
+
throw new Error(
|
|
1912
|
+
`MCP error event: ${JSON.stringify(lastError)}`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
if (collectedResults.length > 1 && payload !== null && typeof payload === "object" && "id" in payload) {
|
|
1916
|
+
const requestId = payload.id;
|
|
1917
|
+
const matched = collectedResults.find(
|
|
1918
|
+
(r) => r !== null && typeof r === "object" && "id" in r && r.id === requestId
|
|
1919
|
+
);
|
|
1920
|
+
if (matched !== void 0) {
|
|
1921
|
+
return matched;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
if (collectedResults.length > 0) {
|
|
1925
|
+
return collectedResults[collectedResults.length - 1];
|
|
1926
|
+
}
|
|
1749
1927
|
return null;
|
|
1750
1928
|
}
|
|
1751
1929
|
// Annotate the CommonJS export names for ESM import in node:
|