@mehmoodqureshi/chrome-mcp 0.1.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/shared/download.d.ts +15 -0
- package/dist/shared/download.js +0 -0
- package/dist/shared/protocol.d.ts +114 -0
- package/dist/shared/protocol.js +55 -0
- package/dist/src/bridge/auth.d.ts +32 -0
- package/dist/src/bridge/auth.js +76 -0
- package/dist/src/bridge/connection.d.ts +48 -0
- package/dist/src/bridge/connection.js +192 -0
- package/dist/src/bridge/datadir.d.ts +8 -0
- package/dist/src/bridge/datadir.js +22 -0
- package/dist/src/bridge/server.d.ts +58 -0
- package/dist/src/bridge/server.js +178 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.js +93 -0
- package/dist/src/config.d.ts +42 -0
- package/dist/src/config.js +188 -0
- package/dist/src/executor/cdp-executor.d.ts +131 -0
- package/dist/src/executor/cdp-executor.js +422 -0
- package/dist/src/executor/extension-executor.d.ts +102 -0
- package/dist/src/executor/extension-executor.js +124 -0
- package/dist/src/executor/manager.d.ts +43 -0
- package/dist/src/executor/manager.js +94 -0
- package/dist/src/executor/select.d.ts +23 -0
- package/dist/src/executor/select.js +53 -0
- package/dist/src/executor/stub-executor.d.ts +60 -0
- package/dist/src/executor/stub-executor.js +118 -0
- package/dist/src/executor/types.d.ts +192 -0
- package/dist/src/executor/types.js +24 -0
- package/dist/src/mcp/envelopes.d.ts +13 -0
- package/dist/src/mcp/envelopes.js +30 -0
- package/dist/src/mcp/helpers.d.ts +37 -0
- package/dist/src/mcp/helpers.js +71 -0
- package/dist/src/mcp/markdown-extract.d.ts +9 -0
- package/dist/src/mcp/markdown-extract.js +61 -0
- package/dist/src/mcp/server.d.ts +18 -0
- package/dist/src/mcp/server.js +82 -0
- package/dist/src/mcp/tools.d.ts +32 -0
- package/dist/src/mcp/tools.js +267 -0
- package/dist/src/mcp/validators.d.ts +32 -0
- package/dist/src/mcp/validators.js +104 -0
- package/dist/src/security/policy.d.ts +48 -0
- package/dist/src/security/policy.js +155 -0
- package/docs/BLUEPRINT.md +596 -0
- package/extension-dist/background.js +567 -0
- package/extension-dist/manifest.json +12 -0
- package/extension-dist/options.html +32 -0
- package/extension-dist/options.js +37 -0
- package/package.json +69 -0
- package/scripts/postinstall.js +50 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/executor/manager.ts — owns the process-global active Executor and the
|
|
4
|
+
* `withReadyExecutor()` accessor every tool routes through (the `withReadyDriver`
|
|
5
|
+
* analog from the LinkedIn repo).
|
|
6
|
+
*
|
|
7
|
+
* Phase 1 holds a single Executor produced by an injected factory (the Stub).
|
|
8
|
+
* Later phases replace the factory with real selection logic (extension-if-
|
|
9
|
+
* responsive else CDP), but the surface the tools see — `ensureReady()` +
|
|
10
|
+
* `policy` — does not change.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ExecutorManager = void 0;
|
|
14
|
+
exports.configureManager = configureManager;
|
|
15
|
+
exports.getManager = getManager;
|
|
16
|
+
exports.resetManagerForTesting = resetManagerForTesting;
|
|
17
|
+
exports.withReadyExecutor = withReadyExecutor;
|
|
18
|
+
const types_1 = require("./types");
|
|
19
|
+
class ExecutorManager {
|
|
20
|
+
opts;
|
|
21
|
+
current = null;
|
|
22
|
+
readying = null;
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.opts = opts;
|
|
25
|
+
}
|
|
26
|
+
get policy() {
|
|
27
|
+
return this.opts.policy;
|
|
28
|
+
}
|
|
29
|
+
/** Inject a ready-made executor (tests, or a later phase's selector). */
|
|
30
|
+
setExecutor(ex) {
|
|
31
|
+
this.current = ex;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Return a ready Executor, constructing it lazily and single-flight-guarding
|
|
35
|
+
* concurrent callers. Throws `NO_BACKEND` when nothing is configured.
|
|
36
|
+
*/
|
|
37
|
+
async ensureReady() {
|
|
38
|
+
if (this.readying)
|
|
39
|
+
return this.readying;
|
|
40
|
+
this.readying = this.doEnsure();
|
|
41
|
+
try {
|
|
42
|
+
return await this.readying;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.readying = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async doEnsure() {
|
|
49
|
+
// Selection strategy (Phase 3+): re-pick the backend each call.
|
|
50
|
+
if (this.opts.select) {
|
|
51
|
+
const chosen = await this.opts.select();
|
|
52
|
+
this.current = chosen;
|
|
53
|
+
await chosen.ensureReady();
|
|
54
|
+
return chosen;
|
|
55
|
+
}
|
|
56
|
+
// Single-backend path (Phase 1 stub).
|
|
57
|
+
if (!this.current) {
|
|
58
|
+
if (!this.opts.makeExecutor) {
|
|
59
|
+
throw new types_1.ExecutorError('NO_BACKEND', 'No Chrome available: pair the extension or enable a backend.');
|
|
60
|
+
}
|
|
61
|
+
this.current = this.opts.makeExecutor();
|
|
62
|
+
}
|
|
63
|
+
await this.current.ensureReady();
|
|
64
|
+
return this.current;
|
|
65
|
+
}
|
|
66
|
+
async dispose() {
|
|
67
|
+
const ex = this.current;
|
|
68
|
+
this.current = null;
|
|
69
|
+
await ex?.dispose();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.ExecutorManager = ExecutorManager;
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Process-global singleton
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
let singleton = null;
|
|
77
|
+
/** Install (or replace) the global manager. Returns it. */
|
|
78
|
+
function configureManager(opts) {
|
|
79
|
+
singleton = new ExecutorManager(opts);
|
|
80
|
+
return singleton;
|
|
81
|
+
}
|
|
82
|
+
function getManager() {
|
|
83
|
+
if (!singleton)
|
|
84
|
+
throw new Error('ExecutorManager has not been configured');
|
|
85
|
+
return singleton;
|
|
86
|
+
}
|
|
87
|
+
/** Tear down the singleton (tests). */
|
|
88
|
+
function resetManagerForTesting() {
|
|
89
|
+
singleton = null;
|
|
90
|
+
}
|
|
91
|
+
async function withReadyExecutor() {
|
|
92
|
+
return getManager().ensureReady();
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/executor/select.ts — backend selection.
|
|
3
|
+
*
|
|
4
|
+
* Re-evaluated on every `ensureReady()`: prefer a *responsive* extension (an
|
|
5
|
+
* 800 ms ping probe, so a dead-but-not-yet-reconnected MV3 worker falls through
|
|
6
|
+
* instead of eating a 30 s command timeout), otherwise the CDP fallback. The
|
|
7
|
+
* extension and CDP executors are cached across calls; only the choice is live.
|
|
8
|
+
*/
|
|
9
|
+
import { type Executor } from './types';
|
|
10
|
+
import { type CdpOptions } from './cdp-executor';
|
|
11
|
+
import type { BridgeServer } from '../bridge/server';
|
|
12
|
+
import type { BackendPreference } from '../config';
|
|
13
|
+
export interface SelectorDeps {
|
|
14
|
+
bridge: BridgeServer;
|
|
15
|
+
cdpFallback: boolean;
|
|
16
|
+
prefer: BackendPreference;
|
|
17
|
+
cdp: CdpOptions;
|
|
18
|
+
pingDeadlineMs?: number;
|
|
19
|
+
/** Test seams — default to the real executors. */
|
|
20
|
+
makeExtension?: (bridge: BridgeServer) => Executor;
|
|
21
|
+
makeCdp?: (opts: CdpOptions) => Executor;
|
|
22
|
+
}
|
|
23
|
+
export declare function createSelector(deps: SelectorDeps): () => Promise<Executor>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/executor/select.ts — backend selection.
|
|
4
|
+
*
|
|
5
|
+
* Re-evaluated on every `ensureReady()`: prefer a *responsive* extension (an
|
|
6
|
+
* 800 ms ping probe, so a dead-but-not-yet-reconnected MV3 worker falls through
|
|
7
|
+
* instead of eating a 30 s command timeout), otherwise the CDP fallback. The
|
|
8
|
+
* extension and CDP executors are cached across calls; only the choice is live.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.createSelector = createSelector;
|
|
12
|
+
const types_1 = require("./types");
|
|
13
|
+
const extension_executor_1 = require("./extension-executor");
|
|
14
|
+
const cdp_executor_1 = require("./cdp-executor");
|
|
15
|
+
function createSelector(deps) {
|
|
16
|
+
const makeExt = deps.makeExtension ?? ((b) => new extension_executor_1.ExtensionExecutor(b));
|
|
17
|
+
const makeCdp = deps.makeCdp ?? ((o) => new cdp_executor_1.CdpExecutor(o));
|
|
18
|
+
const pingMs = deps.pingDeadlineMs ?? 800;
|
|
19
|
+
const ext = makeExt(deps.bridge);
|
|
20
|
+
let cdp = null;
|
|
21
|
+
const cdpAllowed = () => deps.cdpFallback || deps.prefer === 'cdp' || !!deps.cdp.cdpEndpoint;
|
|
22
|
+
const getCdp = () => {
|
|
23
|
+
if (!cdpAllowed())
|
|
24
|
+
return null;
|
|
25
|
+
cdp ??= makeCdp(deps.cdp);
|
|
26
|
+
return cdp;
|
|
27
|
+
};
|
|
28
|
+
const tryExt = async () => {
|
|
29
|
+
if (!deps.bridge.hasActiveExtension())
|
|
30
|
+
return null;
|
|
31
|
+
return (await ext.ping(pingMs)) ? ext : null;
|
|
32
|
+
};
|
|
33
|
+
return async () => {
|
|
34
|
+
if (deps.prefer === 'cdp') {
|
|
35
|
+
const c = getCdp();
|
|
36
|
+
if (c)
|
|
37
|
+
return c;
|
|
38
|
+
const e = await tryExt();
|
|
39
|
+
if (e)
|
|
40
|
+
return e;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const e = await tryExt();
|
|
44
|
+
if (e)
|
|
45
|
+
return e;
|
|
46
|
+
const c = getCdp();
|
|
47
|
+
if (c)
|
|
48
|
+
return c;
|
|
49
|
+
}
|
|
50
|
+
throw new types_1.ExecutorError('NO_BACKEND', 'No Chrome available: pair the extension, attach a --cdp-endpoint, or enable the CDP fallback.');
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/executor/stub-executor.ts — an in-memory Executor with no browser.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs:
|
|
5
|
+
* 1. Lets Phase 1 ship a fully working MCP server you can point Claude at with
|
|
6
|
+
* zero Chrome involved (the CLI uses it until the real backends land).
|
|
7
|
+
* 2. Drives the dispatch/policy/envelope tests with deterministic, canned
|
|
8
|
+
* values (and a couple of forced-failure switches).
|
|
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';
|
|
11
|
+
export interface StubOptions {
|
|
12
|
+
/** URL of the (single) active tab — used to exercise the domain policy gate. */
|
|
13
|
+
activeUrl?: string;
|
|
14
|
+
/** When true, `eval` resolves `{ok:false}` to mimic a page-side throw. */
|
|
15
|
+
evalThrows?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class StubExecutor implements Executor {
|
|
18
|
+
readonly backend: BackendKind;
|
|
19
|
+
private url;
|
|
20
|
+
private readonly evalThrows;
|
|
21
|
+
private ready;
|
|
22
|
+
constructor(opts?: StubOptions);
|
|
23
|
+
private tab;
|
|
24
|
+
status(): ExecutorStatus;
|
|
25
|
+
ensureReady(): Promise<void>;
|
|
26
|
+
ping(): Promise<boolean>;
|
|
27
|
+
dispose(): Promise<void>;
|
|
28
|
+
tabsList(): Promise<TabInfo[]>;
|
|
29
|
+
tabSelect(tabId: TabId): Promise<TabInfo>;
|
|
30
|
+
tabNew(url?: string): Promise<TabInfo>;
|
|
31
|
+
tabClose(tabId: TabId): Promise<{
|
|
32
|
+
closed: true;
|
|
33
|
+
tabId: TabId;
|
|
34
|
+
}>;
|
|
35
|
+
navigate(args: {
|
|
36
|
+
url: string;
|
|
37
|
+
}): Promise<NavResult>;
|
|
38
|
+
back(): Promise<NavResult>;
|
|
39
|
+
forward(): Promise<NavResult>;
|
|
40
|
+
reload(): Promise<NavResult>;
|
|
41
|
+
click(): Promise<ActionOk>;
|
|
42
|
+
type(): Promise<ActionOk>;
|
|
43
|
+
fill(): Promise<ActionOk>;
|
|
44
|
+
press(): Promise<ActionOk>;
|
|
45
|
+
hover(): Promise<ActionOk>;
|
|
46
|
+
scroll(): Promise<ActionOk>;
|
|
47
|
+
getText(_t?: Target): Promise<{
|
|
48
|
+
text: string;
|
|
49
|
+
ref?: string;
|
|
50
|
+
}>;
|
|
51
|
+
getHtml(): Promise<{
|
|
52
|
+
html: string;
|
|
53
|
+
}>;
|
|
54
|
+
screenshot(): Promise<ScreenshotResult>;
|
|
55
|
+
eval(expression: string): Promise<EvalResult>;
|
|
56
|
+
waitFor(): Promise<WaitResult>;
|
|
57
|
+
download(args: {
|
|
58
|
+
suggestedName?: string;
|
|
59
|
+
}): Promise<DownloadResult>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/executor/stub-executor.ts — an in-memory Executor with no browser.
|
|
4
|
+
*
|
|
5
|
+
* Two jobs:
|
|
6
|
+
* 1. Lets Phase 1 ship a fully working MCP server you can point Claude at with
|
|
7
|
+
* zero Chrome involved (the CLI uses it until the real backends land).
|
|
8
|
+
* 2. Drives the dispatch/policy/envelope tests with deterministic, canned
|
|
9
|
+
* values (and a couple of forced-failure switches).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.StubExecutor = void 0;
|
|
13
|
+
/** 1×1 transparent PNG, base64 — a valid image block for screenshot tests. */
|
|
14
|
+
const TINY_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
|
15
|
+
const ok = { ok: true };
|
|
16
|
+
class StubExecutor {
|
|
17
|
+
backend = 'extension';
|
|
18
|
+
url;
|
|
19
|
+
evalThrows;
|
|
20
|
+
ready = false;
|
|
21
|
+
constructor(opts = {}) {
|
|
22
|
+
this.url = opts.activeUrl ?? 'about:blank';
|
|
23
|
+
this.evalThrows = opts.evalThrows ?? false;
|
|
24
|
+
}
|
|
25
|
+
tab() {
|
|
26
|
+
return { tabId: 'extension:stub:1', url: this.url, title: 'Stub Page', active: true, index: 0 };
|
|
27
|
+
}
|
|
28
|
+
status() {
|
|
29
|
+
return {
|
|
30
|
+
ready: this.ready,
|
|
31
|
+
backend: this.backend,
|
|
32
|
+
activeTabId: this.tab().tabId,
|
|
33
|
+
extensionConnected: true,
|
|
34
|
+
cdpAttached: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async ensureReady() {
|
|
38
|
+
this.ready = true;
|
|
39
|
+
}
|
|
40
|
+
async ping() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
async dispose() {
|
|
44
|
+
this.ready = false;
|
|
45
|
+
}
|
|
46
|
+
async tabsList() {
|
|
47
|
+
return [this.tab()];
|
|
48
|
+
}
|
|
49
|
+
async tabSelect(tabId) {
|
|
50
|
+
return { ...this.tab(), tabId };
|
|
51
|
+
}
|
|
52
|
+
async tabNew(url) {
|
|
53
|
+
if (url)
|
|
54
|
+
this.url = url;
|
|
55
|
+
return this.tab();
|
|
56
|
+
}
|
|
57
|
+
async tabClose(tabId) {
|
|
58
|
+
return { closed: true, tabId };
|
|
59
|
+
}
|
|
60
|
+
async navigate(args) {
|
|
61
|
+
this.url = args.url;
|
|
62
|
+
return { url: args.url, title: 'Stub Page', httpStatus: 200 };
|
|
63
|
+
}
|
|
64
|
+
async back() {
|
|
65
|
+
return { url: this.url, title: 'Stub Page' };
|
|
66
|
+
}
|
|
67
|
+
async forward() {
|
|
68
|
+
return { url: this.url, title: 'Stub Page' };
|
|
69
|
+
}
|
|
70
|
+
async reload() {
|
|
71
|
+
return { url: this.url, title: 'Stub Page' };
|
|
72
|
+
}
|
|
73
|
+
async click() {
|
|
74
|
+
return ok;
|
|
75
|
+
}
|
|
76
|
+
async type() {
|
|
77
|
+
return ok;
|
|
78
|
+
}
|
|
79
|
+
async fill() {
|
|
80
|
+
return ok;
|
|
81
|
+
}
|
|
82
|
+
async press() {
|
|
83
|
+
return ok;
|
|
84
|
+
}
|
|
85
|
+
async hover() {
|
|
86
|
+
return ok;
|
|
87
|
+
}
|
|
88
|
+
async scroll() {
|
|
89
|
+
return ok;
|
|
90
|
+
}
|
|
91
|
+
async getText(_t) {
|
|
92
|
+
return { text: 'stub text', ref: 'el_stub_1' };
|
|
93
|
+
}
|
|
94
|
+
async getHtml() {
|
|
95
|
+
return { html: '<html><body><a href="https://example.com">Example</a></body></html>' };
|
|
96
|
+
}
|
|
97
|
+
async screenshot() {
|
|
98
|
+
return { dataBase64: TINY_PNG, mimeType: 'image/png', width: 1, height: 1, truncated: false };
|
|
99
|
+
}
|
|
100
|
+
async eval(expression) {
|
|
101
|
+
if (this.evalThrows || /throw/.test(expression)) {
|
|
102
|
+
return { ok: false, error: 'Error: stub page threw' };
|
|
103
|
+
}
|
|
104
|
+
return { ok: true, value: 'stub-value', type: 'string' };
|
|
105
|
+
}
|
|
106
|
+
async waitFor() {
|
|
107
|
+
return { matched: true, waitedMs: 0 };
|
|
108
|
+
}
|
|
109
|
+
async download(args) {
|
|
110
|
+
return {
|
|
111
|
+
path: `/stub/downloads/${args.suggestedName ?? 'file.download'}`,
|
|
112
|
+
backend: this.backend,
|
|
113
|
+
bytes: 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.StubExecutor = StubExecutor;
|
|
118
|
+
//# sourceMappingURL=stub-executor.js.map
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/executor/types.ts — the single backend-agnostic Executor contract.
|
|
3
|
+
*
|
|
4
|
+
* Plain JSON-serializable in/out: no Playwright `Page`, no CDP session, no DOM
|
|
5
|
+
* handle ever crosses this boundary. Both `ExtensionExecutor` (over the WS
|
|
6
|
+
* bridge) and `CdpExecutor` (Playwright) implement this identical interface, so
|
|
7
|
+
* the tool layer never knows or cares which backend is live.
|
|
8
|
+
*
|
|
9
|
+
* Helpers (extract_links / read_as_markdown / fill_form) are composed in
|
|
10
|
+
* `mcp/helpers.ts` from these primitives; only `download` is privileged.
|
|
11
|
+
*/
|
|
12
|
+
export type BackendKind = 'extension' | 'cdp';
|
|
13
|
+
export type WaitUntil = 'load' | 'domcontentloaded' | 'networkidle';
|
|
14
|
+
export type KeyModifier = 'Alt' | 'Control' | 'Meta' | 'Shift';
|
|
15
|
+
export type MouseButton = 'left' | 'right' | 'middle';
|
|
16
|
+
/**
|
|
17
|
+
* A target element: selector XOR ref. `requireTarget()` enforces exactly-one-of.
|
|
18
|
+
* Refs are minted by getText/extractLinks/waitFor/eval results and are
|
|
19
|
+
* PAGE-scoped — invalidated when that tab navigates. Format:
|
|
20
|
+
* `el_<tabShort>_<backendNodeId>`.
|
|
21
|
+
*/
|
|
22
|
+
export type Target = {
|
|
23
|
+
selector: string;
|
|
24
|
+
ref?: never;
|
|
25
|
+
} | {
|
|
26
|
+
ref: string;
|
|
27
|
+
selector?: never;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Tab handle. ALWAYS prefixed `<backend>:<sessionId>:<rawId>` so a handle minted
|
|
31
|
+
* by one backend/session can never mis-route after a fallback switch or a
|
|
32
|
+
* reconnect (a mismatch becomes a clean `STALE_TAB`, not a wrong-tab action).
|
|
33
|
+
*/
|
|
34
|
+
export type TabId = string;
|
|
35
|
+
export interface TabInfo {
|
|
36
|
+
tabId: TabId;
|
|
37
|
+
url: string;
|
|
38
|
+
title: string;
|
|
39
|
+
active: boolean;
|
|
40
|
+
index: number;
|
|
41
|
+
}
|
|
42
|
+
export interface NavResult {
|
|
43
|
+
url: string;
|
|
44
|
+
title: string;
|
|
45
|
+
httpStatus?: number;
|
|
46
|
+
}
|
|
47
|
+
/** `value` is truncated when serialized > 256KB. */
|
|
48
|
+
export interface EvalResult {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
value?: unknown;
|
|
51
|
+
type?: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface WaitResult {
|
|
55
|
+
matched: boolean;
|
|
56
|
+
ref?: string;
|
|
57
|
+
waitedMs: number;
|
|
58
|
+
}
|
|
59
|
+
export interface ActionOk {
|
|
60
|
+
ok: true;
|
|
61
|
+
}
|
|
62
|
+
export interface ScreenshotResult {
|
|
63
|
+
dataBase64: string;
|
|
64
|
+
mimeType: 'image/png';
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
/** fullPage capture exceeded the height cap; `fullHeight` reports the real size. */
|
|
68
|
+
truncated: boolean;
|
|
69
|
+
fullHeight?: number;
|
|
70
|
+
}
|
|
71
|
+
export interface DownloadResult {
|
|
72
|
+
path: string;
|
|
73
|
+
backend: BackendKind;
|
|
74
|
+
bytes: number;
|
|
75
|
+
mimeType?: string;
|
|
76
|
+
suggestedName?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface ExecutorStatus {
|
|
79
|
+
ready: boolean;
|
|
80
|
+
backend: BackendKind | null;
|
|
81
|
+
activeTabId: TabId | null;
|
|
82
|
+
/** WHY unavailable (port in use, no extension, policy denied, etc.). */
|
|
83
|
+
detail?: string;
|
|
84
|
+
extensionConnected: boolean;
|
|
85
|
+
cdpAttached: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface Executor {
|
|
88
|
+
readonly backend: BackendKind;
|
|
89
|
+
status(): ExecutorStatus;
|
|
90
|
+
/** Idempotent lazy connect/attach + self-heal; single-flight guarded. */
|
|
91
|
+
ensureReady(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Lightweight responsiveness probe (short deadline). Used by
|
|
94
|
+
* `withReadyExecutor` to detect a dead-but-not-yet-reconnected MV3 worker and
|
|
95
|
+
* fall through to CDP instead of eating a full command timeout.
|
|
96
|
+
*/
|
|
97
|
+
ping(deadlineMs?: number): Promise<boolean>;
|
|
98
|
+
/** Close ONLY if we own the browser; never the user's Chrome. */
|
|
99
|
+
dispose(): Promise<void>;
|
|
100
|
+
tabsList(): Promise<TabInfo[]>;
|
|
101
|
+
tabSelect(tabId: TabId): Promise<TabInfo>;
|
|
102
|
+
tabNew(url?: string): Promise<TabInfo>;
|
|
103
|
+
tabClose(tabId: TabId): Promise<{
|
|
104
|
+
closed: true;
|
|
105
|
+
tabId: TabId;
|
|
106
|
+
}>;
|
|
107
|
+
navigate(args: {
|
|
108
|
+
url: string;
|
|
109
|
+
tabId?: TabId;
|
|
110
|
+
waitUntil?: WaitUntil;
|
|
111
|
+
}): Promise<NavResult>;
|
|
112
|
+
back(tabId?: TabId): Promise<NavResult>;
|
|
113
|
+
forward(tabId?: TabId): Promise<NavResult>;
|
|
114
|
+
reload(args?: {
|
|
115
|
+
tabId?: TabId;
|
|
116
|
+
waitUntil?: WaitUntil;
|
|
117
|
+
}): Promise<NavResult>;
|
|
118
|
+
click(t: Target, opts?: {
|
|
119
|
+
tabId?: TabId;
|
|
120
|
+
button?: MouseButton;
|
|
121
|
+
clickCount?: number;
|
|
122
|
+
}): Promise<ActionOk>;
|
|
123
|
+
type(t: Target, text: string, opts?: {
|
|
124
|
+
tabId?: TabId;
|
|
125
|
+
clear?: boolean;
|
|
126
|
+
pressEnter?: boolean;
|
|
127
|
+
keyEvents?: boolean;
|
|
128
|
+
}): Promise<ActionOk>;
|
|
129
|
+
/** Value-set + input/change events (used by fill_form). */
|
|
130
|
+
fill(t: Target, value: string, opts?: {
|
|
131
|
+
tabId?: TabId;
|
|
132
|
+
}): Promise<ActionOk>;
|
|
133
|
+
press(key: string, opts?: {
|
|
134
|
+
tabId?: TabId;
|
|
135
|
+
modifiers?: KeyModifier[];
|
|
136
|
+
}): Promise<ActionOk>;
|
|
137
|
+
hover(t: Target, opts?: {
|
|
138
|
+
tabId?: TabId;
|
|
139
|
+
}): Promise<ActionOk>;
|
|
140
|
+
scroll(opts: {
|
|
141
|
+
tabId?: TabId;
|
|
142
|
+
x?: number;
|
|
143
|
+
y?: number;
|
|
144
|
+
deltaX?: number;
|
|
145
|
+
deltaY?: number;
|
|
146
|
+
target?: Target;
|
|
147
|
+
}): Promise<ActionOk>;
|
|
148
|
+
getText(t?: Target, opts?: {
|
|
149
|
+
tabId?: TabId;
|
|
150
|
+
}): Promise<{
|
|
151
|
+
text: string;
|
|
152
|
+
ref?: string;
|
|
153
|
+
}>;
|
|
154
|
+
getHtml(t?: Target, opts?: {
|
|
155
|
+
tabId?: TabId;
|
|
156
|
+
outer?: boolean;
|
|
157
|
+
}): Promise<{
|
|
158
|
+
html: string;
|
|
159
|
+
}>;
|
|
160
|
+
screenshot(opts?: {
|
|
161
|
+
tabId?: TabId;
|
|
162
|
+
fullPage?: boolean;
|
|
163
|
+
target?: Target;
|
|
164
|
+
}): Promise<ScreenshotResult>;
|
|
165
|
+
eval(expression: string, opts?: {
|
|
166
|
+
tabId?: TabId;
|
|
167
|
+
awaitPromise?: boolean;
|
|
168
|
+
}): Promise<EvalResult>;
|
|
169
|
+
waitFor(opts: {
|
|
170
|
+
tabId?: TabId;
|
|
171
|
+
selector?: string;
|
|
172
|
+
textContains?: string;
|
|
173
|
+
gone?: boolean;
|
|
174
|
+
timeoutMs?: number;
|
|
175
|
+
}): Promise<WaitResult>;
|
|
176
|
+
download(args: {
|
|
177
|
+
url?: string;
|
|
178
|
+
target?: Target;
|
|
179
|
+
tabId?: TabId;
|
|
180
|
+
suggestedName?: string;
|
|
181
|
+
}): Promise<DownloadResult>;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Server-side executor error codes. A superset of the wire `ExecutorErrorCode`
|
|
185
|
+
* (which only carries codes that originate inside the extension); these extra
|
|
186
|
+
* codes describe failures on the server half (no backend, launch failed, etc.).
|
|
187
|
+
*/
|
|
188
|
+
export type ExecutorErrorCodeLocal = 'NO_BACKEND' | 'EXTENSION_DISCONNECTED' | 'TIMEOUT' | 'TAB_NOT_FOUND' | 'STALE_TAB' | 'SELECTOR_NOT_FOUND' | 'REF_EXPIRED' | 'EVAL_FAILED' | 'LAUNCH_FAILED' | 'DETACHED' | 'TARGET_GONE' | 'POLICY_DENIED' | 'DEVTOOLS_OPEN' | 'DOWNLOAD_FAILED' | 'BACKPRESSURE';
|
|
189
|
+
export declare class ExecutorError extends Error {
|
|
190
|
+
readonly code: ExecutorErrorCodeLocal;
|
|
191
|
+
constructor(code: ExecutorErrorCodeLocal, message: string);
|
|
192
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/executor/types.ts — the single backend-agnostic Executor contract.
|
|
4
|
+
*
|
|
5
|
+
* Plain JSON-serializable in/out: no Playwright `Page`, no CDP session, no DOM
|
|
6
|
+
* handle ever crosses this boundary. Both `ExtensionExecutor` (over the WS
|
|
7
|
+
* bridge) and `CdpExecutor` (Playwright) implement this identical interface, so
|
|
8
|
+
* the tool layer never knows or cares which backend is live.
|
|
9
|
+
*
|
|
10
|
+
* Helpers (extract_links / read_as_markdown / fill_form) are composed in
|
|
11
|
+
* `mcp/helpers.ts` from these primitives; only `download` is privileged.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.ExecutorError = void 0;
|
|
15
|
+
class ExecutorError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
constructor(code, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.name = 'ExecutorError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.ExecutorError = ExecutorError;
|
|
24
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/mcp/envelopes.ts — serialize handler results into the MCP `content`
|
|
3
|
+
* envelope. One place so every tool returns a consistent shape.
|
|
4
|
+
*/
|
|
5
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
/** A JSON payload rendered as pretty text (the default for structured results). */
|
|
7
|
+
export declare function jsonResult(data: unknown): CallToolResult;
|
|
8
|
+
/** A plain text payload (e.g. read_as_markdown). */
|
|
9
|
+
export declare function textResult(text: string): CallToolResult;
|
|
10
|
+
/** A base64 image, optionally with a caption line. */
|
|
11
|
+
export declare function imageResult(dataBase64: string, mimeType: string, caption?: string): CallToolResult;
|
|
12
|
+
/** A structured error result — never throws; sets `isError` for the host. */
|
|
13
|
+
export declare function errorResult(message: string): CallToolResult;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/mcp/envelopes.ts — serialize handler results into the MCP `content`
|
|
4
|
+
* envelope. One place so every tool returns a consistent shape.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.jsonResult = jsonResult;
|
|
8
|
+
exports.textResult = textResult;
|
|
9
|
+
exports.imageResult = imageResult;
|
|
10
|
+
exports.errorResult = errorResult;
|
|
11
|
+
/** A JSON payload rendered as pretty text (the default for structured results). */
|
|
12
|
+
function jsonResult(data) {
|
|
13
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
14
|
+
}
|
|
15
|
+
/** A plain text payload (e.g. read_as_markdown). */
|
|
16
|
+
function textResult(text) {
|
|
17
|
+
return { content: [{ type: 'text', text }] };
|
|
18
|
+
}
|
|
19
|
+
/** A base64 image, optionally with a caption line. */
|
|
20
|
+
function imageResult(dataBase64, mimeType, caption) {
|
|
21
|
+
const content = [{ type: 'image', data: dataBase64, mimeType }];
|
|
22
|
+
if (caption)
|
|
23
|
+
content.push({ type: 'text', text: caption });
|
|
24
|
+
return { content };
|
|
25
|
+
}
|
|
26
|
+
/** A structured error result — never throws; sets `isError` for the host. */
|
|
27
|
+
function errorResult(message) {
|
|
28
|
+
return { content: [{ type: 'text', text: message }], isError: true };
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=envelopes.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/mcp/helpers.ts — the high-level tools composed SERVER-SIDE from executor
|
|
3
|
+
* primitives. They never touch the wire directly: `extract_links` and
|
|
4
|
+
* `read_as_markdown` read via primitives; `fill_form` sequences fill+click.
|
|
5
|
+
* (Only `download_file` is privileged and lives on the executor.)
|
|
6
|
+
*/
|
|
7
|
+
import type { Executor } from '../executor/types';
|
|
8
|
+
export interface LinkOut {
|
|
9
|
+
href: string;
|
|
10
|
+
text: string;
|
|
11
|
+
ref?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Collect anchors from the page (or a subtree). Implemented as a single page
|
|
15
|
+
* eval so it is one round-trip; falls back to parsing getHtml if eval is denied.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractLinks(ex: Executor, args: {
|
|
18
|
+
selector?: string;
|
|
19
|
+
sameOriginOnly?: boolean;
|
|
20
|
+
tabId?: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
links: LinkOut[];
|
|
23
|
+
}>;
|
|
24
|
+
/** Read a page (or subtree) as readable markdown. */
|
|
25
|
+
export declare function readAsMarkdown(ex: Executor, args: {
|
|
26
|
+
selector?: string;
|
|
27
|
+
tabId?: string;
|
|
28
|
+
}): Promise<string>;
|
|
29
|
+
/** Fill a set of fields (keyed by selector) and optionally submit. */
|
|
30
|
+
export declare function fillForm(ex: Executor, args: {
|
|
31
|
+
fields: Record<string, string | boolean>;
|
|
32
|
+
submitSelector?: string;
|
|
33
|
+
tabId?: string;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
filled: number;
|
|
36
|
+
submitted: boolean;
|
|
37
|
+
}>;
|