mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-26 08:32: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
|
LICENSE
|
||||||
!lib/**/*.js
|
!lib/**/*.js
|
||||||
!cli.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
|
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.
|
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
|
### Snapshot Mode
|
||||||
|
|
||||||
The Playwright MCP provides a set of tools for browser automation. Here are all available tools:
|
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"
|
"test": "playwright test"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
"./servers/server": "./lib/servers/server.js",
|
"./package.json": "./package.json",
|
||||||
"./servers/screenshot": "./lib/servers/screenshot.js",
|
".": {
|
||||||
"./servers/snapshot": "./lib/servers/snapshot.js",
|
"types": "./index.d.ts",
|
||||||
"./tools/common": "./lib/tools/common.js",
|
"default": "./index.js"
|
||||||
"./tools/screenshot": "./lib/tools/screenshot.js",
|
}
|
||||||
"./tools/snapshot": "./lib/tools/snapshot.js",
|
|
||||||
"./package.json": "./package.json"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.6.1",
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
||||||
|
@ -17,13 +17,13 @@
|
|||||||
import * as playwright from 'playwright';
|
import * as playwright from 'playwright';
|
||||||
|
|
||||||
export class Context {
|
export class Context {
|
||||||
private _launchOptions: playwright.LaunchOptions;
|
private _launchOptions: playwright.LaunchOptions | undefined;
|
||||||
private _browser: playwright.Browser | undefined;
|
private _browser: playwright.Browser | undefined;
|
||||||
private _page: playwright.Page | undefined;
|
private _page: playwright.Page | undefined;
|
||||||
private _console: playwright.ConsoleMessage[] = [];
|
private _console: playwright.ConsoleMessage[] = [];
|
||||||
private _initializePromise: Promise<void> | undefined;
|
private _initializePromise: Promise<void> | undefined;
|
||||||
|
|
||||||
constructor(launchOptions: playwright.LaunchOptions) {
|
constructor(launchOptions?: playwright.LaunchOptions) {
|
||||||
this._launchOptions = 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) {
|
if (process.env.PLAYWRIGHT_WS_ENDPOINT) {
|
||||||
const url = new URL(process.env.PLAYWRIGHT_WS_ENDPOINT);
|
const url = new URL(process.env.PLAYWRIGHT_WS_ENDPOINT);
|
||||||
url.searchParams.set('launch-options', JSON.stringify(launchOptions));
|
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 { program } from 'commander';
|
||||||
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
|
|
||||||
import { Server } from './server';
|
import { createServer } from './index';
|
||||||
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 { LaunchOptions } from './server';
|
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
import type { Tool } from './tools/tool';
|
import type { LaunchOptions } from 'playwright';
|
||||||
import type { Resource } from './resources/resource';
|
|
||||||
|
|
||||||
const packageJSON = require('../package.json');
|
const packageJSON = require('../package.json');
|
||||||
|
|
||||||
@ -37,57 +33,19 @@ program
|
|||||||
const launchOptions: LaunchOptions = {
|
const launchOptions: LaunchOptions = {
|
||||||
headless: !!options.headless,
|
headless: !!options.headless,
|
||||||
};
|
};
|
||||||
const tools = options.vision ? screenshotTools : snapshotTools;
|
const server = createServer({ launchOptions });
|
||||||
const server = new Server({
|
|
||||||
name: 'Playwright',
|
|
||||||
version: packageJSON.version,
|
|
||||||
tools,
|
|
||||||
resources,
|
|
||||||
}, launchOptions);
|
|
||||||
setupExitWatchdog(server);
|
setupExitWatchdog(server);
|
||||||
await server.start();
|
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupExitWatchdog(server: Server) {
|
function setupExitWatchdog(server: Server) {
|
||||||
process.stdin.on('close', async () => {
|
process.stdin.on('close', async () => {
|
||||||
setTimeout(() => process.exit(0), 15000);
|
setTimeout(() => process.exit(0), 15000);
|
||||||
await server?.stop();
|
await server.close();
|
||||||
process.exit(0);
|
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);
|
program.parse(process.argv);
|
||||||
|
107
src/server.ts
107
src/server.ts
@ -14,80 +14,65 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Server as MCPServer } from '@modelcontextprotocol/sdk/server/index.js';
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
||||||
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||||
|
|
||||||
import { Context } from './context';
|
import { Context } from './context';
|
||||||
|
|
||||||
import type { Tool } from './tools/tool';
|
import type { Tool } from './tools/tool';
|
||||||
import type { Resource } from './resources/resource';
|
import type { Resource } from './resources/resource';
|
||||||
|
import type { LaunchOptions } from 'playwright';
|
||||||
|
|
||||||
export type LaunchOptions = {
|
export function createServerWithTools(name: string, version: string, tools: Tool[], resources: Resource[], launchOption?: LaunchOptions): Server {
|
||||||
headless?: boolean;
|
const context = new Context(launchOption);
|
||||||
};
|
const server = new Server({ name, version }, {
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
resources: {},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export class Server {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
private _server: MCPServer;
|
return { tools: tools.map(tool => tool.schema) };
|
||||||
private _tools: Tool[];
|
});
|
||||||
private _context: Context;
|
|
||||||
|
|
||||||
constructor(options: { name: string, version: string, tools: Tool[], resources: Resource[] }, launchOptions: LaunchOptions) {
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||||
const { name, version, tools, resources } = options;
|
return { resources: resources.map(resource => resource.schema) };
|
||||||
this._context = new Context(launchOptions);
|
});
|
||||||
this._server = new MCPServer({ name, version }, {
|
|
||||||
capabilities: {
|
|
||||||
tools: {},
|
|
||||||
resources: {},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this._tools = tools;
|
|
||||||
|
|
||||||
this._server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(CallToolRequestSchema, async request => {
|
||||||
return { tools: tools.map(tool => tool.schema) };
|
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 () => {
|
try {
|
||||||
return { resources: resources.map(resource => resource.schema) };
|
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 => {
|
server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
||||||
const tool = this._tools.find(tool => tool.schema.name === request.params.name);
|
const resource = resources.find(resource => resource.schema.uri === request.params.uri);
|
||||||
if (!tool) {
|
if (!resource)
|
||||||
return {
|
return { contents: [] };
|
||||||
content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
|
|
||||||
isError: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
const contents = await resource.read(context, request.params.uri);
|
||||||
const result = await tool.handle(this._context, request.params.arguments);
|
return { contents };
|
||||||
return result;
|
});
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
content: [{ type: 'text', text: String(error) }],
|
|
||||||
isError: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
server.close = async () => {
|
||||||
const resource = resources.find(resource => resource.schema.uri === request.params.uri);
|
await server.close();
|
||||||
if (!resource)
|
await context.close();
|
||||||
return { contents: [] };
|
};
|
||||||
|
|
||||||
const contents = await resource.read(this._context, request.params.uri);
|
return server;
|
||||||
return { contents };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
const transport = new StdioServerTransport();
|
|
||||||
await this._server.connect(transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
await this._server.close();
|
|
||||||
await this._context.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user