chuskybrowser-mcp 1.18.3 → 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 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 24 tool names, identical JSON-Schema parameters, and the same REST routes. Whether an agent reaches chusky via OpenClaw or MCP, the behavior is identical.
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 24 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`), and 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.
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
- accessKey: (process.env.CHUSKY_ACCESS_KEY || '').trim(),
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
  }
@@ -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
- if (!config.apiKey) {
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.18.3",
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
- const BASE_URL = process.env.CHUSKY_BASE_URL || `http://localhost:${CONFIG.port}`;
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.