@playwright/mcp 0.0.14 → 0.0.16

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`);
@@ -131,7 +131,7 @@ class Context {
131
131
  }
132
132
  async run(tool, params) {
133
133
  // Tab management is done outside of the action() call.
134
- const toolResult = await tool.handle(this, params);
134
+ const toolResult = await tool.handle(this, tool.schema.inputSchema.parse(params));
135
135
  const { code, action, waitForNetwork, captureSnapshot, resultOverride } = toolResult;
136
136
  const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined;
137
137
  if (resultOverride)
@@ -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,160 +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
- _snapshot;
302
- _onPageClose;
303
- constructor(context, page, onPageClose) {
304
- this.context = context;
305
- this.page = page;
306
- this._onPageClose = onPageClose;
307
- page.on('console', event => this._console.push(event));
308
- page.on('framenavigated', frame => {
309
- if (!frame.parentFrame())
310
- this._console.length = 0;
311
- });
312
- page.on('close', () => this._onClose());
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));
321
- page.setDefaultNavigationTimeout(60000);
322
- page.setDefaultTimeout(5000);
323
- }
324
- _onClose() {
325
- this._console.length = 0;
326
- this._onPageClose(this);
327
- }
328
- async navigate(url) {
329
- await this.page.goto(url, { waitUntil: 'domcontentloaded' });
330
- // Cap load event to 5 seconds, the page is operational at this point.
331
- await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
332
- }
333
- hasSnapshot() {
334
- return !!this._snapshot;
335
- }
336
- snapshotOrDie() {
337
- if (!this._snapshot)
338
- throw new Error('No snapshot available');
339
- return this._snapshot;
340
- }
341
- async console() {
342
- return this._console;
343
- }
344
- async captureSnapshot() {
345
- this._snapshot = await PageSnapshot.create(this.page);
346
- }
347
- }
348
- exports.Tab = Tab;
349
- class PageSnapshot {
350
- _frameLocators = [];
351
- _text;
352
- constructor() {
353
- }
354
- static async create(page) {
355
- const snapshot = new PageSnapshot();
356
- await snapshot._build(page);
357
- return snapshot;
358
- }
359
- text() {
360
- return this._text;
361
- }
362
- async _build(page) {
363
- const yamlDocument = await this._snapshotFrame(page);
364
- this._text = [
365
- `- Page Snapshot`,
366
- '```yaml',
367
- yamlDocument.toString({ indentSeq: false }).trim(),
368
- '```',
369
- ].join('\n');
370
- }
371
- async _snapshotFrame(frame) {
372
- const frameIndex = this._frameLocators.push(frame) - 1;
373
- const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true });
374
- const snapshot = yaml_1.default.parseDocument(snapshotString);
375
- const visit = async (node) => {
376
- if (yaml_1.default.isPair(node)) {
377
- await Promise.all([
378
- visit(node.key).then(k => node.key = k),
379
- visit(node.value).then(v => node.value = v)
380
- ]);
381
- }
382
- else if (yaml_1.default.isSeq(node) || yaml_1.default.isMap(node)) {
383
- node.items = await Promise.all(node.items.map(visit));
384
- }
385
- else if (yaml_1.default.isScalar(node)) {
386
- if (typeof node.value === 'string') {
387
- const value = node.value;
388
- if (frameIndex > 0)
389
- node.value = value.replace('[ref=', `[ref=f${frameIndex}`);
390
- if (value.startsWith('iframe ')) {
391
- const ref = value.match(/\[ref=(.*)\]/)?.[1];
392
- if (ref) {
393
- try {
394
- const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
395
- return snapshot.createPair(node.value, childSnapshot);
396
- }
397
- catch (error) {
398
- return snapshot.createPair(node.value, '<could not take iframe snapshot>');
399
- }
400
- }
401
- }
402
- }
403
- }
404
- return node;
405
- };
406
- await visit(snapshot.contents);
407
- return snapshot;
408
- }
409
- refLocator(ref) {
410
- let frame = this._frameLocators[0];
411
- const match = ref.match(/^f(\d+)(.*)/);
412
- if (match) {
413
- const frameIndex = parseInt(match[1], 10);
414
- frame = this._frameLocators[frameIndex];
415
- ref = match[2];
416
- }
417
- if (!frame)
418
- throw new Error(`Frame does not exist. Provide ref from the most current snapshot.`);
419
- 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;
420
301
  }
421
302
  }
422
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"));
@@ -30,6 +27,7 @@ const files_1 = __importDefault(require("./tools/files"));
30
27
  const install_1 = __importDefault(require("./tools/install"));
31
28
  const keyboard_1 = __importDefault(require("./tools/keyboard"));
32
29
  const navigate_1 = __importDefault(require("./tools/navigate"));
30
+ const network_1 = __importDefault(require("./tools/network"));
33
31
  const pdf_1 = __importDefault(require("./tools/pdf"));
34
32
  const snapshot_1 = __importDefault(require("./tools/snapshot"));
35
33
  const tabs_1 = __importDefault(require("./tools/tabs"));
@@ -42,6 +40,7 @@ const snapshotTools = [
42
40
  ...install_1.default,
43
41
  ...(0, keyboard_1.default)(true),
44
42
  ...(0, navigate_1.default)(true),
43
+ ...network_1.default,
45
44
  ...pdf_1.default,
46
45
  ...snapshot_1.default,
47
46
  ...(0, tabs_1.default)(true),
@@ -54,69 +53,18 @@ const screenshotTools = [
54
53
  ...install_1.default,
55
54
  ...(0, keyboard_1.default)(false),
56
55
  ...(0, navigate_1.default)(false),
56
+ ...network_1.default,
57
57
  ...pdf_1.default,
58
58
  ...screen_1.default,
59
59
  ...(0, tabs_1.default)(false),
60
60
  ];
61
61
  const packageJSON = require('../package.json');
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
- };
96
- const allTools = options?.vision ? screenshotTools : snapshotTools;
97
- 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));
98
65
  return (0, server_1.createServerWithTools)({
99
66
  name: 'Playwright',
100
67
  version: packageJSON.version,
101
68
  tools,
102
- resources: [],
103
- browserName,
104
- userDataDir,
105
- launchOptions,
106
- cdpEndpoint: options?.cdpEndpoint,
107
- });
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;
69
+ }, config);
122
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
@@ -19,59 +19,45 @@ exports.ServerList = void 0;
19
19
  exports.createServerWithTools = createServerWithTools;
20
20
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
21
21
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
22
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
22
23
  const context_1 = require("./context");
23
- function createServerWithTools(options) {
24
- const { name, version, tools, resources } = options;
25
- 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);
26
27
  const server = new index_js_1.Server({ name, version }, {
27
28
  capabilities: {
28
29
  tools: {},
29
- resources: {},
30
30
  }
31
31
  });
32
32
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
33
- return { tools: tools.map(tool => tool.schema) };
34
- });
35
- server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
36
- return { resources: resources.map(resource => resource.schema) };
33
+ return {
34
+ tools: tools.map(tool => ({
35
+ name: tool.schema.name,
36
+ description: tool.schema.description,
37
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(tool.schema.inputSchema)
38
+ })),
39
+ };
37
40
  });
38
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
+ });
39
46
  const tool = tools.find(tool => tool.schema.name === request.params.name);
40
- if (!tool) {
41
- return {
42
- content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
43
- isError: true,
44
- };
45
- }
47
+ if (!tool)
48
+ return errorResult(`Tool "${request.params.name}" not found`);
46
49
  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
- }
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());
58
54
  try {
59
55
  return await context.run(tool, request.params.arguments);
60
56
  }
61
57
  catch (error) {
62
- return {
63
- content: [{ type: 'text', text: String(error) }],
64
- isError: true,
65
- };
58
+ return errorResult(String(error));
66
59
  }
67
60
  });
68
- server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
69
- const resource = resources.find(resource => resource.schema.uri === request.params.uri);
70
- if (!resource)
71
- return { contents: [] };
72
- const contents = await resource.read(context, request.params.uri);
73
- return { contents };
74
- });
75
61
  const oldClose = server.close.bind(server);
76
62
  server.close = async () => {
77
63
  await oldClose();