chuskybrowser-mcp 1.18.4 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/lib/config.mjs +4 -1
- package/lib/tool-contracts.mjs +221 -2
- package/package.json +1 -1
- package/server.mjs +7 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A standalone [Model Context Protocol](https://modelcontextprotocol.io) server that exposes chuskybrowser to any MCP-compatible host — Claude Code, Cursor, etc. — without requiring OpenClaw.
|
|
4
4
|
|
|
5
|
-
It mirrors the existing OpenClaw plugin **1:1**: same
|
|
5
|
+
It mirrors the existing OpenClaw plugin **1:1**: same tool names (35 tools), identical JSON-Schema parameters, and the same REST routes. Whether an agent reaches chusky via OpenClaw or MCP, the behavior is identical.
|
|
6
6
|
|
|
7
7
|
The initial MCP server implementation was contributed by [@epicsagas](https://github.com/epicsagas).
|
|
8
8
|
|
|
@@ -170,7 +170,7 @@ claude mcp add chuskybrowser -- node /Users/you/src/chuskybrowser/mcp/server.mjs
|
|
|
170
170
|
| Cursor | Settings → MCP — server shows green |
|
|
171
171
|
| opencode | `opencode mcp list` |
|
|
172
172
|
|
|
173
|
-
You should see
|
|
173
|
+
You should see 35 tools: the 11 browser tools (`chusky_create_tab`, `chusky_snapshot`, `chusky_click`, `chusky_type`, `chusky_navigate`, `chusky_scroll`, `chusky_screenshot`, `chusky_evaluate`, `chusky_list_tabs`, `chusky_close_tab`, `chusky_import_cookies`), two handoff tools (`chusky_handoff_start`, `chusky_handoff_release`), three payment tools (`chusky_pay_arm`, `chusky_pay_status`, `chusky_pay_disarm`), eight AgentCard tools (`agentcard_enrol`, `agentcard_cards`, `agentcard_placeholder`, `agentcard_buy`, `agentcard_merchants`, `agentcard_presets_list`, `agentcard_preset_set`, `agentcard_preset_attach`). The user id comes from `CHUSKY_USER_ID`, never from the model.
|
|
174
174
|
|
|
175
175
|
## Tools
|
|
176
176
|
|
|
@@ -187,6 +187,17 @@ You should see 24 tools: the 11 browser tools (`chusky_create_tab`, `chusky_snap
|
|
|
187
187
|
| `chusky_list_tabs` | List open tabs in this session |
|
|
188
188
|
| `chusky_close_tab` | Close a tab |
|
|
189
189
|
| `chusky_import_cookies` | Import a Netscape cookie file (needs `CHUSKY_API_KEY`) |
|
|
190
|
+
| `chusky_viewer_link` | Live view link for a tab; `control: true` lets the user click and type. Needs `CHUSKY_ACCESS_KEY` on the server |
|
|
191
|
+
| `chusky_extract` | Pull structured JSON from the page using a JSON Schema |
|
|
192
|
+
| `chusky_select` | Choose an option in a native dropdown |
|
|
193
|
+
| `chusky_upload` | Attach a file from the server's uploads folder to a file input |
|
|
194
|
+
| `chusky_wait` | Wait for a selector or a fixed time |
|
|
195
|
+
| `chusky_viewport` | Set the page size in pixels |
|
|
196
|
+
| `chusky_checkpoint` | Check the page for something needing a person; hands the tab over if needed |
|
|
197
|
+
| `chusky_dismiss_overlays` | Close cookie banners and pop-ups |
|
|
198
|
+
| `chusky_page_state` | Current URL, title, loading state and who controls the tab |
|
|
199
|
+
| `chusky_list_downloads` | Files downloaded in a tab |
|
|
200
|
+
| `chusky_list_traces` | Recorded traces for the current user |
|
|
190
201
|
|
|
191
202
|
## Workflow
|
|
192
203
|
|
package/lib/config.mjs
CHANGED
|
@@ -10,7 +10,10 @@ export function loadMcpConfig() {
|
|
|
10
10
|
return {
|
|
11
11
|
port: parseInt(process.env.CHUSKY_PORT || process.env.PORT || '9377', 10),
|
|
12
12
|
apiKey: process.env.CHUSKY_API_KEY || '',
|
|
13
|
-
|
|
13
|
+
// Customers set CHUSKYBROWSER_API_KEY. Self-hosted setups keep CHUSKY_ACCESS_KEY.
|
|
14
|
+
accessKey: (process.env.CHUSKYBROWSER_API_KEY || process.env.CHUSKY_ACCESS_KEY || '').trim(),
|
|
15
|
+
// True when the customer key is set, so requests go through the gateway.
|
|
16
|
+
gateway: Boolean(process.env.CHUSKYBROWSER_API_KEY),
|
|
14
17
|
cookiesDir: process.env.CHUSKY_COOKIES_DIR || join(os.homedir(), '.chusky', 'cookies'),
|
|
15
18
|
};
|
|
16
19
|
}
|
package/lib/tool-contracts.mjs
CHANGED
|
@@ -405,6 +405,138 @@ export const TOOL_DEFS = [
|
|
|
405
405
|
required: ['label'],
|
|
406
406
|
},
|
|
407
407
|
},
|
|
408
|
+
{
|
|
409
|
+
name: 'chusky_viewer_link',
|
|
410
|
+
description:
|
|
411
|
+
'Get a live view link for a tab so the user can watch it. Set control to true to let the user click and type too (the agent is paused on the tab until released). Send the url to the user only, unchanged. Needs CHUSKY_ACCESS_KEY on the server.',
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: 'object',
|
|
414
|
+
properties: {
|
|
415
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
416
|
+
control: { type: 'boolean', description: 'true to let the user control the tab' },
|
|
417
|
+
ttlSeconds: { type: 'number', description: 'How long the link stays valid, in seconds' },
|
|
418
|
+
},
|
|
419
|
+
required: ['tabId'],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: 'chusky_extract',
|
|
424
|
+
description:
|
|
425
|
+
'Pull structured data from the page as JSON. Give a schema describing the fields you want and get the extracted values back. Prefer this to reading the whole snapshot when you only need specific fields.',
|
|
426
|
+
inputSchema: {
|
|
427
|
+
type: 'object',
|
|
428
|
+
properties: {
|
|
429
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
430
|
+
schema: { type: 'object', description: 'JSON Schema of the fields to extract' },
|
|
431
|
+
kinds: { type: 'array', items: { type: 'string' }, description: 'Optional kinds of content to look at, e.g. ["links","tables"]' },
|
|
432
|
+
},
|
|
433
|
+
required: ['tabId'],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'chusky_select',
|
|
438
|
+
description: 'Choose an option in a native dropdown (select element). Give the element by ref from the snapshot, or by CSS selector.',
|
|
439
|
+
inputSchema: {
|
|
440
|
+
type: 'object',
|
|
441
|
+
properties: {
|
|
442
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
443
|
+
ref: { type: 'string', description: 'Element ref from the snapshot, e.g. e4' },
|
|
444
|
+
selector: { type: 'string', description: 'CSS selector, if no ref' },
|
|
445
|
+
option: { type: 'string', description: 'Text or value of the option to choose' },
|
|
446
|
+
},
|
|
447
|
+
required: ['tabId', 'option'],
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'chusky_upload',
|
|
452
|
+
description:
|
|
453
|
+
'Attach a file to a file input on the page. The file must already be in the server uploads folder; give its name or path inside that folder. Target the input by ref from the snapshot, or by CSS selector.',
|
|
454
|
+
inputSchema: {
|
|
455
|
+
type: 'object',
|
|
456
|
+
properties: {
|
|
457
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
458
|
+
path: { type: 'string', description: 'File name or path inside the server uploads folder' },
|
|
459
|
+
ref: { type: 'string', description: 'Element ref of the file input' },
|
|
460
|
+
selector: { type: 'string', description: 'CSS selector of the file input, if no ref' },
|
|
461
|
+
timeout: { type: 'number', description: 'Milliseconds to wait for the input' },
|
|
462
|
+
},
|
|
463
|
+
required: ['tabId', 'path'],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: 'chusky_wait',
|
|
468
|
+
description: 'Wait until an element appears on the page, or for a fixed time. Use after an action that loads new content.',
|
|
469
|
+
inputSchema: {
|
|
470
|
+
type: 'object',
|
|
471
|
+
properties: {
|
|
472
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
473
|
+
selector: { type: 'string', description: 'CSS selector to wait for' },
|
|
474
|
+
timeout: { type: 'number', description: 'Milliseconds to wait before giving up' },
|
|
475
|
+
},
|
|
476
|
+
required: ['tabId'],
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'chusky_viewport',
|
|
481
|
+
description: 'Set the page size in pixels, for example to check a mobile or desktop layout.',
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: 'object',
|
|
484
|
+
properties: {
|
|
485
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
486
|
+
width: { type: 'integer', description: 'Width in pixels' },
|
|
487
|
+
height: { type: 'integer', description: 'Height in pixels' },
|
|
488
|
+
},
|
|
489
|
+
required: ['tabId', 'width', 'height'],
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: 'chusky_checkpoint',
|
|
494
|
+
description:
|
|
495
|
+
'Check the page for something that needs a person (a login, a CAPTCHA, a confirmation). If one is found, the tab is handed to the user and the response includes a controlUrl to send them unchanged.',
|
|
496
|
+
inputSchema: {
|
|
497
|
+
type: 'object',
|
|
498
|
+
properties: {
|
|
499
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
500
|
+
idleMs: { type: 'number', description: 'Milliseconds of inactivity before control returns to the agent' },
|
|
501
|
+
},
|
|
502
|
+
required: ['tabId'],
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: 'chusky_dismiss_overlays',
|
|
507
|
+
description: 'Close cookie banners, pop-ups and overlays that cover the page.',
|
|
508
|
+
inputSchema: {
|
|
509
|
+
type: 'object',
|
|
510
|
+
properties: {
|
|
511
|
+
tabId: { type: 'string', description: 'Tab identifier' },
|
|
512
|
+
maxClicks: { type: 'integer', description: 'Most close or accept clicks to make' },
|
|
513
|
+
},
|
|
514
|
+
required: ['tabId'],
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: 'chusky_page_state',
|
|
519
|
+
description: 'Describe the current page: url, title, whether it is loading, and whether a person has control of the tab.',
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: 'object',
|
|
522
|
+
properties: { tabId: { type: 'string', description: 'Tab identifier' } },
|
|
523
|
+
required: ['tabId'],
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'chusky_list_downloads',
|
|
528
|
+
description: 'List the files downloaded in a tab.',
|
|
529
|
+
inputSchema: {
|
|
530
|
+
type: 'object',
|
|
531
|
+
properties: { tabId: { type: 'string', description: 'Tab identifier' } },
|
|
532
|
+
required: ['tabId'],
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: 'chusky_list_traces',
|
|
537
|
+
description: 'List recorded session traces for the current user, for debugging a run.',
|
|
538
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
539
|
+
},
|
|
408
540
|
];
|
|
409
541
|
|
|
410
542
|
/** Quick name → def lookup. */
|
|
@@ -624,6 +756,91 @@ export function buildRequest(name, args, ctx) {
|
|
|
624
756
|
responseKind: 'json',
|
|
625
757
|
body: { userId, cardId: args.cardId },
|
|
626
758
|
};
|
|
759
|
+
case 'chusky_viewer_link':
|
|
760
|
+
return {
|
|
761
|
+
method: 'POST',
|
|
762
|
+
path: '/live-viewer/links',
|
|
763
|
+
auth: 'accessKey',
|
|
764
|
+
responseKind: 'json',
|
|
765
|
+
body: { userId, tabId: args.tabId, control: args.control, ttlSeconds: args.ttlSeconds },
|
|
766
|
+
};
|
|
767
|
+
case 'chusky_extract':
|
|
768
|
+
return {
|
|
769
|
+
method: 'POST',
|
|
770
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/extract`,
|
|
771
|
+
auth: 'accessKey',
|
|
772
|
+
responseKind: 'json',
|
|
773
|
+
body: { userId, schema: args.schema, kinds: args.kinds },
|
|
774
|
+
};
|
|
775
|
+
case 'chusky_select':
|
|
776
|
+
return {
|
|
777
|
+
method: 'POST',
|
|
778
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/select`,
|
|
779
|
+
auth: 'accessKey',
|
|
780
|
+
responseKind: 'json',
|
|
781
|
+
body: { userId, ref: args.ref, selector: args.selector, option: args.option },
|
|
782
|
+
};
|
|
783
|
+
case 'chusky_upload':
|
|
784
|
+
return {
|
|
785
|
+
method: 'POST',
|
|
786
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/upload`,
|
|
787
|
+
auth: 'accessKey',
|
|
788
|
+
responseKind: 'json',
|
|
789
|
+
body: { userId, path: args.path, ref: args.ref, selector: args.selector, timeout: args.timeout },
|
|
790
|
+
};
|
|
791
|
+
case 'chusky_wait':
|
|
792
|
+
return {
|
|
793
|
+
method: 'POST',
|
|
794
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/wait`,
|
|
795
|
+
auth: 'accessKey',
|
|
796
|
+
responseKind: 'json',
|
|
797
|
+
body: { userId, selector: args.selector, timeout: args.timeout },
|
|
798
|
+
};
|
|
799
|
+
case 'chusky_viewport':
|
|
800
|
+
return {
|
|
801
|
+
method: 'POST',
|
|
802
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/viewport`,
|
|
803
|
+
auth: 'accessKey',
|
|
804
|
+
responseKind: 'json',
|
|
805
|
+
body: { userId, width: args.width, height: args.height },
|
|
806
|
+
};
|
|
807
|
+
case 'chusky_checkpoint':
|
|
808
|
+
return {
|
|
809
|
+
method: 'POST',
|
|
810
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/checkpoint`,
|
|
811
|
+
auth: 'accessKey',
|
|
812
|
+
responseKind: 'json',
|
|
813
|
+
body: { userId, idleMs: args.idleMs },
|
|
814
|
+
};
|
|
815
|
+
case 'chusky_dismiss_overlays':
|
|
816
|
+
return {
|
|
817
|
+
method: 'POST',
|
|
818
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/overlays/dismiss`,
|
|
819
|
+
auth: 'accessKey',
|
|
820
|
+
responseKind: 'json',
|
|
821
|
+
body: { userId, maxClicks: args.maxClicks },
|
|
822
|
+
};
|
|
823
|
+
case 'chusky_page_state':
|
|
824
|
+
return {
|
|
825
|
+
method: 'GET',
|
|
826
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/state?${new URLSearchParams({ userId })}`,
|
|
827
|
+
auth: 'accessKey',
|
|
828
|
+
responseKind: 'json',
|
|
829
|
+
};
|
|
830
|
+
case 'chusky_list_downloads':
|
|
831
|
+
return {
|
|
832
|
+
method: 'GET',
|
|
833
|
+
path: `/tabs/${encodeURIComponent(args.tabId)}/downloads?${new URLSearchParams({ userId })}`,
|
|
834
|
+
auth: 'accessKey',
|
|
835
|
+
responseKind: 'json',
|
|
836
|
+
};
|
|
837
|
+
case 'chusky_list_traces':
|
|
838
|
+
return {
|
|
839
|
+
method: 'GET',
|
|
840
|
+
path: `/sessions/${encodeURIComponent(userId)}/traces`,
|
|
841
|
+
auth: 'accessKey',
|
|
842
|
+
responseKind: 'json',
|
|
843
|
+
};
|
|
627
844
|
default:
|
|
628
845
|
throw new Error(`Unknown tool: ${name}`);
|
|
629
846
|
}
|
|
@@ -642,7 +859,9 @@ export function buildRequest(name, args, ctx) {
|
|
|
642
859
|
* @throws {Error} if CHUSKY_API_KEY is unset.
|
|
643
860
|
*/
|
|
644
861
|
export async function buildCookieRequest(args, ctx, config) {
|
|
645
|
-
|
|
862
|
+
// Customer mode (CHUSKYBROWSER_API_KEY): the gateway holds the server's cookie key,
|
|
863
|
+
// so the host sends the customer key instead.
|
|
864
|
+
if (!config.apiKey && !config.gateway) {
|
|
646
865
|
throw new Error(
|
|
647
866
|
'CHUSKY_API_KEY is not set. Cookie import is disabled unless both the server and the host (MCP/OpenClaw) have CHUSKY_API_KEY.'
|
|
648
867
|
);
|
|
@@ -655,7 +874,7 @@ export async function buildCookieRequest(args, ctx, config) {
|
|
|
655
874
|
return {
|
|
656
875
|
method: 'POST',
|
|
657
876
|
path: `/sessions/${encodeURIComponent(ctx.userId)}/cookies`,
|
|
658
|
-
auth: 'apiKey',
|
|
877
|
+
auth: config.gateway ? 'accessKey' : 'apiKey',
|
|
659
878
|
responseKind: 'json',
|
|
660
879
|
body: { cookies },
|
|
661
880
|
meta: { imported: cookies.length, userId: ctx.userId },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chuskybrowser-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Standalone stdio MCP server exposing the chuskybrowser REST API as MCP tools (Claude Code, Codex, agy, Cursor, opencode). Proxies to an already-running chuskybrowser REST server — does not itself require the browser binary, Playwright, or the core server's dependencies. Initial MCP implementation contributed by @epicsagas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.mjs",
|
package/server.mjs
CHANGED
|
@@ -43,7 +43,13 @@ const VERSION = JSON.parse(
|
|
|
43
43
|
// package reads only its own environment settings; CHUSKY_BASE_URL overrides
|
|
44
44
|
// the derived local URL.
|
|
45
45
|
const CONFIG = loadMcpConfig();
|
|
46
|
-
|
|
46
|
+
// Customers set only CHUSKYBROWSER_API_KEY and reach the built-in gateway address.
|
|
47
|
+
// Self-hosted setups keep CHUSKY_BASE_URL (or fall back to localhost).
|
|
48
|
+
const CUSTOMER_GATEWAY_URL = "https://chuskybrowser-production.up.railway.app/v1";
|
|
49
|
+
const BASE_URL =
|
|
50
|
+
process.env.CHUSKYBROWSER_BASE_URL ||
|
|
51
|
+
(CONFIG.gateway ? CUSTOMER_GATEWAY_URL : process.env.CHUSKY_BASE_URL) ||
|
|
52
|
+
`http://localhost:${CONFIG.port}`;
|
|
47
53
|
|
|
48
54
|
// Per-MCP-server userId so each host session gets an isolated chusky session
|
|
49
55
|
// (cookie/storage partition). Falls back to a random id.
|