opera-devtools-mcp 0.4.2 → 0.5.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,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)
@@ -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',
@@ -938,6 +950,36 @@ export const commands = {
938
950
  description: 'Model ID to use for the chat. Omit to use the browser default. Use opera_list_models to discover available IDs.',
939
951
  required: false,
940
952
  },
953
+ conversationId: {
954
+ name: 'conversationId',
955
+ type: 'string',
956
+ description: 'Conversation ID to continue an existing conversation. Omit to start a new conversation.',
957
+ required: false,
958
+ },
959
+ },
960
+ },
961
+ opera_connect_mcp_server: {
962
+ 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.',
963
+ category: 'Opera',
964
+ args: {
965
+ server: {
966
+ name: 'server',
967
+ type: 'string',
968
+ description: 'The MCP server name to connect.',
969
+ required: true,
970
+ },
971
+ },
972
+ },
973
+ opera_disable_mcp_server: {
974
+ description: 'Disable an MCP server without unregistering it. Only available when connected to Opera Neon.',
975
+ category: 'Opera',
976
+ args: {
977
+ server: {
978
+ name: 'server',
979
+ type: 'string',
980
+ description: 'The MCP server name to disable.',
981
+ required: true,
982
+ },
941
983
  },
942
984
  },
943
985
  opera_do: {
@@ -952,6 +994,18 @@ export const commands = {
952
994
  },
953
995
  },
954
996
  },
997
+ opera_enable_mcp_server: {
998
+ description: 'Enable a disabled MCP server. Only available when connected to Opera Neon.',
999
+ category: 'Opera',
1000
+ args: {
1001
+ server: {
1002
+ name: 'server',
1003
+ type: 'string',
1004
+ description: 'The MCP server name to enable.',
1005
+ required: true,
1006
+ },
1007
+ },
1008
+ },
955
1009
  opera_list_mcp_servers: {
956
1010
  description: 'List MCP servers registered in the browser, including their connection status. Only available when connected to Opera Neon.',
957
1011
  category: 'Opera',
@@ -986,6 +1040,24 @@ export const commands = {
986
1040
  },
987
1041
  },
988
1042
  },
1043
+ opera_register_mcp_server: {
1044
+ 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.',
1045
+ category: 'Opera',
1046
+ args: {
1047
+ server: {
1048
+ name: 'server',
1049
+ type: 'string',
1050
+ description: 'The MCP server name to register.',
1051
+ required: true,
1052
+ },
1053
+ url: {
1054
+ name: 'url',
1055
+ type: 'string',
1056
+ description: 'The HTTP URL of the MCP server.',
1057
+ required: true,
1058
+ },
1059
+ },
1060
+ },
989
1061
  opera_research: {
990
1062
  description: "Ask Opera's built-in AI to research a topic and return a summary. Only available when connected to Opera Neon.",
991
1063
  category: 'Opera',
@@ -1005,6 +1077,18 @@ export const commands = {
1005
1077
  },
1006
1078
  },
1007
1079
  },
1080
+ opera_unregister_mcp_server: {
1081
+ description: 'Remove a registered MCP server and its stored auth tokens. Only available when connected to Opera Neon.',
1082
+ category: 'Opera',
1083
+ args: {
1084
+ server: {
1085
+ name: 'server',
1086
+ type: 'string',
1087
+ description: 'The MCP server name to unregister.',
1088
+ required: true,
1089
+ },
1090
+ },
1091
+ },
1008
1092
  performance_analyze_insight: {
1009
1093
  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
1094
  category: 'Performance',
@@ -71,9 +71,13 @@ export const operaChat = definePageTool({
71
71
  .string()
72
72
  .optional()
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
+ conversationId: zod
75
+ .string()
76
+ .optional()
77
+ .describe('Conversation ID to continue an existing conversation. Omit to start a new conversation.'),
74
78
  },
75
79
  handler: async (request, response) => {
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
77
81
  const session = getCDPSession(request.page.pptrPage);
78
82
  try {
79
83
  const payload = {
@@ -83,6 +87,9 @@ export const operaChat = definePageTool({
83
87
  if (request.params.model !== undefined) {
84
88
  payload['model'] = request.params.model;
85
89
  }
90
+ if (request.params.conversationId !== undefined) {
91
+ payload['conversationId'] = request.params.conversationId;
92
+ }
86
93
  const result = await dispatchAction(session, payload);
87
94
  response.appendResponseLine(result);
88
95
  }
@@ -106,7 +113,7 @@ export const operaDo = definePageTool({
106
113
  .describe('The action to perform, described in natural language.'),
107
114
  },
108
115
  handler: async (request, response) => {
109
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
110
117
  const session = getCDPSession(request.page.pptrPage);
111
118
  try {
112
119
  const result = await dispatchWithStreamedResponse(session, {
@@ -136,7 +143,7 @@ export const operaMake = definePageTool({
136
143
  prompt: zod.string().describe('Description of what to create or generate.'),
137
144
  },
138
145
  handler: async (request, response) => {
139
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
140
147
  const session = getCDPSession(request.page.pptrPage);
141
148
  try {
142
149
  const result = await dispatchAction(session, {
@@ -167,7 +174,7 @@ export const operaResearch = definePageTool({
167
174
  .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
175
  },
169
176
  handler: async (request, response) => {
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
177
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
171
178
  const session = getCDPSession(request.page.pptrPage);
172
179
  const payload = {
173
180
  action: 'research',
@@ -199,7 +206,7 @@ export const operaListModels = definePageTool({
199
206
  },
200
207
  schema: {},
201
208
  handler: async (request, response) => {
202
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
203
210
  const session = getCDPSession(request.page.pptrPage);
204
211
  try {
205
212
  const result = await dispatchAction(session, { action: 'listModels' });
@@ -223,7 +230,7 @@ export const operaListMcpServers = definePageTool({
223
230
  },
224
231
  schema: {},
225
232
  handler: async (request, response) => {
226
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
227
234
  const session = getCDPSession(request.page.pptrPage);
228
235
  try {
229
236
  const result = await dispatchAction(session, {
@@ -255,7 +262,7 @@ export const operaListMcpTools = definePageTool({
255
262
  .describe('The MCP server name (from opera_list_mcp_servers).'),
256
263
  },
257
264
  handler: async (request, response) => {
258
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
265
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
259
266
  const session = getCDPSession(request.page.pptrPage);
260
267
  try {
261
268
  const result = await dispatchAction(session, {
@@ -297,7 +304,7 @@ export const operaCallMcpTool = definePageTool({
297
304
  .describe('Parameters to pass to the tool. Omit if the tool takes none.'),
298
305
  },
299
306
  handler: async (request, response) => {
300
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
301
308
  const session = getCDPSession(request.page.pptrPage);
302
309
  try {
303
310
  const payload = {
@@ -320,4 +327,182 @@ export const operaCallMcpTool = definePageTool({
320
327
  }
321
328
  },
322
329
  });
330
+ export const operaRegisterMcpServer = definePageTool({
331
+ name: 'opera_register_mcp_server',
332
+ description: 'Register an MCP server in the browser. Does not connect or authenticate — ' +
333
+ 'follow with opera_connect_mcp_server. Only available when connected to Opera Neon.',
334
+ blockedByDialog: false,
335
+ verifyFilesSchema: {},
336
+ annotations: {
337
+ category: ToolCategory.OPERA,
338
+ readOnlyHint: false,
339
+ },
340
+ schema: {
341
+ server: zod.string().min(1).describe('The MCP server name to register.'),
342
+ url: zod.string().min(1).describe('The HTTP URL of the MCP server.'),
343
+ },
344
+ handler: async (request, response) => {
345
+ // puppeteer's _client() is internal; cast to the shape getCDPSession needs
346
+ const session = getCDPSession(request.page.pptrPage);
347
+ try {
348
+ const result = await dispatchAction(session, {
349
+ action: 'registerMcpServer',
350
+ type: 'REGISTER_SERVER',
351
+ server: request.params.server,
352
+ transportInfo: { type: 'http', url: request.params.url },
353
+ });
354
+ response.appendResponseLine(result);
355
+ }
356
+ catch (e) {
357
+ response.appendResponseLine(`Opera.dispatchAction(registerMcpServer) failed with error: ${e.message}`);
358
+ }
359
+ },
360
+ });
361
+ export const operaConnectMcpServer = definePageTool({
362
+ name: 'opera_connect_mcp_server',
363
+ description: 'Connect to a registered MCP server. If the server requires OAuth, ' +
364
+ 'the response includes requiresAuth: "needed" — follow with ' +
365
+ 'opera_authenticate_mcp_server. Only available when connected to Opera Neon.',
366
+ blockedByDialog: false,
367
+ verifyFilesSchema: {},
368
+ annotations: {
369
+ category: ToolCategory.OPERA,
370
+ readOnlyHint: false,
371
+ },
372
+ schema: {
373
+ server: zod.string().min(1).describe('The MCP server name to connect.'),
374
+ },
375
+ handler: async (request, response) => {
376
+ const session = getCDPSession(request.page.pptrPage);
377
+ try {
378
+ const result = await dispatchAction(session, {
379
+ action: 'connectMcpServer',
380
+ type: 'CONNECT_SERVER',
381
+ server: request.params.server,
382
+ });
383
+ response.appendResponseLine(result);
384
+ }
385
+ catch (e) {
386
+ response.appendResponseLine(`Opera.dispatchAction(connectMcpServer) failed with error: ${e.message}`);
387
+ }
388
+ },
389
+ });
390
+ export const operaAuthenticateMcpServer = definePageTool({
391
+ name: 'opera_authenticate_mcp_server',
392
+ description: 'Complete OAuth sign-in for an MCP server that requires authentication. ' +
393
+ 'Opens a browser popup for the OAuth flow; requires a headed (visible) browser. ' +
394
+ 'Only available when connected to Opera Neon.',
395
+ blockedByDialog: false,
396
+ verifyFilesSchema: {},
397
+ annotations: {
398
+ category: ToolCategory.OPERA,
399
+ readOnlyHint: false,
400
+ },
401
+ schema: {
402
+ server: zod
403
+ .string()
404
+ .min(1)
405
+ .describe('The MCP server name to authenticate.'),
406
+ },
407
+ handler: async (request, response) => {
408
+ const session = getCDPSession(request.page.pptrPage);
409
+ try {
410
+ const result = await dispatchWithStreamedResponse(session, {
411
+ action: 'authenticateMcpServer',
412
+ type: 'AUTHENTICATE_SERVER',
413
+ server: request.params.server,
414
+ }, chunk => response.sendLog(chunk), request.signal);
415
+ response.appendResponseLine(result);
416
+ }
417
+ catch (e) {
418
+ if (e.name === 'AbortError') {
419
+ throw e;
420
+ }
421
+ response.appendResponseLine(`Opera.dispatchWithStreamedResponse(authenticateMcpServer) failed with error: ${e.message}`);
422
+ }
423
+ },
424
+ });
425
+ export const operaUnregisterMcpServer = definePageTool({
426
+ name: 'opera_unregister_mcp_server',
427
+ description: 'Remove a registered MCP server and its stored auth tokens. ' +
428
+ 'Only available when connected to Opera Neon.',
429
+ blockedByDialog: false,
430
+ verifyFilesSchema: {},
431
+ annotations: {
432
+ category: ToolCategory.OPERA,
433
+ readOnlyHint: false,
434
+ },
435
+ schema: {
436
+ server: zod.string().min(1).describe('The MCP server name to unregister.'),
437
+ },
438
+ handler: async (request, response) => {
439
+ const session = getCDPSession(request.page.pptrPage);
440
+ try {
441
+ const result = await dispatchAction(session, {
442
+ action: 'unregisterMcpServer',
443
+ type: 'UNREGISTER_SERVER',
444
+ server: request.params.server,
445
+ });
446
+ response.appendResponseLine(result);
447
+ }
448
+ catch (e) {
449
+ response.appendResponseLine(`Opera.dispatchAction(unregisterMcpServer) failed with error: ${e.message}`);
450
+ }
451
+ },
452
+ });
453
+ export const operaEnableMcpServer = definePageTool({
454
+ name: 'opera_enable_mcp_server',
455
+ description: 'Enable a disabled MCP server. Only available when connected to Opera Neon.',
456
+ blockedByDialog: false,
457
+ verifyFilesSchema: {},
458
+ annotations: {
459
+ category: ToolCategory.OPERA,
460
+ readOnlyHint: false,
461
+ },
462
+ schema: {
463
+ server: zod.string().min(1).describe('The MCP server name to enable.'),
464
+ },
465
+ handler: async (request, response) => {
466
+ const session = getCDPSession(request.page.pptrPage);
467
+ try {
468
+ const result = await dispatchAction(session, {
469
+ action: 'enableMcpServer',
470
+ type: 'ENABLE_SERVER',
471
+ server: request.params.server,
472
+ });
473
+ response.appendResponseLine(result);
474
+ }
475
+ catch (e) {
476
+ response.appendResponseLine(`Opera.dispatchAction(enableMcpServer) failed with error: ${e.message}`);
477
+ }
478
+ },
479
+ });
480
+ export const operaDisableMcpServer = definePageTool({
481
+ name: 'opera_disable_mcp_server',
482
+ description: 'Disable an MCP server without unregistering it. ' +
483
+ 'Only available when connected to Opera Neon.',
484
+ blockedByDialog: false,
485
+ verifyFilesSchema: {},
486
+ annotations: {
487
+ category: ToolCategory.OPERA,
488
+ readOnlyHint: false,
489
+ },
490
+ schema: {
491
+ server: zod.string().min(1).describe('The MCP server name to disable.'),
492
+ },
493
+ handler: async (request, response) => {
494
+ const session = getCDPSession(request.page.pptrPage);
495
+ try {
496
+ const result = await dispatchAction(session, {
497
+ action: 'disableMcpServer',
498
+ type: 'DISABLE_SERVER',
499
+ server: request.params.server,
500
+ });
501
+ response.appendResponseLine(result);
502
+ }
503
+ catch (e) {
504
+ response.appendResponseLine(`Opera.dispatchAction(disableMcpServer) failed with error: ${e.message}`);
505
+ }
506
+ },
507
+ });
323
508
  //# 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.2';
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.2",
3
+ "version": "0.5.1",
4
4
  "description": "MCP server for Opera DevTools",
5
5
  "type": "module",
6
6
  "bin": {