@mehmoodqureshi/chrome-mcp 0.1.0 → 0.3.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 +38 -5
- package/dist/shared/protocol.d.ts +1 -1
- package/dist/shared/protocol.js +4 -0
- package/dist/shared/snapshot.d.ts +26 -0
- package/dist/shared/snapshot.js +92 -0
- package/dist/src/bridge/auth.d.ts +23 -1
- package/dist/src/bridge/auth.js +68 -1
- package/dist/src/cli.js +7 -1
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +19 -3
- package/dist/src/executor/cdp-executor.d.ts +24 -1
- package/dist/src/executor/cdp-executor.js +72 -1
- package/dist/src/executor/extension-executor.d.ts +24 -1
- package/dist/src/executor/extension-executor.js +14 -2
- package/dist/src/executor/stub-executor.d.ts +10 -1
- package/dist/src/executor/stub-executor.js +19 -0
- package/dist/src/executor/types.d.ts +60 -0
- package/dist/src/mcp/tools.js +43 -2
- package/extension-dist/background.js +307 -25
- package/extension-dist/manifest.json +3 -3
- package/extension-dist/options.js +6 -3
- package/package.json +2 -2
|
@@ -81,10 +81,13 @@ class ExtensionExecutor {
|
|
|
81
81
|
}
|
|
82
82
|
// -- interaction --------------------------------------------------------
|
|
83
83
|
async click(t, opts) {
|
|
84
|
-
return (await this.send('click', { ...targetParams(t), button: opts?.button, clickCount: opts?.clickCount }, { tabId: opts?.tabId }));
|
|
84
|
+
return (await this.send('click', { ...targetParams(t), button: opts?.button, clickCount: opts?.clickCount, trusted: opts?.trusted }, { tabId: opts?.tabId }));
|
|
85
85
|
}
|
|
86
86
|
async type(t, text, opts) {
|
|
87
|
-
return (await this.send('type', { ...targetParams(t), text, clear: opts?.clear, pressEnter: opts?.pressEnter, keyEvents: opts?.keyEvents }, { tabId: opts?.tabId }));
|
|
87
|
+
return (await this.send('type', { ...targetParams(t), text, clear: opts?.clear, pressEnter: opts?.pressEnter, keyEvents: opts?.keyEvents, trusted: opts?.trusted }, { tabId: opts?.tabId }));
|
|
88
|
+
}
|
|
89
|
+
async selectOption(t, values, opts) {
|
|
90
|
+
return (await this.send('select_option', { ...targetParams(t), values }, { tabId: opts?.tabId }));
|
|
88
91
|
}
|
|
89
92
|
async fill(t, value, opts) {
|
|
90
93
|
// No dedicated wire method: a cleared insertText is the fill primitive.
|
|
@@ -106,6 +109,15 @@ class ExtensionExecutor {
|
|
|
106
109
|
async getHtml(t, opts) {
|
|
107
110
|
return (await this.send('get_html', { ...targetParams(t), outer: opts?.outer }, { tabId: opts?.tabId }));
|
|
108
111
|
}
|
|
112
|
+
async snapshot(opts) {
|
|
113
|
+
return (await this.send('snapshot', { interactiveOnly: opts?.interactiveOnly, max: opts?.max }, { tabId: opts?.tabId }));
|
|
114
|
+
}
|
|
115
|
+
async getCookies(opts) {
|
|
116
|
+
return (await this.send('get_cookies', { url: opts?.url }, { tabId: opts?.tabId }));
|
|
117
|
+
}
|
|
118
|
+
async storage(args) {
|
|
119
|
+
return (await this.send('storage', { op: args.op, key: args.key, value: args.value, session: args.session }, { tabId: args.tabId }));
|
|
120
|
+
}
|
|
109
121
|
async screenshot(opts) {
|
|
110
122
|
return (await this.send('screenshot', { fullPage: opts?.fullPage, ...targetParams(opts?.target) }, { tabId: opts?.tabId }));
|
|
111
123
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. Drives the dispatch/policy/envelope tests with deterministic, canned
|
|
8
8
|
* values (and a couple of forced-failure switches).
|
|
9
9
|
*/
|
|
10
|
-
import { type ActionOk, type BackendKind, type DownloadResult, type EvalResult, type Executor, type ExecutorStatus, type NavResult, type ScreenshotResult, type TabId, type TabInfo, type Target, type WaitResult } from './types';
|
|
10
|
+
import { type ActionOk, type BackendKind, type CookieItem, type DownloadResult, type EvalResult, type Executor, type ExecutorStatus, type NavResult, type ScreenshotResult, type SnapshotResult, type StorageOp, type StorageResult, type TabId, type TabInfo, type Target, type WaitResult } from './types';
|
|
11
11
|
export interface StubOptions {
|
|
12
12
|
/** URL of the (single) active tab — used to exercise the domain policy gate. */
|
|
13
13
|
activeUrl?: string;
|
|
@@ -43,6 +43,7 @@ export declare class StubExecutor implements Executor {
|
|
|
43
43
|
fill(): Promise<ActionOk>;
|
|
44
44
|
press(): Promise<ActionOk>;
|
|
45
45
|
hover(): Promise<ActionOk>;
|
|
46
|
+
selectOption(): Promise<ActionOk>;
|
|
46
47
|
scroll(): Promise<ActionOk>;
|
|
47
48
|
getText(_t?: Target): Promise<{
|
|
48
49
|
text: string;
|
|
@@ -51,6 +52,14 @@ export declare class StubExecutor implements Executor {
|
|
|
51
52
|
getHtml(): Promise<{
|
|
52
53
|
html: string;
|
|
53
54
|
}>;
|
|
55
|
+
snapshot(): Promise<SnapshotResult>;
|
|
56
|
+
getCookies(): Promise<{
|
|
57
|
+
cookies: CookieItem[];
|
|
58
|
+
}>;
|
|
59
|
+
storage(args: {
|
|
60
|
+
op: StorageOp;
|
|
61
|
+
key?: string;
|
|
62
|
+
}): Promise<StorageResult>;
|
|
54
63
|
screenshot(): Promise<ScreenshotResult>;
|
|
55
64
|
eval(expression: string): Promise<EvalResult>;
|
|
56
65
|
waitFor(): Promise<WaitResult>;
|
|
@@ -85,6 +85,9 @@ class StubExecutor {
|
|
|
85
85
|
async hover() {
|
|
86
86
|
return ok;
|
|
87
87
|
}
|
|
88
|
+
async selectOption() {
|
|
89
|
+
return ok;
|
|
90
|
+
}
|
|
88
91
|
async scroll() {
|
|
89
92
|
return ok;
|
|
90
93
|
}
|
|
@@ -94,6 +97,22 @@ class StubExecutor {
|
|
|
94
97
|
async getHtml() {
|
|
95
98
|
return { html: '<html><body><a href="https://example.com">Example</a></body></html>' };
|
|
96
99
|
}
|
|
100
|
+
async snapshot() {
|
|
101
|
+
return {
|
|
102
|
+
url: this.url,
|
|
103
|
+
title: 'Stub Page',
|
|
104
|
+
nodes: [{ ref: 'e1', role: 'link', name: 'Example', tag: 'a' }],
|
|
105
|
+
truncated: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async getCookies() {
|
|
109
|
+
return { cookies: [{ name: 'stub', value: '1', domain: 'example.com', path: '/', secure: true, httpOnly: false }] };
|
|
110
|
+
}
|
|
111
|
+
async storage(args) {
|
|
112
|
+
if (args.op === 'get')
|
|
113
|
+
return { ok: true, value: args.key ? 'stub-value' : null, entries: args.key ? undefined : { k: 'stub-value' } };
|
|
114
|
+
return { ok: true };
|
|
115
|
+
}
|
|
97
116
|
async screenshot() {
|
|
98
117
|
return { dataBase64: TINY_PNG, mimeType: 'image/png', width: 1, height: 1, truncated: false };
|
|
99
118
|
}
|
|
@@ -75,6 +75,39 @@ export interface DownloadResult {
|
|
|
75
75
|
mimeType?: string;
|
|
76
76
|
suggestedName?: string;
|
|
77
77
|
}
|
|
78
|
+
/** One interactive/landmark element in an accessibility snapshot. `ref` is stable until the tab navigates. */
|
|
79
|
+
export interface SnapshotNode {
|
|
80
|
+
ref: string;
|
|
81
|
+
role: string;
|
|
82
|
+
name: string;
|
|
83
|
+
tag: string;
|
|
84
|
+
value?: string;
|
|
85
|
+
disabled?: boolean;
|
|
86
|
+
checked?: boolean;
|
|
87
|
+
}
|
|
88
|
+
export interface SnapshotResult {
|
|
89
|
+
url: string;
|
|
90
|
+
title: string;
|
|
91
|
+
nodes: SnapshotNode[];
|
|
92
|
+
truncated: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface CookieItem {
|
|
95
|
+
name: string;
|
|
96
|
+
value: string;
|
|
97
|
+
domain: string;
|
|
98
|
+
path: string;
|
|
99
|
+
secure: boolean;
|
|
100
|
+
httpOnly: boolean;
|
|
101
|
+
expires?: number;
|
|
102
|
+
}
|
|
103
|
+
export type StorageOp = 'get' | 'set' | 'remove' | 'clear';
|
|
104
|
+
export interface StorageResult {
|
|
105
|
+
ok: boolean;
|
|
106
|
+
/** For `get`: the value (or null if absent). For others: omitted. */
|
|
107
|
+
value?: string | null;
|
|
108
|
+
/** For a keyless `get`: the whole store as a flat object. */
|
|
109
|
+
entries?: Record<string, string>;
|
|
110
|
+
}
|
|
78
111
|
export interface ExecutorStatus {
|
|
79
112
|
ready: boolean;
|
|
80
113
|
backend: BackendKind | null;
|
|
@@ -119,12 +152,18 @@ export interface Executor {
|
|
|
119
152
|
tabId?: TabId;
|
|
120
153
|
button?: MouseButton;
|
|
121
154
|
clickCount?: number;
|
|
155
|
+
trusted?: boolean;
|
|
122
156
|
}): Promise<ActionOk>;
|
|
123
157
|
type(t: Target, text: string, opts?: {
|
|
124
158
|
tabId?: TabId;
|
|
125
159
|
clear?: boolean;
|
|
126
160
|
pressEnter?: boolean;
|
|
127
161
|
keyEvents?: boolean;
|
|
162
|
+
trusted?: boolean;
|
|
163
|
+
}): Promise<ActionOk>;
|
|
164
|
+
/** Choose option(s) of a <select> by value or visible label. */
|
|
165
|
+
selectOption(t: Target, values: string[], opts?: {
|
|
166
|
+
tabId?: TabId;
|
|
128
167
|
}): Promise<ActionOk>;
|
|
129
168
|
/** Value-set + input/change events (used by fill_form). */
|
|
130
169
|
fill(t: Target, value: string, opts?: {
|
|
@@ -157,6 +196,27 @@ export interface Executor {
|
|
|
157
196
|
}): Promise<{
|
|
158
197
|
html: string;
|
|
159
198
|
}>;
|
|
199
|
+
/** Accessibility snapshot: interactive/landmark elements with stable refs the model can target. */
|
|
200
|
+
snapshot(opts?: {
|
|
201
|
+
tabId?: TabId;
|
|
202
|
+
interactiveOnly?: boolean;
|
|
203
|
+
max?: number;
|
|
204
|
+
}): Promise<SnapshotResult>;
|
|
205
|
+
/** Read cookies visible to the active tab's URL (or a given url). */
|
|
206
|
+
getCookies(opts?: {
|
|
207
|
+
tabId?: TabId;
|
|
208
|
+
url?: string;
|
|
209
|
+
}): Promise<{
|
|
210
|
+
cookies: CookieItem[];
|
|
211
|
+
}>;
|
|
212
|
+
/** localStorage/sessionStorage get/set/remove/clear for the active tab. */
|
|
213
|
+
storage(args: {
|
|
214
|
+
op: StorageOp;
|
|
215
|
+
key?: string;
|
|
216
|
+
value?: string;
|
|
217
|
+
session?: boolean;
|
|
218
|
+
tabId?: TabId;
|
|
219
|
+
}): Promise<StorageResult>;
|
|
160
220
|
screenshot(opts?: {
|
|
161
221
|
tabId?: TabId;
|
|
162
222
|
fullPage?: boolean;
|
package/dist/src/mcp/tools.js
CHANGED
|
@@ -41,14 +41,18 @@ exports.TOOL_DEFINITIONS = [
|
|
|
41
41
|
{ name: 'back', description: 'Go back in history.', inputSchema: obj({ tabId: { type: 'string' } }) },
|
|
42
42
|
{ name: 'forward', description: 'Go forward in history.', inputSchema: obj({ tabId: { type: 'string' } }) },
|
|
43
43
|
{ name: 'reload', description: 'Reload the active (or given) tab.', inputSchema: obj({ tabId: { type: 'string' }, waitUntil: { type: 'string', enum: ['load', 'domcontentloaded', 'networkidle'] } }) },
|
|
44
|
-
{ name: 'click', description: 'Click an element.', inputSchema: obj({ ...TARGET_PROPS, tabId: { type: 'string' }, button: { type: 'string', enum: ['left', 'right', 'middle'] }, clickCount: { type: 'number' } }) },
|
|
45
|
-
{ name: 'type', description: 'Type text into an element.', inputSchema: obj({ ...TARGET_PROPS, text: { type: 'string' }, tabId: { type: 'string' }, clear: { type: 'boolean' }, pressEnter: { type: 'boolean' }, keyEvents: { type: 'boolean' } }, ['text']) },
|
|
44
|
+
{ name: 'click', description: 'Click an element (target by selector or a snapshot ref). trusted=true uses real OS-level input.', inputSchema: obj({ ...TARGET_PROPS, tabId: { type: 'string' }, button: { type: 'string', enum: ['left', 'right', 'middle'] }, clickCount: { type: 'number' }, trusted: { type: 'boolean' } }) },
|
|
45
|
+
{ name: 'type', description: 'Type text into an element. trusted=true sends real keystrokes (works on React/Vue controlled inputs).', inputSchema: obj({ ...TARGET_PROPS, text: { type: 'string' }, tabId: { type: 'string' }, clear: { type: 'boolean' }, pressEnter: { type: 'boolean' }, keyEvents: { type: 'boolean' }, trusted: { type: 'boolean' } }, ['text']) },
|
|
46
|
+
{ name: 'select_option', description: 'Select option(s) of a <select> by value or visible label.', inputSchema: obj({ ...TARGET_PROPS, values: { type: 'array', items: { type: 'string' } }, tabId: { type: 'string' } }, ['values']) },
|
|
46
47
|
{ name: 'press', description: 'Press a key (with optional modifiers).', inputSchema: obj({ key: { type: 'string' }, modifiers: { type: 'array', items: { type: 'string' } }, tabId: { type: 'string' } }, ['key']) },
|
|
47
48
|
{ name: 'hover', description: 'Hover over an element.', inputSchema: obj({ ...TARGET_PROPS, tabId: { type: 'string' } }) },
|
|
48
49
|
{ name: 'scroll', description: 'Scroll the page or to an element.', inputSchema: obj({ ...TARGET_PROPS, x: { type: 'number' }, y: { type: 'number' }, deltaX: { type: 'number' }, deltaY: { type: 'number' }, tabId: { type: 'string' } }) },
|
|
49
50
|
{ name: 'screenshot', description: 'Capture a PNG screenshot (page or element).', inputSchema: obj({ ...TARGET_PROPS, fullPage: { type: 'boolean' }, tabId: { type: 'string' } }) },
|
|
50
51
|
{ name: 'get_text', description: 'Get visible text of the page or an element.', inputSchema: obj({ ...TARGET_PROPS, tabId: { type: 'string' } }) },
|
|
51
52
|
{ name: 'get_html', description: 'Get HTML of the page or an element.', inputSchema: obj({ ...TARGET_PROPS, outer: { type: 'boolean' }, tabId: { type: 'string' } }) },
|
|
53
|
+
{ name: 'snapshot', description: 'Accessibility snapshot: interactive elements with stable refs to target by `ref` (more reliable than guessing CSS selectors).', inputSchema: obj({ interactiveOnly: { type: 'boolean' }, max: { type: 'number' }, tabId: { type: 'string' } }) },
|
|
54
|
+
{ name: 'get_cookies', description: "Read cookies visible to the tab's URL (or a given url).", inputSchema: obj({ url: { type: 'string' }, tabId: { type: 'string' } }) },
|
|
55
|
+
{ name: 'storage', description: 'Read/write localStorage (or sessionStorage). op: get|set|remove|clear.', inputSchema: obj({ op: { type: 'string', enum: ['get', 'set', 'remove', 'clear'] }, key: { type: 'string' }, value: { type: 'string' }, session: { type: 'boolean' }, tabId: { type: 'string' } }, ['op']) },
|
|
52
56
|
{ name: 'eval', description: 'Evaluate JavaScript in the page (disabled in safe-mode).', inputSchema: obj({ expression: { type: 'string' }, awaitPromise: { type: 'boolean' }, tabId: { type: 'string' } }, ['expression']) },
|
|
53
57
|
{ name: 'wait_for', description: 'Wait for a selector or text to appear/disappear.', inputSchema: obj({ selector: { type: 'string' }, textContains: { type: 'string' }, gone: { type: 'boolean' }, timeoutMs: { type: 'number' }, tabId: { type: 'string' } }) },
|
|
54
58
|
{ name: 'extract_links', description: 'Extract anchors from the page or a subtree.', inputSchema: obj({ selector: { type: 'string' }, sameOriginOnly: { type: 'boolean' }, tabId: { type: 'string' } }) },
|
|
@@ -112,6 +116,7 @@ exports.TOOL_HANDLERS = {
|
|
|
112
116
|
tabId: tabId(a),
|
|
113
117
|
button: (0, validators_1.optionalString)(a, 'button'),
|
|
114
118
|
clickCount: (0, validators_1.optionalNumber)(a, 'clickCount', { min: 1, max: 3 }),
|
|
119
|
+
trusted: (0, validators_1.optionalBoolean)(a, 'trusted'),
|
|
115
120
|
}));
|
|
116
121
|
},
|
|
117
122
|
type: async (a, ctx) => {
|
|
@@ -122,8 +127,17 @@ exports.TOOL_HANDLERS = {
|
|
|
122
127
|
clear: (0, validators_1.optionalBoolean)(a, 'clear'),
|
|
123
128
|
pressEnter: (0, validators_1.optionalBoolean)(a, 'pressEnter'),
|
|
124
129
|
keyEvents: (0, validators_1.optionalBoolean)(a, 'keyEvents'),
|
|
130
|
+
trusted: (0, validators_1.optionalBoolean)(a, 'trusted'),
|
|
125
131
|
}));
|
|
126
132
|
},
|
|
133
|
+
select_option: async (a, ctx) => {
|
|
134
|
+
const t = (0, validators_1.requireTarget)(a);
|
|
135
|
+
await gate(ctx, 'type'); // mutating
|
|
136
|
+
const values = (0, validators_1.optionalStringArray)(a, 'values');
|
|
137
|
+
if (!values || values.length === 0)
|
|
138
|
+
throw new validators_1.McpToolError('"values" must be a non-empty array of strings');
|
|
139
|
+
return (0, envelopes_1.jsonResult)(await ctx.ex.selectOption(t, values, { tabId: tabId(a) }));
|
|
140
|
+
},
|
|
127
141
|
press: async (a, ctx) => {
|
|
128
142
|
await gate(ctx, 'press');
|
|
129
143
|
return (0, envelopes_1.jsonResult)(await ctx.ex.press((0, validators_1.requireString)(a, 'key'), {
|
|
@@ -165,6 +179,33 @@ exports.TOOL_HANDLERS = {
|
|
|
165
179
|
await gate(ctx, 'get_html');
|
|
166
180
|
return (0, envelopes_1.jsonResult)(await ctx.ex.getHtml((0, validators_1.optionalTarget)(a), { tabId: tabId(a), outer: (0, validators_1.optionalBoolean)(a, 'outer') }));
|
|
167
181
|
},
|
|
182
|
+
snapshot: async (a, ctx) => {
|
|
183
|
+
await gate(ctx, 'get_text'); // read of page structure
|
|
184
|
+
return (0, envelopes_1.jsonResult)(await ctx.ex.snapshot({
|
|
185
|
+
tabId: tabId(a),
|
|
186
|
+
interactiveOnly: (0, validators_1.optionalBoolean)(a, 'interactiveOnly'),
|
|
187
|
+
max: (0, validators_1.optionalNumber)(a, 'max', { min: 1, max: 1000 }),
|
|
188
|
+
}));
|
|
189
|
+
},
|
|
190
|
+
get_cookies: async (a, ctx) => {
|
|
191
|
+
await gate(ctx, 'get_text'); // reads tab-scoped secrets; same domain gate as content reads
|
|
192
|
+
return (0, envelopes_1.jsonResult)(await ctx.ex.getCookies({ tabId: tabId(a), url: (0, validators_1.optionalString)(a, 'url') }));
|
|
193
|
+
},
|
|
194
|
+
storage: async (a, ctx) => {
|
|
195
|
+
const op = (0, validators_1.requireString)(a, 'op');
|
|
196
|
+
// get is a read; set/remove/clear mutate.
|
|
197
|
+
await gate(ctx, op === 'get' ? 'get_text' : 'type');
|
|
198
|
+
if ((op === 'set' || op === 'remove') && !(0, validators_1.optionalString)(a, 'key')) {
|
|
199
|
+
throw new validators_1.McpToolError(`storage "${op}" requires a "key"`);
|
|
200
|
+
}
|
|
201
|
+
return (0, envelopes_1.jsonResult)(await ctx.ex.storage({
|
|
202
|
+
op,
|
|
203
|
+
key: (0, validators_1.optionalString)(a, 'key'),
|
|
204
|
+
value: (0, validators_1.optionalString)(a, 'value'),
|
|
205
|
+
session: (0, validators_1.optionalBoolean)(a, 'session'),
|
|
206
|
+
tabId: tabId(a),
|
|
207
|
+
}));
|
|
208
|
+
},
|
|
168
209
|
eval: async (a, ctx) => {
|
|
169
210
|
await gate(ctx, 'eval');
|
|
170
211
|
return (0, envelopes_1.jsonResult)(await ctx.ex.eval((0, validators_1.requireString)(a, 'expression'), {
|