@andrew-chen-wang/camoufox-mcp 0.0.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/config.d.ts +41 -0
- package/index.d.ts +22 -0
- package/index.js +1 -0
- package/lib/browserContextFactory.js +178 -0
- package/lib/config.js +204 -0
- package/lib/connection.js +84 -0
- package/lib/context.js +361 -0
- package/lib/fileUtils.js +32 -0
- package/lib/index.js +66 -0
- package/lib/javascript.js +49 -0
- package/lib/manualPromise.js +111 -0
- package/lib/mcpHttpServer.js +90 -0
- package/lib/package.js +20 -0
- package/lib/pageSnapshot.js +44 -0
- package/lib/resources/resource.js +16 -0
- package/lib/server.js +48 -0
- package/lib/tab.js +107 -0
- package/lib/tools/common.js +68 -0
- package/lib/tools/console.js +77 -0
- package/lib/tools/devtools.js +189 -0
- package/lib/tools/dialogs.js +52 -0
- package/lib/tools/files.js +51 -0
- package/lib/tools/forms.js +67 -0
- package/lib/tools/install.js +57 -0
- package/lib/tools/keyboard.js +158 -0
- package/lib/tools/mouse.js +119 -0
- package/lib/tools/navigate.js +117 -0
- package/lib/tools/network.js +197 -0
- package/lib/tools/pdf.js +49 -0
- package/lib/tools/screenshot.js +80 -0
- package/lib/tools/snapshot.js +307 -0
- package/lib/tools/storage.js +344 -0
- package/lib/tools/tabs.js +172 -0
- package/lib/tools/testing.js +60 -0
- package/lib/tools/tool.js +18 -0
- package/lib/tools/utils.js +87 -0
- package/lib/tools/vision.js +189 -0
- package/lib/tools/wait.js +59 -0
- package/lib/tools.js +73 -0
- package/package.json +48 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import http from 'node:http';
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
19
|
+
import { createConnection } from './index.js';
|
|
20
|
+
export async function createMCPHTTPServer(options = {}) {
|
|
21
|
+
const sessions = new Map();
|
|
22
|
+
const config = mergeConfig(options.config, options.headless);
|
|
23
|
+
const server = http.createServer((req, res) => {
|
|
24
|
+
void handleRequest(req, res, config, sessions);
|
|
25
|
+
});
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
server.on('error', reject);
|
|
28
|
+
server.listen(options.port ?? 0, options.host ?? '127.0.0.1', () => {
|
|
29
|
+
server.removeListener('error', reject);
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const closeSessions = async () => {
|
|
34
|
+
await Promise.all([...sessions.values()].map(session => session.connection.close().catch(() => { })));
|
|
35
|
+
sessions.clear();
|
|
36
|
+
};
|
|
37
|
+
const originalClose = server.close.bind(server);
|
|
38
|
+
server.close = ((callback) => {
|
|
39
|
+
void closeSessions().finally(() => {
|
|
40
|
+
server.closeAllConnections();
|
|
41
|
+
originalClose(callback);
|
|
42
|
+
});
|
|
43
|
+
return server;
|
|
44
|
+
});
|
|
45
|
+
const shutdown = () => {
|
|
46
|
+
void closeSessions().finally(() => {
|
|
47
|
+
server.closeAllConnections();
|
|
48
|
+
originalClose();
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
process.once('SIGINT', shutdown);
|
|
52
|
+
process.once('SIGTERM', shutdown);
|
|
53
|
+
return server;
|
|
54
|
+
}
|
|
55
|
+
async function handleRequest(req, res, config, sessions) {
|
|
56
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
57
|
+
const existingSession = sessionId ? sessions.get(sessionId) : undefined;
|
|
58
|
+
if (existingSession) {
|
|
59
|
+
await existingSession.transport.handleRequest(req, res);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const connection = await createConnection(config);
|
|
63
|
+
const transport = new StreamableHTTPServerTransport({
|
|
64
|
+
sessionIdGenerator: () => randomUUID(),
|
|
65
|
+
});
|
|
66
|
+
transport.onclose = () => {
|
|
67
|
+
if (transport.sessionId) {
|
|
68
|
+
sessions.delete(transport.sessionId);
|
|
69
|
+
void connection.close().catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await connection.connect(transport);
|
|
73
|
+
await transport.handleRequest(req, res);
|
|
74
|
+
if (transport.sessionId)
|
|
75
|
+
sessions.set(transport.sessionId, { connection, transport });
|
|
76
|
+
}
|
|
77
|
+
function mergeConfig(config, headless) {
|
|
78
|
+
if (headless === undefined)
|
|
79
|
+
return config ?? {};
|
|
80
|
+
return {
|
|
81
|
+
...config,
|
|
82
|
+
browser: {
|
|
83
|
+
...config?.browser,
|
|
84
|
+
launchOptions: {
|
|
85
|
+
...config?.browser?.launchOptions,
|
|
86
|
+
headless,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
package/lib/package.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import url from 'node:url';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
const __filename = url.fileURLToPath(import.meta.url);
|
|
20
|
+
export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', 'package.json'), 'utf8'));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { callOnPageNoTrace } from './tools/utils.js';
|
|
17
|
+
export class PageSnapshot {
|
|
18
|
+
_page;
|
|
19
|
+
_text;
|
|
20
|
+
constructor(page) {
|
|
21
|
+
this._page = page;
|
|
22
|
+
}
|
|
23
|
+
static async create(page) {
|
|
24
|
+
const snapshot = new PageSnapshot(page);
|
|
25
|
+
await snapshot._build();
|
|
26
|
+
return snapshot;
|
|
27
|
+
}
|
|
28
|
+
text() {
|
|
29
|
+
return this._text;
|
|
30
|
+
}
|
|
31
|
+
async _build() {
|
|
32
|
+
const snapshot = await callOnPageNoTrace(this._page, page => page._snapshotForAI());
|
|
33
|
+
const snapshotText = typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2);
|
|
34
|
+
this._text = [
|
|
35
|
+
`- Page Snapshot`,
|
|
36
|
+
'```yaml',
|
|
37
|
+
snapshotText,
|
|
38
|
+
'```',
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
41
|
+
refLocator(params) {
|
|
42
|
+
return this._page.locator(`aria-ref=${params.ref}`).describe(params.element);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { createConnection } from './connection.js';
|
|
17
|
+
import { contextFactory } from './browserContextFactory.js';
|
|
18
|
+
export class Server {
|
|
19
|
+
config;
|
|
20
|
+
_connectionList = [];
|
|
21
|
+
_browserConfig;
|
|
22
|
+
_contextFactory;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
this._browserConfig = config.browser;
|
|
26
|
+
this._contextFactory = contextFactory(this._browserConfig);
|
|
27
|
+
}
|
|
28
|
+
async createConnection(transport) {
|
|
29
|
+
const connection = createConnection(this.config, this._contextFactory);
|
|
30
|
+
this._connectionList.push(connection);
|
|
31
|
+
await connection.server.connect(transport);
|
|
32
|
+
return connection;
|
|
33
|
+
}
|
|
34
|
+
setupExitWatchdog() {
|
|
35
|
+
let isExiting = false;
|
|
36
|
+
const handleExit = async () => {
|
|
37
|
+
if (isExiting)
|
|
38
|
+
return;
|
|
39
|
+
isExiting = true;
|
|
40
|
+
setTimeout(() => process.exit(0), 15000);
|
|
41
|
+
await Promise.all(this._connectionList.map(connection => connection.close()));
|
|
42
|
+
process.exit(0);
|
|
43
|
+
};
|
|
44
|
+
process.stdin.on('close', handleExit);
|
|
45
|
+
process.on('SIGINT', handleExit);
|
|
46
|
+
process.on('SIGTERM', handleExit);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/lib/tab.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { PageSnapshot } from './pageSnapshot.js';
|
|
17
|
+
import { callOnPageNoTrace } from './tools/utils.js';
|
|
18
|
+
export class Tab {
|
|
19
|
+
context;
|
|
20
|
+
page;
|
|
21
|
+
_consoleMessages = [];
|
|
22
|
+
_requests = new Map();
|
|
23
|
+
_snapshot;
|
|
24
|
+
_onPageClose;
|
|
25
|
+
constructor(context, page, onPageClose) {
|
|
26
|
+
this.context = context;
|
|
27
|
+
this.page = page;
|
|
28
|
+
this._onPageClose = onPageClose;
|
|
29
|
+
page.on('console', event => this._consoleMessages.push(event));
|
|
30
|
+
page.on('request', request => this._requests.set(request, null));
|
|
31
|
+
page.on('response', response => this._requests.set(response.request(), response));
|
|
32
|
+
page.on('close', () => this._onClose());
|
|
33
|
+
page.on('filechooser', chooser => {
|
|
34
|
+
this.context.setModalState({
|
|
35
|
+
type: 'fileChooser',
|
|
36
|
+
description: 'File chooser',
|
|
37
|
+
fileChooser: chooser,
|
|
38
|
+
}, this);
|
|
39
|
+
});
|
|
40
|
+
page.on('dialog', dialog => this.context.dialogShown(this, dialog));
|
|
41
|
+
page.on('download', download => {
|
|
42
|
+
void this.context.downloadStarted(this, download);
|
|
43
|
+
});
|
|
44
|
+
page.setDefaultNavigationTimeout(60000);
|
|
45
|
+
page.setDefaultTimeout(5000);
|
|
46
|
+
}
|
|
47
|
+
_clearCollectedArtifacts() {
|
|
48
|
+
this._consoleMessages.length = 0;
|
|
49
|
+
this._requests.clear();
|
|
50
|
+
}
|
|
51
|
+
_onClose() {
|
|
52
|
+
this._clearCollectedArtifacts();
|
|
53
|
+
this._onPageClose(this);
|
|
54
|
+
}
|
|
55
|
+
async title() {
|
|
56
|
+
return await callOnPageNoTrace(this.page, page => page.title());
|
|
57
|
+
}
|
|
58
|
+
async waitForLoadState(state, options) {
|
|
59
|
+
await callOnPageNoTrace(this.page, page => page.waitForLoadState(state, options).catch(() => { }));
|
|
60
|
+
}
|
|
61
|
+
async navigate(url) {
|
|
62
|
+
this._clearCollectedArtifacts();
|
|
63
|
+
const downloadEvent = callOnPageNoTrace(this.page, page => page.waitForEvent('download').catch(() => { }));
|
|
64
|
+
try {
|
|
65
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
66
|
+
}
|
|
67
|
+
catch (_e) {
|
|
68
|
+
const e = _e;
|
|
69
|
+
const mightBeDownload = e.message.includes('net::ERR_ABORTED') // chromium
|
|
70
|
+
|| e.message.includes('Download is starting'); // firefox + webkit
|
|
71
|
+
if (!mightBeDownload)
|
|
72
|
+
throw e;
|
|
73
|
+
// on chromium, the download event is fired *after* page.goto rejects, so we wait a lil bit
|
|
74
|
+
const download = await Promise.race([
|
|
75
|
+
downloadEvent,
|
|
76
|
+
new Promise(resolve => setTimeout(resolve, 1000)),
|
|
77
|
+
]);
|
|
78
|
+
if (!download)
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
// Cap load event to 5 seconds, the page is operational at this point.
|
|
82
|
+
await this.waitForLoadState('load', { timeout: 5000 });
|
|
83
|
+
}
|
|
84
|
+
hasSnapshot() {
|
|
85
|
+
return !!this._snapshot;
|
|
86
|
+
}
|
|
87
|
+
snapshotOrDie() {
|
|
88
|
+
if (!this._snapshot)
|
|
89
|
+
throw new Error('No snapshot available');
|
|
90
|
+
return this._snapshot;
|
|
91
|
+
}
|
|
92
|
+
consoleMessages() {
|
|
93
|
+
return this._consoleMessages;
|
|
94
|
+
}
|
|
95
|
+
clearConsoleMessages() {
|
|
96
|
+
this._consoleMessages.length = 0;
|
|
97
|
+
}
|
|
98
|
+
requests() {
|
|
99
|
+
return this._requests;
|
|
100
|
+
}
|
|
101
|
+
clearRequests() {
|
|
102
|
+
this._requests.clear();
|
|
103
|
+
}
|
|
104
|
+
async captureSnapshot() {
|
|
105
|
+
this._snapshot = await PageSnapshot.create(this.page);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
const close = defineTool({
|
|
19
|
+
capability: 'core',
|
|
20
|
+
schema: {
|
|
21
|
+
name: 'browser_close',
|
|
22
|
+
title: 'Close browser',
|
|
23
|
+
description: 'Close the page',
|
|
24
|
+
inputSchema: z.object({}),
|
|
25
|
+
type: 'readOnly',
|
|
26
|
+
},
|
|
27
|
+
handle: async (context) => {
|
|
28
|
+
await context.close();
|
|
29
|
+
return {
|
|
30
|
+
code: [`await page.close()`],
|
|
31
|
+
captureSnapshot: false,
|
|
32
|
+
waitForNetwork: false,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const resize = captureSnapshot => defineTool({
|
|
37
|
+
capability: 'core',
|
|
38
|
+
schema: {
|
|
39
|
+
name: 'browser_resize',
|
|
40
|
+
title: 'Resize browser window',
|
|
41
|
+
description: 'Resize the browser window',
|
|
42
|
+
inputSchema: z.object({
|
|
43
|
+
width: z.number().describe('Width of the browser window'),
|
|
44
|
+
height: z.number().describe('Height of the browser window'),
|
|
45
|
+
}),
|
|
46
|
+
type: 'readOnly',
|
|
47
|
+
},
|
|
48
|
+
handle: async (context, params) => {
|
|
49
|
+
const tab = context.currentTabOrDie();
|
|
50
|
+
const code = [
|
|
51
|
+
`// Resize browser window to ${params.width}x${params.height}`,
|
|
52
|
+
`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`
|
|
53
|
+
];
|
|
54
|
+
const action = async () => {
|
|
55
|
+
await tab.page.setViewportSize({ width: params.width, height: params.height });
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
code,
|
|
59
|
+
action,
|
|
60
|
+
captureSnapshot,
|
|
61
|
+
waitForNetwork: true
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
export default (captureSnapshot) => [
|
|
66
|
+
close,
|
|
67
|
+
resize(captureSnapshot)
|
|
68
|
+
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
const levels = ['debug', 'info', 'warning', 'error'];
|
|
19
|
+
const levelWeight = {
|
|
20
|
+
debug: 0,
|
|
21
|
+
info: 1,
|
|
22
|
+
warning: 2,
|
|
23
|
+
error: 3,
|
|
24
|
+
};
|
|
25
|
+
const consoleMessages = defineTool({
|
|
26
|
+
capability: 'core',
|
|
27
|
+
schema: {
|
|
28
|
+
name: 'browser_console_messages',
|
|
29
|
+
title: 'Get console messages',
|
|
30
|
+
description: 'Returns all console messages',
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
level: z.enum(levels).optional().describe('Minimum console level to include.'),
|
|
33
|
+
}),
|
|
34
|
+
type: 'readOnly',
|
|
35
|
+
},
|
|
36
|
+
handle: async (context, params) => {
|
|
37
|
+
const messages = context.currentTabOrDie().consoleMessages().filter(message => {
|
|
38
|
+
const normalized = normalizeConsoleType(message.type());
|
|
39
|
+
const threshold = params.level ?? 'info';
|
|
40
|
+
return levelWeight[normalized] >= levelWeight[threshold];
|
|
41
|
+
});
|
|
42
|
+
const log = messages.map(message => `[${message.type().toUpperCase()}] ${message.text()}`).join('\n');
|
|
43
|
+
return {
|
|
44
|
+
code: ['// <internal code to get console messages>'],
|
|
45
|
+
action: async () => ({ content: [{ type: 'text', text: log }] }),
|
|
46
|
+
captureSnapshot: false,
|
|
47
|
+
waitForNetwork: false,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const consoleClear = defineTool({
|
|
52
|
+
capability: 'core',
|
|
53
|
+
schema: {
|
|
54
|
+
name: 'browser_console_clear',
|
|
55
|
+
title: 'Clear console messages',
|
|
56
|
+
description: 'Clear collected console messages',
|
|
57
|
+
inputSchema: z.object({}),
|
|
58
|
+
type: 'destructive',
|
|
59
|
+
},
|
|
60
|
+
handle: async (context) => {
|
|
61
|
+
context.currentTabOrDie().clearConsoleMessages();
|
|
62
|
+
return {
|
|
63
|
+
code: ['// <internal code to clear console messages>'],
|
|
64
|
+
captureSnapshot: false,
|
|
65
|
+
waitForNetwork: false,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
function normalizeConsoleType(type) {
|
|
70
|
+
if (type === 'error' || type === 'warning' || type === 'info')
|
|
71
|
+
return type;
|
|
72
|
+
return 'debug';
|
|
73
|
+
}
|
|
74
|
+
export default [
|
|
75
|
+
consoleMessages,
|
|
76
|
+
consoleClear,
|
|
77
|
+
];
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { outputFile } from '../config.js';
|
|
19
|
+
import { defineTool } from './tool.js';
|
|
20
|
+
import { resolveLocator } from './utils.js';
|
|
21
|
+
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
|
22
|
+
const evaluate = defineTool({
|
|
23
|
+
capability: 'core',
|
|
24
|
+
schema: {
|
|
25
|
+
name: 'browser_evaluate',
|
|
26
|
+
title: 'Evaluate JavaScript',
|
|
27
|
+
description: 'Evaluate JavaScript expression on page or element',
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
function: z.string(),
|
|
30
|
+
filename: z.string().optional(),
|
|
31
|
+
element: z.string().optional(),
|
|
32
|
+
ref: z.string().optional(),
|
|
33
|
+
selector: z.string().optional(),
|
|
34
|
+
}).refine(value => {
|
|
35
|
+
const targets = Number(!!value.ref) + Number(!!value.selector);
|
|
36
|
+
if (value.element)
|
|
37
|
+
return targets === 1;
|
|
38
|
+
return targets === 0;
|
|
39
|
+
}, {
|
|
40
|
+
message: 'Provide element with exactly one of ref or selector, or provide no target.',
|
|
41
|
+
path: ['ref'],
|
|
42
|
+
}),
|
|
43
|
+
type: 'destructive',
|
|
44
|
+
},
|
|
45
|
+
handle: async (context, params) => ({
|
|
46
|
+
code: ['// <internal code to evaluate JavaScript>'],
|
|
47
|
+
action: async () => {
|
|
48
|
+
const page = context.currentTabOrDie().page;
|
|
49
|
+
const fn = new Function(`return (${params.function});`)();
|
|
50
|
+
const result = params.element
|
|
51
|
+
? await resolveLocator(context.currentTabOrDie(), { element: params.element, ref: params.ref, selector: params.selector }).evaluate(fn)
|
|
52
|
+
: await page.evaluate(fn);
|
|
53
|
+
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
54
|
+
if (params.filename) {
|
|
55
|
+
const fileName = await outputFile(context.config, params.filename);
|
|
56
|
+
await fs.writeFile(fileName, text, 'utf8');
|
|
57
|
+
return { content: [{ type: 'text', text: fileName }] };
|
|
58
|
+
}
|
|
59
|
+
return { content: [{ type: 'text', text }] };
|
|
60
|
+
},
|
|
61
|
+
captureSnapshot: false,
|
|
62
|
+
waitForNetwork: false,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
const runCode = defineTool({
|
|
66
|
+
capability: 'core',
|
|
67
|
+
schema: {
|
|
68
|
+
name: 'browser_run_code',
|
|
69
|
+
title: 'Run Playwright code',
|
|
70
|
+
description: 'Run a Playwright code snippet against the current page',
|
|
71
|
+
inputSchema: z.object({
|
|
72
|
+
code: z.string(),
|
|
73
|
+
}),
|
|
74
|
+
type: 'destructive',
|
|
75
|
+
},
|
|
76
|
+
handle: async (context, params) => ({
|
|
77
|
+
code: ['// <internal code to run Playwright snippet>'],
|
|
78
|
+
action: async () => {
|
|
79
|
+
const page = context.currentTabOrDie().page;
|
|
80
|
+
const fn = new AsyncFunction('page', `return (${params.code})(page);`);
|
|
81
|
+
const result = await fn(page);
|
|
82
|
+
if (result === undefined)
|
|
83
|
+
return;
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
88
|
+
}],
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
captureSnapshot: true,
|
|
92
|
+
waitForNetwork: true,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
const startTracing = defineTool({
|
|
96
|
+
capability: 'core',
|
|
97
|
+
schema: {
|
|
98
|
+
name: 'browser_start_tracing',
|
|
99
|
+
title: 'Start tracing',
|
|
100
|
+
description: 'Start Playwright tracing',
|
|
101
|
+
inputSchema: z.object({}),
|
|
102
|
+
type: 'destructive',
|
|
103
|
+
},
|
|
104
|
+
handle: async (context) => ({
|
|
105
|
+
code: ['// <internal code to start tracing>'],
|
|
106
|
+
action: async () => {
|
|
107
|
+
await context.startTracing();
|
|
108
|
+
},
|
|
109
|
+
captureSnapshot: false,
|
|
110
|
+
waitForNetwork: false,
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
const stopTracing = defineTool({
|
|
114
|
+
capability: 'core',
|
|
115
|
+
schema: {
|
|
116
|
+
name: 'browser_stop_tracing',
|
|
117
|
+
title: 'Stop tracing',
|
|
118
|
+
description: 'Stop Playwright tracing and save the trace to a file',
|
|
119
|
+
inputSchema: z.object({
|
|
120
|
+
filename: z.string().optional(),
|
|
121
|
+
}),
|
|
122
|
+
type: 'destructive',
|
|
123
|
+
},
|
|
124
|
+
handle: async (context, params) => ({
|
|
125
|
+
code: ['// <internal code to stop tracing>'],
|
|
126
|
+
action: async () => {
|
|
127
|
+
const fileName = await context.stopTracing(params.filename);
|
|
128
|
+
return { content: [{ type: 'text', text: fileName }] };
|
|
129
|
+
},
|
|
130
|
+
captureSnapshot: false,
|
|
131
|
+
waitForNetwork: false,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
const getConfig = defineTool({
|
|
135
|
+
capability: 'core',
|
|
136
|
+
schema: {
|
|
137
|
+
name: 'browser_get_config',
|
|
138
|
+
title: 'Get config',
|
|
139
|
+
description: 'Return the resolved browser config',
|
|
140
|
+
inputSchema: z.object({}),
|
|
141
|
+
type: 'readOnly',
|
|
142
|
+
},
|
|
143
|
+
handle: async (context) => ({
|
|
144
|
+
code: ['// <internal code to get config>'],
|
|
145
|
+
action: async () => ({
|
|
146
|
+
content: [{ type: 'text', text: JSON.stringify(context.config, null, 2) }],
|
|
147
|
+
}),
|
|
148
|
+
captureSnapshot: false,
|
|
149
|
+
waitForNetwork: false,
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
function unsupportedTool(name, title, description) {
|
|
153
|
+
return defineTool({
|
|
154
|
+
capability: 'core',
|
|
155
|
+
schema: {
|
|
156
|
+
name,
|
|
157
|
+
title,
|
|
158
|
+
description,
|
|
159
|
+
inputSchema: z.object({
|
|
160
|
+
filename: z.string().optional(),
|
|
161
|
+
step: z.boolean().optional(),
|
|
162
|
+
location: z.string().optional(),
|
|
163
|
+
}).partial(),
|
|
164
|
+
type: 'readOnly',
|
|
165
|
+
},
|
|
166
|
+
handle: async () => ({
|
|
167
|
+
code: [`// ${name} is not supported in camoufox-mcp`],
|
|
168
|
+
captureSnapshot: false,
|
|
169
|
+
waitForNetwork: false,
|
|
170
|
+
resultOverride: {
|
|
171
|
+
content: [{
|
|
172
|
+
type: 'text',
|
|
173
|
+
text: `${name} is not supported by camoufox-mcp yet.`,
|
|
174
|
+
}],
|
|
175
|
+
isError: true,
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export default [
|
|
181
|
+
evaluate,
|
|
182
|
+
runCode,
|
|
183
|
+
startTracing,
|
|
184
|
+
stopTracing,
|
|
185
|
+
getConfig,
|
|
186
|
+
unsupportedTool('browser_start_video', 'Start video', 'Start video recording'),
|
|
187
|
+
unsupportedTool('browser_stop_video', 'Stop video', 'Stop video recording'),
|
|
188
|
+
unsupportedTool('browser_resume', 'Resume debugger', 'Resume or step a paused debugging session'),
|
|
189
|
+
];
|