opera-devtools-mcp 0.4.1 → 0.5.0

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,15 +205,21 @@ 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** (8 tools)
208
+ - **Opera** (14 tools)
209
+ - [`opera_authenticate_mcp_server`](docs/tool-reference.md#opera_authenticate_mcp_server)
209
210
  - [`opera_call_mcp_tool`](docs/tool-reference.md#opera_call_mcp_tool)
210
211
  - [`opera_chat`](docs/tool-reference.md#opera_chat)
212
+ - [`opera_connect_mcp_server`](docs/tool-reference.md#opera_connect_mcp_server)
213
+ - [`opera_disable_mcp_server`](docs/tool-reference.md#opera_disable_mcp_server)
211
214
  - [`opera_do`](docs/tool-reference.md#opera_do)
215
+ - [`opera_enable_mcp_server`](docs/tool-reference.md#opera_enable_mcp_server)
212
216
  - [`opera_list_mcp_servers`](docs/tool-reference.md#opera_list_mcp_servers)
213
217
  - [`opera_list_mcp_tools`](docs/tool-reference.md#opera_list_mcp_tools)
214
218
  - [`opera_list_models`](docs/tool-reference.md#opera_list_models)
215
219
  - [`opera_make`](docs/tool-reference.md#opera_make)
220
+ - [`opera_register_mcp_server`](docs/tool-reference.md#opera_register_mcp_server)
216
221
  - [`opera_research`](docs/tool-reference.md#opera_research)
222
+ - [`opera_unregister_mcp_server`](docs/tool-reference.md#opera_unregister_mcp_server)
217
223
  - **Extensions** (5 tools)
218
224
  - [`install_extension`](docs/tool-reference.md#install_extension)
219
225
  - [`list_extensions`](docs/tool-reference.md#list_extensions)
@@ -553,19 +553,20 @@ export class McpPage {
553
553
  return this.#resolveElementHandle(node, uid);
554
554
  }
555
555
  async #resolveElementHandle(node, uid) {
556
- const message = `Element with uid ${uid} no longer exists on the page.`;
556
+ let handle;
557
557
  try {
558
- const handle = await node.elementHandle();
559
- if (!handle) {
560
- throw new Error(message);
561
- }
562
- return handle;
558
+ handle = await node.elementHandle();
563
559
  }
564
560
  catch (error) {
565
- throw new Error(message, {
561
+ const reason = error instanceof Error ? error.message : String(error);
562
+ throw new Error(`Failed to resolve element with uid ${uid}: ${reason}`, {
566
563
  cause: error,
567
564
  });
568
565
  }
566
+ if (!handle) {
567
+ throw new Error(`Element with uid ${uid} no longer exists on the page.`);
568
+ }
569
+ return handle;
569
570
  }
570
571
  getAXNodeByUid(uid) {
571
572
  return this.textSnapshot?.idToNode.get(uid);
@@ -898,6 +898,18 @@ export const commands = {
898
898
  },
899
899
  },
900
900
  },
901
+ opera_authenticate_mcp_server: {
902
+ description: 'Complete OAuth sign-in for an MCP server that requires authentication. Opens a browser popup for the OAuth flow; requires a headed (visible) browser. 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 to authenticate.',
909
+ required: true,
910
+ },
911
+ },
912
+ },
901
913
  opera_call_mcp_tool: {
902
914
  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
915
  category: 'Opera',
@@ -940,6 +952,30 @@ export const commands = {
940
952
  },
941
953
  },
942
954
  },
955
+ opera_connect_mcp_server: {
956
+ description: 'Connect to a registered MCP server. If the server requires OAuth, the response includes requiresAuth: "needed" — follow with opera_authenticate_mcp_server. Only available when connected to Opera Neon.',
957
+ category: 'Opera',
958
+ args: {
959
+ server: {
960
+ name: 'server',
961
+ type: 'string',
962
+ description: 'The MCP server name to connect.',
963
+ required: true,
964
+ },
965
+ },
966
+ },
967
+ opera_disable_mcp_server: {
968
+ description: 'Disable an MCP server without unregistering it. Only available when connected to Opera Neon.',
969
+ category: 'Opera',
970
+ args: {
971
+ server: {
972
+ name: 'server',
973
+ type: 'string',
974
+ description: 'The MCP server name to disable.',
975
+ required: true,
976
+ },
977
+ },
978
+ },
943
979
  opera_do: {
944
980
  description: "Instruct Opera's built-in AI to perform an action on the current page and return the result. Only available when connected to Opera Neon.",
945
981
  category: 'Opera',
@@ -952,6 +988,18 @@ export const commands = {
952
988
  },
953
989
  },
954
990
  },
991
+ opera_enable_mcp_server: {
992
+ description: 'Enable a disabled MCP server. Only available when connected to Opera Neon.',
993
+ category: 'Opera',
994
+ args: {
995
+ server: {
996
+ name: 'server',
997
+ type: 'string',
998
+ description: 'The MCP server name to enable.',
999
+ required: true,
1000
+ },
1001
+ },
1002
+ },
955
1003
  opera_list_mcp_servers: {
956
1004
  description: 'List MCP servers registered in the browser, including their connection status. Only available when connected to Opera Neon.',
957
1005
  category: 'Opera',
@@ -986,6 +1034,24 @@ export const commands = {
986
1034
  },
987
1035
  },
988
1036
  },
1037
+ opera_register_mcp_server: {
1038
+ description: 'Register an MCP server in the browser. Does not connect or authenticate — follow with opera_connect_mcp_server. Only available when connected to Opera Neon.',
1039
+ category: 'Opera',
1040
+ args: {
1041
+ server: {
1042
+ name: 'server',
1043
+ type: 'string',
1044
+ description: 'The MCP server name to register.',
1045
+ required: true,
1046
+ },
1047
+ url: {
1048
+ name: 'url',
1049
+ type: 'string',
1050
+ description: 'The HTTP URL of the MCP server.',
1051
+ required: true,
1052
+ },
1053
+ },
1054
+ },
989
1055
  opera_research: {
990
1056
  description: "Ask Opera's built-in AI to research a topic and return a summary. Only available when connected to Opera Neon.",
991
1057
  category: 'Opera',
@@ -1005,6 +1071,18 @@ export const commands = {
1005
1071
  },
1006
1072
  },
1007
1073
  },
1074
+ opera_unregister_mcp_server: {
1075
+ description: 'Remove a registered MCP server and its stored auth tokens. Only available when connected to Opera Neon.',
1076
+ category: 'Opera',
1077
+ args: {
1078
+ server: {
1079
+ name: 'server',
1080
+ type: 'string',
1081
+ description: 'The MCP server name to unregister.',
1082
+ required: true,
1083
+ },
1084
+ },
1085
+ },
1008
1086
  performance_analyze_insight: {
1009
1087
  description: 'Provides more detailed information on a specific Performance Insight of an insight set that was highlighted in the results of a trace recording.',
1010
1088
  category: 'Performance',
@@ -73,7 +73,7 @@ export const operaChat = definePageTool({
73
73
  .describe('Model ID to use for the chat. Omit to use the browser default. Use opera_list_models to discover available IDs.'),
74
74
  },
75
75
  handler: async (request, response) => {
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
77
77
  const session = getCDPSession(request.page.pptrPage);
78
78
  try {
79
79
  const payload = {
@@ -106,7 +106,7 @@ export const operaDo = definePageTool({
106
106
  .describe('The action to perform, described in natural language.'),
107
107
  },
108
108
  handler: async (request, response) => {
109
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
110
110
  const session = getCDPSession(request.page.pptrPage);
111
111
  try {
112
112
  const result = await dispatchWithStreamedResponse(session, {
@@ -136,7 +136,7 @@ export const operaMake = definePageTool({
136
136
  prompt: zod.string().describe('Description of what to create or generate.'),
137
137
  },
138
138
  handler: async (request, response) => {
139
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
140
140
  const session = getCDPSession(request.page.pptrPage);
141
141
  try {
142
142
  const result = await dispatchAction(session, {
@@ -167,7 +167,7 @@ export const operaResearch = definePageTool({
167
167
  .describe('Depth of research. "local" uses only on-page context, "one-minute" performs a quick web search, "deep" performs a thorough multi-source search.'),
168
168
  },
169
169
  handler: async (request, response) => {
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
171
171
  const session = getCDPSession(request.page.pptrPage);
172
172
  const payload = {
173
173
  action: 'research',
@@ -199,7 +199,7 @@ export const operaListModels = definePageTool({
199
199
  },
200
200
  schema: {},
201
201
  handler: async (request, response) => {
202
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
203
203
  const session = getCDPSession(request.page.pptrPage);
204
204
  try {
205
205
  const result = await dispatchAction(session, { action: 'listModels' });
@@ -223,7 +223,7 @@ export const operaListMcpServers = definePageTool({
223
223
  },
224
224
  schema: {},
225
225
  handler: async (request, response) => {
226
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
227
227
  const session = getCDPSession(request.page.pptrPage);
228
228
  try {
229
229
  const result = await dispatchAction(session, {
@@ -255,7 +255,7 @@ export const operaListMcpTools = definePageTool({
255
255
  .describe('The MCP server name (from opera_list_mcp_servers).'),
256
256
  },
257
257
  handler: async (request, response) => {
258
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
259
259
  const session = getCDPSession(request.page.pptrPage);
260
260
  try {
261
261
  const result = await dispatchAction(session, {
@@ -297,7 +297,7 @@ export const operaCallMcpTool = definePageTool({
297
297
  .describe('Parameters to pass to the tool. Omit if the tool takes none.'),
298
298
  },
299
299
  handler: async (request, response) => {
300
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
301
301
  const session = getCDPSession(request.page.pptrPage);
302
302
  try {
303
303
  const payload = {
@@ -320,4 +320,182 @@ export const operaCallMcpTool = definePageTool({
320
320
  }
321
321
  },
322
322
  });
323
+ export const operaRegisterMcpServer = definePageTool({
324
+ name: 'opera_register_mcp_server',
325
+ description: 'Register an MCP server in the browser. Does not connect or authenticate — ' +
326
+ 'follow with opera_connect_mcp_server. Only available when connected to Opera Neon.',
327
+ blockedByDialog: false,
328
+ verifyFilesSchema: {},
329
+ annotations: {
330
+ category: ToolCategory.OPERA,
331
+ readOnlyHint: false,
332
+ },
333
+ schema: {
334
+ server: zod.string().min(1).describe('The MCP server name to register.'),
335
+ url: zod.string().min(1).describe('The HTTP URL of the MCP server.'),
336
+ },
337
+ handler: async (request, response) => {
338
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
339
+ const session = getCDPSession(request.page.pptrPage);
340
+ try {
341
+ const result = await dispatchAction(session, {
342
+ action: 'registerMcpServer',
343
+ type: 'REGISTER_SERVER',
344
+ server: request.params.server,
345
+ transportInfo: { type: 'http', url: request.params.url },
346
+ });
347
+ response.appendResponseLine(result);
348
+ }
349
+ catch (e) {
350
+ response.appendResponseLine(`Opera.dispatchAction(registerMcpServer) failed with error: ${e.message}`);
351
+ }
352
+ },
353
+ });
354
+ export const operaConnectMcpServer = definePageTool({
355
+ name: 'opera_connect_mcp_server',
356
+ description: 'Connect to a registered MCP server. If the server requires OAuth, ' +
357
+ 'the response includes requiresAuth: "needed" — follow with ' +
358
+ 'opera_authenticate_mcp_server. Only available when connected to Opera Neon.',
359
+ blockedByDialog: false,
360
+ verifyFilesSchema: {},
361
+ annotations: {
362
+ category: ToolCategory.OPERA,
363
+ readOnlyHint: false,
364
+ },
365
+ schema: {
366
+ server: zod.string().min(1).describe('The MCP server name to connect.'),
367
+ },
368
+ handler: async (request, response) => {
369
+ const session = getCDPSession(request.page.pptrPage);
370
+ try {
371
+ const result = await dispatchAction(session, {
372
+ action: 'connectMcpServer',
373
+ type: 'CONNECT_SERVER',
374
+ server: request.params.server,
375
+ });
376
+ response.appendResponseLine(result);
377
+ }
378
+ catch (e) {
379
+ response.appendResponseLine(`Opera.dispatchAction(connectMcpServer) failed with error: ${e.message}`);
380
+ }
381
+ },
382
+ });
383
+ export const operaAuthenticateMcpServer = definePageTool({
384
+ name: 'opera_authenticate_mcp_server',
385
+ description: 'Complete OAuth sign-in for an MCP server that requires authentication. ' +
386
+ 'Opens a browser popup for the OAuth flow; requires a headed (visible) browser. ' +
387
+ 'Only available when connected to Opera Neon.',
388
+ blockedByDialog: false,
389
+ verifyFilesSchema: {},
390
+ annotations: {
391
+ category: ToolCategory.OPERA,
392
+ readOnlyHint: false,
393
+ },
394
+ schema: {
395
+ server: zod
396
+ .string()
397
+ .min(1)
398
+ .describe('The MCP server name to authenticate.'),
399
+ },
400
+ handler: async (request, response) => {
401
+ const session = getCDPSession(request.page.pptrPage);
402
+ try {
403
+ const result = await dispatchWithStreamedResponse(session, {
404
+ action: 'authenticateMcpServer',
405
+ type: 'AUTHENTICATE_SERVER',
406
+ server: request.params.server,
407
+ }, chunk => response.sendLog(chunk), request.signal);
408
+ response.appendResponseLine(result);
409
+ }
410
+ catch (e) {
411
+ if (e.name === 'AbortError') {
412
+ throw e;
413
+ }
414
+ response.appendResponseLine(`Opera.dispatchWithStreamedResponse(authenticateMcpServer) failed with error: ${e.message}`);
415
+ }
416
+ },
417
+ });
418
+ export const operaUnregisterMcpServer = definePageTool({
419
+ name: 'opera_unregister_mcp_server',
420
+ description: 'Remove a registered MCP server and its stored auth tokens. ' +
421
+ 'Only available when connected to Opera Neon.',
422
+ blockedByDialog: false,
423
+ verifyFilesSchema: {},
424
+ annotations: {
425
+ category: ToolCategory.OPERA,
426
+ readOnlyHint: false,
427
+ },
428
+ schema: {
429
+ server: zod.string().min(1).describe('The MCP server name to unregister.'),
430
+ },
431
+ handler: async (request, response) => {
432
+ const session = getCDPSession(request.page.pptrPage);
433
+ try {
434
+ const result = await dispatchAction(session, {
435
+ action: 'unregisterMcpServer',
436
+ type: 'UNREGISTER_SERVER',
437
+ server: request.params.server,
438
+ });
439
+ response.appendResponseLine(result);
440
+ }
441
+ catch (e) {
442
+ response.appendResponseLine(`Opera.dispatchAction(unregisterMcpServer) failed with error: ${e.message}`);
443
+ }
444
+ },
445
+ });
446
+ export const operaEnableMcpServer = definePageTool({
447
+ name: 'opera_enable_mcp_server',
448
+ description: 'Enable a disabled MCP server. Only available when connected to Opera Neon.',
449
+ blockedByDialog: false,
450
+ verifyFilesSchema: {},
451
+ annotations: {
452
+ category: ToolCategory.OPERA,
453
+ readOnlyHint: false,
454
+ },
455
+ schema: {
456
+ server: zod.string().min(1).describe('The MCP server name to enable.'),
457
+ },
458
+ handler: async (request, response) => {
459
+ const session = getCDPSession(request.page.pptrPage);
460
+ try {
461
+ const result = await dispatchAction(session, {
462
+ action: 'enableMcpServer',
463
+ type: 'ENABLE_SERVER',
464
+ server: request.params.server,
465
+ });
466
+ response.appendResponseLine(result);
467
+ }
468
+ catch (e) {
469
+ response.appendResponseLine(`Opera.dispatchAction(enableMcpServer) failed with error: ${e.message}`);
470
+ }
471
+ },
472
+ });
473
+ export const operaDisableMcpServer = definePageTool({
474
+ name: 'opera_disable_mcp_server',
475
+ description: 'Disable an MCP server without unregistering it. ' +
476
+ 'Only available when connected to Opera Neon.',
477
+ blockedByDialog: false,
478
+ verifyFilesSchema: {},
479
+ annotations: {
480
+ category: ToolCategory.OPERA,
481
+ readOnlyHint: false,
482
+ },
483
+ schema: {
484
+ server: zod.string().min(1).describe('The MCP server name to disable.'),
485
+ },
486
+ handler: async (request, response) => {
487
+ const session = getCDPSession(request.page.pptrPage);
488
+ try {
489
+ const result = await dispatchAction(session, {
490
+ action: 'disableMcpServer',
491
+ type: 'DISABLE_SERVER',
492
+ server: request.params.server,
493
+ });
494
+ response.appendResponseLine(result);
495
+ }
496
+ catch (e) {
497
+ response.appendResponseLine(`Opera.dispatchAction(disableMcpServer) failed with error: ${e.message}`);
498
+ }
499
+ },
500
+ });
323
501
  //# sourceMappingURL=opera.js.map
@@ -5,5 +5,5 @@
5
5
  *
6
6
  * Modified by Opera Software AS.
7
7
  */
8
- export const VERSION = '0.4.0';
8
+ export const VERSION = '0.5.0';
9
9
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opera-devtools-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server for Opera DevTools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,6 @@
29
29
  "prepare": "node scripts/prepare.ts && node scripts/setup-intake.ts",
30
30
  "sync": "npm ci && git submodule update --init",
31
31
  "verify-upstream-seam": "node scripts/verify-upstream-seam.ts",
32
- "verify-server-json-version": "node scripts/verify-server-json-version.ts",
33
32
  "update-lighthouse": "node scripts/update-lighthouse.ts",
34
33
  "update-metrics": "node scripts/update_metrics.ts",
35
34
  "verify-npm-package": "node scripts/verify-npm-package.js",