chuskybrowser-mcp 1.18.4 → 1.19.1

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 38 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`), and three login vault tools (`chusky_vault_list_logins`, `chusky_vault_fill_login`, `chusky_vault_delete_login`). 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
  }
@@ -193,6 +193,37 @@ export const TOOL_DEFS = [
193
193
  required: ['tabId'],
194
194
  },
195
195
  },
196
+ {
197
+ name: 'chusky_vault_list_logins',
198
+ description:
199
+ "List the saved site logins for this user: site, label and id. Never returns usernames or passwords. Use the id with chusky_vault_fill_login.",
200
+ inputSchema: { type: 'object', properties: {}, required: [] },
201
+ },
202
+ {
203
+ name: 'chusky_vault_fill_login',
204
+ description:
205
+ "Fill a saved login into the sign-in form on the current tab. The password goes straight into the page and is never returned to you. Works on the page's own sign-in form only; refuses any page outside the login's allowed sites. If the site asks for a verification code, returns status 'needs_code' and the person must enter it in the live view before you continue.",
206
+ inputSchema: {
207
+ type: 'object',
208
+ properties: {
209
+ tabId: { type: 'string', description: 'Tab identifier' },
210
+ loginId: { type: 'string', description: 'Login id from chusky_vault_list_logins' },
211
+ submit: { type: 'boolean', description: 'Press Enter after the password is filled' },
212
+ },
213
+ required: ['tabId', 'loginId'],
214
+ },
215
+ },
216
+ {
217
+ name: 'chusky_vault_delete_login',
218
+ description: 'Delete a saved site login for this user.',
219
+ inputSchema: {
220
+ type: 'object',
221
+ properties: {
222
+ loginId: { type: 'string', description: 'Login id from chusky_vault_list_logins' },
223
+ },
224
+ required: ['loginId'],
225
+ },
226
+ },
196
227
  {
197
228
  name: 'chusky_evaluate',
198
229
  description:
@@ -405,6 +436,138 @@ export const TOOL_DEFS = [
405
436
  required: ['label'],
406
437
  },
407
438
  },
439
+ {
440
+ name: 'chusky_viewer_link',
441
+ description:
442
+ '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.',
443
+ inputSchema: {
444
+ type: 'object',
445
+ properties: {
446
+ tabId: { type: 'string', description: 'Tab identifier' },
447
+ control: { type: 'boolean', description: 'true to let the user control the tab' },
448
+ ttlSeconds: { type: 'number', description: 'How long the link stays valid, in seconds' },
449
+ },
450
+ required: ['tabId'],
451
+ },
452
+ },
453
+ {
454
+ name: 'chusky_extract',
455
+ description:
456
+ '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.',
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {
460
+ tabId: { type: 'string', description: 'Tab identifier' },
461
+ schema: { type: 'object', description: 'JSON Schema of the fields to extract' },
462
+ kinds: { type: 'array', items: { type: 'string' }, description: 'Optional kinds of content to look at, e.g. ["links","tables"]' },
463
+ },
464
+ required: ['tabId'],
465
+ },
466
+ },
467
+ {
468
+ name: 'chusky_select',
469
+ description: 'Choose an option in a native dropdown (select element). Give the element by ref from the snapshot, or by CSS selector.',
470
+ inputSchema: {
471
+ type: 'object',
472
+ properties: {
473
+ tabId: { type: 'string', description: 'Tab identifier' },
474
+ ref: { type: 'string', description: 'Element ref from the snapshot, e.g. e4' },
475
+ selector: { type: 'string', description: 'CSS selector, if no ref' },
476
+ option: { type: 'string', description: 'Text or value of the option to choose' },
477
+ },
478
+ required: ['tabId', 'option'],
479
+ },
480
+ },
481
+ {
482
+ name: 'chusky_upload',
483
+ description:
484
+ '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.',
485
+ inputSchema: {
486
+ type: 'object',
487
+ properties: {
488
+ tabId: { type: 'string', description: 'Tab identifier' },
489
+ path: { type: 'string', description: 'File name or path inside the server uploads folder' },
490
+ ref: { type: 'string', description: 'Element ref of the file input' },
491
+ selector: { type: 'string', description: 'CSS selector of the file input, if no ref' },
492
+ timeout: { type: 'number', description: 'Milliseconds to wait for the input' },
493
+ },
494
+ required: ['tabId', 'path'],
495
+ },
496
+ },
497
+ {
498
+ name: 'chusky_wait',
499
+ description: 'Wait until an element appears on the page, or for a fixed time. Use after an action that loads new content.',
500
+ inputSchema: {
501
+ type: 'object',
502
+ properties: {
503
+ tabId: { type: 'string', description: 'Tab identifier' },
504
+ selector: { type: 'string', description: 'CSS selector to wait for' },
505
+ timeout: { type: 'number', description: 'Milliseconds to wait before giving up' },
506
+ },
507
+ required: ['tabId'],
508
+ },
509
+ },
510
+ {
511
+ name: 'chusky_viewport',
512
+ description: 'Set the page size in pixels, for example to check a mobile or desktop layout.',
513
+ inputSchema: {
514
+ type: 'object',
515
+ properties: {
516
+ tabId: { type: 'string', description: 'Tab identifier' },
517
+ width: { type: 'integer', description: 'Width in pixels' },
518
+ height: { type: 'integer', description: 'Height in pixels' },
519
+ },
520
+ required: ['tabId', 'width', 'height'],
521
+ },
522
+ },
523
+ {
524
+ name: 'chusky_checkpoint',
525
+ description:
526
+ '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.',
527
+ inputSchema: {
528
+ type: 'object',
529
+ properties: {
530
+ tabId: { type: 'string', description: 'Tab identifier' },
531
+ idleMs: { type: 'number', description: 'Milliseconds of inactivity before control returns to the agent' },
532
+ },
533
+ required: ['tabId'],
534
+ },
535
+ },
536
+ {
537
+ name: 'chusky_dismiss_overlays',
538
+ description: 'Close cookie banners, pop-ups and overlays that cover the page.',
539
+ inputSchema: {
540
+ type: 'object',
541
+ properties: {
542
+ tabId: { type: 'string', description: 'Tab identifier' },
543
+ maxClicks: { type: 'integer', description: 'Most close or accept clicks to make' },
544
+ },
545
+ required: ['tabId'],
546
+ },
547
+ },
548
+ {
549
+ name: 'chusky_page_state',
550
+ description: 'Describe the current page: url, title, whether it is loading, and whether a person has control of the tab.',
551
+ inputSchema: {
552
+ type: 'object',
553
+ properties: { tabId: { type: 'string', description: 'Tab identifier' } },
554
+ required: ['tabId'],
555
+ },
556
+ },
557
+ {
558
+ name: 'chusky_list_downloads',
559
+ description: 'List the files downloaded in a tab.',
560
+ inputSchema: {
561
+ type: 'object',
562
+ properties: { tabId: { type: 'string', description: 'Tab identifier' } },
563
+ required: ['tabId'],
564
+ },
565
+ },
566
+ {
567
+ name: 'chusky_list_traces',
568
+ description: 'List recorded session traces for the current user, for debugging a run.',
569
+ inputSchema: { type: 'object', properties: {}, required: [] },
570
+ },
408
571
  ];
409
572
 
410
573
  /** Quick name → def lookup. */
@@ -502,6 +665,28 @@ export function buildRequest(name, args, ctx) {
502
665
  auth: 'accessKey',
503
666
  responseKind: 'json',
504
667
  };
668
+ case 'chusky_vault_list_logins':
669
+ return {
670
+ method: 'GET',
671
+ path: `/vault/logins?${new URLSearchParams({ userId })}`,
672
+ auth: 'accessKey',
673
+ responseKind: 'json',
674
+ };
675
+ case 'chusky_vault_fill_login':
676
+ return {
677
+ method: 'POST',
678
+ path: `/tabs/${args.tabId}/vault/fill`,
679
+ auth: 'accessKey',
680
+ responseKind: 'json',
681
+ body: { userId, loginId: args.loginId, submit: args.submit === true },
682
+ };
683
+ case 'chusky_vault_delete_login':
684
+ return {
685
+ method: 'DELETE',
686
+ path: `/vault/logins/${args.loginId}?${new URLSearchParams({ userId })}`,
687
+ auth: 'accessKey',
688
+ responseKind: 'json',
689
+ };
505
690
  case 'chusky_evaluate':
506
691
  return {
507
692
  method: 'POST',
@@ -624,6 +809,91 @@ export function buildRequest(name, args, ctx) {
624
809
  responseKind: 'json',
625
810
  body: { userId, cardId: args.cardId },
626
811
  };
812
+ case 'chusky_viewer_link':
813
+ return {
814
+ method: 'POST',
815
+ path: '/live-viewer/links',
816
+ auth: 'accessKey',
817
+ responseKind: 'json',
818
+ body: { userId, tabId: args.tabId, control: args.control, ttlSeconds: args.ttlSeconds },
819
+ };
820
+ case 'chusky_extract':
821
+ return {
822
+ method: 'POST',
823
+ path: `/tabs/${encodeURIComponent(args.tabId)}/extract`,
824
+ auth: 'accessKey',
825
+ responseKind: 'json',
826
+ body: { userId, schema: args.schema, kinds: args.kinds },
827
+ };
828
+ case 'chusky_select':
829
+ return {
830
+ method: 'POST',
831
+ path: `/tabs/${encodeURIComponent(args.tabId)}/select`,
832
+ auth: 'accessKey',
833
+ responseKind: 'json',
834
+ body: { userId, ref: args.ref, selector: args.selector, option: args.option },
835
+ };
836
+ case 'chusky_upload':
837
+ return {
838
+ method: 'POST',
839
+ path: `/tabs/${encodeURIComponent(args.tabId)}/upload`,
840
+ auth: 'accessKey',
841
+ responseKind: 'json',
842
+ body: { userId, path: args.path, ref: args.ref, selector: args.selector, timeout: args.timeout },
843
+ };
844
+ case 'chusky_wait':
845
+ return {
846
+ method: 'POST',
847
+ path: `/tabs/${encodeURIComponent(args.tabId)}/wait`,
848
+ auth: 'accessKey',
849
+ responseKind: 'json',
850
+ body: { userId, selector: args.selector, timeout: args.timeout },
851
+ };
852
+ case 'chusky_viewport':
853
+ return {
854
+ method: 'POST',
855
+ path: `/tabs/${encodeURIComponent(args.tabId)}/viewport`,
856
+ auth: 'accessKey',
857
+ responseKind: 'json',
858
+ body: { userId, width: args.width, height: args.height },
859
+ };
860
+ case 'chusky_checkpoint':
861
+ return {
862
+ method: 'POST',
863
+ path: `/tabs/${encodeURIComponent(args.tabId)}/checkpoint`,
864
+ auth: 'accessKey',
865
+ responseKind: 'json',
866
+ body: { userId, idleMs: args.idleMs },
867
+ };
868
+ case 'chusky_dismiss_overlays':
869
+ return {
870
+ method: 'POST',
871
+ path: `/tabs/${encodeURIComponent(args.tabId)}/overlays/dismiss`,
872
+ auth: 'accessKey',
873
+ responseKind: 'json',
874
+ body: { userId, maxClicks: args.maxClicks },
875
+ };
876
+ case 'chusky_page_state':
877
+ return {
878
+ method: 'GET',
879
+ path: `/tabs/${encodeURIComponent(args.tabId)}/state?${new URLSearchParams({ userId })}`,
880
+ auth: 'accessKey',
881
+ responseKind: 'json',
882
+ };
883
+ case 'chusky_list_downloads':
884
+ return {
885
+ method: 'GET',
886
+ path: `/tabs/${encodeURIComponent(args.tabId)}/downloads?${new URLSearchParams({ userId })}`,
887
+ auth: 'accessKey',
888
+ responseKind: 'json',
889
+ };
890
+ case 'chusky_list_traces':
891
+ return {
892
+ method: 'GET',
893
+ path: `/sessions/${encodeURIComponent(userId)}/traces`,
894
+ auth: 'accessKey',
895
+ responseKind: 'json',
896
+ };
627
897
  default:
628
898
  throw new Error(`Unknown tool: ${name}`);
629
899
  }
@@ -642,7 +912,9 @@ export function buildRequest(name, args, ctx) {
642
912
  * @throws {Error} if CHUSKY_API_KEY is unset.
643
913
  */
644
914
  export async function buildCookieRequest(args, ctx, config) {
645
- if (!config.apiKey) {
915
+ // Customer mode (CHUSKYBROWSER_API_KEY): the gateway holds the server's cookie key,
916
+ // so the host sends the customer key instead.
917
+ if (!config.apiKey && !config.gateway) {
646
918
  throw new Error(
647
919
  'CHUSKY_API_KEY is not set. Cookie import is disabled unless both the server and the host (MCP/OpenClaw) have CHUSKY_API_KEY.'
648
920
  );
@@ -655,7 +927,7 @@ export async function buildCookieRequest(args, ctx, config) {
655
927
  return {
656
928
  method: 'POST',
657
929
  path: `/sessions/${encodeURIComponent(ctx.userId)}/cookies`,
658
- auth: 'apiKey',
930
+ auth: config.gateway ? 'accessKey' : 'apiKey',
659
931
  responseKind: 'json',
660
932
  body: { cookies },
661
933
  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.4",
3
+ "version": "1.19.1",
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.