chore: separate proxy client from external (#877)

This commit is contained in:
Yury Semikhatsky
2025-08-12 18:05:45 -07:00
committed by GitHub
parent c091a11d76
commit 8572ab300c
4 changed files with 58 additions and 55 deletions

View File

@@ -14,48 +14,51 @@
* limitations under the License.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { ListRootsRequestSchema, PingRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { logUnhandledError } from '../utils/log.js';
import { packageJSON } from '../utils/package.js';
import { ToolDefinition, ServerBackend, ToolResponse } from './server.js';
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { ToolDefinition, ServerBackend, ToolResponse } from './server.js';
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
type NonEmptyArray<T> = [T, ...T[]];
export type ClientFactory = {
export type MCPFactory = {
name: string;
description: string;
create(server: Server): Promise<Client>;
create(): Promise<Transport>;
};
export type ClientFactoryList = NonEmptyArray<ClientFactory>;
export type MCPFactoryList = NonEmptyArray<MCPFactory>;
export class ProxyBackend implements ServerBackend {
name = 'Playwright MCP Client Switcher';
version = packageJSON.version;
private _clientFactories: ClientFactoryList;
private _mcpFactories: MCPFactoryList;
private _currentClient: Client | undefined;
private _contextSwitchTool: ToolDefinition;
private _tools: ToolDefinition[] = [];
private _server: Server | undefined;
constructor(clientFactories: ClientFactoryList) {
this._clientFactories = clientFactories;
constructor(clientFactories: MCPFactoryList) {
this._mcpFactories = clientFactories;
this._contextSwitchTool = this._defineContextSwitchTool();
}
async initialize(server: Server): Promise<void> {
this._server = server;
await this._setCurrentClient(this._clientFactories[0]);
await this._setCurrentClient(this._mcpFactories[0]);
}
tools(): ToolDefinition[] {
if (this._clientFactories.length === 1)
if (this._mcpFactories.length === 1)
return this._tools;
return [
...this._tools,
@@ -79,7 +82,7 @@ export class ProxyBackend implements ServerBackend {
private async _callContextSwitchTool(params: any): Promise<ToolResponse> {
try {
const factory = this._clientFactories.find(factory => factory.name === params.name);
const factory = this._mcpFactories.find(factory => factory.name === params.name);
if (!factory)
throw new Error('Unknown connection method: ' + params.name);
@@ -100,10 +103,10 @@ export class ProxyBackend implements ServerBackend {
name: 'browser_connect',
description: [
'Connect to a browser using one of the available methods:',
...this._clientFactories.map(factory => `- "${factory.name}": ${factory.description}`),
...this._mcpFactories.map(factory => `- "${factory.name}": ${factory.description}`),
].join('\n'),
inputSchema: zodToJsonSchema(z.object({
name: z.enum(this._clientFactories.map(factory => factory.name) as [string, ...string[]]).default(this._clientFactories[0].name).describe('The method to use to connect to the browser'),
name: z.enum(this._mcpFactories.map(factory => factory.name) as [string, ...string[]]).default(this._mcpFactories[0].name).describe('The method to use to connect to the browser'),
}), { strictUnions: true }) as ToolDefinition['inputSchema'],
annotations: {
title: 'Connect to a browser context',
@@ -113,9 +116,32 @@ export class ProxyBackend implements ServerBackend {
};
}
private async _setCurrentClient(factory: ClientFactory) {
private async _setCurrentClient(factory: MCPFactory) {
await this._currentClient?.close();
this._currentClient = await factory.create(this._server!);
this._currentClient = undefined;
const client = new Client({ name: 'Playwright MCP Proxy', version: packageJSON.version });
client.registerCapabilities({
roots: {
listRoots: true,
},
});
client.setRequestHandler(ListRootsRequestSchema, async () => {
const clientName = this._server!.getClientVersion()?.name;
if (this._server!.getClientCapabilities()?.roots && (
clientName === 'Visual Studio Code' ||
clientName === 'Visual Studio Code - Insiders')) {
const { roots } = await this._server!.listRoots();
return { roots };
}
return { roots: [] };
});
client.setRequestHandler(PingRequestSchema, () => ({}));
const transport = await factory.create();
await client.connect(transport);
this._currentClient = client;
const tools = await this._currentClient.listTools();
this._tools = tools.tools;
}