@wordbricks/playwright-mcp 0.1.24 → 0.1.26

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.
Files changed (82) hide show
  1. package/lib/browserContextFactory.js +399 -0
  2. package/lib/browserServerBackend.js +86 -0
  3. package/lib/config.js +300 -0
  4. package/lib/context.js +311 -0
  5. package/lib/extension/cdpRelay.js +352 -0
  6. package/lib/extension/extensionContextFactory.js +56 -0
  7. package/lib/frameworkPatterns.js +35 -0
  8. package/lib/hooks/antiBotDetectionHook.js +178 -0
  9. package/lib/hooks/core.js +145 -0
  10. package/lib/hooks/eventConsumer.js +52 -0
  11. package/lib/hooks/events.js +42 -0
  12. package/lib/hooks/formatToolCallEvent.js +12 -0
  13. package/lib/hooks/frameworkStateHook.js +182 -0
  14. package/lib/hooks/grouping.js +72 -0
  15. package/lib/hooks/jsonLdDetectionHook.js +182 -0
  16. package/lib/hooks/networkFilters.js +82 -0
  17. package/lib/hooks/networkSetup.js +61 -0
  18. package/lib/hooks/networkTrackingHook.js +67 -0
  19. package/lib/hooks/pageHeightHook.js +75 -0
  20. package/lib/hooks/registry.js +41 -0
  21. package/lib/hooks/requireTabHook.js +26 -0
  22. package/lib/hooks/schema.js +89 -0
  23. package/lib/hooks/waitHook.js +33 -0
  24. package/lib/index.js +41 -0
  25. package/lib/mcp/inProcessTransport.js +71 -0
  26. package/lib/mcp/proxyBackend.js +130 -0
  27. package/lib/mcp/server.js +91 -0
  28. package/lib/mcp/tool.js +44 -0
  29. package/lib/mcp/transport.js +188 -0
  30. package/lib/playwrightTransformer.js +520 -0
  31. package/lib/program.js +112 -0
  32. package/lib/response.js +192 -0
  33. package/lib/sessionLog.js +123 -0
  34. package/lib/tab.js +251 -0
  35. package/lib/tools/common.js +55 -0
  36. package/lib/tools/console.js +33 -0
  37. package/lib/tools/dialogs.js +50 -0
  38. package/lib/tools/evaluate.js +62 -0
  39. package/lib/tools/extractFrameworkState.js +225 -0
  40. package/lib/tools/files.js +48 -0
  41. package/lib/tools/form.js +66 -0
  42. package/lib/tools/getSnapshot.js +36 -0
  43. package/lib/tools/getVisibleHtml.js +68 -0
  44. package/lib/tools/install.js +51 -0
  45. package/lib/tools/keyboard.js +83 -0
  46. package/lib/tools/mouse.js +97 -0
  47. package/lib/tools/navigate.js +66 -0
  48. package/lib/tools/network.js +121 -0
  49. package/lib/tools/networkDetail.js +238 -0
  50. package/lib/tools/networkSearch/bodySearch.js +161 -0
  51. package/lib/tools/networkSearch/grouping.js +37 -0
  52. package/lib/tools/networkSearch/helpers.js +32 -0
  53. package/lib/tools/networkSearch/searchHtml.js +76 -0
  54. package/lib/tools/networkSearch/types.js +1 -0
  55. package/lib/tools/networkSearch/urlSearch.js +124 -0
  56. package/lib/tools/networkSearch.js +278 -0
  57. package/lib/tools/pdf.js +41 -0
  58. package/lib/tools/repl.js +414 -0
  59. package/lib/tools/screenshot.js +103 -0
  60. package/lib/tools/scroll.js +131 -0
  61. package/lib/tools/snapshot.js +161 -0
  62. package/lib/tools/tabs.js +62 -0
  63. package/lib/tools/tool.js +35 -0
  64. package/lib/tools/utils.js +78 -0
  65. package/lib/tools/wait.js +60 -0
  66. package/lib/tools.js +68 -0
  67. package/lib/utils/adBlockFilter.js +90 -0
  68. package/lib/utils/codegen.js +55 -0
  69. package/lib/utils/extensionPath.js +10 -0
  70. package/lib/utils/fileUtils.js +40 -0
  71. package/lib/utils/graphql.js +269 -0
  72. package/lib/utils/guid.js +22 -0
  73. package/lib/utils/httpServer.js +39 -0
  74. package/lib/utils/log.js +21 -0
  75. package/lib/utils/manualPromise.js +111 -0
  76. package/lib/utils/networkFormat.js +14 -0
  77. package/lib/utils/package.js +20 -0
  78. package/lib/utils/result.js +2 -0
  79. package/lib/utils/sanitizeHtml.js +130 -0
  80. package/lib/utils/truncate.js +103 -0
  81. package/lib/utils/withTimeout.js +7 -0
  82. package/package.json +11 -1
@@ -0,0 +1,82 @@
1
+ const MEANINGFUL_RESOURCE_TYPES = ["document", "xhr", "fetch"];
2
+ const ALLOWED_METHODS = ["GET", "POST"];
3
+ const EXCLUDED_EXTENSIONS = [
4
+ ".svg",
5
+ ".css",
6
+ ".map", // JS files and source maps
7
+ ];
8
+ const hasExcludedExtension = (url) => {
9
+ return EXCLUDED_EXTENSIONS.some((ext) => {
10
+ const extRegex = new RegExp(`${ext.replace(".", "\\.")}(\\?|#|$)`, "i");
11
+ return extRegex.test(url);
12
+ });
13
+ };
14
+ export const isAntiBotUrl = (url) => {
15
+ if (url.includes("challenges.cloudflare.com"))
16
+ return true;
17
+ if (url.includes(".awswaf.com"))
18
+ return true;
19
+ return false;
20
+ };
21
+ const isSuccessfulStatus = (status) => {
22
+ // Status 0 is for failed requests, which we want to capture
23
+ // 2xx status codes are successful
24
+ // Exclude 204 No Content
25
+ return status === 0 || (status >= 200 && status < 300 && status !== 204);
26
+ };
27
+ export const shouldCaptureRequest = (method, url, status, resourceType) => {
28
+ if (isAntiBotUrl(url))
29
+ return true;
30
+ return (!hasExcludedExtension(url) &&
31
+ MEANINGFUL_RESOURCE_TYPES.includes(resourceType) &&
32
+ ALLOWED_METHODS.includes(method) &&
33
+ isSuccessfulStatus(status));
34
+ };
35
+ /**
36
+ * Format URL with trimmed parameters
37
+ */
38
+ export const formatUrlWithTrimmedParams = (url) => {
39
+ try {
40
+ const urlObj = new URL(url);
41
+ const params = urlObj.searchParams;
42
+ if (params.toString()) {
43
+ const trimmedParams = new URLSearchParams();
44
+ params.forEach((value, key) => {
45
+ if (value.length > 5)
46
+ trimmedParams.set(key, value.substring(0, 5) + "...");
47
+ else
48
+ trimmedParams.set(key, value);
49
+ });
50
+ return `${urlObj.origin}${urlObj.pathname}?${trimmedParams.toString()}`;
51
+ }
52
+ return url;
53
+ }
54
+ catch {
55
+ // If URL parsing fails, return as is
56
+ return url;
57
+ }
58
+ };
59
+ /**
60
+ * Normalize a pathname by removing trailing slashes (except root)
61
+ */
62
+ export const normalizePathname = (pathname) => {
63
+ if (!pathname)
64
+ return "/";
65
+ if (pathname === "/")
66
+ return "/";
67
+ return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
68
+ };
69
+ /**
70
+ * Normalize URL for grouping by ignoring query/hash and trailing slash
71
+ */
72
+ export const normalizeUrlForGrouping = (url) => {
73
+ try {
74
+ const u = new URL(url);
75
+ return `${u.origin}${normalizePathname(u.pathname)}`;
76
+ }
77
+ catch {
78
+ // Fallback for non-standard/relative URLs: strip query/hash and trailing slash
79
+ const base = url.split(/[?#]/)[0] || "/";
80
+ return normalizePathname(base);
81
+ }
82
+ };
@@ -0,0 +1,61 @@
1
+ import { trackEvent } from "./events.js";
2
+ import { shouldCaptureRequest } from "./networkFilters.js";
3
+ const eventIdToEntryMap = new WeakMap();
4
+ const getEventIdMap = (context) => {
5
+ let map = eventIdToEntryMap.get(context);
6
+ if (!map) {
7
+ map = new Map();
8
+ eventIdToEntryMap.set(context, map);
9
+ }
10
+ return map;
11
+ };
12
+ export const getNetworkEventEntry = (context, id) => getEventIdMap(context).get(id);
13
+ export const setupNetworkTracking = (context, page) => {
14
+ page.on("response", async (response) => {
15
+ const request = response.request();
16
+ const method = request.method();
17
+ const url = request.url();
18
+ const status = response.status();
19
+ const resourceType = request.resourceType();
20
+ // Apply filters before saving the event
21
+ if (shouldCaptureRequest(method, url, status, resourceType)) {
22
+ const setCookies = await response
23
+ .headerValues("set-cookie")
24
+ .catch(() => []);
25
+ const cookieValues = setCookies.length ? setCookies : undefined;
26
+ const networkData = {
27
+ method,
28
+ url,
29
+ status,
30
+ resourceType,
31
+ postData: request.postData() || undefined,
32
+ setCookies: cookieValues,
33
+ };
34
+ const id = trackEvent(context, {
35
+ type: "network-request",
36
+ data: networkData,
37
+ });
38
+ getEventIdMap(context).set(id, { request, response });
39
+ }
40
+ });
41
+ page.on("requestfailed", (request) => {
42
+ const method = request.method();
43
+ const url = request.url();
44
+ const status = 0; // Failed requests have status 0
45
+ const resourceType = request.resourceType();
46
+ if (shouldCaptureRequest(method, url, status, resourceType)) {
47
+ const networkData = {
48
+ method,
49
+ url,
50
+ status,
51
+ resourceType,
52
+ postData: request.postData() || undefined,
53
+ };
54
+ const id = trackEvent(context, {
55
+ type: "network-request",
56
+ data: networkData,
57
+ });
58
+ getEventIdMap(context).set(id, { request });
59
+ }
60
+ });
61
+ };
@@ -0,0 +1,67 @@
1
+ import { filter, pipe, toArray } from "@fxts/core";
2
+ import { parseGraphQLRequestFromHttp } from "../utils/graphql.js";
3
+ import { formatNetworkSummaryLine } from "../utils/networkFormat.js";
4
+ import { Ok } from "../utils/result.js";
5
+ import { getEventStore, isEventType } from "./events.js";
6
+ import { defineGroupingRule } from "./grouping.js";
7
+ import { normalizeUrlForGrouping } from "./networkFilters.js";
8
+ import { hookNameSchema } from "./schema.js";
9
+ const networkTrackingPreHook = {
10
+ name: hookNameSchema.enum["network-tracking-pre"],
11
+ handler: async (_ctx) => {
12
+ // Pre-hook now just acts as a marker, event consumption happens elsewhere
13
+ return Ok(undefined);
14
+ },
15
+ };
16
+ const networkTrackingPostHook = {
17
+ name: hookNameSchema.enum["network-tracking-post"],
18
+ handler: async (_ctx) => {
19
+ // Post-hook now just acts as a marker, network event collection happens automatically
20
+ return Ok(undefined);
21
+ },
22
+ };
23
+ export const networkTrackingHooks = {
24
+ pre: networkTrackingPreHook,
25
+ post: networkTrackingPostHook,
26
+ };
27
+ export const formatNetworkEvent = (event) => {
28
+ const { method, url, status, postData, setCookies } = event.data;
29
+ const summary = formatNetworkSummaryLine({ method, url, status, postData });
30
+ if (!setCookies || setCookies.length === 0)
31
+ return summary;
32
+ const names = setCookies
33
+ .map((cookie) => {
34
+ const firstPart = cookie.split(";", 1)[0];
35
+ const [name] = firstPart.split("=", 1);
36
+ return name?.trim();
37
+ })
38
+ .filter((name) => !!name);
39
+ if (!names.length)
40
+ return summary;
41
+ return `${summary} | Set-Cookie keys: ${names.join(", ")}`;
42
+ };
43
+ const computeNetworkGroupKey = (event) => {
44
+ const method = (event.data.method || "").toUpperCase();
45
+ const baseUrl = normalizeUrlForGrouping(event.data.url);
46
+ const gql = parseGraphQLRequestFromHttp(event.data.method, event.data.url, {}, event.data.postData);
47
+ if (gql) {
48
+ const type = gql.operationType === "unknown" ? "operation" : gql.operationType;
49
+ const op = gql.operationName ? `${type} ${gql.operationName}` : type;
50
+ return `${method} ${baseUrl} [GraphQL: ${op}]`;
51
+ }
52
+ return `${method} ${baseUrl}`;
53
+ };
54
+ export const networkGroupingRule = defineGroupingRule({
55
+ match: (e) => e.type === "network-request",
56
+ keyOf: (e) => computeNetworkGroupKey(e),
57
+ summaryOf: (first, run) => {
58
+ const key = computeNetworkGroupKey(first);
59
+ const count = run.length;
60
+ const firstStatus = first.data.status;
61
+ const allSameStatus = run.every((e) => e.data.status === firstStatus);
62
+ return allSameStatus
63
+ ? `${key} → ${firstStatus} (x${count})`
64
+ : `${key} (x${count})`;
65
+ },
66
+ });
67
+ export const listNetworkEvents = (context) => pipe(context, getEventStore, (store) => store.events.values(), filter(isEventType("network-request")), toArray);
@@ -0,0 +1,75 @@
1
+ import { Err, Ok } from "../utils/result.js";
2
+ import { trackEvent } from "./events.js";
3
+ import { hookNameSchema } from "./schema.js";
4
+ const pageHeightStateMap = new WeakMap();
5
+ const capturePageHeight = async (ctx) => {
6
+ const tab = ctx.tab || ctx.context.currentTab();
7
+ if (!tab)
8
+ return undefined;
9
+ try {
10
+ return await tab.page.evaluate(() => ({
11
+ pageHeight: document.documentElement.scrollHeight,
12
+ scrollY: window.scrollY,
13
+ }));
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ };
19
+ const pageHeightPreHook = {
20
+ name: hookNameSchema.enum["page-height-pre"],
21
+ handler: async (ctx) => {
22
+ try {
23
+ const initialState = await capturePageHeight(ctx);
24
+ if (!initialState)
25
+ return Ok(undefined);
26
+ // Store initial state for post-hook (keyed by Context)
27
+ pageHeightStateMap.set(ctx.context, initialState);
28
+ return Ok(undefined);
29
+ }
30
+ catch (error) {
31
+ return Err(error);
32
+ }
33
+ },
34
+ };
35
+ const pageHeightPostHook = {
36
+ name: hookNameSchema.enum["page-height-post"],
37
+ handler: async (ctx) => {
38
+ try {
39
+ const initialState = pageHeightStateMap.get(ctx.context);
40
+ if (!initialState)
41
+ return Ok(undefined);
42
+ const finalState = await capturePageHeight(ctx);
43
+ if (!finalState)
44
+ return Ok(undefined);
45
+ const heightChange = finalState.pageHeight - initialState.pageHeight;
46
+ const tab = ctx.tab || ctx.context.currentTab();
47
+ const url = tab ? tab.page.url() : "unknown";
48
+ trackEvent(ctx.context, {
49
+ type: "page-height-change",
50
+ data: {
51
+ previousHeight: initialState.pageHeight,
52
+ currentHeight: finalState.pageHeight,
53
+ heightChange: heightChange,
54
+ scrollY: finalState.scrollY,
55
+ url: url,
56
+ },
57
+ timestamp: Date.now(),
58
+ });
59
+ return Ok(undefined);
60
+ }
61
+ catch (error) {
62
+ return Err(error);
63
+ }
64
+ },
65
+ };
66
+ export const pageHeightHooks = {
67
+ pre: pageHeightPreHook,
68
+ post: pageHeightPostHook,
69
+ };
70
+ export const formatPageHeightEvent = (event) => {
71
+ const { previousHeight, currentHeight, heightChange } = event.data;
72
+ if (heightChange === 0)
73
+ return `Page height unchanged.`;
74
+ return `Page height changed: ${previousHeight}px → ${currentHeight}px (${heightChange > 0 ? "+" : ""}${heightChange}px)`;
75
+ };
@@ -0,0 +1,41 @@
1
+ import { pipe, reduce, values } from "@fxts/core";
2
+ import { antiBotDetectionHooks } from "./antiBotDetectionHook.js";
3
+ import { createHookRegistry, setToolHooks } from "./core.js";
4
+ import { frameworkStateHooks } from "./frameworkStateHook.js";
5
+ import { registerGroupingRule } from "./grouping.js";
6
+ import { jsonLdDetectionHooks } from "./jsonLdDetectionHook.js";
7
+ import { networkGroupingRule, networkTrackingHooks, } from "./networkTrackingHook.js";
8
+ import { pageHeightHooks } from "./pageHeightHook.js";
9
+ import { requireTabHooks } from "./requireTabHook.js";
10
+ import { toolNameSchema } from "./schema.js";
11
+ import { waitHooks } from "./waitHook.js";
12
+ const COMMON_HOOKS = {
13
+ preHooks: [
14
+ requireTabHooks.pre,
15
+ networkTrackingHooks.pre,
16
+ antiBotDetectionHooks.pre,
17
+ pageHeightHooks.pre,
18
+ frameworkStateHooks.pre,
19
+ jsonLdDetectionHooks.pre,
20
+ ],
21
+ postHooks: [
22
+ networkTrackingHooks.post,
23
+ antiBotDetectionHooks.post,
24
+ frameworkStateHooks.post,
25
+ jsonLdDetectionHooks.post,
26
+ pageHeightHooks.post,
27
+ waitHooks.post,
28
+ ],
29
+ };
30
+ const toolHooksConfig = [
31
+ // Example hook registration
32
+ // {
33
+ // toolName: toolNameSchema.enum.browser_click,
34
+ // preHooks: [],
35
+ // postHooks: [],
36
+ // },
37
+ ];
38
+ export const buildHookRegistry = () => {
39
+ registerGroupingRule("network-request", networkGroupingRule);
40
+ return pipe(createHookRegistry(), (registry) => reduce((acc, toolName) => setToolHooks(acc, { toolName, ...COMMON_HOOKS }), registry, values(toolNameSchema.enum)), (registry) => reduce(setToolHooks, registry, toolHooksConfig));
41
+ };
@@ -0,0 +1,26 @@
1
+ import { Err, Ok } from "../utils/result.js";
2
+ import { hookNameSchema, toolNameSchema } from "./schema.js";
3
+ const MESSAGE = 'No open tabs. Use the "browser_navigate" tool to navigate to a page first.';
4
+ const requireTabPreHook = {
5
+ name: hookNameSchema.enum["require-tab-pre"],
6
+ handler: async (ctx) => {
7
+ try {
8
+ // Allow navigate tool to create/open a tab
9
+ if (ctx.toolName === toolNameSchema.enum.browser_navigate)
10
+ return Ok(undefined);
11
+ // If no current tab, emit a standardized error and stop execution
12
+ if (!ctx.context.currentTab()) {
13
+ // Include tabs section in response so the message is rendered consistently
14
+ ctx.response.setIncludeTabs();
15
+ return Err(new Error(MESSAGE));
16
+ }
17
+ return Ok(undefined);
18
+ }
19
+ catch (error) {
20
+ return Err(error);
21
+ }
22
+ },
23
+ };
24
+ export const requireTabHooks = {
25
+ pre: requireTabPreHook,
26
+ };
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ export const hookNameSchema = z.enum([
3
+ "network-tracking-pre",
4
+ "network-tracking-post",
5
+ "page-height-pre",
6
+ "page-height-post",
7
+ "wait-post",
8
+ "framework-state-pre",
9
+ "framework-state-post",
10
+ "json-ld-detection-pre",
11
+ "json-ld-detection-post",
12
+ "require-tab-pre",
13
+ "anti-bot-detection-pre",
14
+ "anti-bot-detection-post",
15
+ ]);
16
+ // Tool names enum - should match actual tool names in
17
+ export const toolNameSchema = z.enum([
18
+ "browser_click",
19
+ "browser_extract_framework_state",
20
+ "browser_fill_form",
21
+ "browser_get_snapshot",
22
+ // 'browser_get_visible_html',
23
+ "browser_navigate",
24
+ // 'browser_navigate_back',
25
+ // 'browser_navigate_forward',
26
+ "browser_network_detail",
27
+ "browser_network_search",
28
+ "browser_press_key",
29
+ "browser_reload",
30
+ "browser_repl",
31
+ "browser_scroll",
32
+ "browser_type",
33
+ "browser_wait",
34
+ ]);
35
+ export const EventTypeSchema = z.enum([
36
+ "network-request",
37
+ "page-height-change",
38
+ "wait",
39
+ "tool-call",
40
+ "framework-state",
41
+ "json-ld",
42
+ "anti-bot",
43
+ ]);
44
+ export const NetworkRequestEventDataSchema = z.object({
45
+ method: z.string(),
46
+ url: z.string(),
47
+ status: z.number(),
48
+ resourceType: z.string(),
49
+ postData: z.string().optional(),
50
+ setCookies: z.array(z.string()).optional(),
51
+ responseSize: z.number().optional(),
52
+ });
53
+ export const PageHeightChangeEventDataSchema = z.object({
54
+ previousHeight: z.number(),
55
+ currentHeight: z.number(),
56
+ heightChange: z.number(),
57
+ scrollY: z.number(),
58
+ url: z.string(),
59
+ });
60
+ export const WaitEventDataSchema = z.object({
61
+ duration: z.number(),
62
+ });
63
+ export const ToolCallEventDataSchema = z.object({
64
+ toolName: z.string(),
65
+ params: z.record(z.unknown()).optional(),
66
+ executionTime: z.number().optional(),
67
+ success: z.boolean().optional(),
68
+ });
69
+ export const FrameworkStateEventDataSchema = z.object({
70
+ state: z.record(z.any()),
71
+ changes: z.array(z.string()).optional(),
72
+ action: z.enum(["detected", "changed"]),
73
+ });
74
+ export const JsonLdEventDataSchema = z.object({
75
+ state: z.record(z.object({
76
+ count: z.number(),
77
+ indices: z.array(z.number()),
78
+ })),
79
+ changes: z.array(z.string()).optional(),
80
+ action: z.enum(["detected", "changed"]),
81
+ });
82
+ export const AntiBotEventDataSchema = z.object({
83
+ provider: z.enum(["cloudflare-turnstile", "aws-waf"]),
84
+ detectionMethod: z.literal("network-request"),
85
+ url: z.string(),
86
+ status: z.number(),
87
+ action: z.enum(["detected", "resolved", "still-blocked"]),
88
+ waitMs: z.number().optional(),
89
+ });
@@ -0,0 +1,33 @@
1
+ import ms from "ms";
2
+ import { Err, Ok } from "../utils/result.js";
3
+ import { trackEvent } from "./events.js";
4
+ import { hookNameSchema } from "./schema.js";
5
+ export const WAIT_TIME_STR = "0.5s";
6
+ export const WAIT_TIME_MS = ms(WAIT_TIME_STR);
7
+ const waitPostHook = {
8
+ name: hookNameSchema.enum["wait-post"],
9
+ handler: async (ctx) => {
10
+ try {
11
+ const tab = ctx.tab || ctx.context.currentTab();
12
+ if (!tab)
13
+ return Ok(undefined);
14
+ const startTime = Date.now();
15
+ await tab.page.waitForTimeout(WAIT_TIME_MS);
16
+ trackEvent(ctx.context, {
17
+ type: "wait",
18
+ data: {
19
+ duration: WAIT_TIME_MS,
20
+ },
21
+ timestamp: startTime,
22
+ });
23
+ return Ok(undefined);
24
+ }
25
+ catch (error) {
26
+ return Err(error);
27
+ }
28
+ },
29
+ };
30
+ export const waitHooks = {
31
+ post: waitPostHook,
32
+ };
33
+ export const formatWaitEvent = (event) => `Waited ${event.data.duration}ms`;
package/lib/index.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { contextFactory } from "./browserContextFactory.js";
17
+ import { BrowserServerBackend } from "./browserServerBackend.js";
18
+ import { resolveConfig } from "./config.js";
19
+ import * as mcpServer from "./mcp/server.js";
20
+ export async function createConnection(userConfig, contextGetter) {
21
+ const config = await resolveConfig(userConfig || {});
22
+ const factory = contextGetter
23
+ ? new SimpleBrowserContextFactory(contextGetter)
24
+ : contextFactory(config);
25
+ return mcpServer.createServer(new BrowserServerBackend(config, factory), false);
26
+ }
27
+ class SimpleBrowserContextFactory {
28
+ name = "custom";
29
+ description = "Connect to a browser using a custom context getter";
30
+ _contextGetter;
31
+ constructor(contextGetter) {
32
+ this._contextGetter = contextGetter;
33
+ }
34
+ async createContext() {
35
+ const browserContext = await this._contextGetter();
36
+ return {
37
+ browserContext,
38
+ close: () => browserContext.close(),
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export class InProcessTransport {
17
+ _server;
18
+ _serverTransport;
19
+ _connected = false;
20
+ constructor(server) {
21
+ this._server = server;
22
+ this._serverTransport = new InProcessServerTransport(this);
23
+ }
24
+ async start() {
25
+ if (this._connected)
26
+ throw new Error("InprocessTransport already started!");
27
+ await this._server.connect(this._serverTransport);
28
+ this._connected = true;
29
+ }
30
+ async send(message, options) {
31
+ if (!this._connected)
32
+ throw new Error("Transport not connected");
33
+ this._serverTransport._receiveFromClient(message);
34
+ }
35
+ async close() {
36
+ if (this._connected) {
37
+ this._connected = false;
38
+ this.onclose?.();
39
+ this._serverTransport.onclose?.();
40
+ }
41
+ }
42
+ onclose;
43
+ onerror;
44
+ onmessage;
45
+ sessionId;
46
+ setProtocolVersion;
47
+ _receiveFromServer(message, extra) {
48
+ this.onmessage?.(message, extra);
49
+ }
50
+ }
51
+ class InProcessServerTransport {
52
+ _clientTransport;
53
+ constructor(clientTransport) {
54
+ this._clientTransport = clientTransport;
55
+ }
56
+ async start() { }
57
+ async send(message, options) {
58
+ this._clientTransport._receiveFromServer(message);
59
+ }
60
+ async close() {
61
+ this.onclose?.();
62
+ }
63
+ onclose;
64
+ onerror;
65
+ onmessage;
66
+ sessionId;
67
+ setProtocolVersion;
68
+ _receiveFromClient(message) {
69
+ this.onmessage?.(message);
70
+ }
71
+ }