opera-devtools-mcp 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -205,9 +205,12 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
205
205
  - [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
206
206
  - [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
207
207
  - [`get_heapsnapshot_summary`](docs/tool-reference.md#get_heapsnapshot_summary)
208
- - **Opera** (5 tools)
208
+ - **Opera** (8 tools)
209
+ - [`opera_call_mcp_tool`](docs/tool-reference.md#opera_call_mcp_tool)
209
210
  - [`opera_chat`](docs/tool-reference.md#opera_chat)
210
211
  - [`opera_do`](docs/tool-reference.md#opera_do)
212
+ - [`opera_list_mcp_servers`](docs/tool-reference.md#opera_list_mcp_servers)
213
+ - [`opera_list_mcp_tools`](docs/tool-reference.md#opera_list_mcp_tools)
211
214
  - [`opera_list_models`](docs/tool-reference.md#opera_list_models)
212
215
  - [`opera_make`](docs/tool-reference.md#opera_make)
213
216
  - [`opera_research`](docs/tool-reference.md#opera_research)
@@ -898,6 +898,30 @@ export const commands = {
898
898
  },
899
899
  },
900
900
  },
901
+ opera_call_mcp_tool: {
902
+ description: 'Execute a tool on a specific MCP server registered in the browser. Use opera_list_mcp_servers to discover available servers and tools. Only available when connected to Opera Neon.',
903
+ category: 'Opera',
904
+ args: {
905
+ server: {
906
+ name: 'server',
907
+ type: 'string',
908
+ description: 'The MCP server name (from opera_list_mcp_servers).',
909
+ required: true,
910
+ },
911
+ tool: {
912
+ name: 'tool',
913
+ type: 'string',
914
+ description: 'The tool name to execute on the server.',
915
+ required: true,
916
+ },
917
+ parameters: {
918
+ name: 'parameters',
919
+ type: 'object',
920
+ description: 'Parameters to pass to the tool. Omit if the tool takes none.',
921
+ required: false,
922
+ },
923
+ },
924
+ },
901
925
  opera_chat: {
902
926
  description: "Send a chat prompt to Opera's built-in AI and return the response. Only available when connected to Opera Neon.",
903
927
  category: 'Opera',
@@ -928,6 +952,23 @@ export const commands = {
928
952
  },
929
953
  },
930
954
  },
955
+ opera_list_mcp_servers: {
956
+ description: 'List MCP servers registered in the browser, including their connection status. Only available when connected to Opera Neon.',
957
+ category: 'Opera',
958
+ args: {},
959
+ },
960
+ opera_list_mcp_tools: {
961
+ description: 'List the tools exposed by a single MCP server. Prefer opera_list_mcp_servers when discovering everything at once; use this for a targeted refresh of one server. Only available when connected to Opera Neon.',
962
+ category: 'Opera',
963
+ args: {
964
+ server: {
965
+ name: 'server',
966
+ type: 'string',
967
+ description: 'The MCP server name (from opera_list_mcp_servers).',
968
+ required: true,
969
+ },
970
+ },
971
+ },
931
972
  opera_list_models: {
932
973
  description: 'List available AI models for Opera chat. Returns model IDs, display names, and which is the default. Only available when connected to Opera Neon.',
933
974
  category: 'Opera',
@@ -210,4 +210,114 @@ export const operaListModels = definePageTool({
210
210
  }
211
211
  },
212
212
  });
213
+ export const operaListMcpServers = definePageTool({
214
+ name: 'opera_list_mcp_servers',
215
+ description: 'List MCP servers registered in the browser, ' +
216
+ 'including their connection status. ' +
217
+ 'Only available when connected to Opera Neon.',
218
+ blockedByDialog: false,
219
+ verifyFilesSchema: {},
220
+ annotations: {
221
+ category: ToolCategory.OPERA,
222
+ readOnlyHint: true,
223
+ },
224
+ schema: {},
225
+ handler: async (request, response) => {
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ const session = getCDPSession(request.page.pptrPage);
228
+ try {
229
+ const result = await dispatchAction(session, {
230
+ action: 'listMcpServers',
231
+ type: 'LIST_SERVERS',
232
+ });
233
+ response.appendResponseLine(result);
234
+ }
235
+ catch (e) {
236
+ response.appendResponseLine(`Opera.dispatchAction(listMcpServers) failed with error: ${e.message}`);
237
+ }
238
+ },
239
+ });
240
+ export const operaListMcpTools = definePageTool({
241
+ name: 'opera_list_mcp_tools',
242
+ description: 'List the tools exposed by a single MCP server. Prefer opera_list_mcp_servers ' +
243
+ 'when discovering everything at once; use this for a targeted refresh of one server. ' +
244
+ 'Only available when connected to Opera Neon.',
245
+ blockedByDialog: false,
246
+ verifyFilesSchema: {},
247
+ annotations: {
248
+ category: ToolCategory.OPERA,
249
+ readOnlyHint: true,
250
+ },
251
+ schema: {
252
+ server: zod
253
+ .string()
254
+ .min(1)
255
+ .describe('The MCP server name (from opera_list_mcp_servers).'),
256
+ },
257
+ handler: async (request, response) => {
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ const session = getCDPSession(request.page.pptrPage);
260
+ try {
261
+ const result = await dispatchAction(session, {
262
+ action: 'listMcpTools',
263
+ server: request.params.server,
264
+ type: 'LIST_TOOLS',
265
+ });
266
+ response.appendResponseLine(result);
267
+ }
268
+ catch (e) {
269
+ response.appendResponseLine(`Opera.dispatchAction(listMcpTools) failed with error: ${e.message}`);
270
+ }
271
+ },
272
+ });
273
+ export const operaCallMcpTool = definePageTool({
274
+ name: 'opera_call_mcp_tool',
275
+ description: 'Execute a tool on a specific MCP server registered in the browser. ' +
276
+ 'Use opera_list_mcp_servers to discover available ' +
277
+ 'servers and tools. ' +
278
+ 'Only available when connected to Opera Neon.',
279
+ blockedByDialog: false,
280
+ verifyFilesSchema: {},
281
+ annotations: {
282
+ category: ToolCategory.OPERA,
283
+ readOnlyHint: false,
284
+ },
285
+ schema: {
286
+ server: zod
287
+ .string()
288
+ .min(1)
289
+ .describe('The MCP server name (from opera_list_mcp_servers).'),
290
+ tool: zod
291
+ .string()
292
+ .min(1)
293
+ .describe('The tool name to execute on the server.'),
294
+ parameters: zod
295
+ .record(zod.unknown())
296
+ .optional()
297
+ .describe('Parameters to pass to the tool. Omit if the tool takes none.'),
298
+ },
299
+ handler: async (request, response) => {
300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
+ const session = getCDPSession(request.page.pptrPage);
302
+ try {
303
+ const payload = {
304
+ action: 'callMcpTool',
305
+ server: request.params.server,
306
+ toolName: request.params.tool,
307
+ type: 'EXECUTE_TOOL',
308
+ };
309
+ if (request.params.parameters !== undefined) {
310
+ payload['parameters'] = request.params.parameters;
311
+ }
312
+ const result = await dispatchWithStreamedResponse(session, payload, chunk => response.sendLog(chunk), request.signal);
313
+ response.appendResponseLine(result);
314
+ }
315
+ catch (e) {
316
+ if (e.name === 'AbortError') {
317
+ throw e;
318
+ }
319
+ response.appendResponseLine(`Opera.dispatchWithStreamedResponse(callMcpTool) failed with error: ${e.message}`);
320
+ }
321
+ },
322
+ });
213
323
  //# sourceMappingURL=opera.js.map
@@ -20,6 +20,7 @@ const SUPPORTED_ZOD_TYPES = [
20
20
  'ZodBoolean',
21
21
  'ZodArray',
22
22
  'ZodEnum',
23
+ 'ZodRecord',
23
24
  ];
24
25
  function isZodType(type) {
25
26
  return SUPPORTED_ZOD_TYPES.includes(type);
@@ -69,7 +70,7 @@ export function transformArgName(zodType, name) {
69
70
  if (zodType === 'ZodString') {
70
71
  transformed = `${snakeCaseName}_length`;
71
72
  }
72
- else if (zodType === 'ZodArray') {
73
+ else if (zodType === 'ZodArray' || zodType === 'ZodRecord') {
73
74
  transformed = `${snakeCaseName}_count`;
74
75
  }
75
76
  else {
@@ -78,7 +79,9 @@ export function transformArgName(zodType, name) {
78
79
  return stripUnderscoreBeforeNumber(transformed);
79
80
  }
80
81
  export function transformArgType(zodType) {
81
- if (zodType === 'ZodString' || zodType === 'ZodArray') {
82
+ if (zodType === 'ZodString' ||
83
+ zodType === 'ZodArray' ||
84
+ zodType === 'ZodRecord') {
82
85
  return 'number';
83
86
  }
84
87
  switch (zodType) {
@@ -110,6 +113,9 @@ function transformValue(zodType, value) {
110
113
  else if (zodType === 'ZodArray') {
111
114
  return value.length;
112
115
  }
116
+ else if (zodType === 'ZodRecord') {
117
+ return Object.keys(value).length;
118
+ }
113
119
  else {
114
120
  return value;
115
121
  }
@@ -127,6 +133,9 @@ function hasEquivalentType(zodType, value) {
127
133
  else if (zodType === 'ZodBoolean') {
128
134
  return typeof value === 'boolean';
129
135
  }
136
+ else if (zodType === 'ZodRecord') {
137
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
138
+ }
130
139
  else if (zodType === 'ZodEnum') {
131
140
  return (typeof value === 'string' ||
132
141
  typeof value === 'number' ||
@@ -4,6 +4,7 @@
4
4
  "lighthouse": "13.4.1",
5
5
  "semver": "7.8.5",
6
6
  "yargs": "18.1.0",
7
+ "zod": "3.25.76",
7
8
  "puppeteer-core": "25.6.0",
8
9
  "https://github.com/ChromeDevTools/devtools-frontend": "eaad2491924990cb310a4c753b2851e64d515f00"
9
10
  }
@@ -5,8 +5,5 @@
5
5
  *
6
6
  * Modified by Opera Software AS.
7
7
  */
8
- // If moved update release-please config
9
- // x-release-please-start-version
10
- export const VERSION = '0.3.0';
11
- // x-release-please-end
8
+ export const VERSION = '0.4.0';
12
9
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opera-devtools-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "MCP server for Opera DevTools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -84,7 +84,8 @@
84
84
  "sinon": "^22.0.0",
85
85
  "typescript": "^6.0.2",
86
86
  "typescript-eslint": "^8.43.0",
87
- "yargs": "18.1.0"
87
+ "yargs": "18.1.0",
88
+ "zod": "^3.25.76"
88
89
  },
89
90
  "engines": {
90
91
  "node": "^20.19.0 || ^22.12.0 || >=23"