@playwright/mcp 0.0.13 → 0.0.14

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.
package/lib/context.js CHANGED
@@ -51,24 +51,46 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
51
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
52
52
  };
53
53
  Object.defineProperty(exports, "__esModule", { value: true });
54
- exports.Context = void 0;
54
+ exports.Tab = exports.Context = void 0;
55
55
  exports.generateLocator = generateLocator;
56
56
  const playwright = __importStar(require("playwright"));
57
57
  const yaml_1 = __importDefault(require("yaml"));
58
58
  const utils_1 = require("./tools/utils");
59
+ const manualPromise_1 = require("./manualPromise");
59
60
  class Context {
61
+ tools;
60
62
  options;
61
63
  _browser;
62
64
  _browserContext;
63
65
  _tabs = [];
64
66
  _currentTab;
65
- constructor(options) {
67
+ _modalStates = [];
68
+ _pendingAction;
69
+ constructor(tools, options) {
70
+ this.tools = tools;
66
71
  this.options = options;
67
72
  }
73
+ modalStates() {
74
+ return this._modalStates;
75
+ }
76
+ setModalState(modalState, inTab) {
77
+ this._modalStates.push({ ...modalState, tab: inTab });
78
+ }
79
+ clearModalState(modalState) {
80
+ this._modalStates = this._modalStates.filter(state => state !== modalState);
81
+ }
82
+ modalStatesMarkdown() {
83
+ const result = ['### Modal state'];
84
+ for (const state of this._modalStates) {
85
+ const tool = this.tools.find(tool => tool.clearsModalState === state.type);
86
+ result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
87
+ }
88
+ return result;
89
+ }
68
90
  tabs() {
69
91
  return this._tabs;
70
92
  }
71
- currentTab() {
93
+ currentTabOrDie() {
72
94
  if (!this._currentTab)
73
95
  throw new Error('No current snapshot available. Capture a snapshot of navigate to a new location first.');
74
96
  return this._currentTab;
@@ -89,7 +111,7 @@ class Context {
89
111
  await context.newPage();
90
112
  return this._currentTab;
91
113
  }
92
- async listTabs() {
114
+ async listTabsMarkdown() {
93
115
  if (!this._tabs.length)
94
116
  return '### No tabs open';
95
117
  const lines = ['### Open tabs'];
@@ -103,9 +125,103 @@ class Context {
103
125
  return lines.join('\n');
104
126
  }
105
127
  async closeTab(index) {
106
- const tab = index === undefined ? this.currentTab() : this._tabs[index - 1];
107
- await tab.page.close();
108
- return await this.listTabs();
128
+ const tab = index === undefined ? this._currentTab : this._tabs[index - 1];
129
+ await tab?.page.close();
130
+ return await this.listTabsMarkdown();
131
+ }
132
+ async run(tool, params) {
133
+ // Tab management is done outside of the action() call.
134
+ const toolResult = await tool.handle(this, params);
135
+ const { code, action, waitForNetwork, captureSnapshot, resultOverride } = toolResult;
136
+ const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined;
137
+ if (resultOverride)
138
+ return resultOverride;
139
+ if (!this._currentTab) {
140
+ return {
141
+ content: [{
142
+ type: 'text',
143
+ text: 'No open pages available. Use the "browser_navigate" tool to navigate to a page first.',
144
+ }],
145
+ };
146
+ }
147
+ const tab = this.currentTabOrDie();
148
+ // TODO: race against modal dialogs to resolve clicks.
149
+ let actionResult;
150
+ try {
151
+ if (waitForNetwork)
152
+ actionResult = await (0, utils_1.waitForCompletion)(this, tab.page, async () => racingAction?.()) ?? undefined;
153
+ else
154
+ actionResult = await racingAction?.() ?? undefined;
155
+ }
156
+ finally {
157
+ if (captureSnapshot && !this._javaScriptBlocked())
158
+ await tab.captureSnapshot();
159
+ }
160
+ const result = [];
161
+ result.push(`- Ran Playwright code:
162
+ \`\`\`js
163
+ ${code.join('\n')}
164
+ \`\`\`
165
+ `);
166
+ if (this.modalStates().length) {
167
+ result.push(...this.modalStatesMarkdown());
168
+ return {
169
+ content: [{
170
+ type: 'text',
171
+ text: result.join('\n'),
172
+ }],
173
+ };
174
+ }
175
+ if (this.tabs().length > 1)
176
+ result.push(await this.listTabsMarkdown(), '');
177
+ if (this.tabs().length > 1)
178
+ result.push('### Current tab');
179
+ result.push(`- Page URL: ${tab.page.url()}`, `- Page Title: ${await tab.page.title()}`);
180
+ if (captureSnapshot && tab.hasSnapshot())
181
+ result.push(tab.snapshotOrDie().text());
182
+ const content = actionResult?.content ?? [];
183
+ return {
184
+ content: [
185
+ ...content,
186
+ {
187
+ type: 'text',
188
+ text: result.join('\n'),
189
+ }
190
+ ],
191
+ };
192
+ }
193
+ async waitForTimeout(time) {
194
+ if (this._currentTab && !this._javaScriptBlocked())
195
+ await this._currentTab.page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
196
+ else
197
+ await new Promise(f => setTimeout(f, time));
198
+ }
199
+ async _raceAgainstModalDialogs(action) {
200
+ this._pendingAction = {
201
+ dialogShown: new manualPromise_1.ManualPromise(),
202
+ };
203
+ let result;
204
+ try {
205
+ await Promise.race([
206
+ action().then(r => result = r),
207
+ this._pendingAction.dialogShown,
208
+ ]);
209
+ }
210
+ finally {
211
+ this._pendingAction = undefined;
212
+ }
213
+ return result;
214
+ }
215
+ _javaScriptBlocked() {
216
+ return this._modalStates.some(state => state.type === 'dialog');
217
+ }
218
+ dialogShown(tab, dialog) {
219
+ this.setModalState({
220
+ type: 'dialog',
221
+ description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
222
+ dialog,
223
+ }, tab);
224
+ this._pendingAction?.dialogShown.resolve();
109
225
  }
110
226
  _onPageCreated(page) {
111
227
  const tab = new Tab(this, page, tab => this._onPageClosed(tab));
@@ -114,23 +230,26 @@ class Context {
114
230
  this._currentTab = tab;
115
231
  }
116
232
  _onPageClosed(tab) {
233
+ this._modalStates = this._modalStates.filter(state => state.tab !== tab);
117
234
  const index = this._tabs.indexOf(tab);
118
235
  if (index === -1)
119
236
  return;
120
237
  this._tabs.splice(index, 1);
121
238
  if (this._currentTab === tab)
122
239
  this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)];
123
- const browser = this._browser;
124
- if (this._browserContext && !this._tabs.length) {
125
- void this._browserContext.close().then(() => browser?.close()).catch(() => { });
126
- this._browser = undefined;
127
- this._browserContext = undefined;
128
- }
240
+ if (this._browserContext && !this._tabs.length)
241
+ void this.close();
129
242
  }
130
243
  async close() {
131
244
  if (!this._browserContext)
132
245
  return;
133
- await this._browserContext.close();
246
+ const browserContext = this._browserContext;
247
+ const browser = this._browser;
248
+ this._browserContext = undefined;
249
+ this._browser = undefined;
250
+ await browserContext?.close().then(async () => {
251
+ await browser?.close();
252
+ }).catch(() => { });
134
253
  }
135
254
  async _ensureBrowserContext() {
136
255
  if (!this._browserContext) {
@@ -179,7 +298,6 @@ class Tab {
179
298
  context;
180
299
  page;
181
300
  _console = [];
182
- _fileChooser;
183
301
  _snapshot;
184
302
  _onPageClose;
185
303
  constructor(context, page, onPageClose) {
@@ -192,12 +310,18 @@ class Tab {
192
310
  this._console.length = 0;
193
311
  });
194
312
  page.on('close', () => this._onClose());
195
- page.on('filechooser', chooser => this._fileChooser = chooser);
313
+ page.on('filechooser', chooser => {
314
+ this.context.setModalState({
315
+ type: 'fileChooser',
316
+ description: 'File chooser',
317
+ fileChooser: chooser,
318
+ }, this);
319
+ });
320
+ page.on('dialog', dialog => this.context.dialogShown(this, dialog));
196
321
  page.setDefaultNavigationTimeout(60000);
197
322
  page.setDefaultTimeout(5000);
198
323
  }
199
324
  _onClose() {
200
- this._fileChooser = undefined;
201
325
  this._console.length = 0;
202
326
  this._onPageClose(this);
203
327
  }
@@ -206,54 +330,10 @@ class Tab {
206
330
  // Cap load event to 5 seconds, the page is operational at this point.
207
331
  await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
208
332
  }
209
- async run(callback, options) {
210
- let runResult;
211
- try {
212
- if (!options?.noClearFileChooser)
213
- this._fileChooser = undefined;
214
- if (options?.waitForCompletion)
215
- runResult = await (0, utils_1.waitForCompletion)(this.page, () => callback(this)) ?? undefined;
216
- else
217
- runResult = await callback(this) ?? undefined;
218
- }
219
- finally {
220
- if (options?.captureSnapshot)
221
- this._snapshot = await PageSnapshot.create(this.page);
222
- }
223
- const result = [];
224
- result.push(`- Ran code:
225
- \`\`\`js
226
- ${runResult.code.join('\n')}
227
- \`\`\`
228
- `);
229
- if (this.context.tabs().length > 1)
230
- result.push(await this.context.listTabs(), '');
231
- if (this._snapshot) {
232
- if (this.context.tabs().length > 1)
233
- result.push('### Current tab');
234
- result.push(this._snapshot.text({ hasFileChooser: !!this._fileChooser }));
235
- }
236
- return {
237
- content: [{
238
- type: 'text',
239
- text: result.join('\n'),
240
- }],
241
- };
242
- }
243
- async runAndWait(callback, options) {
244
- return await this.run(callback, {
245
- waitForCompletion: true,
246
- ...options,
247
- });
333
+ hasSnapshot() {
334
+ return !!this._snapshot;
248
335
  }
249
- async runAndWaitWithSnapshot(callback, options) {
250
- return await this.run(tab => callback(tab.lastSnapshot()), {
251
- captureSnapshot: true,
252
- waitForCompletion: true,
253
- ...options,
254
- });
255
- }
256
- lastSnapshot() {
336
+ snapshotOrDie() {
257
337
  if (!this._snapshot)
258
338
  throw new Error('No snapshot available');
259
339
  return this._snapshot;
@@ -261,13 +341,11 @@ ${runResult.code.join('\n')}
261
341
  async console() {
262
342
  return this._console;
263
343
  }
264
- async submitFileChooser(paths) {
265
- if (!this._fileChooser)
266
- throw new Error('No file chooser visible');
267
- await this._fileChooser.setFiles(paths);
268
- this._fileChooser = undefined;
344
+ async captureSnapshot() {
345
+ this._snapshot = await PageSnapshot.create(this.page);
269
346
  }
270
347
  }
348
+ exports.Tab = Tab;
271
349
  class PageSnapshot {
272
350
  _frameLocators = [];
273
351
  _text;
@@ -278,21 +356,17 @@ class PageSnapshot {
278
356
  await snapshot._build(page);
279
357
  return snapshot;
280
358
  }
281
- text(options) {
282
- const results = [];
283
- if (options.hasFileChooser) {
284
- results.push('- There is a file chooser visible that requires browser_file_upload to be called');
285
- results.push('');
286
- }
287
- results.push(this._text);
288
- return results.join('\n');
359
+ text() {
360
+ return this._text;
289
361
  }
290
362
  async _build(page) {
291
363
  const yamlDocument = await this._snapshotFrame(page);
292
- const lines = [];
293
- lines.push(`- Page URL: ${page.url()}`, `- Page Title: ${await page.title()}`);
294
- lines.push(`- Page Snapshot`, '```yaml', yamlDocument.toString().trim(), '```', '');
295
- this._text = lines.join('\n');
364
+ this._text = [
365
+ `- Page Snapshot`,
366
+ '```yaml',
367
+ yamlDocument.toString({ indentSeq: false }).trim(),
368
+ '```',
369
+ ].join('\n');
296
370
  }
297
371
  async _snapshotFrame(frame) {
298
372
  const frameIndex = this._frameLocators.push(frame) - 1;
package/lib/index.js CHANGED
@@ -19,8 +19,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.createServer = createServer;
22
+ const path_1 = __importDefault(require("path"));
23
+ const os_1 = __importDefault(require("os"));
24
+ const fs_1 = __importDefault(require("fs"));
22
25
  const server_1 = require("./server");
23
26
  const common_1 = __importDefault(require("./tools/common"));
27
+ const console_1 = __importDefault(require("./tools/console"));
28
+ const dialogs_1 = __importDefault(require("./tools/dialogs"));
24
29
  const files_1 = __importDefault(require("./tools/files"));
25
30
  const install_1 = __importDefault(require("./tools/install"));
26
31
  const keyboard_1 = __importDefault(require("./tools/keyboard"));
@@ -29,9 +34,10 @@ const pdf_1 = __importDefault(require("./tools/pdf"));
29
34
  const snapshot_1 = __importDefault(require("./tools/snapshot"));
30
35
  const tabs_1 = __importDefault(require("./tools/tabs"));
31
36
  const screen_1 = __importDefault(require("./tools/screen"));
32
- const console_1 = require("./resources/console");
33
37
  const snapshotTools = [
34
38
  ...(0, common_1.default)(true),
39
+ ...console_1.default,
40
+ ...(0, dialogs_1.default)(true),
35
41
  ...(0, files_1.default)(true),
36
42
  ...install_1.default,
37
43
  ...(0, keyboard_1.default)(true),
@@ -42,6 +48,8 @@ const snapshotTools = [
42
48
  ];
43
49
  const screenshotTools = [
44
50
  ...(0, common_1.default)(false),
51
+ ...console_1.default,
52
+ ...(0, dialogs_1.default)(false),
45
53
  ...(0, files_1.default)(false),
46
54
  ...install_1.default,
47
55
  ...(0, keyboard_1.default)(false),
@@ -50,21 +58,65 @@ const screenshotTools = [
50
58
  ...screen_1.default,
51
59
  ...(0, tabs_1.default)(false),
52
60
  ];
53
- const resources = [
54
- console_1.console,
55
- ];
56
61
  const packageJSON = require('../package.json');
57
- function createServer(options) {
62
+ async function createServer(options) {
63
+ let browserName;
64
+ let channel;
65
+ switch (options?.browser) {
66
+ case 'chrome':
67
+ case 'chrome-beta':
68
+ case 'chrome-canary':
69
+ case 'chrome-dev':
70
+ case 'msedge':
71
+ case 'msedge-beta':
72
+ case 'msedge-canary':
73
+ case 'msedge-dev':
74
+ browserName = 'chromium';
75
+ channel = options.browser;
76
+ break;
77
+ case 'chromium':
78
+ browserName = 'chromium';
79
+ break;
80
+ case 'firefox':
81
+ browserName = 'firefox';
82
+ break;
83
+ case 'webkit':
84
+ browserName = 'webkit';
85
+ break;
86
+ default:
87
+ browserName = 'chromium';
88
+ channel = 'chrome';
89
+ }
90
+ const userDataDir = options?.userDataDir ?? await createUserDataDir(browserName);
91
+ const launchOptions = {
92
+ headless: !!(options?.headless ?? (os_1.default.platform() === 'linux' && !process.env.DISPLAY)),
93
+ channel,
94
+ executablePath: options?.executablePath,
95
+ };
58
96
  const allTools = options?.vision ? screenshotTools : snapshotTools;
59
97
  const tools = allTools.filter(tool => !options?.capabilities || tool.capability === 'core' || options.capabilities.includes(tool.capability));
60
98
  return (0, server_1.createServerWithTools)({
61
99
  name: 'Playwright',
62
100
  version: packageJSON.version,
63
101
  tools,
64
- resources,
65
- browserName: options?.browserName,
66
- userDataDir: options?.userDataDir ?? '',
67
- launchOptions: options?.launchOptions,
102
+ resources: [],
103
+ browserName,
104
+ userDataDir,
105
+ launchOptions,
68
106
  cdpEndpoint: options?.cdpEndpoint,
69
107
  });
70
108
  }
109
+ async function createUserDataDir(browserName) {
110
+ let cacheDirectory;
111
+ if (process.platform === 'linux')
112
+ cacheDirectory = process.env.XDG_CACHE_HOME || path_1.default.join(os_1.default.homedir(), '.cache');
113
+ else if (process.platform === 'darwin')
114
+ cacheDirectory = path_1.default.join(os_1.default.homedir(), 'Library', 'Caches');
115
+ else if (process.platform === 'win32')
116
+ cacheDirectory = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
117
+ else
118
+ throw new Error('Unsupported platform: ' + process.platform);
119
+ const result = path_1.default.join(cacheDirectory, 'ms-playwright', `mcp-${browserName}-profile`);
120
+ await fs_1.default.promises.mkdir(result, { recursive: true });
121
+ return result;
122
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.LongStandingScope = exports.ManualPromise = void 0;
19
+ class ManualPromise extends Promise {
20
+ _resolve;
21
+ _reject;
22
+ _isDone;
23
+ constructor() {
24
+ let resolve;
25
+ let reject;
26
+ super((f, r) => {
27
+ resolve = f;
28
+ reject = r;
29
+ });
30
+ this._isDone = false;
31
+ this._resolve = resolve;
32
+ this._reject = reject;
33
+ }
34
+ isDone() {
35
+ return this._isDone;
36
+ }
37
+ resolve(t) {
38
+ this._isDone = true;
39
+ this._resolve(t);
40
+ }
41
+ reject(e) {
42
+ this._isDone = true;
43
+ this._reject(e);
44
+ }
45
+ static get [Symbol.species]() {
46
+ return Promise;
47
+ }
48
+ get [Symbol.toStringTag]() {
49
+ return 'ManualPromise';
50
+ }
51
+ }
52
+ exports.ManualPromise = ManualPromise;
53
+ class LongStandingScope {
54
+ _terminateError;
55
+ _closeError;
56
+ _terminatePromises = new Map();
57
+ _isClosed = false;
58
+ reject(error) {
59
+ this._isClosed = true;
60
+ this._terminateError = error;
61
+ for (const p of this._terminatePromises.keys())
62
+ p.resolve(error);
63
+ }
64
+ close(error) {
65
+ this._isClosed = true;
66
+ this._closeError = error;
67
+ for (const [p, frames] of this._terminatePromises)
68
+ p.resolve(cloneError(error, frames));
69
+ }
70
+ isClosed() {
71
+ return this._isClosed;
72
+ }
73
+ static async raceMultiple(scopes, promise) {
74
+ return Promise.race(scopes.map(s => s.race(promise)));
75
+ }
76
+ async race(promise) {
77
+ return this._race(Array.isArray(promise) ? promise : [promise], false);
78
+ }
79
+ async safeRace(promise, defaultValue) {
80
+ return this._race([promise], true, defaultValue);
81
+ }
82
+ async _race(promises, safe, defaultValue) {
83
+ const terminatePromise = new ManualPromise();
84
+ const frames = captureRawStack();
85
+ if (this._terminateError)
86
+ terminatePromise.resolve(this._terminateError);
87
+ if (this._closeError)
88
+ terminatePromise.resolve(cloneError(this._closeError, frames));
89
+ this._terminatePromises.set(terminatePromise, frames);
90
+ try {
91
+ return await Promise.race([
92
+ terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
93
+ ...promises
94
+ ]);
95
+ }
96
+ finally {
97
+ this._terminatePromises.delete(terminatePromise);
98
+ }
99
+ }
100
+ }
101
+ exports.LongStandingScope = LongStandingScope;
102
+ function cloneError(error, frames) {
103
+ const clone = new Error();
104
+ clone.name = error.name;
105
+ clone.message = error.message;
106
+ clone.stack = [error.name + ':' + error.message, ...frames].join('\n');
107
+ return clone;
108
+ }
109
+ function captureRawStack() {
110
+ const stackTraceLimit = Error.stackTraceLimit;
111
+ Error.stackTraceLimit = 50;
112
+ const error = new Error();
113
+ const stack = error.stack || '';
114
+ Error.stackTraceLimit = stackTraceLimit;
115
+ return stack.split('\n');
116
+ }
package/lib/program.js CHANGED
@@ -19,9 +19,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  const http_1 = __importDefault(require("http"));
22
- const fs_1 = __importDefault(require("fs"));
23
- const os_1 = __importDefault(require("os"));
24
- const path_1 = __importDefault(require("path"));
25
22
  const commander_1 = require("commander");
26
23
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
27
24
  const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
@@ -41,43 +38,11 @@ commander_1.program
41
38
  .option('--user-data-dir <path>', 'Path to the user data directory')
42
39
  .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
43
40
  .action(async (options) => {
44
- let browserName;
45
- let channel;
46
- switch (options.browser) {
47
- case 'chrome':
48
- case 'chrome-beta':
49
- case 'chrome-canary':
50
- case 'chrome-dev':
51
- case 'msedge':
52
- case 'msedge-beta':
53
- case 'msedge-canary':
54
- case 'msedge-dev':
55
- browserName = 'chromium';
56
- channel = options.browser;
57
- break;
58
- case 'chromium':
59
- browserName = 'chromium';
60
- break;
61
- case 'firefox':
62
- browserName = 'firefox';
63
- break;
64
- case 'webkit':
65
- browserName = 'webkit';
66
- break;
67
- default:
68
- browserName = 'chromium';
69
- channel = 'chrome';
70
- }
71
- const launchOptions = {
72
- headless: !!(options.headless ?? (os_1.default.platform() === 'linux' && !process.env.DISPLAY)),
73
- channel,
74
- executablePath: options.executablePath,
75
- };
76
- const userDataDir = options.userDataDir ?? await createUserDataDir(browserName);
77
41
  const serverList = new server_1.ServerList(() => (0, index_1.createServer)({
78
- browserName,
79
- userDataDir,
80
- launchOptions,
42
+ browser: options.browser,
43
+ userDataDir: options.userDataDir,
44
+ headless: options.headless,
45
+ executablePath: options.executablePath,
81
46
  vision: !!options.vision,
82
47
  cdpEndpoint: options.cdpEndpoint,
83
48
  capabilities: options.caps?.split(',').map((c) => c.trim()),
@@ -102,20 +67,6 @@ function setupExitWatchdog(serverList) {
102
67
  process.on('SIGTERM', handleExit);
103
68
  }
104
69
  commander_1.program.parse(process.argv);
105
- async function createUserDataDir(browserName) {
106
- let cacheDirectory;
107
- if (process.platform === 'linux')
108
- cacheDirectory = process.env.XDG_CACHE_HOME || path_1.default.join(os_1.default.homedir(), '.cache');
109
- else if (process.platform === 'darwin')
110
- cacheDirectory = path_1.default.join(os_1.default.homedir(), 'Library', 'Caches');
111
- else if (process.platform === 'win32')
112
- cacheDirectory = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
113
- else
114
- throw new Error('Unsupported platform: ' + process.platform);
115
- const result = path_1.default.join(cacheDirectory, 'ms-playwright', `mcp-${browserName}-profile`);
116
- await fs_1.default.promises.mkdir(result, { recursive: true });
117
- return result;
118
- }
119
70
  async function startSSEServer(port, serverList) {
120
71
  const sessions = new Map();
121
72
  const httpServer = http_1.default.createServer(async (req, res) => {
package/lib/server.js CHANGED
@@ -22,7 +22,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
22
22
  const context_1 = require("./context");
23
23
  function createServerWithTools(options) {
24
24
  const { name, version, tools, resources } = options;
25
- const context = new context_1.Context(options);
25
+ const context = new context_1.Context(tools, options);
26
26
  const server = new index_js_1.Server({ name, version }, {
27
27
  capabilities: {
28
28
  tools: {},
@@ -43,9 +43,20 @@ function createServerWithTools(options) {
43
43
  isError: true,
44
44
  };
45
45
  }
46
+ const modalStates = context.modalStates().map(state => state.type);
47
+ if ((tool.clearsModalState && !modalStates.includes(tool.clearsModalState)) ||
48
+ (!tool.clearsModalState && modalStates.length)) {
49
+ const text = [
50
+ `Tool "${request.params.name}" does not handle the modal state.`,
51
+ ...context.modalStatesMarkdown(),
52
+ ].join('\n');
53
+ return {
54
+ content: [{ type: 'text', text }],
55
+ isError: true,
56
+ };
57
+ }
46
58
  try {
47
- const result = await tool.handle(context, request.params.arguments);
48
- return result;
59
+ return await context.run(tool, request.params.arguments);
49
60
  }
50
61
  catch (error) {
51
62
  return {
@@ -75,7 +86,7 @@ class ServerList {
75
86
  this._serverFactory = serverFactory;
76
87
  }
77
88
  async create() {
78
- const server = this._serverFactory();
89
+ const server = await this._serverFactory();
79
90
  this._servers.push(server);
80
91
  return server;
81
92
  }