@playwright/mcp 0.0.15 → 0.0.17

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
@@ -47,28 +47,26 @@ var __importStar = (this && this.__importStar) || (function () {
47
47
  return result;
48
48
  };
49
49
  })();
50
- var __importDefault = (this && this.__importDefault) || function (mod) {
51
- return (mod && mod.__esModule) ? mod : { "default": mod };
52
- };
53
50
  Object.defineProperty(exports, "__esModule", { value: true });
54
- exports.Tab = exports.Context = void 0;
51
+ exports.Context = void 0;
55
52
  exports.generateLocator = generateLocator;
56
53
  const playwright = __importStar(require("playwright"));
57
- const yaml_1 = __importDefault(require("yaml"));
58
54
  const utils_1 = require("./tools/utils");
59
55
  const manualPromise_1 = require("./manualPromise");
56
+ const tab_1 = require("./tab");
60
57
  class Context {
61
58
  tools;
62
- options;
59
+ config;
63
60
  _browser;
64
61
  _browserContext;
62
+ _createBrowserContextPromise;
65
63
  _tabs = [];
66
64
  _currentTab;
67
65
  _modalStates = [];
68
66
  _pendingAction;
69
- constructor(tools, options) {
67
+ constructor(tools, config) {
70
68
  this.tools = tools;
71
- this.options = options;
69
+ this.config = config;
72
70
  }
73
71
  modalStates() {
74
72
  return this._modalStates;
@@ -81,6 +79,8 @@ class Context {
81
79
  }
82
80
  modalStatesMarkdown() {
83
81
  const result = ['### Modal state'];
82
+ if (this._modalStates.length === 0)
83
+ result.push('- There is no modal state present');
84
84
  for (const state of this._modalStates) {
85
85
  const tool = this.tools.find(tool => tool.clearsModalState === state.type);
86
86
  result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
@@ -224,7 +224,7 @@ ${code.join('\n')}
224
224
  this._pendingAction?.dialogShown.resolve();
225
225
  }
226
226
  _onPageCreated(page) {
227
- const tab = new Tab(this, page, tab => this._onPageClosed(tab));
227
+ const tab = new tab_1.Tab(this, page, tab => this._onPageClosed(tab));
228
228
  this._tabs.push(tab);
229
229
  if (!this._currentTab)
230
230
  this._currentTab = tab;
@@ -245,6 +245,7 @@ ${code.join('\n')}
245
245
  return;
246
246
  const browserContext = this._browserContext;
247
247
  const browser = this._browser;
248
+ this._createBrowserContextPromise = undefined;
248
249
  this._browserContext = undefined;
249
250
  this._browser = undefined;
250
251
  await browserContext?.close().then(async () => {
@@ -263,170 +264,40 @@ ${code.join('\n')}
263
264
  return this._browserContext;
264
265
  }
265
266
  async _createBrowserContext() {
266
- if (this.options.remoteEndpoint) {
267
- const url = new URL(this.options.remoteEndpoint);
268
- if (this.options.browserName)
269
- url.searchParams.set('browser', this.options.browserName);
270
- if (this.options.launchOptions)
271
- url.searchParams.set('launch-options', JSON.stringify(this.options.launchOptions));
272
- const browser = await playwright[this.options.browserName ?? 'chromium'].connect(String(url));
267
+ if (!this._createBrowserContextPromise)
268
+ this._createBrowserContextPromise = this._innerCreateBrowserContext();
269
+ return this._createBrowserContextPromise;
270
+ }
271
+ async _innerCreateBrowserContext() {
272
+ if (this.config.browser?.remoteEndpoint) {
273
+ const url = new URL(this.config.browser?.remoteEndpoint);
274
+ if (this.config.browser.browserName)
275
+ url.searchParams.set('browser', this.config.browser.browserName);
276
+ if (this.config.browser.launchOptions)
277
+ url.searchParams.set('launch-options', JSON.stringify(this.config.browser.launchOptions));
278
+ const browser = await playwright[this.config.browser?.browserName ?? 'chromium'].connect(String(url));
273
279
  const browserContext = await browser.newContext();
274
280
  return { browser, browserContext };
275
281
  }
276
- if (this.options.cdpEndpoint) {
277
- const browser = await playwright.chromium.connectOverCDP(this.options.cdpEndpoint);
282
+ if (this.config.browser?.cdpEndpoint) {
283
+ const browser = await playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint);
278
284
  const browserContext = browser.contexts()[0];
279
285
  return { browser, browserContext };
280
286
  }
281
- const browserContext = await this._launchPersistentContext();
287
+ const browserContext = await launchPersistentContext(this.config.browser);
282
288
  return { browserContext };
283
289
  }
284
- async _launchPersistentContext() {
285
- try {
286
- const browserType = this.options.browserName ? playwright[this.options.browserName] : playwright.chromium;
287
- return await browserType.launchPersistentContext(this.options.userDataDir, this.options.launchOptions);
288
- }
289
- catch (error) {
290
- if (error.message.includes('Executable doesn\'t exist'))
291
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
292
- throw error;
293
- }
294
- }
295
290
  }
296
291
  exports.Context = Context;
297
- class Tab {
298
- context;
299
- page;
300
- _console = [];
301
- _requests = new Map();
302
- _snapshot;
303
- _onPageClose;
304
- constructor(context, page, onPageClose) {
305
- this.context = context;
306
- this.page = page;
307
- this._onPageClose = onPageClose;
308
- page.on('console', event => this._console.push(event));
309
- page.on('request', request => this._requests.set(request, null));
310
- page.on('response', response => this._requests.set(response.request(), response));
311
- page.on('framenavigated', frame => {
312
- if (!frame.parentFrame())
313
- this._clearCollectedArtifacts();
314
- });
315
- page.on('close', () => this._onClose());
316
- page.on('filechooser', chooser => {
317
- this.context.setModalState({
318
- type: 'fileChooser',
319
- description: 'File chooser',
320
- fileChooser: chooser,
321
- }, this);
322
- });
323
- page.on('dialog', dialog => this.context.dialogShown(this, dialog));
324
- page.setDefaultNavigationTimeout(60000);
325
- page.setDefaultTimeout(5000);
326
- }
327
- _clearCollectedArtifacts() {
328
- this._console.length = 0;
329
- this._requests.clear();
330
- }
331
- _onClose() {
332
- this._clearCollectedArtifacts();
333
- this._onPageClose(this);
334
- }
335
- async navigate(url) {
336
- await this.page.goto(url, { waitUntil: 'domcontentloaded' });
337
- // Cap load event to 5 seconds, the page is operational at this point.
338
- await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
339
- }
340
- hasSnapshot() {
341
- return !!this._snapshot;
342
- }
343
- snapshotOrDie() {
344
- if (!this._snapshot)
345
- throw new Error('No snapshot available');
346
- return this._snapshot;
347
- }
348
- console() {
349
- return this._console;
350
- }
351
- requests() {
352
- return this._requests;
353
- }
354
- async captureSnapshot() {
355
- this._snapshot = await PageSnapshot.create(this.page);
356
- }
357
- }
358
- exports.Tab = Tab;
359
- class PageSnapshot {
360
- _frameLocators = [];
361
- _text;
362
- constructor() {
363
- }
364
- static async create(page) {
365
- const snapshot = new PageSnapshot();
366
- await snapshot._build(page);
367
- return snapshot;
368
- }
369
- text() {
370
- return this._text;
371
- }
372
- async _build(page) {
373
- const yamlDocument = await this._snapshotFrame(page);
374
- this._text = [
375
- `- Page Snapshot`,
376
- '```yaml',
377
- yamlDocument.toString({ indentSeq: false }).trim(),
378
- '```',
379
- ].join('\n');
380
- }
381
- async _snapshotFrame(frame) {
382
- const frameIndex = this._frameLocators.push(frame) - 1;
383
- const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true, emitGeneric: true });
384
- const snapshot = yaml_1.default.parseDocument(snapshotString);
385
- const visit = async (node) => {
386
- if (yaml_1.default.isPair(node)) {
387
- await Promise.all([
388
- visit(node.key).then(k => node.key = k),
389
- visit(node.value).then(v => node.value = v)
390
- ]);
391
- }
392
- else if (yaml_1.default.isSeq(node) || yaml_1.default.isMap(node)) {
393
- node.items = await Promise.all(node.items.map(visit));
394
- }
395
- else if (yaml_1.default.isScalar(node)) {
396
- if (typeof node.value === 'string') {
397
- const value = node.value;
398
- if (frameIndex > 0)
399
- node.value = value.replace('[ref=', `[ref=f${frameIndex}`);
400
- if (value.startsWith('iframe ')) {
401
- const ref = value.match(/\[ref=(.*)\]/)?.[1];
402
- if (ref) {
403
- try {
404
- const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
405
- return snapshot.createPair(node.value, childSnapshot);
406
- }
407
- catch (error) {
408
- return snapshot.createPair(node.value, '<could not take iframe snapshot>');
409
- }
410
- }
411
- }
412
- }
413
- }
414
- return node;
415
- };
416
- await visit(snapshot.contents);
417
- return snapshot;
418
- }
419
- refLocator(ref) {
420
- let frame = this._frameLocators[0];
421
- const match = ref.match(/^f(\d+)(.*)/);
422
- if (match) {
423
- const frameIndex = parseInt(match[1], 10);
424
- frame = this._frameLocators[frameIndex];
425
- ref = match[2];
426
- }
427
- if (!frame)
428
- throw new Error(`Frame does not exist. Provide ref from the most current snapshot.`);
429
- return frame.locator(`aria-ref=${ref}`);
292
+ async function launchPersistentContext(browserConfig) {
293
+ try {
294
+ const browserType = browserConfig?.browserName ? playwright[browserConfig.browserName] : playwright.chromium;
295
+ return await browserType.launchPersistentContext(browserConfig?.userDataDir || '', { ...browserConfig?.launchOptions, ...browserConfig?.contextOptions });
296
+ }
297
+ catch (error) {
298
+ if (error.message.includes('Executable doesn\'t exist'))
299
+ throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
300
+ throw error;
430
301
  }
431
302
  }
432
303
  async function generateLocator(locator) {
package/lib/index.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
  exports.createServer = createServer;
22
- const path_1 = __importDefault(require("path"));
23
- const os_1 = __importDefault(require("os"));
24
- const fs_1 = __importDefault(require("fs"));
25
22
  const server_1 = require("./server");
26
23
  const common_1 = __importDefault(require("./tools/common"));
27
24
  const console_1 = __importDefault(require("./tools/console"));
@@ -62,64 +59,12 @@ const screenshotTools = [
62
59
  ...(0, tabs_1.default)(false),
63
60
  ];
64
61
  const packageJSON = require('../package.json');
65
- async function createServer(options) {
66
- let browserName;
67
- let channel;
68
- switch (options?.browser) {
69
- case 'chrome':
70
- case 'chrome-beta':
71
- case 'chrome-canary':
72
- case 'chrome-dev':
73
- case 'msedge':
74
- case 'msedge-beta':
75
- case 'msedge-canary':
76
- case 'msedge-dev':
77
- browserName = 'chromium';
78
- channel = options.browser;
79
- break;
80
- case 'chromium':
81
- browserName = 'chromium';
82
- break;
83
- case 'firefox':
84
- browserName = 'firefox';
85
- break;
86
- case 'webkit':
87
- browserName = 'webkit';
88
- break;
89
- default:
90
- browserName = 'chromium';
91
- channel = 'chrome';
92
- }
93
- const userDataDir = options?.userDataDir ?? await createUserDataDir(browserName);
94
- const launchOptions = {
95
- headless: !!(options?.headless ?? (os_1.default.platform() === 'linux' && !process.env.DISPLAY)),
96
- channel,
97
- executablePath: options?.executablePath,
98
- };
99
- const allTools = options?.vision ? screenshotTools : snapshotTools;
100
- const tools = allTools.filter(tool => !options?.capabilities || tool.capability === 'core' || options.capabilities.includes(tool.capability));
62
+ async function createServer(config = {}) {
63
+ const allTools = config.vision ? screenshotTools : snapshotTools;
64
+ const tools = allTools.filter(tool => !config.capabilities || tool.capability === 'core' || config.capabilities.includes(tool.capability));
101
65
  return (0, server_1.createServerWithTools)({
102
66
  name: 'Playwright',
103
67
  version: packageJSON.version,
104
68
  tools,
105
- resources: [],
106
- browserName,
107
- userDataDir,
108
- launchOptions,
109
- cdpEndpoint: options?.cdpEndpoint,
110
- });
111
- }
112
- async function createUserDataDir(browserName) {
113
- let cacheDirectory;
114
- if (process.platform === 'linux')
115
- cacheDirectory = process.env.XDG_CACHE_HOME || path_1.default.join(os_1.default.homedir(), '.cache');
116
- else if (process.platform === 'darwin')
117
- cacheDirectory = path_1.default.join(os_1.default.homedir(), 'Library', 'Caches');
118
- else if (process.platform === 'win32')
119
- cacheDirectory = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
120
- else
121
- throw new Error('Unsupported platform: ' + process.platform);
122
- const result = path_1.default.join(cacheDirectory, 'ms-playwright', `mcp-${browserName}-profile`);
123
- await fs_1.default.promises.mkdir(result, { recursive: true });
124
- return result;
69
+ }, config);
125
70
  }
@@ -0,0 +1,96 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.PageSnapshot = void 0;
22
+ const yaml_1 = __importDefault(require("yaml"));
23
+ class PageSnapshot {
24
+ _frameLocators = [];
25
+ _text;
26
+ constructor() {
27
+ }
28
+ static async create(page) {
29
+ const snapshot = new PageSnapshot();
30
+ await snapshot._build(page);
31
+ return snapshot;
32
+ }
33
+ text() {
34
+ return this._text;
35
+ }
36
+ async _build(page) {
37
+ const yamlDocument = await this._snapshotFrame(page);
38
+ this._text = [
39
+ `- Page Snapshot`,
40
+ '```yaml',
41
+ yamlDocument.toString({ indentSeq: false }).trim(),
42
+ '```',
43
+ ].join('\n');
44
+ }
45
+ async _snapshotFrame(frame) {
46
+ const frameIndex = this._frameLocators.push(frame) - 1;
47
+ const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true, emitGeneric: true });
48
+ const snapshot = yaml_1.default.parseDocument(snapshotString);
49
+ const visit = async (node) => {
50
+ if (yaml_1.default.isPair(node)) {
51
+ await Promise.all([
52
+ visit(node.key).then(k => node.key = k),
53
+ visit(node.value).then(v => node.value = v)
54
+ ]);
55
+ }
56
+ else if (yaml_1.default.isSeq(node) || yaml_1.default.isMap(node)) {
57
+ node.items = await Promise.all(node.items.map(visit));
58
+ }
59
+ else if (yaml_1.default.isScalar(node)) {
60
+ if (typeof node.value === 'string') {
61
+ const value = node.value;
62
+ if (frameIndex > 0)
63
+ node.value = value.replace('[ref=', `[ref=f${frameIndex}`);
64
+ if (value.startsWith('iframe ')) {
65
+ const ref = value.match(/\[ref=(.*)\]/)?.[1];
66
+ if (ref) {
67
+ try {
68
+ const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
69
+ return snapshot.createPair(node.value, childSnapshot);
70
+ }
71
+ catch (error) {
72
+ return snapshot.createPair(node.value, '<could not take iframe snapshot>');
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return node;
79
+ };
80
+ await visit(snapshot.contents);
81
+ return snapshot;
82
+ }
83
+ refLocator(ref) {
84
+ let frame = this._frameLocators[0];
85
+ const match = ref.match(/^f(\d+)(.*)/);
86
+ if (match) {
87
+ const frameIndex = parseInt(match[1], 10);
88
+ frame = this._frameLocators[frameIndex];
89
+ ref = match[2];
90
+ }
91
+ if (!frame)
92
+ throw new Error(`Frame does not exist. Provide ref from the most current snapshot.`);
93
+ return frame.locator(`aria-ref=${ref}`);
94
+ }
95
+ }
96
+ exports.PageSnapshot = PageSnapshot;
package/lib/program.js CHANGED
@@ -14,17 +14,12 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- var __importDefault = (this && this.__importDefault) || function (mod) {
18
- return (mod && mod.__esModule) ? mod : { "default": mod };
19
- };
20
17
  Object.defineProperty(exports, "__esModule", { value: true });
21
- const http_1 = __importDefault(require("http"));
22
18
  const commander_1 = require("commander");
23
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
24
- const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
25
19
  const index_1 = require("./index");
26
20
  const server_1 = require("./server");
27
- const assert_1 = __importDefault(require("assert"));
21
+ const transport_1 = require("./transport");
22
+ const config_1 = require("./config");
28
23
  const packageJSON = require('../package.json');
29
24
  commander_1.program
30
25
  .version('Version ' + packageJSON.version)
@@ -34,27 +29,20 @@ commander_1.program
34
29
  .option('--cdp-endpoint <endpoint>', 'CDP endpoint to connect to.')
35
30
  .option('--executable-path <path>', 'Path to the browser executable.')
36
31
  .option('--headless', 'Run browser in headless mode, headed by default')
37
- .option('--port <port>', 'Port to listen on for SSE transport.')
32
+ .option('--device <device>', 'Device to emulate, for example: "iPhone 15"')
38
33
  .option('--user-data-dir <path>', 'Path to the user data directory')
34
+ .option('--port <port>', 'Port to listen on for SSE transport.')
35
+ .option('--host <host>', 'Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.')
39
36
  .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
37
+ .option('--config <path>', 'Path to the configuration file.')
40
38
  .action(async (options) => {
41
- const serverList = new server_1.ServerList(() => (0, index_1.createServer)({
42
- browser: options.browser,
43
- userDataDir: options.userDataDir,
44
- headless: options.headless,
45
- executablePath: options.executablePath,
46
- vision: !!options.vision,
47
- cdpEndpoint: options.cdpEndpoint,
48
- capabilities: options.caps?.split(',').map((c) => c.trim()),
49
- }));
39
+ const config = await (0, config_1.resolveConfig)(options);
40
+ const serverList = new server_1.ServerList(() => (0, index_1.createServer)(config));
50
41
  setupExitWatchdog(serverList);
51
- if (options.port) {
52
- startSSEServer(+options.port, serverList);
53
- }
54
- else {
55
- const server = await serverList.create();
56
- await server.connect(new stdio_js_1.StdioServerTransport());
57
- }
42
+ if (options.port)
43
+ (0, transport_1.startHttpTransport)(+options.port, options.host, serverList);
44
+ else
45
+ await (0, transport_1.startStdioTransport)(serverList);
58
46
  });
59
47
  function setupExitWatchdog(serverList) {
60
48
  const handleExit = async () => {
@@ -67,64 +55,3 @@ function setupExitWatchdog(serverList) {
67
55
  process.on('SIGTERM', handleExit);
68
56
  }
69
57
  commander_1.program.parse(process.argv);
70
- async function startSSEServer(port, serverList) {
71
- const sessions = new Map();
72
- const httpServer = http_1.default.createServer(async (req, res) => {
73
- if (req.method === 'POST') {
74
- const searchParams = new URL(`http://localhost${req.url}`).searchParams;
75
- const sessionId = searchParams.get('sessionId');
76
- if (!sessionId) {
77
- res.statusCode = 400;
78
- res.end('Missing sessionId');
79
- return;
80
- }
81
- const transport = sessions.get(sessionId);
82
- if (!transport) {
83
- res.statusCode = 404;
84
- res.end('Session not found');
85
- return;
86
- }
87
- await transport.handlePostMessage(req, res);
88
- return;
89
- }
90
- else if (req.method === 'GET') {
91
- const transport = new sse_js_1.SSEServerTransport('/sse', res);
92
- sessions.set(transport.sessionId, transport);
93
- const server = await serverList.create();
94
- res.on('close', () => {
95
- sessions.delete(transport.sessionId);
96
- serverList.close(server).catch(e => console.error(e));
97
- });
98
- await server.connect(transport);
99
- return;
100
- }
101
- else {
102
- res.statusCode = 405;
103
- res.end('Method not allowed');
104
- }
105
- });
106
- httpServer.listen(port, () => {
107
- const address = httpServer.address();
108
- (0, assert_1.default)(address, 'Could not bind server socket');
109
- let url;
110
- if (typeof address === 'string') {
111
- url = address;
112
- }
113
- else {
114
- const resolvedPort = address.port;
115
- let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
116
- if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
117
- resolvedHost = 'localhost';
118
- url = `http://${resolvedHost}:${resolvedPort}`;
119
- }
120
- console.log(`Listening on ${url}`);
121
- console.log('Put this in your client config:');
122
- console.log(JSON.stringify({
123
- 'mcpServers': {
124
- 'playwright': {
125
- 'url': `${url}/sse`
126
- }
127
- }
128
- }, undefined, 2));
129
- });
130
- }
package/lib/server.js CHANGED
@@ -21,13 +21,12 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
21
21
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
22
22
  const zod_to_json_schema_1 = require("zod-to-json-schema");
23
23
  const context_1 = require("./context");
24
- function createServerWithTools(options) {
25
- const { name, version, tools, resources } = options;
26
- const context = new context_1.Context(tools, options);
24
+ function createServerWithTools(serverOptions, config) {
25
+ const { name, version, tools } = serverOptions;
26
+ const context = new context_1.Context(tools, config);
27
27
  const server = new index_js_1.Server({ name, version }, {
28
28
  capabilities: {
29
29
  tools: {},
30
- resources: {},
31
30
  }
32
31
  });
33
32
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
@@ -39,46 +38,26 @@ function createServerWithTools(options) {
39
38
  })),
40
39
  };
41
40
  });
42
- server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
43
- return { resources: resources.map(resource => resource.schema) };
44
- });
45
41
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
42
+ const errorResult = (...messages) => ({
43
+ content: [{ type: 'text', text: messages.join('\n') }],
44
+ isError: true,
45
+ });
46
46
  const tool = tools.find(tool => tool.schema.name === request.params.name);
47
- if (!tool) {
48
- return {
49
- content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
50
- isError: true,
51
- };
52
- }
47
+ if (!tool)
48
+ return errorResult(`Tool "${request.params.name}" not found`);
53
49
  const modalStates = context.modalStates().map(state => state.type);
54
- if ((tool.clearsModalState && !modalStates.includes(tool.clearsModalState)) ||
55
- (!tool.clearsModalState && modalStates.length)) {
56
- const text = [
57
- `Tool "${request.params.name}" does not handle the modal state.`,
58
- ...context.modalStatesMarkdown(),
59
- ].join('\n');
60
- return {
61
- content: [{ type: 'text', text }],
62
- isError: true,
63
- };
64
- }
50
+ if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState))
51
+ return errorResult(`The tool "${request.params.name}" can only be used when there is related modal state present.`, ...context.modalStatesMarkdown());
52
+ if (!tool.clearsModalState && modalStates.length)
53
+ return errorResult(`Tool "${request.params.name}" does not handle the modal state.`, ...context.modalStatesMarkdown());
65
54
  try {
66
55
  return await context.run(tool, request.params.arguments);
67
56
  }
68
57
  catch (error) {
69
- return {
70
- content: [{ type: 'text', text: String(error) }],
71
- isError: true,
72
- };
58
+ return errorResult(String(error));
73
59
  }
74
60
  });
75
- server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
76
- const resource = resources.find(resource => resource.schema.uri === request.params.uri);
77
- if (!resource)
78
- return { contents: [] };
79
- const contents = await resource.read(context, request.params.uri);
80
- return { contents };
81
- });
82
61
  const oldClose = server.close.bind(server);
83
62
  server.close = async () => {
84
63
  await oldClose();