mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-28 01:32:27 +08:00
chore: wire one tool in-process (#753)
This commit is contained in:
parent
c63b7823e1
commit
e0fb748ccc
3
package-lock.json
generated
3
package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.16.0",
|
"@modelcontextprotocol/sdk": "^1.16.0",
|
||||||
"commander": "^13.1.0",
|
"commander": "^13.1.0",
|
||||||
"debug": "^4.4.1",
|
"debug": "^4.4.1",
|
||||||
|
"dotenv": "^17.2.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"playwright": "1.55.0-alpha-1752701791000",
|
"playwright": "1.55.0-alpha-1752701791000",
|
||||||
"playwright-core": "1.55.0-alpha-1752701791000",
|
"playwright-core": "1.55.0-alpha-1752701791000",
|
||||||
@ -34,7 +35,6 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||||
"@typescript-eslint/parser": "^8.26.1",
|
"@typescript-eslint/parser": "^8.26.1",
|
||||||
"@typescript-eslint/utils": "^8.26.1",
|
"@typescript-eslint/utils": "^8.26.1",
|
||||||
"dotenv": "^17.2.0",
|
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-notice": "^1.0.0",
|
"eslint-plugin-notice": "^1.0.0",
|
||||||
@ -1289,7 +1289,6 @@
|
|||||||
"version": "17.2.0",
|
"version": "17.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz",
|
||||||
"integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==",
|
"integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.16.0",
|
"@modelcontextprotocol/sdk": "^1.16.0",
|
||||||
"commander": "^13.1.0",
|
"commander": "^13.1.0",
|
||||||
"debug": "^4.4.1",
|
"debug": "^4.4.1",
|
||||||
|
"dotenv": "^17.2.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"playwright": "1.55.0-alpha-1752701791000",
|
"playwright": "1.55.0-alpha-1752701791000",
|
||||||
"playwright-core": "1.55.0-alpha-1752701791000",
|
"playwright-core": "1.55.0-alpha-1752701791000",
|
||||||
@ -61,7 +62,6 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||||
"@typescript-eslint/parser": "^8.26.1",
|
"@typescript-eslint/parser": "^8.26.1",
|
||||||
"@typescript-eslint/utils": "^8.26.1",
|
"@typescript-eslint/utils": "^8.26.1",
|
||||||
"dotenv": "^17.2.0",
|
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-notice": "^1.0.0",
|
"eslint-plugin-notice": "^1.0.0",
|
||||||
|
@ -14,25 +14,23 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
import url from 'url';
|
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
||||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
||||||
|
|
||||||
import { OpenAIDelegate } from './loopOpenAI.js';
|
import { contextFactory } from '../browserContextFactory.js';
|
||||||
import { runTask } from './loop.js';
|
import { BrowserServerBackend } from '../browserServerBackend.js';
|
||||||
import { packageJSON } from '../package.js';
|
import { Context } from '../context.js';
|
||||||
|
import { logUnhandledError } from '../log.js';
|
||||||
|
import { InProcessTransport } from '../mcp/inProcessTransport.js';
|
||||||
|
import * as mcpServer from '../mcp/server.js';
|
||||||
import * as mcpTransport from '../mcp/transport.js';
|
import * as mcpTransport from '../mcp/transport.js';
|
||||||
|
import { packageJSON } from '../package.js';
|
||||||
|
import { runTask } from './loop.js';
|
||||||
|
import { OpenAIDelegate } from './loopOpenAI.js';
|
||||||
|
|
||||||
import type { FullConfig } from '../config.js';
|
import type { FullConfig } from '../config.js';
|
||||||
import type { ServerBackend } from '../mcp/server.js';
|
import type { ServerBackend } from '../mcp/server.js';
|
||||||
import type * as mcpServer from '../mcp/server.js';
|
|
||||||
|
|
||||||
const __filename = url.fileURLToPath(import.meta.url);
|
|
||||||
|
|
||||||
const delegate = new OpenAIDelegate();
|
|
||||||
|
|
||||||
const oneToolSchema: mcpServer.ToolSchema<any> = {
|
const oneToolSchema: mcpServer.ToolSchema<any> = {
|
||||||
name: 'browser',
|
name: 'browser',
|
||||||
@ -46,7 +44,7 @@ const oneToolSchema: mcpServer.ToolSchema<any> = {
|
|||||||
|
|
||||||
export async function runOneTool(config: FullConfig) {
|
export async function runOneTool(config: FullConfig) {
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const serverBackendFactory = () => new OneToolServerBackend();
|
const serverBackendFactory = () => new OneToolServerBackend(config);
|
||||||
await mcpTransport.start(serverBackendFactory, config.server);
|
await mcpTransport.start(serverBackendFactory, config.server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,19 +52,17 @@ class OneToolServerBackend implements ServerBackend {
|
|||||||
readonly name = 'Playwright';
|
readonly name = 'Playwright';
|
||||||
readonly version = packageJSON.version;
|
readonly version = packageJSON.version;
|
||||||
private _innerClient: Client | undefined;
|
private _innerClient: Client | undefined;
|
||||||
|
private _config: FullConfig;
|
||||||
|
|
||||||
|
constructor(config: FullConfig) {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
const transport = new StdioClientTransport({
|
|
||||||
command: 'node',
|
|
||||||
args: [
|
|
||||||
path.resolve(__filename, '../../../cli.js'),
|
|
||||||
],
|
|
||||||
stderr: 'inherit',
|
|
||||||
env: process.env as Record<string, string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = new Client({ name: 'Playwright Proxy', version: '1.0.0' });
|
const client = new Client({ name: 'Playwright Proxy', version: '1.0.0' });
|
||||||
await client.connect(transport);
|
const browserContextFactory = contextFactory(this._config.browser);
|
||||||
|
const server = mcpServer.createServer(new BrowserServerBackend(this._config, browserContextFactory));
|
||||||
|
await client.connect(new InProcessTransport(server));
|
||||||
await client.ping();
|
await client.ping();
|
||||||
this._innerClient = client;
|
this._innerClient = client;
|
||||||
}
|
}
|
||||||
@ -76,9 +72,14 @@ class OneToolServerBackend implements ServerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async callTool(schema: mcpServer.ToolSchema<any>, parsedArguments: any): Promise<mcpServer.ToolResponse> {
|
async callTool(schema: mcpServer.ToolSchema<any>, parsedArguments: any): Promise<mcpServer.ToolResponse> {
|
||||||
const result = await runTask(delegate!, this._innerClient!, parsedArguments.task as string);
|
const delegate = new OpenAIDelegate();
|
||||||
|
const result = await runTask(delegate, this._innerClient!, parsedArguments.task as string);
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: result }],
|
content: [{ type: 'text', text: result }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverClosed() {
|
||||||
|
void Context.disposeAll().catch(logUnhandledError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
92
src/mcp/inProcessTransport.ts
Normal file
92
src/mcp/inProcessTransport.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
|
import type { Transport, TransportSendOptions } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||||
|
import type { JSONRPCMessage, MessageExtraInfo } from '@modelcontextprotocol/sdk/types.js';
|
||||||
|
|
||||||
|
export class InProcessTransport implements Transport {
|
||||||
|
private _server: Server;
|
||||||
|
private _serverTransport: InProcessServerTransport;
|
||||||
|
private _connected: boolean = false;
|
||||||
|
|
||||||
|
constructor(server: Server) {
|
||||||
|
this._server = server;
|
||||||
|
this._serverTransport = new InProcessServerTransport(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<void> {
|
||||||
|
if (this._connected)
|
||||||
|
throw new Error('InprocessTransport already started!');
|
||||||
|
|
||||||
|
await this._server.connect(this._serverTransport);
|
||||||
|
this._connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void> {
|
||||||
|
if (!this._connected)
|
||||||
|
throw new Error('Transport not connected');
|
||||||
|
|
||||||
|
|
||||||
|
this._serverTransport._receiveFromClient(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
if (this._connected) {
|
||||||
|
this._connected = false;
|
||||||
|
this.onclose?.();
|
||||||
|
this._serverTransport.onclose?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onclose?: (() => void) | undefined;
|
||||||
|
onerror?: ((error: Error) => void) | undefined;
|
||||||
|
onmessage?: ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void) | undefined;
|
||||||
|
sessionId?: string | undefined;
|
||||||
|
setProtocolVersion?: ((version: string) => void) | undefined;
|
||||||
|
|
||||||
|
_receiveFromServer(message: JSONRPCMessage, extra?: MessageExtraInfo): void {
|
||||||
|
this.onmessage?.(message, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InProcessServerTransport implements Transport {
|
||||||
|
private _clientTransport: InProcessTransport;
|
||||||
|
|
||||||
|
constructor(clientTransport: InProcessTransport) {
|
||||||
|
this._clientTransport = clientTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void> {
|
||||||
|
this._clientTransport._receiveFromServer(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
this.onclose?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
onclose?: (() => void) | undefined;
|
||||||
|
onerror?: ((error: Error) => void) | undefined;
|
||||||
|
onmessage?: ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void) | undefined;
|
||||||
|
sessionId?: string | undefined;
|
||||||
|
setProtocolVersion?: ((version: string) => void) | undefined;
|
||||||
|
_receiveFromClient(message: JSONRPCMessage): void {
|
||||||
|
this.onmessage?.(message);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user