@wordbricks/playwright-mcp 0.1.20 → 0.1.23

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.
Files changed (89) hide show
  1. package/cli-wrapper.js +15 -14
  2. package/cli.js +1 -1
  3. package/config.d.ts +11 -6
  4. package/index.d.ts +7 -5
  5. package/index.js +1 -1
  6. package/package.json +34 -57
  7. package/LICENSE +0 -202
  8. package/lib/browserContextFactory.js +0 -326
  9. package/lib/browserServerBackend.js +0 -84
  10. package/lib/config.js +0 -286
  11. package/lib/context.js +0 -309
  12. package/lib/extension/cdpRelay.js +0 -346
  13. package/lib/extension/extensionContextFactory.js +0 -56
  14. package/lib/frameworkPatterns.js +0 -35
  15. package/lib/hooks/antiBotDetectionHook.js +0 -171
  16. package/lib/hooks/core.js +0 -144
  17. package/lib/hooks/eventConsumer.js +0 -52
  18. package/lib/hooks/events.js +0 -42
  19. package/lib/hooks/formatToolCallEvent.js +0 -16
  20. package/lib/hooks/frameworkStateHook.js +0 -182
  21. package/lib/hooks/grouping.js +0 -72
  22. package/lib/hooks/jsonLdDetectionHook.js +0 -175
  23. package/lib/hooks/networkFilters.js +0 -82
  24. package/lib/hooks/networkSetup.js +0 -59
  25. package/lib/hooks/networkTrackingHook.js +0 -67
  26. package/lib/hooks/pageHeightHook.js +0 -75
  27. package/lib/hooks/registry.js +0 -42
  28. package/lib/hooks/requireTabHook.js +0 -26
  29. package/lib/hooks/schema.js +0 -89
  30. package/lib/hooks/waitHook.js +0 -33
  31. package/lib/index.js +0 -39
  32. package/lib/mcp/inProcessTransport.js +0 -72
  33. package/lib/mcp/proxyBackend.js +0 -115
  34. package/lib/mcp/server.js +0 -86
  35. package/lib/mcp/tool.js +0 -38
  36. package/lib/mcp/transport.js +0 -181
  37. package/lib/playwrightTransformer.js +0 -497
  38. package/lib/program.js +0 -110
  39. package/lib/response.js +0 -186
  40. package/lib/sessionLog.js +0 -121
  41. package/lib/tab.js +0 -249
  42. package/lib/tools/common.js +0 -55
  43. package/lib/tools/console.js +0 -33
  44. package/lib/tools/dialogs.js +0 -47
  45. package/lib/tools/evaluate.js +0 -53
  46. package/lib/tools/extractFrameworkState.js +0 -214
  47. package/lib/tools/files.js +0 -45
  48. package/lib/tools/form.js +0 -57
  49. package/lib/tools/getSnapshot.js +0 -37
  50. package/lib/tools/getVisibleHtml.js +0 -52
  51. package/lib/tools/install.js +0 -51
  52. package/lib/tools/keyboard.js +0 -78
  53. package/lib/tools/mouse.js +0 -99
  54. package/lib/tools/navigate.js +0 -70
  55. package/lib/tools/network.js +0 -123
  56. package/lib/tools/networkDetail.js +0 -229
  57. package/lib/tools/networkSearch/bodySearch.js +0 -147
  58. package/lib/tools/networkSearch/grouping.js +0 -28
  59. package/lib/tools/networkSearch/helpers.js +0 -32
  60. package/lib/tools/networkSearch/searchHtml.js +0 -67
  61. package/lib/tools/networkSearch/types.js +0 -1
  62. package/lib/tools/networkSearch/urlSearch.js +0 -82
  63. package/lib/tools/networkSearch.js +0 -268
  64. package/lib/tools/pdf.js +0 -40
  65. package/lib/tools/repl.js +0 -402
  66. package/lib/tools/screenshot.js +0 -79
  67. package/lib/tools/scroll.js +0 -126
  68. package/lib/tools/snapshot.js +0 -144
  69. package/lib/tools/tabs.js +0 -59
  70. package/lib/tools/tool.js +0 -33
  71. package/lib/tools/utils.js +0 -74
  72. package/lib/tools/wait.js +0 -55
  73. package/lib/tools.js +0 -67
  74. package/lib/utils/adBlockFilter.js +0 -87
  75. package/lib/utils/codegen.js +0 -51
  76. package/lib/utils/extensionPath.js +0 -10
  77. package/lib/utils/fileUtils.js +0 -36
  78. package/lib/utils/graphql.js +0 -258
  79. package/lib/utils/guid.js +0 -22
  80. package/lib/utils/httpServer.js +0 -39
  81. package/lib/utils/log.js +0 -21
  82. package/lib/utils/manualPromise.js +0 -111
  83. package/lib/utils/networkFormat.js +0 -12
  84. package/lib/utils/package.js +0 -20
  85. package/lib/utils/result.js +0 -2
  86. package/lib/utils/sanitizeHtml.js +0 -98
  87. package/lib/utils/truncate.js +0 -103
  88. package/lib/utils/withTimeout.js +0 -7
  89. package/src/index.ts +0 -50
@@ -1,75 +0,0 @@
1
- import { Ok, Err } from '../utils/result.js';
2
- import { hookNameSchema } from './schema.js';
3
- import { trackEvent } from './events.js';
4
- const pageHeightStateMap = new WeakMap();
5
- const capturePageHeight = async (ctx) => {
6
- const tab = ctx.tab || ctx.context.currentTab();
7
- if (!tab)
8
- return undefined;
9
- try {
10
- return await tab.page.evaluate(() => ({
11
- pageHeight: document.documentElement.scrollHeight,
12
- scrollY: window.scrollY
13
- }));
14
- }
15
- catch {
16
- return undefined;
17
- }
18
- };
19
- const pageHeightPreHook = {
20
- name: hookNameSchema.enum['page-height-pre'],
21
- handler: async (ctx) => {
22
- try {
23
- const initialState = await capturePageHeight(ctx);
24
- if (!initialState)
25
- return Ok(undefined);
26
- // Store initial state for post-hook (keyed by Context)
27
- pageHeightStateMap.set(ctx.context, initialState);
28
- return Ok(undefined);
29
- }
30
- catch (error) {
31
- return Err(error);
32
- }
33
- },
34
- };
35
- const pageHeightPostHook = {
36
- name: hookNameSchema.enum['page-height-post'],
37
- handler: async (ctx) => {
38
- try {
39
- const initialState = pageHeightStateMap.get(ctx.context);
40
- if (!initialState)
41
- return Ok(undefined);
42
- const finalState = await capturePageHeight(ctx);
43
- if (!finalState)
44
- return Ok(undefined);
45
- const heightChange = finalState.pageHeight - initialState.pageHeight;
46
- const tab = ctx.tab || ctx.context.currentTab();
47
- const url = tab ? tab.page.url() : 'unknown';
48
- trackEvent(ctx.context, {
49
- type: 'page-height-change',
50
- data: {
51
- previousHeight: initialState.pageHeight,
52
- currentHeight: finalState.pageHeight,
53
- heightChange: heightChange,
54
- scrollY: finalState.scrollY,
55
- url: url,
56
- },
57
- timestamp: Date.now(),
58
- });
59
- return Ok(undefined);
60
- }
61
- catch (error) {
62
- return Err(error);
63
- }
64
- },
65
- };
66
- export const pageHeightHooks = {
67
- pre: pageHeightPreHook,
68
- post: pageHeightPostHook,
69
- };
70
- export const formatPageHeightEvent = (event) => {
71
- const { previousHeight, currentHeight, heightChange } = event.data;
72
- if (heightChange === 0)
73
- return `Page height unchanged.`;
74
- return `Page height changed: ${previousHeight}px → ${currentHeight}px (${heightChange > 0 ? '+' : ''}${heightChange}px)`;
75
- };
@@ -1,42 +0,0 @@
1
- import { pipe, reduce, values } from '@fxts/core';
2
- import { createHookRegistry, setToolHooks } from './core.js';
3
- import { networkTrackingHooks } from './networkTrackingHook.js';
4
- import { pageHeightHooks } from './pageHeightHook.js';
5
- import { waitHooks } from './waitHook.js';
6
- import { frameworkStateHooks } from './frameworkStateHook.js';
7
- import { jsonLdDetectionHooks } from './jsonLdDetectionHook.js';
8
- import { toolNameSchema } from './schema.js';
9
- import { requireTabHooks } from './requireTabHook.js';
10
- import { registerGroupingRule } from './grouping.js';
11
- import { networkGroupingRule } from './networkTrackingHook.js';
12
- import { antiBotDetectionHooks } from './antiBotDetectionHook.js';
13
- const COMMON_HOOKS = {
14
- preHooks: [
15
- requireTabHooks.pre,
16
- networkTrackingHooks.pre,
17
- antiBotDetectionHooks.pre,
18
- pageHeightHooks.pre,
19
- frameworkStateHooks.pre,
20
- jsonLdDetectionHooks.pre
21
- ],
22
- postHooks: [
23
- networkTrackingHooks.post,
24
- antiBotDetectionHooks.post,
25
- frameworkStateHooks.post,
26
- jsonLdDetectionHooks.post,
27
- pageHeightHooks.post,
28
- waitHooks.post
29
- ],
30
- };
31
- const toolHooksConfig = [
32
- // Example hook registration
33
- // {
34
- // toolName: toolNameSchema.enum.browser_click,
35
- // preHooks: [],
36
- // postHooks: [],
37
- // },
38
- ];
39
- export const buildHookRegistry = () => {
40
- registerGroupingRule('network-request', networkGroupingRule);
41
- return pipe(createHookRegistry(), registry => reduce((acc, toolName) => setToolHooks(acc, { toolName, ...COMMON_HOOKS }), registry, values(toolNameSchema.enum)), registry => reduce(setToolHooks, registry, toolHooksConfig));
42
- };
@@ -1,26 +0,0 @@
1
- import { Ok, Err } from '../utils/result.js';
2
- import { hookNameSchema, toolNameSchema } from './schema.js';
3
- const MESSAGE = 'No open tabs. Use the "browser_navigate" tool to navigate to a page first.';
4
- const requireTabPreHook = {
5
- name: hookNameSchema.enum['require-tab-pre'],
6
- handler: async (ctx) => {
7
- try {
8
- // Allow navigate tool to create/open a tab
9
- if (ctx.toolName === toolNameSchema.enum.browser_navigate)
10
- return Ok(undefined);
11
- // If no current tab, emit a standardized error and stop execution
12
- if (!ctx.context.currentTab()) {
13
- // Include tabs section in response so the message is rendered consistently
14
- ctx.response.setIncludeTabs();
15
- return Err(new Error(MESSAGE));
16
- }
17
- return Ok(undefined);
18
- }
19
- catch (error) {
20
- return Err(error);
21
- }
22
- },
23
- };
24
- export const requireTabHooks = {
25
- pre: requireTabPreHook,
26
- };
@@ -1,89 +0,0 @@
1
- import { z } from 'zod';
2
- export const hookNameSchema = z.enum([
3
- 'network-tracking-pre',
4
- 'network-tracking-post',
5
- 'page-height-pre',
6
- 'page-height-post',
7
- 'wait-post',
8
- 'framework-state-pre',
9
- 'framework-state-post',
10
- 'json-ld-detection-pre',
11
- 'json-ld-detection-post',
12
- 'require-tab-pre',
13
- 'anti-bot-detection-pre',
14
- 'anti-bot-detection-post',
15
- ]);
16
- // Tool names enum - should match actual tool names in
17
- export const toolNameSchema = z.enum([
18
- 'browser_click',
19
- 'browser_extract_framework_state',
20
- 'browser_fill_form',
21
- 'browser_get_snapshot',
22
- // 'browser_get_visible_html',
23
- 'browser_navigate',
24
- // 'browser_navigate_back',
25
- // 'browser_navigate_forward',
26
- 'browser_network_detail',
27
- 'browser_network_search',
28
- 'browser_press_key',
29
- 'browser_reload',
30
- 'browser_repl',
31
- 'browser_scroll',
32
- 'browser_type',
33
- 'browser_wait',
34
- ]);
35
- export const EventTypeSchema = z.enum([
36
- 'network-request',
37
- 'page-height-change',
38
- 'wait',
39
- 'tool-call',
40
- 'framework-state',
41
- 'json-ld',
42
- 'anti-bot',
43
- ]);
44
- export const NetworkRequestEventDataSchema = z.object({
45
- method: z.string(),
46
- url: z.string(),
47
- status: z.number(),
48
- resourceType: z.string(),
49
- postData: z.string().optional(),
50
- setCookies: z.array(z.string()).optional(),
51
- responseSize: z.number().optional(),
52
- });
53
- export const PageHeightChangeEventDataSchema = z.object({
54
- previousHeight: z.number(),
55
- currentHeight: z.number(),
56
- heightChange: z.number(),
57
- scrollY: z.number(),
58
- url: z.string(),
59
- });
60
- export const WaitEventDataSchema = z.object({
61
- duration: z.number(),
62
- });
63
- export const ToolCallEventDataSchema = z.object({
64
- toolName: z.string(),
65
- params: z.record(z.unknown()).optional(),
66
- executionTime: z.number().optional(),
67
- success: z.boolean().optional(),
68
- });
69
- export const FrameworkStateEventDataSchema = z.object({
70
- state: z.record(z.any()),
71
- changes: z.array(z.string()).optional(),
72
- action: z.enum(['detected', 'changed']),
73
- });
74
- export const JsonLdEventDataSchema = z.object({
75
- state: z.record(z.object({
76
- count: z.number(),
77
- indices: z.array(z.number()),
78
- })),
79
- changes: z.array(z.string()).optional(),
80
- action: z.enum(['detected', 'changed']),
81
- });
82
- export const AntiBotEventDataSchema = z.object({
83
- provider: z.enum(['cloudflare-turnstile', 'aws-waf']),
84
- detectionMethod: z.literal('network-request'),
85
- url: z.string(),
86
- status: z.number(),
87
- action: z.enum(['detected', 'resolved', 'still-blocked']),
88
- waitMs: z.number().optional(),
89
- });
@@ -1,33 +0,0 @@
1
- import ms from 'ms';
2
- import { Ok, Err } from '../utils/result.js';
3
- import { trackEvent } from './events.js';
4
- import { hookNameSchema } from './schema.js';
5
- export const WAIT_TIME_STR = '0.5s';
6
- export const WAIT_TIME_MS = ms(WAIT_TIME_STR);
7
- const waitPostHook = {
8
- name: hookNameSchema.enum['wait-post'],
9
- handler: async (ctx) => {
10
- try {
11
- const tab = ctx.tab || ctx.context.currentTab();
12
- if (!tab)
13
- return Ok(undefined);
14
- const startTime = Date.now();
15
- await tab.page.waitForTimeout(WAIT_TIME_MS);
16
- trackEvent(ctx.context, {
17
- type: 'wait',
18
- data: {
19
- duration: WAIT_TIME_MS,
20
- },
21
- timestamp: startTime,
22
- });
23
- return Ok(undefined);
24
- }
25
- catch (error) {
26
- return Err(error);
27
- }
28
- },
29
- };
30
- export const waitHooks = {
31
- post: waitPostHook,
32
- };
33
- export const formatWaitEvent = (event) => `Waited ${event.data.duration}ms`;
package/lib/index.js DELETED
@@ -1,39 +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 { BrowserServerBackend } from './browserServerBackend.js';
17
- import { resolveConfig } from './config.js';
18
- import { contextFactory } from './browserContextFactory.js';
19
- import * as mcpServer from './mcp/server.js';
20
- export async function createConnection(userConfig = {}, contextGetter) {
21
- const config = await resolveConfig(userConfig);
22
- const factory = contextGetter ? new SimpleBrowserContextFactory(contextGetter) : contextFactory(config);
23
- return mcpServer.createServer(new BrowserServerBackend(config, factory), false);
24
- }
25
- class SimpleBrowserContextFactory {
26
- name = 'custom';
27
- description = 'Connect to a browser using a custom context getter';
28
- _contextGetter;
29
- constructor(contextGetter) {
30
- this._contextGetter = contextGetter;
31
- }
32
- async createContext() {
33
- const browserContext = await this._contextGetter();
34
- return {
35
- browserContext,
36
- close: () => browserContext.close()
37
- };
38
- }
39
- }
@@ -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
- }
@@ -1,115 +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 { z } from 'zod';
17
- import { zodToJsonSchema } from 'zod-to-json-schema';
18
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
19
- import { ListRootsRequestSchema, PingRequestSchema } from '@modelcontextprotocol/sdk/types.js';
20
- import { logUnhandledError } from '../utils/log.js';
21
- import { packageJSON } from '../utils/package.js';
22
- export class ProxyBackend {
23
- name = 'Playwright MCP Client Switcher';
24
- version = packageJSON.version;
25
- _mcpProviders;
26
- _currentClient;
27
- _contextSwitchTool;
28
- _roots = [];
29
- constructor(mcpProviders) {
30
- this._mcpProviders = mcpProviders;
31
- this._contextSwitchTool = this._defineContextSwitchTool();
32
- }
33
- async initialize(server) {
34
- if (this._currentClient)
35
- return;
36
- const version = server.getClientVersion();
37
- const capabilities = server.getClientCapabilities();
38
- if (capabilities?.roots && version && clientsWithRoots.includes(version.name)) {
39
- const { roots } = await server.listRoots();
40
- this._roots = roots;
41
- }
42
- await this._setCurrentClient(this._mcpProviders[0]);
43
- }
44
- async listTools() {
45
- const response = await this._currentClient.listTools();
46
- if (this._mcpProviders.length === 1)
47
- return response.tools;
48
- return [
49
- ...response.tools,
50
- this._contextSwitchTool,
51
- ];
52
- }
53
- async callTool(name, args) {
54
- if (name === this._contextSwitchTool.name)
55
- return this._callContextSwitchTool(args);
56
- return await this._currentClient.callTool({
57
- name,
58
- arguments: args,
59
- });
60
- }
61
- serverClosed() {
62
- void this._currentClient?.close().catch(logUnhandledError);
63
- this._currentClient = undefined;
64
- }
65
- async _callContextSwitchTool(params) {
66
- try {
67
- const factory = this._mcpProviders.find(factory => factory.name === params.name);
68
- if (!factory)
69
- throw new Error('Unknown connection method: ' + params.name);
70
- await this._setCurrentClient(factory);
71
- return {
72
- content: [{ type: 'text', text: '### Result\nSuccessfully changed connection method.\n' }],
73
- };
74
- }
75
- catch (error) {
76
- return {
77
- content: [{ type: 'text', text: `### Result\nError: ${error}\n` }],
78
- isError: true,
79
- };
80
- }
81
- }
82
- _defineContextSwitchTool() {
83
- return {
84
- name: 'browser_connect',
85
- description: [
86
- 'Connect to a browser using one of the available methods:',
87
- ...this._mcpProviders.map(factory => `- "${factory.name}": ${factory.description}`),
88
- ].join('\n'),
89
- inputSchema: zodToJsonSchema(z.object({
90
- name: z.enum(this._mcpProviders.map(factory => factory.name)).default(this._mcpProviders[0].name).describe('The method to use to connect to the browser'),
91
- }), { strictUnions: true }),
92
- annotations: {
93
- title: 'Connect to a browser context',
94
- readOnlyHint: true,
95
- openWorldHint: false,
96
- },
97
- };
98
- }
99
- async _setCurrentClient(factory) {
100
- await this._currentClient?.close();
101
- this._currentClient = undefined;
102
- const client = new Client({ name: 'Playwright MCP Proxy', version: packageJSON.version });
103
- client.registerCapabilities({
104
- roots: {
105
- listRoots: true,
106
- },
107
- });
108
- client.setRequestHandler(ListRootsRequestSchema, () => ({ roots: this._roots }));
109
- client.setRequestHandler(PingRequestSchema, () => ({}));
110
- const transport = await factory.connect();
111
- await client.connect(transport);
112
- this._currentClient = client;
113
- }
114
- }
115
- const clientsWithRoots = ['Visual Studio Code', 'Visual Studio Code - Insiders'];
package/lib/mcp/server.js DELETED
@@ -1,86 +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 { Server } from '@modelcontextprotocol/sdk/server/index.js';
18
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
19
- import ms from 'ms';
20
- import { ManualPromise } from '../utils/manualPromise.js';
21
- import { logUnhandledError } from '../utils/log.js';
22
- const serverDebug = debug('pw:mcp:server');
23
- export async function connect(serverBackendFactory, transport, runHeartbeat) {
24
- const backend = serverBackendFactory();
25
- const server = createServer(backend, runHeartbeat);
26
- await server.connect(transport);
27
- return { name: backend.name, version: backend.version };
28
- }
29
- export function createServer(backend, runHeartbeat) {
30
- const initializedPromise = new ManualPromise();
31
- const server = new Server({ name: backend.name, version: backend.version }, {
32
- capabilities: {
33
- tools: {},
34
- }
35
- });
36
- server.setRequestHandler(ListToolsRequestSchema, async () => {
37
- serverDebug('listTools');
38
- await initializedPromise;
39
- const tools = await backend.listTools();
40
- return { tools };
41
- });
42
- let heartbeatRunning = false;
43
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
44
- serverDebug('callTool', request);
45
- await initializedPromise;
46
- if (runHeartbeat && !heartbeatRunning) {
47
- heartbeatRunning = true;
48
- startHeartbeat(server);
49
- }
50
- try {
51
- return await backend.callTool(request.params.name, request.params.arguments || {});
52
- }
53
- catch (error) {
54
- return {
55
- content: [{ type: 'text', text: '### Result\n' + String(error) }],
56
- isError: true,
57
- };
58
- }
59
- });
60
- addServerListener(server, 'initialized', () => {
61
- backend.initialize?.(server).then(() => initializedPromise.resolve()).catch(logUnhandledError);
62
- });
63
- addServerListener(server, 'close', () => backend.serverClosed?.());
64
- return server;
65
- }
66
- const startHeartbeat = (server) => {
67
- const beat = () => {
68
- serverDebug('Health check...');
69
- Promise.race([
70
- server.ping(),
71
- new Promise((_, reject) => setTimeout(() => reject(new Error('ping timeout')), ms('5s'))),
72
- ]).then(() => {
73
- setTimeout(beat, ms('3s'));
74
- }).catch(() => {
75
- void server.close();
76
- });
77
- };
78
- beat();
79
- };
80
- function addServerListener(server, event, listener) {
81
- const oldListener = server[`on${event}`];
82
- server[`on${event}`] = () => {
83
- oldListener?.();
84
- listener();
85
- };
86
- }
package/lib/mcp/tool.js DELETED
@@ -1,38 +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 { zodToJsonSchema } from 'zod-to-json-schema';
17
- import { z } from 'zod';
18
- const typesWithIntent = ['action', 'assertion', 'input'];
19
- export function toMcpTool(tool, options) {
20
- const inputSchema = options?.addIntent && typesWithIntent.includes(tool.type) ? tool.inputSchema.extend({
21
- intent: z.string().describe('The intent of the call, for example the test step description plan idea')
22
- }) : tool.inputSchema;
23
- const readOnly = tool.type === 'readOnly' || tool.type === 'assertion';
24
- return {
25
- name: tool.name,
26
- description: tool.description,
27
- inputSchema: zodToJsonSchema(inputSchema, { strictUnions: true }),
28
- annotations: {
29
- title: tool.title,
30
- readOnlyHint: readOnly,
31
- destructiveHint: !readOnly,
32
- openWorldHint: true,
33
- },
34
- };
35
- }
36
- export function defineToolSchema(tool) {
37
- return tool;
38
- }