@playwright/mcp 0.0.36 → 0.0.37-alpha-2025-09-09
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 +82 -47
- package/cli.js +7 -1
- package/config.d.ts +24 -0
- package/index.d.ts +1 -1
- package/index.js +2 -2
- package/package.json +14 -40
- package/lib/browserContextFactory.js +0 -211
- package/lib/browserServerBackend.js +0 -77
- package/lib/config.js +0 -246
- package/lib/context.js +0 -226
- package/lib/extension/cdpRelay.js +0 -358
- package/lib/extension/extensionContextFactory.js +0 -56
- package/lib/extension/protocol.js +0 -18
- package/lib/index.js +0 -40
- package/lib/loop/loop.js +0 -69
- package/lib/loop/loopClaude.js +0 -152
- package/lib/loop/loopOpenAI.js +0 -141
- package/lib/loop/main.js +0 -60
- package/lib/loopTools/context.js +0 -67
- package/lib/loopTools/main.js +0 -54
- package/lib/loopTools/perform.js +0 -32
- package/lib/loopTools/snapshot.js +0 -29
- package/lib/loopTools/tool.js +0 -18
- package/lib/mcp/http.js +0 -135
- package/lib/mcp/inProcessTransport.js +0 -72
- package/lib/mcp/manualPromise.js +0 -111
- package/lib/mcp/mdb.js +0 -198
- package/lib/mcp/proxyBackend.js +0 -104
- package/lib/mcp/server.js +0 -123
- package/lib/mcp/tool.js +0 -32
- package/lib/program.js +0 -132
- package/lib/response.js +0 -165
- package/lib/sessionLog.js +0 -121
- package/lib/tab.js +0 -249
- package/lib/tools/common.js +0 -55
- package/lib/tools/console.js +0 -33
- package/lib/tools/dialogs.js +0 -47
- package/lib/tools/evaluate.js +0 -53
- package/lib/tools/files.js +0 -44
- package/lib/tools/form.js +0 -57
- package/lib/tools/install.js +0 -53
- package/lib/tools/keyboard.js +0 -78
- package/lib/tools/mouse.js +0 -99
- package/lib/tools/navigate.js +0 -54
- package/lib/tools/network.js +0 -41
- package/lib/tools/pdf.js +0 -40
- package/lib/tools/screenshot.js +0 -79
- package/lib/tools/snapshot.js +0 -139
- package/lib/tools/tabs.js +0 -59
- package/lib/tools/tool.js +0 -33
- package/lib/tools/utils.js +0 -74
- package/lib/tools/verify.js +0 -137
- package/lib/tools/wait.js +0 -55
- package/lib/tools.js +0 -54
- package/lib/utils/codegen.js +0 -49
- package/lib/utils/fileUtils.js +0 -36
- package/lib/utils/guid.js +0 -22
- package/lib/utils/log.js +0 -21
- package/lib/utils/package.js +0 -20
- package/lib/vscode/host.js +0 -128
- package/lib/vscode/main.js +0 -62
package/lib/mcp/http.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
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 assert from 'assert';
|
|
17
|
-
import http from 'http';
|
|
18
|
-
import crypto from 'crypto';
|
|
19
|
-
import debug from 'debug';
|
|
20
|
-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
21
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
22
|
-
import * as mcpServer from './server.js';
|
|
23
|
-
const testDebug = debug('pw:mcp:test');
|
|
24
|
-
export async function startHttpServer(config, abortSignal) {
|
|
25
|
-
const { host, port } = config;
|
|
26
|
-
const httpServer = http.createServer();
|
|
27
|
-
decorateServer(httpServer);
|
|
28
|
-
await new Promise((resolve, reject) => {
|
|
29
|
-
httpServer.on('error', reject);
|
|
30
|
-
abortSignal?.addEventListener('abort', () => {
|
|
31
|
-
httpServer.close();
|
|
32
|
-
reject(new Error('Aborted'));
|
|
33
|
-
});
|
|
34
|
-
httpServer.listen(port, host, () => {
|
|
35
|
-
resolve();
|
|
36
|
-
httpServer.removeListener('error', reject);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
return httpServer;
|
|
40
|
-
}
|
|
41
|
-
export function httpAddressToString(address) {
|
|
42
|
-
assert(address, 'Could not bind server socket');
|
|
43
|
-
if (typeof address === 'string')
|
|
44
|
-
return address;
|
|
45
|
-
const resolvedPort = address.port;
|
|
46
|
-
let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
|
|
47
|
-
if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
|
|
48
|
-
resolvedHost = 'localhost';
|
|
49
|
-
return `http://${resolvedHost}:${resolvedPort}`;
|
|
50
|
-
}
|
|
51
|
-
export async function installHttpTransport(httpServer, serverBackendFactory) {
|
|
52
|
-
const sseSessions = new Map();
|
|
53
|
-
const streamableSessions = new Map();
|
|
54
|
-
httpServer.on('request', async (req, res) => {
|
|
55
|
-
const url = new URL(`http://localhost${req.url}`);
|
|
56
|
-
if (url.pathname.startsWith('/sse'))
|
|
57
|
-
await handleSSE(serverBackendFactory, req, res, url, sseSessions);
|
|
58
|
-
else
|
|
59
|
-
await handleStreamable(serverBackendFactory, req, res, streamableSessions);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
async function handleSSE(serverBackendFactory, req, res, url, sessions) {
|
|
63
|
-
if (req.method === 'POST') {
|
|
64
|
-
const sessionId = url.searchParams.get('sessionId');
|
|
65
|
-
if (!sessionId) {
|
|
66
|
-
res.statusCode = 400;
|
|
67
|
-
return res.end('Missing sessionId');
|
|
68
|
-
}
|
|
69
|
-
const transport = sessions.get(sessionId);
|
|
70
|
-
if (!transport) {
|
|
71
|
-
res.statusCode = 404;
|
|
72
|
-
return res.end('Session not found');
|
|
73
|
-
}
|
|
74
|
-
return await transport.handlePostMessage(req, res);
|
|
75
|
-
}
|
|
76
|
-
else if (req.method === 'GET') {
|
|
77
|
-
const transport = new SSEServerTransport('/sse', res);
|
|
78
|
-
sessions.set(transport.sessionId, transport);
|
|
79
|
-
testDebug(`create SSE session: ${transport.sessionId}`);
|
|
80
|
-
await mcpServer.connect(serverBackendFactory, transport, false);
|
|
81
|
-
res.on('close', () => {
|
|
82
|
-
testDebug(`delete SSE session: ${transport.sessionId}`);
|
|
83
|
-
sessions.delete(transport.sessionId);
|
|
84
|
-
});
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
res.statusCode = 405;
|
|
88
|
-
res.end('Method not allowed');
|
|
89
|
-
}
|
|
90
|
-
async function handleStreamable(serverBackendFactory, req, res, sessions) {
|
|
91
|
-
const sessionId = req.headers['mcp-session-id'];
|
|
92
|
-
if (sessionId) {
|
|
93
|
-
const transport = sessions.get(sessionId);
|
|
94
|
-
if (!transport) {
|
|
95
|
-
res.statusCode = 404;
|
|
96
|
-
res.end('Session not found');
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
return await transport.handleRequest(req, res);
|
|
100
|
-
}
|
|
101
|
-
if (req.method === 'POST') {
|
|
102
|
-
const transport = new StreamableHTTPServerTransport({
|
|
103
|
-
sessionIdGenerator: () => crypto.randomUUID(),
|
|
104
|
-
onsessioninitialized: async (sessionId) => {
|
|
105
|
-
testDebug(`create http session: ${transport.sessionId}`);
|
|
106
|
-
await mcpServer.connect(serverBackendFactory, transport, true);
|
|
107
|
-
sessions.set(sessionId, transport);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
transport.onclose = () => {
|
|
111
|
-
if (!transport.sessionId)
|
|
112
|
-
return;
|
|
113
|
-
sessions.delete(transport.sessionId);
|
|
114
|
-
testDebug(`delete http session: ${transport.sessionId}`);
|
|
115
|
-
};
|
|
116
|
-
await transport.handleRequest(req, res);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
res.statusCode = 400;
|
|
120
|
-
res.end('Invalid request');
|
|
121
|
-
}
|
|
122
|
-
function decorateServer(server) {
|
|
123
|
-
const sockets = new Set();
|
|
124
|
-
server.on('connection', socket => {
|
|
125
|
-
sockets.add(socket);
|
|
126
|
-
socket.once('close', () => sockets.delete(socket));
|
|
127
|
-
});
|
|
128
|
-
const close = server.close;
|
|
129
|
-
server.close = (callback) => {
|
|
130
|
-
for (const socket of sockets)
|
|
131
|
-
socket.destroy();
|
|
132
|
-
sockets.clear();
|
|
133
|
-
return close.call(server, callback);
|
|
134
|
-
};
|
|
135
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
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 class InProcessTransport {
|
|
17
|
-
_server;
|
|
18
|
-
_serverTransport;
|
|
19
|
-
_connected = false;
|
|
20
|
-
constructor(server) {
|
|
21
|
-
this._server = server;
|
|
22
|
-
this._serverTransport = new InProcessServerTransport(this);
|
|
23
|
-
}
|
|
24
|
-
async start() {
|
|
25
|
-
if (this._connected)
|
|
26
|
-
throw new Error('InprocessTransport already started!');
|
|
27
|
-
await this._server.connect(this._serverTransport);
|
|
28
|
-
this._connected = true;
|
|
29
|
-
}
|
|
30
|
-
async send(message, options) {
|
|
31
|
-
if (!this._connected)
|
|
32
|
-
throw new Error('Transport not connected');
|
|
33
|
-
this._serverTransport._receiveFromClient(message);
|
|
34
|
-
}
|
|
35
|
-
async close() {
|
|
36
|
-
if (this._connected) {
|
|
37
|
-
this._connected = false;
|
|
38
|
-
this.onclose?.();
|
|
39
|
-
this._serverTransport.onclose?.();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
onclose;
|
|
43
|
-
onerror;
|
|
44
|
-
onmessage;
|
|
45
|
-
sessionId;
|
|
46
|
-
setProtocolVersion;
|
|
47
|
-
_receiveFromServer(message, extra) {
|
|
48
|
-
this.onmessage?.(message, extra);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
class InProcessServerTransport {
|
|
52
|
-
_clientTransport;
|
|
53
|
-
constructor(clientTransport) {
|
|
54
|
-
this._clientTransport = clientTransport;
|
|
55
|
-
}
|
|
56
|
-
async start() {
|
|
57
|
-
}
|
|
58
|
-
async send(message, options) {
|
|
59
|
-
this._clientTransport._receiveFromServer(message);
|
|
60
|
-
}
|
|
61
|
-
async close() {
|
|
62
|
-
this.onclose?.();
|
|
63
|
-
}
|
|
64
|
-
onclose;
|
|
65
|
-
onerror;
|
|
66
|
-
onmessage;
|
|
67
|
-
sessionId;
|
|
68
|
-
setProtocolVersion;
|
|
69
|
-
_receiveFromClient(message) {
|
|
70
|
-
this.onmessage?.(message);
|
|
71
|
-
}
|
|
72
|
-
}
|
package/lib/mcp/manualPromise.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
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 class ManualPromise extends Promise {
|
|
17
|
-
_resolve;
|
|
18
|
-
_reject;
|
|
19
|
-
_isDone;
|
|
20
|
-
constructor() {
|
|
21
|
-
let resolve;
|
|
22
|
-
let reject;
|
|
23
|
-
super((f, r) => {
|
|
24
|
-
resolve = f;
|
|
25
|
-
reject = r;
|
|
26
|
-
});
|
|
27
|
-
this._isDone = false;
|
|
28
|
-
this._resolve = resolve;
|
|
29
|
-
this._reject = reject;
|
|
30
|
-
}
|
|
31
|
-
isDone() {
|
|
32
|
-
return this._isDone;
|
|
33
|
-
}
|
|
34
|
-
resolve(t) {
|
|
35
|
-
this._isDone = true;
|
|
36
|
-
this._resolve(t);
|
|
37
|
-
}
|
|
38
|
-
reject(e) {
|
|
39
|
-
this._isDone = true;
|
|
40
|
-
this._reject(e);
|
|
41
|
-
}
|
|
42
|
-
static get [Symbol.species]() {
|
|
43
|
-
return Promise;
|
|
44
|
-
}
|
|
45
|
-
get [Symbol.toStringTag]() {
|
|
46
|
-
return 'ManualPromise';
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export class LongStandingScope {
|
|
50
|
-
_terminateError;
|
|
51
|
-
_closeError;
|
|
52
|
-
_terminatePromises = new Map();
|
|
53
|
-
_isClosed = false;
|
|
54
|
-
reject(error) {
|
|
55
|
-
this._isClosed = true;
|
|
56
|
-
this._terminateError = error;
|
|
57
|
-
for (const p of this._terminatePromises.keys())
|
|
58
|
-
p.resolve(error);
|
|
59
|
-
}
|
|
60
|
-
close(error) {
|
|
61
|
-
this._isClosed = true;
|
|
62
|
-
this._closeError = error;
|
|
63
|
-
for (const [p, frames] of this._terminatePromises)
|
|
64
|
-
p.resolve(cloneError(error, frames));
|
|
65
|
-
}
|
|
66
|
-
isClosed() {
|
|
67
|
-
return this._isClosed;
|
|
68
|
-
}
|
|
69
|
-
static async raceMultiple(scopes, promise) {
|
|
70
|
-
return Promise.race(scopes.map(s => s.race(promise)));
|
|
71
|
-
}
|
|
72
|
-
async race(promise) {
|
|
73
|
-
return this._race(Array.isArray(promise) ? promise : [promise], false);
|
|
74
|
-
}
|
|
75
|
-
async safeRace(promise, defaultValue) {
|
|
76
|
-
return this._race([promise], true, defaultValue);
|
|
77
|
-
}
|
|
78
|
-
async _race(promises, safe, defaultValue) {
|
|
79
|
-
const terminatePromise = new ManualPromise();
|
|
80
|
-
const frames = captureRawStack();
|
|
81
|
-
if (this._terminateError)
|
|
82
|
-
terminatePromise.resolve(this._terminateError);
|
|
83
|
-
if (this._closeError)
|
|
84
|
-
terminatePromise.resolve(cloneError(this._closeError, frames));
|
|
85
|
-
this._terminatePromises.set(terminatePromise, frames);
|
|
86
|
-
try {
|
|
87
|
-
return await Promise.race([
|
|
88
|
-
terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
|
|
89
|
-
...promises
|
|
90
|
-
]);
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
this._terminatePromises.delete(terminatePromise);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function cloneError(error, frames) {
|
|
98
|
-
const clone = new Error();
|
|
99
|
-
clone.name = error.name;
|
|
100
|
-
clone.message = error.message;
|
|
101
|
-
clone.stack = [error.name + ':' + error.message, ...frames].join('\n');
|
|
102
|
-
return clone;
|
|
103
|
-
}
|
|
104
|
-
function captureRawStack() {
|
|
105
|
-
const stackTraceLimit = Error.stackTraceLimit;
|
|
106
|
-
Error.stackTraceLimit = 50;
|
|
107
|
-
const error = new Error();
|
|
108
|
-
const stack = error.stack || '';
|
|
109
|
-
Error.stackTraceLimit = stackTraceLimit;
|
|
110
|
-
return stack.split('\n');
|
|
111
|
-
}
|
package/lib/mcp/mdb.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
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 debug from 'debug';
|
|
17
|
-
import { z } from 'zod';
|
|
18
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
19
|
-
import { PingRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
21
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
22
|
-
import { defineToolSchema } from './tool.js';
|
|
23
|
-
import * as mcpServer from './server.js';
|
|
24
|
-
import * as mcpHttp from './http.js';
|
|
25
|
-
import { wrapInProcess } from './server.js';
|
|
26
|
-
import { ManualPromise } from './manualPromise.js';
|
|
27
|
-
const mdbDebug = debug('pw:mcp:mdb');
|
|
28
|
-
const errorsDebug = debug('pw:mcp:errors');
|
|
29
|
-
export class MDBBackend {
|
|
30
|
-
_stack = [];
|
|
31
|
-
_interruptPromise;
|
|
32
|
-
_topLevelBackend;
|
|
33
|
-
_initialized = false;
|
|
34
|
-
constructor(topLevelBackend) {
|
|
35
|
-
this._topLevelBackend = topLevelBackend;
|
|
36
|
-
}
|
|
37
|
-
async initialize(server) {
|
|
38
|
-
if (this._initialized)
|
|
39
|
-
return;
|
|
40
|
-
this._initialized = true;
|
|
41
|
-
const transport = await wrapInProcess(this._topLevelBackend);
|
|
42
|
-
await this._pushClient(transport);
|
|
43
|
-
}
|
|
44
|
-
async listTools() {
|
|
45
|
-
const response = await this._client().listTools();
|
|
46
|
-
return response.tools;
|
|
47
|
-
}
|
|
48
|
-
async callTool(name, args) {
|
|
49
|
-
if (name === pushToolsSchema.name)
|
|
50
|
-
return await this._pushTools(pushToolsSchema.inputSchema.parse(args || {}));
|
|
51
|
-
const interruptPromise = new ManualPromise();
|
|
52
|
-
this._interruptPromise = interruptPromise;
|
|
53
|
-
let [entry] = this._stack;
|
|
54
|
-
// Pop the client while the tool is not found.
|
|
55
|
-
while (entry && !entry.toolNames.includes(name)) {
|
|
56
|
-
mdbDebug('popping client from stack for ', name);
|
|
57
|
-
this._stack.shift();
|
|
58
|
-
await entry.client.close();
|
|
59
|
-
entry = this._stack[0];
|
|
60
|
-
}
|
|
61
|
-
if (!entry)
|
|
62
|
-
throw new Error(`Tool ${name} not found in the tool stack`);
|
|
63
|
-
const resultPromise = new ManualPromise();
|
|
64
|
-
entry.resultPromise = resultPromise;
|
|
65
|
-
this._client().callTool({
|
|
66
|
-
name,
|
|
67
|
-
arguments: args,
|
|
68
|
-
}).then(result => {
|
|
69
|
-
resultPromise.resolve(result);
|
|
70
|
-
}).catch(e => {
|
|
71
|
-
mdbDebug('error in client call', e);
|
|
72
|
-
if (this._stack.length < 2)
|
|
73
|
-
throw e;
|
|
74
|
-
this._stack.shift();
|
|
75
|
-
const prevEntry = this._stack[0];
|
|
76
|
-
void prevEntry.resultPromise.then(result => resultPromise.resolve(result));
|
|
77
|
-
});
|
|
78
|
-
const result = await Promise.race([interruptPromise, resultPromise]);
|
|
79
|
-
if (interruptPromise.isDone())
|
|
80
|
-
mdbDebug('client call intercepted', result);
|
|
81
|
-
else
|
|
82
|
-
mdbDebug('client call result', result);
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
_client() {
|
|
86
|
-
const [entry] = this._stack;
|
|
87
|
-
if (!entry)
|
|
88
|
-
throw new Error('No debugging backend available');
|
|
89
|
-
return entry.client;
|
|
90
|
-
}
|
|
91
|
-
async _pushTools(params) {
|
|
92
|
-
mdbDebug('pushing tools to the stack', params.mcpUrl);
|
|
93
|
-
const transport = new StreamableHTTPClientTransport(new URL(params.mcpUrl));
|
|
94
|
-
await this._pushClient(transport, params.introMessage);
|
|
95
|
-
return { content: [{ type: 'text', text: 'Tools pushed' }] };
|
|
96
|
-
}
|
|
97
|
-
async _pushClient(transport, introMessage) {
|
|
98
|
-
mdbDebug('pushing client to the stack');
|
|
99
|
-
const client = new Client({ name: 'Internal client', version: '0.0.0' });
|
|
100
|
-
client.setRequestHandler(PingRequestSchema, () => ({}));
|
|
101
|
-
await client.connect(transport);
|
|
102
|
-
mdbDebug('connected to the new client');
|
|
103
|
-
const { tools } = await client.listTools();
|
|
104
|
-
this._stack.unshift({ client, toolNames: tools.map(tool => tool.name), resultPromise: undefined });
|
|
105
|
-
mdbDebug('new tools added to the stack:', tools.map(tool => tool.name));
|
|
106
|
-
mdbDebug('interrupting current call:', !!this._interruptPromise);
|
|
107
|
-
this._interruptPromise?.resolve({
|
|
108
|
-
content: [{
|
|
109
|
-
type: 'text',
|
|
110
|
-
text: introMessage || '',
|
|
111
|
-
}],
|
|
112
|
-
});
|
|
113
|
-
this._interruptPromise = undefined;
|
|
114
|
-
return { content: [{ type: 'text', text: 'Tools pushed' }] };
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const pushToolsSchema = defineToolSchema({
|
|
118
|
-
name: 'mdb_push_tools',
|
|
119
|
-
title: 'Push MCP tools to the tools stack',
|
|
120
|
-
description: 'Push MCP tools to the tools stack',
|
|
121
|
-
inputSchema: z.object({
|
|
122
|
-
mcpUrl: z.string(),
|
|
123
|
-
introMessage: z.string().optional(),
|
|
124
|
-
}),
|
|
125
|
-
type: 'readOnly',
|
|
126
|
-
});
|
|
127
|
-
export async function runMainBackend(backendFactory, options) {
|
|
128
|
-
const mdbBackend = new MDBBackend(backendFactory.create());
|
|
129
|
-
// Start HTTP unconditionally.
|
|
130
|
-
const factory = {
|
|
131
|
-
...backendFactory,
|
|
132
|
-
create: () => mdbBackend
|
|
133
|
-
};
|
|
134
|
-
const url = await startAsHttp(factory, { port: options?.port || 0 });
|
|
135
|
-
process.env.PLAYWRIGHT_DEBUGGER_MCP = url;
|
|
136
|
-
if (options?.port !== undefined)
|
|
137
|
-
return url;
|
|
138
|
-
// Start stdio conditionally.
|
|
139
|
-
await mcpServer.connect(factory, new StdioServerTransport(), false);
|
|
140
|
-
}
|
|
141
|
-
export async function runOnPauseBackendLoop(mdbUrl, backend, introMessage) {
|
|
142
|
-
const wrappedBackend = new OnceTimeServerBackendWrapper(backend);
|
|
143
|
-
const factory = {
|
|
144
|
-
name: 'on-pause-backend',
|
|
145
|
-
nameInConfig: 'on-pause-backend',
|
|
146
|
-
version: '0.0.0',
|
|
147
|
-
create: () => wrappedBackend,
|
|
148
|
-
};
|
|
149
|
-
const httpServer = await mcpHttp.startHttpServer({ port: 0 });
|
|
150
|
-
await mcpHttp.installHttpTransport(httpServer, factory);
|
|
151
|
-
const url = mcpHttp.httpAddressToString(httpServer.address());
|
|
152
|
-
const client = new Client({ name: 'Internal client', version: '0.0.0' });
|
|
153
|
-
client.setRequestHandler(PingRequestSchema, () => ({}));
|
|
154
|
-
const transport = new StreamableHTTPClientTransport(new URL(mdbUrl));
|
|
155
|
-
await client.connect(transport);
|
|
156
|
-
const pushToolsResult = await client.callTool({
|
|
157
|
-
name: pushToolsSchema.name,
|
|
158
|
-
arguments: {
|
|
159
|
-
mcpUrl: url,
|
|
160
|
-
introMessage,
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
if (pushToolsResult.isError)
|
|
164
|
-
errorsDebug('Failed to push tools', pushToolsResult.content);
|
|
165
|
-
await transport.terminateSession();
|
|
166
|
-
await client.close();
|
|
167
|
-
await wrappedBackend.waitForClosed();
|
|
168
|
-
httpServer.close();
|
|
169
|
-
}
|
|
170
|
-
async function startAsHttp(backendFactory, options) {
|
|
171
|
-
const httpServer = await mcpHttp.startHttpServer(options);
|
|
172
|
-
await mcpHttp.installHttpTransport(httpServer, backendFactory);
|
|
173
|
-
return mcpHttp.httpAddressToString(httpServer.address());
|
|
174
|
-
}
|
|
175
|
-
class OnceTimeServerBackendWrapper {
|
|
176
|
-
_backend;
|
|
177
|
-
_selfDestructPromise = new ManualPromise();
|
|
178
|
-
constructor(backend) {
|
|
179
|
-
this._backend = backend;
|
|
180
|
-
this._backend.requestSelfDestruct = () => this._selfDestructPromise.resolve();
|
|
181
|
-
}
|
|
182
|
-
async initialize(server, clientVersion, roots) {
|
|
183
|
-
await this._backend.initialize?.(server, clientVersion, roots);
|
|
184
|
-
}
|
|
185
|
-
async listTools() {
|
|
186
|
-
return this._backend.listTools();
|
|
187
|
-
}
|
|
188
|
-
async callTool(name, args) {
|
|
189
|
-
return this._backend.callTool(name, args);
|
|
190
|
-
}
|
|
191
|
-
serverClosed(server) {
|
|
192
|
-
this._backend.serverClosed?.(server);
|
|
193
|
-
this._selfDestructPromise.resolve();
|
|
194
|
-
}
|
|
195
|
-
async waitForClosed() {
|
|
196
|
-
await this._selfDestructPromise;
|
|
197
|
-
}
|
|
198
|
-
}
|
package/lib/mcp/proxyBackend.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
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 debug from 'debug';
|
|
17
|
-
import { z } from 'zod';
|
|
18
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
19
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
20
|
-
import { ListRootsRequestSchema, PingRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
21
|
-
const errorsDebug = debug('pw:mcp:errors');
|
|
22
|
-
export class ProxyBackend {
|
|
23
|
-
_mcpProviders;
|
|
24
|
-
_currentClient;
|
|
25
|
-
_contextSwitchTool;
|
|
26
|
-
_roots = [];
|
|
27
|
-
constructor(mcpProviders) {
|
|
28
|
-
this._mcpProviders = mcpProviders;
|
|
29
|
-
this._contextSwitchTool = this._defineContextSwitchTool();
|
|
30
|
-
}
|
|
31
|
-
async initialize(server, clientVersion, roots) {
|
|
32
|
-
this._roots = roots;
|
|
33
|
-
await this._setCurrentClient(this._mcpProviders[0]);
|
|
34
|
-
}
|
|
35
|
-
async listTools() {
|
|
36
|
-
const response = await this._currentClient.listTools();
|
|
37
|
-
if (this._mcpProviders.length === 1)
|
|
38
|
-
return response.tools;
|
|
39
|
-
return [
|
|
40
|
-
...response.tools,
|
|
41
|
-
this._contextSwitchTool,
|
|
42
|
-
];
|
|
43
|
-
}
|
|
44
|
-
async callTool(name, args) {
|
|
45
|
-
if (name === this._contextSwitchTool.name)
|
|
46
|
-
return this._callContextSwitchTool(args);
|
|
47
|
-
return await this._currentClient.callTool({
|
|
48
|
-
name,
|
|
49
|
-
arguments: args,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
serverClosed() {
|
|
53
|
-
void this._currentClient?.close().catch(errorsDebug);
|
|
54
|
-
}
|
|
55
|
-
async _callContextSwitchTool(params) {
|
|
56
|
-
try {
|
|
57
|
-
const factory = this._mcpProviders.find(factory => factory.name === params.name);
|
|
58
|
-
if (!factory)
|
|
59
|
-
throw new Error('Unknown connection method: ' + params.name);
|
|
60
|
-
await this._setCurrentClient(factory);
|
|
61
|
-
return {
|
|
62
|
-
content: [{ type: 'text', text: '### Result\nSuccessfully changed connection method.\n' }],
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
return {
|
|
67
|
-
content: [{ type: 'text', text: `### Result\nError: ${error}\n` }],
|
|
68
|
-
isError: true,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
_defineContextSwitchTool() {
|
|
73
|
-
return {
|
|
74
|
-
name: 'browser_connect',
|
|
75
|
-
description: [
|
|
76
|
-
'Connect to a browser using one of the available methods:',
|
|
77
|
-
...this._mcpProviders.map(factory => `- "${factory.name}": ${factory.description}`),
|
|
78
|
-
].join('\n'),
|
|
79
|
-
inputSchema: zodToJsonSchema(z.object({
|
|
80
|
-
name: z.enum(this._mcpProviders.map(factory => factory.name)).default(this._mcpProviders[0].name).describe('The method to use to connect to the browser'),
|
|
81
|
-
}), { strictUnions: true }),
|
|
82
|
-
annotations: {
|
|
83
|
-
title: 'Connect to a browser context',
|
|
84
|
-
readOnlyHint: true,
|
|
85
|
-
openWorldHint: false,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
async _setCurrentClient(factory) {
|
|
90
|
-
await this._currentClient?.close();
|
|
91
|
-
this._currentClient = undefined;
|
|
92
|
-
const client = new Client({ name: 'Playwright MCP Proxy', version: '0.0.0' });
|
|
93
|
-
client.registerCapabilities({
|
|
94
|
-
roots: {
|
|
95
|
-
listRoots: true,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
client.setRequestHandler(ListRootsRequestSchema, () => ({ roots: this._roots }));
|
|
99
|
-
client.setRequestHandler(PingRequestSchema, () => ({}));
|
|
100
|
-
const transport = await factory.connect();
|
|
101
|
-
await client.connect(transport);
|
|
102
|
-
this._currentClient = client;
|
|
103
|
-
}
|
|
104
|
-
}
|