@mehmoodqureshi/chrome-mcp 0.6.0 → 0.6.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 +4 -0
- package/dist/src/mcp/server.d.ts +2 -2
- package/dist/src/mcp/server.js +4 -3
- package/dist/src/mcp/tools.d.ts +10 -7
- package/dist/src/mcp/tools.js +57 -62
- package/extension-dist/background.js +7 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# chrome-mcp
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Mehmoodqureshi/chrome-mcp/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@mehmoodqureshi/chrome-mcp)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
Drive a **real Chrome browser** from Claude (or any MCP host). One pluggable
|
|
4
8
|
`Executor` interface, two backends:
|
|
5
9
|
|
package/dist/src/mcp/server.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* NOTHING may be written to stdout except the JSON-RPC stream — all diagnostics
|
|
7
7
|
* go to stderr via `logErr`.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
10
|
/** stderr only — never stdout in stdio mode. */
|
|
11
11
|
export declare function logErr(message: string): void;
|
|
12
12
|
/** Build a fresh `Server` with the full tool surface registered (no transport). */
|
|
13
|
-
export declare function createServer(version?: string):
|
|
13
|
+
export declare function createServer(version?: string): McpServer;
|
|
14
14
|
/** Start over stdio. Idempotent. */
|
|
15
15
|
export declare function startMcpServer(version?: string): Promise<void>;
|
|
16
16
|
/** Stop and release the transport. Idempotent, best-effort. */
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -13,7 +13,7 @@ exports.createServer = createServer;
|
|
|
13
13
|
exports.startMcpServer = startMcpServer;
|
|
14
14
|
exports.stopMcpServer = stopMcpServer;
|
|
15
15
|
exports.isMcpServerRunning = isMcpServerRunning;
|
|
16
|
-
const
|
|
16
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
17
17
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
18
18
|
const tools_1 = require("./tools");
|
|
19
19
|
const SERVER_NAME = 'chrome-mcp';
|
|
@@ -28,9 +28,10 @@ function logErr(message) {
|
|
|
28
28
|
}
|
|
29
29
|
/** Build a fresh `Server` with the full tool surface registered (no transport). */
|
|
30
30
|
function createServer(version = DEFAULT_VERSION) {
|
|
31
|
-
const srv = new
|
|
31
|
+
const srv = new mcp_js_1.McpServer({ name: SERVER_NAME, version }, { capabilities: { tools: {} } });
|
|
32
32
|
(0, tools_1.registerTools)(srv);
|
|
33
|
-
|
|
33
|
+
// `McpServer` wraps the low-level `Server`, which owns the `onerror` hook.
|
|
34
|
+
srv.server.onerror = (err) => {
|
|
34
35
|
logErr(`server error: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
35
36
|
};
|
|
36
37
|
return srv;
|
package/dist/src/mcp/tools.d.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/mcp/tools.ts — the MCP tool surface: the advertised catalog
|
|
3
|
-
* (`TOOL_DEFINITIONS`), the name→handler dispatch
|
|
4
|
-
* never-throw firewall (`dispatchToolCall`), and
|
|
5
|
-
*
|
|
3
|
+
* (`TOOL_DEFINITIONS`, each with a zod `inputSchema`), the name→handler dispatch
|
|
4
|
+
* (`TOOL_HANDLERS`), the never-throw firewall (`dispatchToolCall`), and
|
|
5
|
+
* `registerTools()` which registers every tool on an `McpServer` via
|
|
6
|
+
* `registerTool` — the SDK validates the zod schema before dispatch runs.
|
|
6
7
|
*
|
|
7
8
|
* Each handler: validate args → **policy-gate against the relevant URL** → call
|
|
8
9
|
* the active Executor (or a server-side helper) → serialize via an envelope.
|
|
9
10
|
* Nothing here throws to the transport: `dispatchToolCall` renders any thrown
|
|
10
11
|
* `Error` as an `isError` result.
|
|
11
12
|
*/
|
|
12
|
-
import type {
|
|
13
|
-
import {
|
|
13
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
+
import { z } from 'zod';
|
|
14
16
|
import type { Executor } from '../executor/types';
|
|
15
17
|
import { type Policy } from '../security/policy';
|
|
16
18
|
export interface ToolDefinition {
|
|
17
19
|
name: string;
|
|
18
20
|
description: string;
|
|
19
|
-
|
|
21
|
+
/** zod raw shape passed to `McpServer.registerTool`; the SDK validates it before dispatch. */
|
|
22
|
+
inputSchema: z.ZodRawShape;
|
|
20
23
|
}
|
|
21
24
|
export declare const TOOL_DEFINITIONS: ToolDefinition[];
|
|
22
25
|
interface ToolCtx {
|
|
@@ -30,5 +33,5 @@ export declare function resetRateLimiter(): void;
|
|
|
30
33
|
export declare function dispatchToolCall(name: string, rawArgs: unknown): Promise<CallToolResult>;
|
|
31
34
|
/** Assert the catalog and the dispatch table describe the same tool set. */
|
|
32
35
|
export declare function assertNoDrift(): void;
|
|
33
|
-
export declare function registerTools(server:
|
|
36
|
+
export declare function registerTools(server: McpServer): void;
|
|
34
37
|
export {};
|
package/dist/src/mcp/tools.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* src/mcp/tools.ts — the MCP tool surface: the advertised catalog
|
|
4
|
-
* (`TOOL_DEFINITIONS`), the name→handler dispatch
|
|
5
|
-
* never-throw firewall (`dispatchToolCall`), and
|
|
6
|
-
*
|
|
4
|
+
* (`TOOL_DEFINITIONS`, each with a zod `inputSchema`), the name→handler dispatch
|
|
5
|
+
* (`TOOL_HANDLERS`), the never-throw firewall (`dispatchToolCall`), and
|
|
6
|
+
* `registerTools()` which registers every tool on an `McpServer` via
|
|
7
|
+
* `registerTool` — the SDK validates the zod schema before dispatch runs.
|
|
7
8
|
*
|
|
8
9
|
* Each handler: validate args → **policy-gate against the relevant URL** → call
|
|
9
10
|
* the active Executor (or a server-side helper) → serialize via an envelope.
|
|
@@ -17,7 +18,7 @@ exports.dispatchToolCall = dispatchToolCall;
|
|
|
17
18
|
exports.assertNoDrift = assertNoDrift;
|
|
18
19
|
exports.registerTools = registerTools;
|
|
19
20
|
const node_path_1 = require("node:path");
|
|
20
|
-
const
|
|
21
|
+
const zod_1 = require("zod");
|
|
21
22
|
const types_1 = require("../executor/types");
|
|
22
23
|
const manager_1 = require("../executor/manager");
|
|
23
24
|
const policy_1 = require("../security/policy");
|
|
@@ -27,62 +28,57 @@ const helpers_1 = require("./helpers");
|
|
|
27
28
|
const tasks_1 = require("../bridge/tasks");
|
|
28
29
|
const workspace_1 = require("../bridge/workspace");
|
|
29
30
|
const validators_1 = require("./validators");
|
|
31
|
+
/** Shared selector|ref target — both optional; a handler that needs one calls `requireTarget`. */
|
|
30
32
|
const TARGET_PROPS = {
|
|
31
|
-
selector:
|
|
32
|
-
ref:
|
|
33
|
+
selector: zod_1.z.string().describe('CSS selector (exactly one of selector|ref)').optional(),
|
|
34
|
+
ref: zod_1.z.string().describe('Element ref from a prior read (exactly one of selector|ref)').optional(),
|
|
33
35
|
};
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
properties,
|
|
37
|
-
required,
|
|
38
|
-
additionalProperties: false,
|
|
39
|
-
});
|
|
36
|
+
const tabIdField = zod_1.z.string().describe('Target tab id (defaults to the active tab)').optional();
|
|
37
|
+
const waitUntilField = zod_1.z.enum(['load', 'domcontentloaded', 'networkidle']).describe('When to consider navigation done').optional();
|
|
40
38
|
exports.TOOL_DEFINITIONS = [
|
|
41
|
-
{ name: 'tabs_list', description: 'List open browser tabs.', inputSchema:
|
|
42
|
-
{ name: 'tab_select', description: 'Make a tab active by tabId.', inputSchema:
|
|
43
|
-
{ name: 'tab_new', description: 'Open a NEW tab (optionally at a URL) and focus it. Prefer this over `navigate` when the user says "open"/"go to" a site — `navigate` REPLACES the current tab. Pass active:false to open in the background (used by parallel batches).', inputSchema:
|
|
44
|
-
{ name: 'tab_close', description: 'Close a tab by tabId.', inputSchema:
|
|
45
|
-
{ name: 'navigate', description: 'Navigate a tab to a URL, REPLACING its current page. Acts on the active tab unless tabId is given — to open a site without losing the current page, use `tab_new` instead.', inputSchema:
|
|
46
|
-
{ name: 'back', description: 'Go back in history.', inputSchema:
|
|
47
|
-
{ name: 'forward', description: 'Go forward in history.', inputSchema:
|
|
48
|
-
{ name: 'reload', description: 'Reload the active (or given) tab.', inputSchema:
|
|
49
|
-
{ name: 'click', description: 'Click an element (target by selector or a snapshot ref). trusted=true uses real OS-level input.', inputSchema:
|
|
50
|
-
{ name: 'type', description: 'Type text into an element. trusted=true sends real keystrokes (works on React/Vue controlled inputs).', inputSchema:
|
|
51
|
-
{ name: 'select_option', description: 'Select option(s) of a <select> by value or visible label.', inputSchema:
|
|
52
|
-
{ name: 'press', description: 'Press a key (with optional modifiers).', inputSchema:
|
|
53
|
-
{ name: 'hover', description: 'Hover over an element.', inputSchema:
|
|
54
|
-
{ name: 'scroll', description: 'Scroll the page or to an element.', inputSchema:
|
|
55
|
-
{ name: 'screenshot', description: 'Capture a PNG screenshot (page or element).', inputSchema:
|
|
56
|
-
{ name: 'get_text', description: 'Get visible text of the page or an element.', inputSchema:
|
|
57
|
-
{ name: 'get_html', description: 'Get HTML of the page or an element.', inputSchema:
|
|
58
|
-
{ name: 'snapshot', description: 'Accessibility snapshot: interactive elements with stable refs to target by `ref` (more reliable than guessing CSS selectors).', inputSchema:
|
|
59
|
-
{ name: 'get_cookies', description: "Read cookies visible to the tab's URL (or a given url).", inputSchema:
|
|
60
|
-
{ name: 'storage', description: 'Read/write localStorage (or sessionStorage). op: get|set|remove|clear.', inputSchema:
|
|
61
|
-
{ name: 'eval', description: 'Evaluate JavaScript in the page (disabled in safe-mode).', inputSchema:
|
|
62
|
-
{ name: 'wait_for', description: 'Wait for a selector or text to appear/disappear.', inputSchema:
|
|
63
|
-
{ name: 'extract_links', description: 'Extract anchors from the page or a subtree.', inputSchema:
|
|
64
|
-
{ name: 'read_as_markdown', description: 'Read the page (or subtree) as readable markdown.', inputSchema:
|
|
65
|
-
{ name: 'fill_form', description: 'Fill multiple fields (keyed by selector) and optionally submit.', inputSchema:
|
|
66
|
-
{ name: 'download_file', description: 'Download a file by URL or from a link element.', inputSchema:
|
|
67
|
-
{ name: 'upload_file', description: 'Set local file(s) on a file <input> (target by selector or ref) — uploads without the OS dialog. Requires --enable-uploads. `files` are absolute local paths.', inputSchema:
|
|
68
|
-
{ name: 'chrome_status', description: 'Report backend/session status.', inputSchema:
|
|
69
|
-
{ name: 'profile_use', description: 'Switch the active browser profile (identity). Subsequent downloads, results, screenshots, and the action log are stored under profiles/<name>/. Resets the active task to "default" unless you then call task_new.', inputSchema:
|
|
70
|
-
{ name: 'task_new', description: 'Start a new task (run) under the active profile. Creates profiles/<profile>/tasks/<name>/ with downloads/, results/, screenshots/ and makes it the active task so all captured artifacts land there.', inputSchema:
|
|
71
|
-
{ name: 'tasks_list', description: 'List every task across all profiles under the data dir, with sizes and download counts.', inputSchema:
|
|
72
|
-
{ name: 'task_status', description: 'Report the active profile/task and the folder paths where this run\'s artifacts are stored.', inputSchema:
|
|
39
|
+
{ name: 'tabs_list', description: 'List open browser tabs.', inputSchema: {} },
|
|
40
|
+
{ name: 'tab_select', description: 'Make a tab active by tabId.', inputSchema: { tabId: zod_1.z.string() } },
|
|
41
|
+
{ name: 'tab_new', description: 'Open a NEW tab (optionally at a URL) and focus it. Prefer this over `navigate` when the user says "open"/"go to" a site — `navigate` REPLACES the current tab. Pass active:false to open in the background (used by parallel batches).', inputSchema: { url: zod_1.z.string().optional(), active: zod_1.z.boolean().optional() } },
|
|
42
|
+
{ name: 'tab_close', description: 'Close a tab by tabId.', inputSchema: { tabId: zod_1.z.string() } },
|
|
43
|
+
{ name: 'navigate', description: 'Navigate a tab to a URL, REPLACING its current page. Acts on the active tab unless tabId is given — to open a site without losing the current page, use `tab_new` instead.', inputSchema: { url: zod_1.z.string(), tabId: tabIdField, waitUntil: waitUntilField } },
|
|
44
|
+
{ name: 'back', description: 'Go back in history.', inputSchema: { tabId: tabIdField } },
|
|
45
|
+
{ name: 'forward', description: 'Go forward in history.', inputSchema: { tabId: tabIdField } },
|
|
46
|
+
{ name: 'reload', description: 'Reload the active (or given) tab.', inputSchema: { tabId: tabIdField, waitUntil: waitUntilField } },
|
|
47
|
+
{ name: 'click', description: 'Click an element (target by selector or a snapshot ref). trusted=true uses real OS-level input.', inputSchema: { ...TARGET_PROPS, tabId: tabIdField, button: zod_1.z.enum(['left', 'right', 'middle']).optional(), clickCount: zod_1.z.number().optional(), trusted: zod_1.z.boolean().optional() } },
|
|
48
|
+
{ name: 'type', description: 'Type text into an element. trusted=true sends real keystrokes (works on React/Vue controlled inputs).', inputSchema: { ...TARGET_PROPS, text: zod_1.z.string(), tabId: tabIdField, clear: zod_1.z.boolean().optional(), pressEnter: zod_1.z.boolean().optional(), keyEvents: zod_1.z.boolean().optional(), trusted: zod_1.z.boolean().optional() } },
|
|
49
|
+
{ name: 'select_option', description: 'Select option(s) of a <select> by value or visible label.', inputSchema: { ...TARGET_PROPS, values: zod_1.z.array(zod_1.z.string()), tabId: tabIdField } },
|
|
50
|
+
{ name: 'press', description: 'Press a key (with optional modifiers).', inputSchema: { key: zod_1.z.string(), modifiers: zod_1.z.array(zod_1.z.string()).optional(), tabId: tabIdField } },
|
|
51
|
+
{ name: 'hover', description: 'Hover over an element.', inputSchema: { ...TARGET_PROPS, tabId: tabIdField } },
|
|
52
|
+
{ name: 'scroll', description: 'Scroll the page or to an element.', inputSchema: { ...TARGET_PROPS, x: zod_1.z.number().optional(), y: zod_1.z.number().optional(), deltaX: zod_1.z.number().optional(), deltaY: zod_1.z.number().optional(), tabId: tabIdField } },
|
|
53
|
+
{ name: 'screenshot', description: 'Capture a PNG screenshot (page or element).', inputSchema: { ...TARGET_PROPS, fullPage: zod_1.z.boolean().optional(), tabId: tabIdField } },
|
|
54
|
+
{ name: 'get_text', description: 'Get visible text of the page or an element.', inputSchema: { ...TARGET_PROPS, tabId: tabIdField } },
|
|
55
|
+
{ name: 'get_html', description: 'Get HTML of the page or an element.', inputSchema: { ...TARGET_PROPS, outer: zod_1.z.boolean().optional(), tabId: tabIdField } },
|
|
56
|
+
{ name: 'snapshot', description: 'Accessibility snapshot: interactive elements with stable refs to target by `ref` (more reliable than guessing CSS selectors).', inputSchema: { interactiveOnly: zod_1.z.boolean().optional(), max: zod_1.z.number().optional(), tabId: tabIdField } },
|
|
57
|
+
{ name: 'get_cookies', description: "Read cookies visible to the tab's URL (or a given url).", inputSchema: { url: zod_1.z.string().optional(), tabId: tabIdField } },
|
|
58
|
+
{ name: 'storage', description: 'Read/write localStorage (or sessionStorage). op: get|set|remove|clear.', inputSchema: { op: zod_1.z.enum(['get', 'set', 'remove', 'clear']), key: zod_1.z.string().optional(), value: zod_1.z.string().optional(), session: zod_1.z.boolean().optional(), tabId: tabIdField } },
|
|
59
|
+
{ name: 'eval', description: 'Evaluate JavaScript in the page (disabled in safe-mode).', inputSchema: { expression: zod_1.z.string(), awaitPromise: zod_1.z.boolean().optional(), tabId: tabIdField } },
|
|
60
|
+
{ name: 'wait_for', description: 'Wait for a selector or text to appear/disappear.', inputSchema: { selector: zod_1.z.string().optional(), textContains: zod_1.z.string().optional(), gone: zod_1.z.boolean().optional(), timeoutMs: zod_1.z.number().optional(), tabId: tabIdField } },
|
|
61
|
+
{ name: 'extract_links', description: 'Extract anchors from the page or a subtree.', inputSchema: { selector: zod_1.z.string().optional(), sameOriginOnly: zod_1.z.boolean().optional(), tabId: tabIdField } },
|
|
62
|
+
{ name: 'read_as_markdown', description: 'Read the page (or subtree) as readable markdown.', inputSchema: { selector: zod_1.z.string().optional(), tabId: tabIdField } },
|
|
63
|
+
{ name: 'fill_form', description: 'Fill multiple fields (keyed by selector) and optionally submit.', inputSchema: { fields: zod_1.z.record(zod_1.z.string(), zod_1.z.union([zod_1.z.string(), zod_1.z.boolean()])), submitSelector: zod_1.z.string().optional(), tabId: tabIdField } },
|
|
64
|
+
{ name: 'download_file', description: 'Download a file by URL or from a link element.', inputSchema: { url: zod_1.z.string().optional(), ...TARGET_PROPS, suggestedName: zod_1.z.string().optional(), tabId: tabIdField } },
|
|
65
|
+
{ name: 'upload_file', description: 'Set local file(s) on a file <input> (target by selector or ref) — uploads without the OS dialog. Requires --enable-uploads. `files` are absolute local paths.', inputSchema: { ...TARGET_PROPS, files: zod_1.z.array(zod_1.z.string()), tabId: tabIdField } },
|
|
66
|
+
{ name: 'chrome_status', description: 'Report backend/session status.', inputSchema: {} },
|
|
67
|
+
{ name: 'profile_use', description: 'Switch the active browser profile (identity). Subsequent downloads, results, screenshots, and the action log are stored under profiles/<name>/. Resets the active task to "default" unless you then call task_new.', inputSchema: { name: zod_1.z.string().describe('Profile name (becomes a folder; sanitized to a safe path segment).') } },
|
|
68
|
+
{ name: 'task_new', description: 'Start a new task (run) under the active profile. Creates profiles/<profile>/tasks/<name>/ with downloads/, results/, screenshots/ and makes it the active task so all captured artifacts land there.', inputSchema: { name: zod_1.z.string().describe('Task name (becomes a folder; sanitized to a safe path segment).') } },
|
|
69
|
+
{ name: 'tasks_list', description: 'List every task across all profiles under the data dir, with sizes and download counts.', inputSchema: {} },
|
|
70
|
+
{ name: 'task_status', description: 'Report the active profile/task and the folder paths where this run\'s artifacts are stored.', inputSchema: {} },
|
|
73
71
|
{
|
|
74
72
|
name: 'batch',
|
|
75
73
|
description: 'Run multiple tool calls in one request — parallel (default) or serial. Each op is { tool, args } and goes through the same policy gate, rate limit, and error handling as a direct call. In parallel mode, tab-scoped ops MUST pass an explicit tabId (the active-tab default is unsafe under concurrency). Use to drive several tabs at once (e.g. open tabs, then batch get_text across them). Cannot be nested.',
|
|
76
|
-
inputSchema:
|
|
77
|
-
ops:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
maxConcurrency: { type: 'number', description: 'Parallel mode: max ops in flight at once (default 6).' },
|
|
85
|
-
}, ['ops']),
|
|
74
|
+
inputSchema: {
|
|
75
|
+
ops: zod_1.z
|
|
76
|
+
.array(zod_1.z.object({ tool: zod_1.z.string(), args: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional() }))
|
|
77
|
+
.describe('Operations to run; each is a tool name + its args.'),
|
|
78
|
+
mode: zod_1.z.enum(['parallel', 'serial']).describe('Default "parallel".').optional(),
|
|
79
|
+
stopOnError: zod_1.z.boolean().describe('Serial mode only: stop after the first failing op (the rest are skipped).').optional(),
|
|
80
|
+
maxConcurrency: zod_1.z.number().describe('Parallel mode: max ops in flight at once (default 6).').optional(),
|
|
81
|
+
},
|
|
86
82
|
},
|
|
87
83
|
];
|
|
88
84
|
/** Resolve the URL the policy should be evaluated against (the active tab). */
|
|
@@ -441,13 +437,12 @@ function assertNoDrift() {
|
|
|
441
437
|
// ---------------------------------------------------------------------------
|
|
442
438
|
function registerTools(server) {
|
|
443
439
|
assertNoDrift();
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}))
|
|
450
|
-
}
|
|
451
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => dispatchToolCall(req.params.name, req.params.arguments));
|
|
440
|
+
// Register each tool with its zod `inputSchema`. The SDK advertises it in
|
|
441
|
+
// `tools/list` and validates arguments before invoking the handler, which
|
|
442
|
+
// just routes back through `dispatchToolCall` — our never-throw firewall that
|
|
443
|
+
// applies the rate limit, executor readiness, policy gate, and history log.
|
|
444
|
+
for (const d of exports.TOOL_DEFINITIONS) {
|
|
445
|
+
server.registerTool(d.name, { description: d.description, inputSchema: d.inputSchema }, async (args) => dispatchToolCall(d.name, args));
|
|
446
|
+
}
|
|
452
447
|
}
|
|
453
448
|
//# sourceMappingURL=tools.js.map
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
constructor(deps) {
|
|
40
40
|
this.deps = deps;
|
|
41
41
|
}
|
|
42
|
+
deps;
|
|
42
43
|
ws = null;
|
|
43
44
|
state = "idle";
|
|
44
45
|
isConnected() {
|
|
@@ -424,6 +425,7 @@
|
|
|
424
425
|
super(message);
|
|
425
426
|
this.code = code;
|
|
426
427
|
}
|
|
428
|
+
code;
|
|
427
429
|
};
|
|
428
430
|
var DOWNLOAD_TIMEOUT_MS = 12e4;
|
|
429
431
|
function waitForDownloadComplete(id) {
|
|
@@ -525,7 +527,7 @@
|
|
|
525
527
|
async function waitForSelector(tabId, selector, timeoutMs = 5e3) {
|
|
526
528
|
const found = await execInTab(
|
|
527
529
|
tabId,
|
|
528
|
-
(s, timeout, interval) => new Promise((resolve) => {
|
|
530
|
+
((s, timeout, interval) => new Promise((resolve) => {
|
|
529
531
|
const deadline = Date.now() + timeout;
|
|
530
532
|
const tick = () => {
|
|
531
533
|
if (document.querySelector(s)) return resolve(true);
|
|
@@ -533,7 +535,7 @@
|
|
|
533
535
|
setTimeout(tick, interval);
|
|
534
536
|
};
|
|
535
537
|
tick();
|
|
536
|
-
}),
|
|
538
|
+
})),
|
|
537
539
|
[selector, timeoutMs, 120]
|
|
538
540
|
);
|
|
539
541
|
return found === true;
|
|
@@ -1034,7 +1036,7 @@
|
|
|
1034
1036
|
const start = Date.now();
|
|
1035
1037
|
const matched = await execInTab(
|
|
1036
1038
|
id,
|
|
1037
|
-
(sel, text, gone, timeoutMs, interval) => new Promise((resolve) => {
|
|
1039
|
+
((sel, text, gone, timeoutMs, interval) => new Promise((resolve) => {
|
|
1038
1040
|
const deadline = Date.now() + timeoutMs;
|
|
1039
1041
|
const hit = () => {
|
|
1040
1042
|
let present;
|
|
@@ -1049,7 +1051,7 @@
|
|
|
1049
1051
|
setTimeout(tick, interval);
|
|
1050
1052
|
};
|
|
1051
1053
|
tick();
|
|
1052
|
-
}),
|
|
1054
|
+
})),
|
|
1053
1055
|
[cmd.params.selector ?? null, cmd.params.textContains ?? null, cmd.params.gone === true, timeout, 150]
|
|
1054
1056
|
);
|
|
1055
1057
|
return { matched: matched === true, waitedMs: Date.now() - start };
|
|
@@ -1113,6 +1115,7 @@
|
|
|
1113
1115
|
if (!HANDLED.has(m)) throw new Error(`router drift: no handler for wire method "${m}"`);
|
|
1114
1116
|
}
|
|
1115
1117
|
}
|
|
1118
|
+
deps;
|
|
1116
1119
|
async dispatch(cmd) {
|
|
1117
1120
|
try {
|
|
1118
1121
|
const policy = this.deps.getPolicy();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mehmoodqureshi/chrome-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Drive a real Chrome browser over MCP. A stdio MCP server (CLI) plus an MV3 extension, behind one pluggable Executor (extension via chrome.scripting, or a Playwright CDP fallback).",
|
|
5
5
|
"author": "Mehmood Ur Rehman Qureshi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,15 +45,16 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
46
|
"typecheck:ext": "tsc -p tsconfig.ext.json",
|
|
47
47
|
"clean": "rimraf dist extension-dist",
|
|
48
|
-
"test": "npm run clean && npm run build && npm run build:ext && node --test dist/test",
|
|
48
|
+
"test": "npm run clean && npm run build && npm run build:ext && node --test dist/test/*.test.js",
|
|
49
49
|
"test:hitl": "npm run build && node dist/hitl/index.js",
|
|
50
50
|
"prepack": "npm run clean && npm run build && npm run build:ext",
|
|
51
51
|
"postinstall": "node scripts/postinstall.js"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@modelcontextprotocol/sdk": "^1.0
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
55
|
"playwright": "^1.49.1",
|
|
56
|
-
"ws": "^8.18.0"
|
|
56
|
+
"ws": "^8.18.0",
|
|
57
|
+
"zod": "^4.4.3"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@types/chrome": "^0.0.287",
|