mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-25 16:02:26 +08:00
chore: export server for custom transports (#20)
Fixes https://github.com/microsoft/playwright-mcp/issues/11
This commit is contained in:
parent
a394c5be52
commit
8f3214a06a
@ -3,3 +3,4 @@ README.md
|
||||
LICENSE
|
||||
!lib/**/*.js
|
||||
!cli.js
|
||||
!index.*
|
||||
|
14
README.md
14
README.md
@ -121,6 +121,20 @@ To use Vision Mode, add the `--vision` flag when starting the server:
|
||||
Vision Mode works best with the computer use models that are able to interact with elements using
|
||||
X Y coordinate space, based on the provided screenshot.
|
||||
|
||||
### Programmatic usage with custom transports
|
||||
|
||||
```js
|
||||
import { createServer } from '@playwright/mcp';
|
||||
|
||||
// ...
|
||||
|
||||
const server = createServer({
|
||||
launchOptions: { headless: true }
|
||||
});
|
||||
transport = new SSEServerTransport("/messages", res);
|
||||
server.connect(transport);
|
||||
```
|
||||
|
||||
### Snapshot Mode
|
||||
|
||||
The Playwright MCP provides a set of tools for browser automation. Here are all available tools:
|
||||
|
31
index.d.ts
vendored
Normal file
31
index.d.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 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 { LaunchOptions } from 'playwright';
|
||||
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
|
||||
type Options = {
|
||||
launchOptions?: LaunchOptions;
|
||||
/**
|
||||
* Use screenshots instead of snapshots. Less accurate, reliable and overall
|
||||
* slower, but contains visual representation of the page.
|
||||
* @default false
|
||||
*/
|
||||
vision?: boolean;
|
||||
};
|
||||
|
||||
export function createServer(options?: Options): Server;
|
19
index.js
Executable file
19
index.js
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { createServer } = require('./lib/index');
|
||||
module.exports = { createServer };
|
12
package.json
12
package.json
@ -21,13 +21,11 @@
|
||||
"test": "playwright test"
|
||||
},
|
||||
"exports": {
|
||||
"./servers/server": "./lib/servers/server.js",
|
||||
"./servers/screenshot": "./lib/servers/screenshot.js",
|
||||
"./servers/snapshot": "./lib/servers/snapshot.js",
|
||||
"./tools/common": "./lib/tools/common.js",
|
||||
"./tools/screenshot": "./lib/tools/screenshot.js",
|
||||
"./tools/snapshot": "./lib/tools/snapshot.js",
|
||||
"./package.json": "./package.json"
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"default": "./index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.6.1",
|
||||
|
@ -17,13 +17,13 @@
|
||||
import * as playwright from 'playwright';
|
||||
|
||||
export class Context {
|
||||
private _launchOptions: playwright.LaunchOptions;
|
||||
private _launchOptions: playwright.LaunchOptions | undefined;
|
||||
private _browser: playwright.Browser | undefined;
|
||||
private _page: playwright.Page | undefined;
|
||||
private _console: playwright.ConsoleMessage[] = [];
|
||||
private _initializePromise: Promise<void> | undefined;
|
||||
|
||||
constructor(launchOptions: playwright.LaunchOptions) {
|
||||
constructor(launchOptions?: playwright.LaunchOptions) {
|
||||
this._launchOptions = launchOptions;
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ export class Context {
|
||||
}
|
||||
}
|
||||
|
||||
async function createBrowser(launchOptions: playwright.LaunchOptions): Promise<playwright.Browser> {
|
||||
async function createBrowser(launchOptions?: playwright.LaunchOptions): Promise<playwright.Browser> {
|
||||
if (process.env.PLAYWRIGHT_WS_ENDPOINT) {
|
||||
const url = new URL(process.env.PLAYWRIGHT_WS_ENDPOINT);
|
||||
url.searchParams.set('launch-options', JSON.stringify(launchOptions));
|
||||
|
77
src/index.ts
Normal file
77
src/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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 { createServerWithTools } from './server';
|
||||
import * as snapshot from './tools/snapshot';
|
||||
import * as common from './tools/common';
|
||||
import * as screenshot from './tools/screenshot';
|
||||
import { console } from './resources/console';
|
||||
|
||||
import type { Tool } from './tools/tool';
|
||||
import type { Resource } from './resources/resource';
|
||||
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import type { LaunchOptions } from 'playwright';
|
||||
|
||||
const commonTools: Tool[] = [
|
||||
common.pressKey,
|
||||
common.wait,
|
||||
common.pdf,
|
||||
common.close,
|
||||
];
|
||||
|
||||
const snapshotTools: Tool[] = [
|
||||
common.navigate(true),
|
||||
common.goBack(true),
|
||||
common.goForward(true),
|
||||
snapshot.snapshot,
|
||||
snapshot.click,
|
||||
snapshot.hover,
|
||||
snapshot.type,
|
||||
...commonTools,
|
||||
];
|
||||
|
||||
const screenshotTools: Tool[] = [
|
||||
common.navigate(false),
|
||||
common.goBack(false),
|
||||
common.goForward(false),
|
||||
screenshot.screenshot,
|
||||
screenshot.moveMouse,
|
||||
screenshot.click,
|
||||
screenshot.drag,
|
||||
screenshot.type,
|
||||
...commonTools,
|
||||
];
|
||||
|
||||
const resources: Resource[] = [
|
||||
console,
|
||||
];
|
||||
|
||||
type Options = {
|
||||
vision?: boolean;
|
||||
launchOptions?: LaunchOptions;
|
||||
};
|
||||
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
export function createServer(options?: Options): Server {
|
||||
const tools = options?.vision ? screenshotTools : snapshotTools;
|
||||
return createServerWithTools(
|
||||
'Playwright',
|
||||
packageJSON.version,
|
||||
tools,
|
||||
resources,
|
||||
options?.launchOptions);
|
||||
}
|
@ -15,16 +15,12 @@
|
||||
*/
|
||||
|
||||
import { program } from 'commander';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
import { Server } from './server';
|
||||
import * as snapshot from './tools/snapshot';
|
||||
import * as common from './tools/common';
|
||||
import * as screenshot from './tools/screenshot';
|
||||
import { console } from './resources/console';
|
||||
import { createServer } from './index';
|
||||
|
||||
import type { LaunchOptions } from './server';
|
||||
import type { Tool } from './tools/tool';
|
||||
import type { Resource } from './resources/resource';
|
||||
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import type { LaunchOptions } from 'playwright';
|
||||
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
@ -37,57 +33,19 @@ program
|
||||
const launchOptions: LaunchOptions = {
|
||||
headless: !!options.headless,
|
||||
};
|
||||
const tools = options.vision ? screenshotTools : snapshotTools;
|
||||
const server = new Server({
|
||||
name: 'Playwright',
|
||||
version: packageJSON.version,
|
||||
tools,
|
||||
resources,
|
||||
}, launchOptions);
|
||||
const server = createServer({ launchOptions });
|
||||
setupExitWatchdog(server);
|
||||
await server.start();
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
function setupExitWatchdog(server: Server) {
|
||||
process.stdin.on('close', async () => {
|
||||
setTimeout(() => process.exit(0), 15000);
|
||||
await server?.stop();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
const commonTools: Tool[] = [
|
||||
common.pressKey,
|
||||
common.wait,
|
||||
common.pdf,
|
||||
common.close,
|
||||
];
|
||||
|
||||
const snapshotTools: Tool[] = [
|
||||
common.navigate(true),
|
||||
common.goBack(true),
|
||||
common.goForward(true),
|
||||
snapshot.snapshot,
|
||||
snapshot.click,
|
||||
snapshot.hover,
|
||||
snapshot.type,
|
||||
...commonTools,
|
||||
];
|
||||
|
||||
const screenshotTools: Tool[] = [
|
||||
common.navigate(false),
|
||||
common.goBack(false),
|
||||
common.goForward(false),
|
||||
screenshot.screenshot,
|
||||
screenshot.moveMouse,
|
||||
screenshot.click,
|
||||
screenshot.drag,
|
||||
screenshot.type,
|
||||
...commonTools,
|
||||
];
|
||||
|
||||
const resources: Resource[] = [
|
||||
console,
|
||||
];
|
||||
|
||||
program.parse(process.argv);
|
||||
|
107
src/server.ts
107
src/server.ts
@ -14,80 +14,65 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Server as MCPServer } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
import { Context } from './context';
|
||||
|
||||
import type { Tool } from './tools/tool';
|
||||
import type { Resource } from './resources/resource';
|
||||
import type { LaunchOptions } from 'playwright';
|
||||
|
||||
export type LaunchOptions = {
|
||||
headless?: boolean;
|
||||
};
|
||||
export function createServerWithTools(name: string, version: string, tools: Tool[], resources: Resource[], launchOption?: LaunchOptions): Server {
|
||||
const context = new Context(launchOption);
|
||||
const server = new Server({ name, version }, {
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {},
|
||||
}
|
||||
});
|
||||
|
||||
export class Server {
|
||||
private _server: MCPServer;
|
||||
private _tools: Tool[];
|
||||
private _context: Context;
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return { tools: tools.map(tool => tool.schema) };
|
||||
});
|
||||
|
||||
constructor(options: { name: string, version: string, tools: Tool[], resources: Resource[] }, launchOptions: LaunchOptions) {
|
||||
const { name, version, tools, resources } = options;
|
||||
this._context = new Context(launchOptions);
|
||||
this._server = new MCPServer({ name, version }, {
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {},
|
||||
}
|
||||
});
|
||||
this._tools = tools;
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return { resources: resources.map(resource => resource.schema) };
|
||||
});
|
||||
|
||||
this._server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return { tools: tools.map(tool => tool.schema) };
|
||||
});
|
||||
server.setRequestHandler(CallToolRequestSchema, async request => {
|
||||
const tool = tools.find(tool => tool.schema.name === request.params.name);
|
||||
if (!tool) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
this._server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return { resources: resources.map(resource => resource.schema) };
|
||||
});
|
||||
try {
|
||||
const result = await tool.handle(context, request.params.arguments);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: 'text', text: String(error) }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this._server.setRequestHandler(CallToolRequestSchema, async request => {
|
||||
const tool = this._tools.find(tool => tool.schema.name === request.params.name);
|
||||
if (!tool) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
||||
const resource = resources.find(resource => resource.schema.uri === request.params.uri);
|
||||
if (!resource)
|
||||
return { contents: [] };
|
||||
|
||||
try {
|
||||
const result = await tool.handle(this._context, request.params.arguments);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: 'text', text: String(error) }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
const contents = await resource.read(context, request.params.uri);
|
||||
return { contents };
|
||||
});
|
||||
|
||||
this._server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
||||
const resource = resources.find(resource => resource.schema.uri === request.params.uri);
|
||||
if (!resource)
|
||||
return { contents: [] };
|
||||
server.close = async () => {
|
||||
await server.close();
|
||||
await context.close();
|
||||
};
|
||||
|
||||
const contents = await resource.read(this._context, request.params.uri);
|
||||
return { contents };
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
const transport = new StdioServerTransport();
|
||||
await this._server.connect(transport);
|
||||
}
|
||||
|
||||
async stop() {
|
||||
await this._server.close();
|
||||
await this._context.close();
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user