@mario.andreschak/mcp-browser 3.42.1 → 3.44.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/dist/resources.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ensureBrowserGateway, browserSandboxAllowAll } from './gateway.js';
1
2
  const MCP_APPS_PROTOCOL_VERSION = '2026-01-26';
2
3
  const APP_MIME_TYPE = 'text/html;profile=mcp-app';
3
4
  export const BROWSER_APP_URI = 'ui://browser/view';
@@ -7,72 +8,292 @@ export function browserListResources() {
7
8
  uri: BROWSER_APP_URI,
8
9
  name: 'browser_view',
9
10
  mimeType: APP_MIME_TYPE,
10
- description: 'Interactive view of the isolated server-side Patchright browser session.',
11
+ description: 'Live view of the isolated server-side Patchright browser session.',
11
12
  }],
12
13
  };
13
14
  }
14
- export function browserReadResource(uri) {
15
+ /**
16
+ * Read the MCP App resource.
17
+ *
18
+ * The returned document is only a shell: it performs the MCP handshake, opens
19
+ * the browser session, and then frames the real UI served by the loopback
20
+ * gateway. The gateway origin is granted here through `_meta.ui.csp`, and the
21
+ * bearer token is templated into the shell so it never reaches the model.
22
+ */
23
+ export async function browserReadResource(uri) {
15
24
  if (uri !== BROWSER_APP_URI)
16
25
  throw new Error(`Unknown browser resource: ${uri}`);
26
+ const gateway = await ensureBrowserGateway();
27
+ const origins = gateway ? [gateway.origin] : [];
28
+ // Escape hatch: when the persisted `network.allowAllMcpAppContent` setting is
29
+ // enabled (propagated as FLUJO_MCP_APP_SANDBOX_ALLOW_ALL), widen the CSP so
30
+ // the shell can frame the gateway regardless of origin. This is intended only
31
+ // for hosted deployments behind a rewriting reverse proxy.
32
+ const allowAll = browserSandboxAllowAll();
33
+ const frameDomains = allowAll ? ['*'] : origins;
34
+ const connectDomains = allowAll ? ['*'] : origins;
35
+ const resourceDomains = allowAll ? ['*'] : origins;
17
36
  return {
18
37
  contents: [{
19
38
  uri,
20
39
  mimeType: APP_MIME_TYPE,
21
- text: BROWSER_APP_HTML,
40
+ text: renderBrowserAppHtml(gateway),
22
41
  _meta: {
23
42
  ui: {
24
- csp: { connectDomains: [], resourceDomains: [] },
43
+ prefersBorder: false,
44
+ csp: {
45
+ // frameDomains carries the live view; connect/resource keep the
46
+ // screenshot fallback working when the gateway is unavailable.
47
+ frameDomains,
48
+ connectDomains,
49
+ resourceDomains,
50
+ },
25
51
  permissions: {},
26
52
  },
27
53
  },
28
54
  }],
29
55
  };
30
56
  }
31
- const BROWSER_APP_HTML = `<!doctype html>
57
+ /**
58
+ * The MCP App shell.
59
+ *
60
+ * Kept deliberately small: everything the host's app CSP would restrict
61
+ * (workers, blob URLs, media pipelines) lives in the framed gateway document
62
+ * instead. The shell owns the MCP bridge, so navigation still travels the tool
63
+ * channel and stays visible to the model.
64
+ */
65
+ export function renderBrowserAppHtml(gateway) {
66
+ const config = JSON.stringify({
67
+ origin: gateway?.origin ?? '',
68
+ token: gateway?.token ?? '',
69
+ }).replace(/</g, '\\u003c');
70
+ return `<!doctype html>
32
71
  <html>
33
72
  <head>
34
73
  <meta charset="utf-8" />
35
74
  <style>
36
- :root{--bg:#fff;--fg:#171717;--muted:#666;--border:#ddd;--accent:#1565c0;--panel:#f6f7f8;--danger:#b42318}
37
- [data-theme="dark"]{--bg:#171717;--fg:#eee;--muted:#aaa;--border:#444;--accent:#64b5f6;--panel:#242424;--danger:#ff8a80}
38
- *{box-sizing:border-box}html,body{margin:0;min-height:100%}body{font:13px/1.45 system-ui,sans-serif;background:var(--bg);color:var(--fg)}
39
- #wrap{padding:10px}.row{display:flex;gap:6px;align-items:center;margin-bottom:7px;flex-wrap:wrap}input,button{font:inherit;border:1px solid var(--border);border-radius:5px;background:var(--bg);color:var(--fg);padding:5px 8px}button{cursor:pointer}button:hover{border-color:var(--accent)}#url{flex:1;min-width:220px}.selector{flex:1;min-width:150px}.status{color:var(--muted);min-height:19px}.error{color:var(--danger)}.viewport{border:1px solid var(--border);border-radius:7px;background:var(--panel);min-height:160px;display:grid;place-items:center;overflow:auto}.viewport img{display:block;max-width:100%;height:auto}.empty{color:var(--muted);padding:25px}.meta{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin:5px 0}details{margin-top:7px}pre{white-space:pre-wrap;word-break:break-word;max-height:180px;overflow:auto;background:var(--panel);padding:7px;border-radius:5px}
75
+ :root{--bg:#fff;--fg:#202124;--muted:#5f6368;--border:#dadce0;--danger:#c5221f}
76
+ html[data-theme="dark"]{--bg:#202124;--fg:#e8eaed;--muted:#9aa0a6;--border:#3c4043;--danger:#f28b82}
77
+ *{box-sizing:border-box}html,body{height:100%;margin:0;overflow:hidden}
78
+ body{background:var(--bg);color:var(--fg);font:13px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
79
+ #frame{width:100%;height:100%;border:0;display:block;background:var(--bg)}
80
+ #fallback{display:none;height:100%;flex-direction:column}
81
+ #fallback.show{display:flex}
82
+ #frame.hide{display:none}
83
+ .bar{display:flex;gap:6px;align-items:center;padding:6px;border-bottom:1px solid var(--border)}
84
+ .bar input{flex:1;min-width:0;font:inherit;padding:6px 10px;border:1px solid var(--border);border-radius:14px;background:var(--bg);color:inherit}
85
+ .bar button{font:inherit;padding:6px 10px;border:1px solid var(--border);border-radius:14px;background:var(--bg);color:inherit;cursor:pointer}
86
+ #shot{flex:1;min-height:0;display:grid;place-items:center;overflow:hidden}
87
+ #shot img{max-width:100%;max-height:100%;display:block}
88
+ #note{padding:6px 10px;color:var(--muted);font-size:12px}
89
+ #note.error{color:var(--danger)}
40
90
  </style>
41
91
  </head>
42
92
  <body>
43
- <div id="wrap">
44
- <div class="row"><input id="url" type="url" placeholder="https://example.com" /><button id="go">Go</button><button id="refresh">Refresh view</button><button id="close">Close</button></div>
45
- <div class="row"><input id="selector" class="selector" placeholder="CSS selector" /><button id="click">Click</button><input id="text" class="selector" placeholder="Text to fill" /><button id="fill">Fill</button></div>
46
- <div id="status" class="status">Initializing isolated browser…</div>
47
- <div id="meta" class="meta"></div>
48
- <div id="viewport" class="viewport"><div class="empty">No screenshot yet.</div></div>
49
- <details><summary>Visible page text</summary><pre id="snapshot"></pre></details>
93
+ <iframe id="frame" title="Live browser view" allow="autoplay; clipboard-read; clipboard-write; fullscreen" referrerpolicy="no-referrer"></iframe>
94
+ <div id="fallback">
95
+ <div class="bar">
96
+ <button id="fbBack" title="Back">&#8592;</button>
97
+ <button id="fbForward" title="Forward">&#8594;</button>
98
+ <button id="fbReload" title="Reload">&#8635;</button>
99
+ <input id="fbUrl" type="text" placeholder="https://example.com" />
100
+ <button id="fbGo">Go</button>
101
+ </div>
102
+ <div id="shot"></div>
103
+ <div id="note"></div>
50
104
  </div>
51
105
  <script>
52
106
  (function(){
53
- var parentWin=window.parent,idc=1,pending={},sessionId="",started=false;
54
- var statusEl=document.getElementById("status"),metaEl=document.getElementById("meta"),viewEl=document.getElementById("viewport"),snapEl=document.getElementById("snapshot");
55
- function post(m){parentWin.postMessage(m,"*")}
56
- function rpc(method,params){return new Promise(function(resolve,reject){var id=idc++;pending[id]={resolve:resolve,reject:reject};post({jsonrpc:"2.0",id:id,method:method,params:params||{}})})}
57
- function notify(method,params){post({jsonrpc:"2.0",method:method,params:params||{}})}
58
- function setStatus(text,error){statusEl.textContent=text;statusEl.className="status"+(error?" error":"")}
59
- function payload(result){if(result&&result.structuredContent)return result.structuredContent;try{var t=result&&result.content&&result.content[0]&&result.content[0].text;return t?JSON.parse(t):{}}catch(e){return{}}}
60
- function failMessage(result){var p=payload(result);return p&&p.error&&p.error.message?p.error.message:"Browser operation failed"}
61
- function apply(data){if(!data)return;if(data.sessionId)sessionId=data.sessionId;if(data.url){document.getElementById("url").value=data.url;metaEl.textContent=(data.title?data.title+" ":"")+data.url}if(typeof data.text==="string")snapEl.textContent=data.text}
62
- function imageOf(result){var items=result&&result.content||[];for(var i=0;i<items.length;i++){if(items[i].type==="image"&&items[i].data)return "data:"+(items[i].mimeType||"image/png")+";base64,"+items[i].data}return""}
63
- async function call(name,args){setStatus("Working…");var result=await rpc("tools/call",{name:name,arguments:args||{}});if(result&&result.isError)throw new Error(failMessage(result));apply(payload(result));return result}
64
- async function refresh(){if(!sessionId)return;try{var result=await call("browser_screenshot",{sessionId:sessionId});var image=imageOf(result);viewEl.innerHTML="";if(image){var img=document.createElement("img");img.src=image;img.alt="Current server-side browser viewport";viewEl.appendChild(img)}else{viewEl.textContent="Screenshot was not available."}var snap=await call("browser_snapshot",{sessionId:sessionId});apply(payload(snap));setStatus("Ready");size()}catch(e){setStatus(e&&e.message?e.message:String(e),true)}}
65
- async function open(initialUrl,requestedId){if(started)return;started=true;try{var args={};if(initialUrl)args.url=initialUrl;if(requestedId)args.sessionId=requestedId;var result=await call("browser_open",args);apply(payload(result));await refresh()}catch(e){setStatus(e&&e.message?e.message:String(e),true)}}
66
- async function navigate(){var url=document.getElementById("url").value.trim();if(!url)return setStatus("Enter an HTTP(S) URL.",true);try{if(!sessionId){started=false;await open(url,"")}else{await call("browser_navigate",{sessionId:sessionId,url:url});await refresh()}}catch(e){setStatus(e&&e.message?e.message:String(e),true)}}
67
- async function click(){var selector=document.getElementById("selector").value.trim();if(!selector)return setStatus("Enter a selector.",true);try{await call("browser_click",{sessionId:sessionId,selector:selector});await refresh()}catch(e){setStatus(e&&e.message?e.message:String(e),true)}}
68
- async function fill(){var selector=document.getElementById("selector").value.trim();if(!selector)return setStatus("Enter a selector.",true);try{await call("browser_type",{sessionId:sessionId,selector:selector,text:document.getElementById("text").value});await refresh()}catch(e){setStatus(e&&e.message?e.message:String(e),true)}}
69
- function size(){try{notify("ui/notifications/size-changed",{width:0,height:Math.ceil(document.documentElement.getBoundingClientRect().height)})}catch(e){}}
70
- document.getElementById("go").onclick=navigate;document.getElementById("refresh").onclick=refresh;document.getElementById("click").onclick=click;document.getElementById("fill").onclick=fill;
71
- document.getElementById("close").onclick=async function(){if(!sessionId)return;try{await call("browser_close",{sessionId:sessionId});sessionId="";viewEl.innerHTML='<div class="empty">Browser session closed.</div>';setStatus("Closed");size()}catch(e){setStatus(e&&e.message?e.message:String(e),true)}};
72
- window.addEventListener("message",function(event){var m=event.data;if(!m||m.jsonrpc!=="2.0")return;if(m.id!==undefined&&(m.result!==undefined||m.error!==undefined)){var p=pending[m.id];if(!p)return;delete pending[m.id];if(m.error)p.reject(new Error(m.error.message||"RPC error"));else p.resolve(m.result);return}if(m.method==="ui/notifications/host-context-changed"){var theme=m.params&&m.params.theme;document.documentElement.setAttribute("data-theme",theme==="dark"?"dark":"light");return}if(m.method==="ui/notifications/tool-input"){var a=m.params&&m.params.arguments||{};if(!started)open(typeof a.url==="string"?a.url:"",typeof a.sessionId==="string"?a.sessionId:"");return}if(m.method==="ui/notifications/tool-result"){apply(payload(m.params));return}if(m.method==="ping"&&m.id!==undefined){post({jsonrpc:"2.0",id:m.id,result:{}});return}if(m.method==="ui/resource-teardown"&&m.id!==undefined){post({jsonrpc:"2.0",id:m.id,result:{}});return}});
73
- rpc("ui/initialize",{appInfo:{name:"flujo-browser-view",version:"1.0.0"},appCapabilities:{availableDisplayModes:["inline","fullscreen","pip"]},protocolVersion:"${MCP_APPS_PROTOCOL_VERSION}"}).then(function(result){var theme=result&&result.hostContext&&result.hostContext.theme;document.documentElement.setAttribute("data-theme",theme==="dark"?"dark":"light");notify("ui/notifications/initialized",{});setTimeout(function(){if(!started)open("","")},400)}).catch(function(e){setStatus("App initialization failed: "+(e&&e.message?e.message:e),true)});
107
+ "use strict";
108
+ var GATEWAY = ${config};
109
+ var parentWin = window.parent, idc = 1, pending = {}, sessionId = "", starting = false;
110
+ var frame = document.getElementById("frame");
111
+ var fallback = document.getElementById("fallback");
112
+ var shot = document.getElementById("shot");
113
+ var note = document.getElementById("note");
114
+ var fbUrl = document.getElementById("fbUrl");
115
+ var theme = "light", pollTimer = 0, usingFallback = false;
116
+
117
+ function post(m){ parentWin.postMessage(m, "*"); }
118
+ function rpc(method, params){
119
+ return new Promise(function(resolve, reject){
120
+ var id = idc++;
121
+ pending[id] = { resolve: resolve, reject: reject };
122
+ post({ jsonrpc: "2.0", id: id, method: method, params: params || {} });
123
+ });
124
+ }
125
+ function notify(method, params){ post({ jsonrpc: "2.0", method: method, params: params || {} }); }
126
+ function errorText(e){ return e && e.message ? e.message : String(e); }
127
+ function payload(result){
128
+ if (result && result.structuredContent) return result.structuredContent;
129
+ try {
130
+ var t = result && result.content && result.content[0] && result.content[0].text;
131
+ return t ? JSON.parse(t) : {};
132
+ } catch (e) { return {}; }
133
+ }
134
+ function failMessage(result){
135
+ var p = payload(result);
136
+ return p && p.error && p.error.message ? p.error.message : "Browser operation failed";
137
+ }
138
+ async function call(name, args){
139
+ var result = await rpc("tools/call", { name: name, arguments: args || {} });
140
+ if (result && result.isError) throw new Error(failMessage(result));
141
+ return result;
142
+ }
143
+ function toView(message){
144
+ if (usingFallback || !frame.contentWindow) return;
145
+ try { frame.contentWindow.postMessage(Object.assign({ source: "flujo-browser-shell" }, message), "*"); } catch (e) {}
146
+ }
147
+ function setNote(text, isError){
148
+ note.textContent = text || "";
149
+ note.className = isError ? "error" : "";
150
+ }
151
+
152
+ /* ---------- live view ---------- */
153
+ function mountLiveView(){
154
+ frame.src = GATEWAY.origin + "/view?s=" + encodeURIComponent(sessionId) + "&t=" + encodeURIComponent(GATEWAY.token);
155
+ }
156
+
157
+ /* ---------- screenshot fallback ---------- */
158
+ function useFallback(reason){
159
+ if (usingFallback) return;
160
+ usingFallback = true;
161
+ frame.classList.add("hide");
162
+ fallback.classList.add("show");
163
+ setNote(reason || "Live streaming is unavailable; showing periodic screenshots.", true);
164
+ pollOnce();
165
+ }
166
+ async function pollOnce(){
167
+ if (!usingFallback || !sessionId) return;
168
+ try {
169
+ var result = await call("browser_screenshot", { sessionId: sessionId });
170
+ var items = (result && result.content) || [];
171
+ for (var i = 0; i < items.length; i++){
172
+ if (items[i].type === "image" && items[i].data){
173
+ shot.innerHTML = "";
174
+ var img = document.createElement("img");
175
+ img.src = "data:" + (items[i].mimeType || "image/png") + ";base64," + items[i].data;
176
+ img.alt = "Browser viewport";
177
+ shot.appendChild(img);
178
+ break;
179
+ }
180
+ }
181
+ var state = payload(result);
182
+ if (state.url && document.activeElement !== fbUrl) fbUrl.value = state.url;
183
+ setNote("Screenshot fallback \\u2014 updated " + new Date().toLocaleTimeString());
184
+ } catch (e) {
185
+ setNote(errorText(e), true);
186
+ }
187
+ clearTimeout(pollTimer);
188
+ pollTimer = setTimeout(pollOnce, 2000);
189
+ }
190
+ function fallbackAction(kind, url){
191
+ return async function(){
192
+ try {
193
+ if (kind === "navigate") await call("browser_navigate", { sessionId: sessionId, url: url() });
194
+ else await call("browser_" + kind, { sessionId: sessionId });
195
+ pollOnce();
196
+ } catch (e) { setNote(errorText(e), true); }
197
+ };
198
+ }
199
+ document.getElementById("fbBack").onclick = fallbackAction("back");
200
+ document.getElementById("fbForward").onclick = fallbackAction("forward");
201
+ document.getElementById("fbReload").onclick = fallbackAction("reload");
202
+ document.getElementById("fbGo").onclick = fallbackAction("navigate", function(){ return fbUrl.value.trim(); });
203
+ fbUrl.onkeydown = function(e){ if (e.key === "Enter"){ e.preventDefault(); document.getElementById("fbGo").click(); } };
204
+
205
+ /* ---------- session ---------- */
206
+ async function start(initialUrl, requestedId){
207
+ if (starting) return;
208
+ starting = true;
209
+ try {
210
+ var args = {};
211
+ if (initialUrl) args.url = initialUrl;
212
+ if (requestedId) args.sessionId = requestedId;
213
+ var result = await call("browser_open", args);
214
+ var state = payload(result);
215
+ sessionId = state.sessionId || "";
216
+ if (!sessionId) throw new Error("The browser session did not report an id.");
217
+ if (GATEWAY.origin && GATEWAY.token) mountLiveView();
218
+ else useFallback("The live stream gateway is disabled, so this view falls back to screenshots.");
219
+ } catch (e) {
220
+ starting = false;
221
+ useFallback(errorText(e));
222
+ }
223
+ }
224
+
225
+ /* ---------- commands relayed from the framed view ---------- */
226
+ async function runViewCommand(data){
227
+ try {
228
+ if (data.type === "navigate" && data.url) await call("browser_navigate", { sessionId: sessionId, url: data.url });
229
+ else if (data.type === "back") await call("browser_back", { sessionId: sessionId });
230
+ else if (data.type === "forward") await call("browser_forward", { sessionId: sessionId });
231
+ else if (data.type === "reload") await call("browser_reload", { sessionId: sessionId });
232
+ else if (data.type === "close"){
233
+ await call("browser_close", { sessionId: sessionId });
234
+ notify("ui/notifications/request-teardown", {});
235
+ } else if (data.type === "fullscreen"){
236
+ await rpc("ui/request-display-mode", { mode: "fullscreen" });
237
+ }
238
+ } catch (e) {
239
+ toView({ type: "error", message: errorText(e) });
240
+ } finally {
241
+ toView({ type: "loading", value: false });
242
+ }
243
+ }
244
+
245
+ window.addEventListener("message", function(event){
246
+ var m = event.data;
247
+ if (m && m.source === "flujo-browser-view"){
248
+ if (event.source !== frame.contentWindow) return;
249
+ if (m.type === "ready") toView({ type: "theme", theme: theme });
250
+ else void runViewCommand(m);
251
+ return;
252
+ }
253
+ if (!m || m.jsonrpc !== "2.0") return;
254
+ if (m.id !== undefined && (m.result !== undefined || m.error !== undefined)){
255
+ var p = pending[m.id];
256
+ if (!p) return;
257
+ delete pending[m.id];
258
+ if (m.error) p.reject(new Error(m.error.message || "RPC error"));
259
+ else p.resolve(m.result);
260
+ return;
261
+ }
262
+ if (m.method === "ui/notifications/host-context-changed"){
263
+ theme = (m.params && m.params.theme) === "dark" ? "dark" : "light";
264
+ document.documentElement.setAttribute("data-theme", theme);
265
+ toView({ type: "theme", theme: theme });
266
+ return;
267
+ }
268
+ if (m.method === "ui/notifications/tool-input"){
269
+ var a = (m.params && m.params.arguments) || {};
270
+ if (!starting) void start(typeof a.url === "string" ? a.url : "", typeof a.sessionId === "string" ? a.sessionId : "");
271
+ return;
272
+ }
273
+ if (m.method === "ping" && m.id !== undefined){ post({ jsonrpc: "2.0", id: m.id, result: {} }); return; }
274
+ if (m.method === "ui/resource-teardown" && m.id !== undefined){
275
+ clearTimeout(pollTimer);
276
+ post({ jsonrpc: "2.0", id: m.id, result: {} });
277
+ return;
278
+ }
279
+ });
280
+
281
+ rpc("ui/initialize", {
282
+ appInfo: { name: "flujo-browser-view", version: "3.0.0" },
283
+ appCapabilities: { availableDisplayModes: ["inline", "fullscreen", "pip"] },
284
+ protocolVersion: "${MCP_APPS_PROTOCOL_VERSION}"
285
+ }).then(function(result){
286
+ theme = (result && result.hostContext && result.hostContext.theme) === "dark" ? "dark" : "light";
287
+ document.documentElement.setAttribute("data-theme", theme);
288
+ notify("ui/notifications/initialized", {});
289
+ notify("ui/notifications/size-changed", { width: 0, height: 640 });
290
+ setTimeout(function(){ if (!starting) void start("", ""); }, 300);
291
+ }).catch(function(e){
292
+ useFallback("App initialization failed: " + errorText(e));
293
+ });
74
294
  })();
75
295
  </script>
76
296
  </body>
77
297
  </html>`;
298
+ }
78
299
  //# sourceMappingURL=resources.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAEA,MAAM,yBAAyB,GAAG,YAAY,CAAC;AAC/C,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAClD,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAEnD,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,SAAS,EAAE,CAAC;gBACV,GAAG,EAAE,eAAe;gBACpB,IAAI,EAAE,cAAc;gBACpB,QAAQ,EAAE,aAAa;gBACvB,WAAW,EAAE,0EAA0E;aACxF,CAAC;KACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,IAAI,GAAG,KAAK,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IACjF,OAAO;QACL,QAAQ,EAAE,CAAC;gBACT,GAAG;gBACH,QAAQ,EAAE,aAAa;gBACvB,IAAI,EAAE,gBAAgB;gBACtB,KAAK,EAAE;oBACL,EAAE,EAAE;wBACF,GAAG,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE;wBAChD,WAAW,EAAE,EAAE;qBAChB;iBACF;aACwC,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oKA0C2I,yBAAyB;;;;QAIrL,CAAC"}
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAA+B,MAAM,cAAc,CAAC;AAEzG,MAAM,yBAAyB,GAAG,YAAY,CAAC;AAC/C,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAClD,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAEnD,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,SAAS,EAAE,CAAC;gBACV,GAAG,EAAE,eAAe;gBACpB,IAAI,EAAE,cAAc;gBACpB,QAAQ,EAAE,aAAa;gBACvB,WAAW,EAAE,mEAAmE;aACjF,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAW;IACnD,IAAI,GAAG,KAAK,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,8EAA8E;IAC9E,4EAA4E;IAC5E,8EAA8E;IAC9E,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;IAC1C,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAClD,MAAM,eAAe,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACnD,OAAO;QACL,QAAQ,EAAE,CAAC;gBACT,GAAG;gBACH,QAAQ,EAAE,aAAa;gBACvB,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;gBACnC,KAAK,EAAE;oBACL,EAAE,EAAE;wBACF,aAAa,EAAE,KAAK;wBACpB,GAAG,EAAE;4BACH,gEAAgE;4BAChE,+DAA+D;4BAC/D,YAAY;4BACZ,cAAc;4BACd,eAAe;yBAChB;wBACD,WAAW,EAAE,EAAE;qBAChB;iBACF;aACwC,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgC;IACnE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;QAC5B,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE;QAC7B,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE;KAC5B,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsCS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAgLA,yBAAyB;;;;;;;;;;;;;QAazC,CAAC;AACT,CAAC"}
package/dist/runtime.d.ts CHANGED
@@ -1,20 +1,69 @@
1
- import { type BrowserContext, type Page } from 'patchright';
1
+ import { type Browser, type BrowserContext, type Page } from 'patchright';
2
2
  export type BrowserErrorCode = 'BROWSER_UNAVAILABLE' | 'CANCELLED' | 'INVALID_ARGUMENT' | 'NAVIGATION_BLOCKED' | 'NOT_FOUND' | 'SESSION_LIMIT' | 'TIMEOUT' | 'UNEXPECTED';
3
+ export type BrowserFailureCategory = 'cancelled' | 'input' | 'policy' | 'runtime' | 'site';
4
+ export type BrowserMode = 'sandbox' | 'trusted';
5
+ export type BrowserWindowVisibility = 'minimized' | 'offscreen' | 'visible';
6
+ export declare function failureCategoryForCode(code: BrowserErrorCode): BrowserFailureCategory;
3
7
  export declare class BrowserMcpError extends Error {
4
8
  readonly code: BrowserErrorCode;
5
- constructor(code: BrowserErrorCode, message: string);
9
+ readonly category: BrowserFailureCategory;
10
+ constructor(code: BrowserErrorCode, message: string, category?: BrowserFailureCategory);
6
11
  }
12
+ export type PolicyBlock = {
13
+ url: string;
14
+ reason: string;
15
+ topLevel: boolean;
16
+ };
7
17
  export type BrowserSession = {
8
18
  id: string;
19
+ mode: BrowserMode;
9
20
  context: BrowserContext;
10
21
  page: Page;
11
22
  touchedAt: number;
12
23
  documentRequests: number;
13
24
  navigationBlocked: boolean;
25
+ blockedRequestCount: number;
26
+ lastPolicyBlock?: PolicyBlock;
27
+ };
28
+ export declare function integerEnv(name: string, fallback: number, min: number, max: number): number;
29
+ export declare function enabledEnv(name: string): boolean;
30
+ export declare function browserMode(): BrowserMode;
31
+ /** Viewport the live view starts at before the app reports its real size. */
32
+ export declare function defaultViewport(): {
33
+ width: number;
34
+ height: number;
14
35
  };
15
36
  export declare function timeoutMs(value: unknown): number;
16
37
  export declare function assertNavigationAllowed(input: string): Promise<URL>;
38
+ /** Persistence root for `browser_record_*` artifacts (WebM/WAV/muxed output). */
39
+ export declare function recordingRoot(): string;
40
+ /** A fresh scratch directory under the runtime root, used for Playwright's `recordVideo` output before it is copied out. */
41
+ export declare function ensureScratchDir(prefix: string): Promise<string>;
42
+ /** Persist the latest screenshot and return the absolute host path reported to MCP clients. */
43
+ export declare function writeScreenshotArtifact(sessionId: string, fullPage: boolean, png: Buffer): Promise<string>;
44
+ /** Exported for the recording/capture modules, which need their own contexts on the same browser instance. */
45
+ export declare function acquireBrowser(): Promise<Browser>;
46
+ /** Enforce the same SSRF/navigation policy on sessions, recordings, and capture contexts. */
47
+ export declare function installRequestPolicy(context: BrowserContext): Promise<void>;
17
48
  export declare function openSession(requestedId: unknown, signal: AbortSignal): Promise<BrowserSession>;
49
+ /** Register a session created outside `openSession()` (used by the recording module, which owns its own context lifecycle). */
50
+ export declare function registerSession(session: BrowserSession): BrowserSession;
51
+ export type CaptureContext = {
52
+ context: BrowserContext;
53
+ page: Page;
54
+ };
55
+ /**
56
+ * An ephemeral, isolated context for still capture: not the user-facing
57
+ * session map, no `lastSessionId` side effects, always closed by the caller
58
+ * in a `finally`. `reducedMotion: 'reduce'` plus the caller's `animations:
59
+ * 'disabled'` screenshot option are the two halves of the determinism ladder.
60
+ */
61
+ export declare function createCaptureContext(signal: AbortSignal, viewport: {
62
+ width: number;
63
+ height: number;
64
+ deviceScaleFactor?: number;
65
+ colorScheme?: 'light' | 'dark';
66
+ }): Promise<CaptureContext>;
18
67
  export declare function getSession(value: unknown): BrowserSession;
19
68
  export declare function closeSession(value: unknown): Promise<boolean>;
20
69
  export declare function runCancellable<T>(session: BrowserSession, signal: AbortSignal, operation: () => Promise<T>): Promise<T>;
@@ -22,5 +71,12 @@ export declare function resetNavigationCounter(session: BrowserSession): void;
22
71
  export declare function publicPageState(session: BrowserSession): {
23
72
  sessionId: string;
24
73
  url: string;
74
+ mode: BrowserMode;
75
+ policy: {
76
+ blockedRequestCount: number;
77
+ lastBlockedRequest?: PolicyBlock;
78
+ };
25
79
  };
80
+ export declare function browserDiagnostics(session?: BrowserSession): Promise<Record<string, unknown>>;
81
+ export declare function browserExtensions(): Promise<Record<string, unknown>>;
26
82
  export declare function shutdownBrowserRuntime(): Promise<void>;