@mario.andreschak/mcp-browser 3.39.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/index.d.ts +4 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/resources.d.ts +6 -0
- package/dist/resources.js +78 -0
- package/dist/resources.js.map +1 -0
- package/dist/runtime.d.ts +26 -0
- package/dist/runtime.js +343 -0
- package/dist/runtime.js.map +1 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +283 -0
- package/dist/tools.js.map +1 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { browserCallTool, browserToolDefinitions } from './tools.js';
|
|
6
|
+
import { browserListResources, browserReadResource } from './resources.js';
|
|
7
|
+
import { shutdownBrowserRuntime } from './runtime.js';
|
|
8
|
+
export * from './tools.js';
|
|
9
|
+
export * from './resources.js';
|
|
10
|
+
export * from './runtime.js';
|
|
11
|
+
const server = new Server({ name: '@mario.andreschak/mcp-browser', version: '3.38.0' }, { capabilities: { tools: {}, resources: {} } });
|
|
12
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: browserToolDefinitions() }));
|
|
13
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => browserCallTool(request.params.name, request.params.arguments ?? {}, extra.signal));
|
|
14
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => browserListResources());
|
|
15
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
|
|
16
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => browserReadResource(request.params.uri));
|
|
17
|
+
const transport = new StdioServerTransport();
|
|
18
|
+
let closing = false;
|
|
19
|
+
async function shutdown() {
|
|
20
|
+
if (closing)
|
|
21
|
+
return;
|
|
22
|
+
closing = true;
|
|
23
|
+
await shutdownBrowserRuntime();
|
|
24
|
+
await server.close().catch(() => undefined);
|
|
25
|
+
}
|
|
26
|
+
process.once('SIGINT', () => void shutdown());
|
|
27
|
+
process.once('SIGTERM', () => void shutdown());
|
|
28
|
+
process.once('SIGHUP', () => void shutdown());
|
|
29
|
+
process.once('beforeExit', () => void shutdownBrowserRuntime());
|
|
30
|
+
server.connect(transport).catch((error) => {
|
|
31
|
+
process.stderr.write(`@mario.andreschak/mcp-browser failed: ${error instanceof Error ? error.name : 'unknown error'}\n`);
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,kCAAkC,EAClC,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAE7B,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,+BAA+B,EAAE,OAAO,EAAE,QAAQ,EAAE,EAC5D,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,CAC/C,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,CAAC,CAAC,CAAC;AACpG,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CACvE,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CACnF,CAAC;AACF,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,oBAAoB,EAAE,CAAC,CAAC;AACzF,MAAM,CAAC,iBAAiB,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AACtG,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,mBAAmB,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAEhH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,IAAI,OAAO,GAAG,KAAK,CAAC;AACpB,KAAK,UAAU,QAAQ;IACrB,IAAI,OAAO;QAAE,OAAO;IACpB,OAAO,GAAG,IAAI,CAAC;IACf,MAAM,sBAAsB,EAAE,CAAC;IAC/B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAC9C,CAAC;AACD,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC9C,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC/C,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC9C,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK,sBAAsB,EAAE,CAAC,CAAC;AAEhE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC;IACzH,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Resource, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
export declare const BROWSER_APP_URI = "ui://browser/view";
|
|
3
|
+
export declare function browserListResources(): {
|
|
4
|
+
resources: Resource[];
|
|
5
|
+
};
|
|
6
|
+
export declare function browserReadResource(uri: string): ReadResourceResult;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const MCP_APPS_PROTOCOL_VERSION = '2026-01-26';
|
|
2
|
+
const APP_MIME_TYPE = 'text/html;profile=mcp-app';
|
|
3
|
+
export const BROWSER_APP_URI = 'ui://browser/view';
|
|
4
|
+
export function browserListResources() {
|
|
5
|
+
return {
|
|
6
|
+
resources: [{
|
|
7
|
+
uri: BROWSER_APP_URI,
|
|
8
|
+
name: 'browser_view',
|
|
9
|
+
mimeType: APP_MIME_TYPE,
|
|
10
|
+
description: 'Interactive view of the isolated server-side Patchright browser session.',
|
|
11
|
+
}],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function browserReadResource(uri) {
|
|
15
|
+
if (uri !== BROWSER_APP_URI)
|
|
16
|
+
throw new Error(`Unknown browser resource: ${uri}`);
|
|
17
|
+
return {
|
|
18
|
+
contents: [{
|
|
19
|
+
uri,
|
|
20
|
+
mimeType: APP_MIME_TYPE,
|
|
21
|
+
text: BROWSER_APP_HTML,
|
|
22
|
+
_meta: {
|
|
23
|
+
ui: {
|
|
24
|
+
csp: { connectDomains: [], resourceDomains: [] },
|
|
25
|
+
permissions: {},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const BROWSER_APP_HTML = `<!doctype html>
|
|
32
|
+
<html>
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="utf-8" />
|
|
35
|
+
<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}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<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>
|
|
50
|
+
</div>
|
|
51
|
+
<script>
|
|
52
|
+
(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)});
|
|
74
|
+
})();
|
|
75
|
+
</script>
|
|
76
|
+
</body>
|
|
77
|
+
</html>`;
|
|
78
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type BrowserContext, type Page } from 'patchright';
|
|
2
|
+
export type BrowserErrorCode = 'BROWSER_UNAVAILABLE' | 'CANCELLED' | 'INVALID_ARGUMENT' | 'NAVIGATION_BLOCKED' | 'NOT_FOUND' | 'SESSION_LIMIT' | 'TIMEOUT' | 'UNEXPECTED';
|
|
3
|
+
export declare class BrowserMcpError extends Error {
|
|
4
|
+
readonly code: BrowserErrorCode;
|
|
5
|
+
constructor(code: BrowserErrorCode, message: string);
|
|
6
|
+
}
|
|
7
|
+
export type BrowserSession = {
|
|
8
|
+
id: string;
|
|
9
|
+
context: BrowserContext;
|
|
10
|
+
page: Page;
|
|
11
|
+
touchedAt: number;
|
|
12
|
+
documentRequests: number;
|
|
13
|
+
navigationBlocked: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function timeoutMs(value: unknown): number;
|
|
16
|
+
export declare function assertNavigationAllowed(input: string): Promise<URL>;
|
|
17
|
+
export declare function openSession(requestedId: unknown, signal: AbortSignal): Promise<BrowserSession>;
|
|
18
|
+
export declare function getSession(value: unknown): BrowserSession;
|
|
19
|
+
export declare function closeSession(value: unknown): Promise<boolean>;
|
|
20
|
+
export declare function runCancellable<T>(session: BrowserSession, signal: AbortSignal, operation: () => Promise<T>): Promise<T>;
|
|
21
|
+
export declare function resetNavigationCounter(session: BrowserSession): void;
|
|
22
|
+
export declare function publicPageState(session: BrowserSession): {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
url: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function shutdownBrowserRuntime(): Promise<void>;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import { lookup } from 'node:dns/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { chromium } from 'patchright';
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
9
|
+
const MAX_TIMEOUT_MS = 60_000;
|
|
10
|
+
const DEFAULT_IDLE_MS = 10 * 60_000;
|
|
11
|
+
const DEFAULT_MAX_SESSIONS = 4;
|
|
12
|
+
const DEFAULT_MAX_REDIRECTS = 10;
|
|
13
|
+
const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
14
|
+
export class BrowserMcpError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
constructor(code, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.name = 'BrowserMcpError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let browser;
|
|
23
|
+
let browserPromise;
|
|
24
|
+
let runtimeRoot;
|
|
25
|
+
const sessions = new Map();
|
|
26
|
+
function integerEnv(name, fallback, min, max) {
|
|
27
|
+
const raw = Number.parseInt(process.env[name] ?? '', 10);
|
|
28
|
+
return Number.isFinite(raw) ? Math.min(max, Math.max(min, raw)) : fallback;
|
|
29
|
+
}
|
|
30
|
+
function enabledEnv(name) {
|
|
31
|
+
return /^(1|true|yes|on)$/i.test(process.env[name]?.trim() ?? '');
|
|
32
|
+
}
|
|
33
|
+
export function timeoutMs(value) {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return DEFAULT_TIMEOUT_MS;
|
|
36
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
37
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'timeoutMs must be a finite number.');
|
|
38
|
+
}
|
|
39
|
+
return Math.min(MAX_TIMEOUT_MS, Math.max(1_000, Math.trunc(value)));
|
|
40
|
+
}
|
|
41
|
+
function allowedOrigins() {
|
|
42
|
+
const origins = new Set();
|
|
43
|
+
for (const entry of (process.env.FLUJO_BROWSER_ALLOWED_ORIGINS ?? '').split(',')) {
|
|
44
|
+
const trimmed = entry.trim();
|
|
45
|
+
if (!trimmed)
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(trimmed);
|
|
49
|
+
if ((url.protocol === 'http:' || url.protocol === 'https:') && url.origin === trimmed.replace(/\/$/, '')) {
|
|
50
|
+
origins.add(url.origin);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Invalid policy entries never widen access.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return origins;
|
|
58
|
+
}
|
|
59
|
+
function isPrivateAddress(address) {
|
|
60
|
+
const normalized = address.toLowerCase().replace(/^::ffff:/, '');
|
|
61
|
+
if (normalized === '::1' || normalized === '::' || normalized.startsWith('fe80:'))
|
|
62
|
+
return true;
|
|
63
|
+
if (normalized.startsWith('fc') || normalized.startsWith('fd'))
|
|
64
|
+
return true;
|
|
65
|
+
if (isIP(normalized) !== 4)
|
|
66
|
+
return false;
|
|
67
|
+
const parts = normalized.split('.').map(Number);
|
|
68
|
+
return parts[0] === 0
|
|
69
|
+
|| parts[0] === 10
|
|
70
|
+
|| parts[0] === 127
|
|
71
|
+
|| (parts[0] === 169 && parts[1] === 254)
|
|
72
|
+
|| (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|
|
73
|
+
|| (parts[0] === 192 && parts[1] === 168)
|
|
74
|
+
|| (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127)
|
|
75
|
+
|| parts[0] >= 224;
|
|
76
|
+
}
|
|
77
|
+
export async function assertNavigationAllowed(input) {
|
|
78
|
+
let url;
|
|
79
|
+
try {
|
|
80
|
+
url = new URL(input);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The URL is malformed.');
|
|
84
|
+
}
|
|
85
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
86
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'Only HTTP and HTTPS URLs are allowed.');
|
|
87
|
+
}
|
|
88
|
+
if (url.username || url.password) {
|
|
89
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'URLs containing credentials are not allowed.');
|
|
90
|
+
}
|
|
91
|
+
const configuredOrigins = allowedOrigins();
|
|
92
|
+
if (configuredOrigins.size > 0 && !configuredOrigins.has(url.origin)) {
|
|
93
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The URL origin is not allowed by browser policy.');
|
|
94
|
+
}
|
|
95
|
+
if (!enabledEnv('FLUJO_BROWSER_ALLOW_PRIVATE_HOSTS')) {
|
|
96
|
+
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
97
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname.endsWith('.local')) {
|
|
98
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'Private and local network destinations are blocked.');
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const addresses = isIP(hostname)
|
|
102
|
+
? [{ address: hostname }]
|
|
103
|
+
: await lookup(hostname, { all: true, verbatim: true });
|
|
104
|
+
if (addresses.length === 0 || addresses.some(({ address }) => isPrivateAddress(address))) {
|
|
105
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'Private and local network destinations are blocked.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error instanceof BrowserMcpError)
|
|
110
|
+
throw error;
|
|
111
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The destination hostname could not be resolved safely.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return url;
|
|
115
|
+
}
|
|
116
|
+
async function ensureRuntimeRoot() {
|
|
117
|
+
if (!runtimeRoot)
|
|
118
|
+
runtimeRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'flujo-browser-'));
|
|
119
|
+
return runtimeRoot;
|
|
120
|
+
}
|
|
121
|
+
async function acquireBrowser() {
|
|
122
|
+
if (browser?.isConnected())
|
|
123
|
+
return browser;
|
|
124
|
+
if (browserPromise)
|
|
125
|
+
return browserPromise;
|
|
126
|
+
browserPromise = (async () => {
|
|
127
|
+
const downloadsPath = await ensureRuntimeRoot();
|
|
128
|
+
try {
|
|
129
|
+
const launched = await chromium.launch({
|
|
130
|
+
headless: true,
|
|
131
|
+
downloadsPath,
|
|
132
|
+
...(process.env.FLUJO_BROWSER_EXECUTABLE_PATH
|
|
133
|
+
? { executablePath: process.env.FLUJO_BROWSER_EXECUTABLE_PATH }
|
|
134
|
+
: {}),
|
|
135
|
+
});
|
|
136
|
+
browser = launched;
|
|
137
|
+
launched.once('disconnected', () => {
|
|
138
|
+
if (browser === launched)
|
|
139
|
+
browser = undefined;
|
|
140
|
+
sessions.clear();
|
|
141
|
+
});
|
|
142
|
+
return launched;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
throw new BrowserMcpError('BROWSER_UNAVAILABLE', 'Patchright could not start Chromium. Install the managed browser binary and check the server platform prerequisites.');
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
browserPromise = undefined;
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
return browserPromise;
|
|
152
|
+
}
|
|
153
|
+
function validateSessionId(value) {
|
|
154
|
+
if (value === undefined || value === '')
|
|
155
|
+
return randomUUID();
|
|
156
|
+
if (typeof value !== 'string' || !SESSION_ID_PATTERN.test(value)) {
|
|
157
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'sessionId must contain 1-64 letters, digits, underscores, or hyphens.');
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
async function closeSessionInternal(id) {
|
|
162
|
+
const session = sessions.get(id);
|
|
163
|
+
if (!session)
|
|
164
|
+
return false;
|
|
165
|
+
sessions.delete(id);
|
|
166
|
+
await session.context.close().catch(() => undefined);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
export async function openSession(requestedId, signal) {
|
|
170
|
+
if (signal.aborted)
|
|
171
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
172
|
+
const id = validateSessionId(requestedId);
|
|
173
|
+
const existing = sessions.get(id);
|
|
174
|
+
if (existing) {
|
|
175
|
+
existing.touchedAt = Date.now();
|
|
176
|
+
return existing;
|
|
177
|
+
}
|
|
178
|
+
const maxSessions = integerEnv('FLUJO_BROWSER_MAX_SESSIONS', DEFAULT_MAX_SESSIONS, 1, 32);
|
|
179
|
+
if (sessions.size >= maxSessions) {
|
|
180
|
+
throw new BrowserMcpError('SESSION_LIMIT', `The browser session limit (${maxSessions}) has been reached.`);
|
|
181
|
+
}
|
|
182
|
+
const activeBrowser = await acquireBrowser();
|
|
183
|
+
if (signal.aborted)
|
|
184
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
185
|
+
let context;
|
|
186
|
+
let contextClosePromise;
|
|
187
|
+
let cancelled = false;
|
|
188
|
+
const closeContext = () => {
|
|
189
|
+
if (!context)
|
|
190
|
+
return Promise.resolve();
|
|
191
|
+
contextClosePromise ??= context.close().catch(() => undefined);
|
|
192
|
+
return contextClosePromise;
|
|
193
|
+
};
|
|
194
|
+
const onAbort = () => {
|
|
195
|
+
cancelled = true;
|
|
196
|
+
void closeContext();
|
|
197
|
+
};
|
|
198
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
199
|
+
try {
|
|
200
|
+
context = await activeBrowser.newContext({
|
|
201
|
+
acceptDownloads: false,
|
|
202
|
+
serviceWorkers: 'block',
|
|
203
|
+
viewport: { width: 1280, height: 720 },
|
|
204
|
+
});
|
|
205
|
+
if (cancelled || signal.aborted) {
|
|
206
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
207
|
+
}
|
|
208
|
+
const session = {
|
|
209
|
+
id,
|
|
210
|
+
context,
|
|
211
|
+
page: await context.newPage(),
|
|
212
|
+
touchedAt: Date.now(),
|
|
213
|
+
documentRequests: 0,
|
|
214
|
+
navigationBlocked: false,
|
|
215
|
+
};
|
|
216
|
+
if (cancelled || signal.aborted) {
|
|
217
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
218
|
+
}
|
|
219
|
+
const maxRedirects = integerEnv('FLUJO_BROWSER_MAX_REDIRECTS', DEFAULT_MAX_REDIRECTS, 0, 50);
|
|
220
|
+
await context.route('**/*', async (route) => {
|
|
221
|
+
const request = route.request();
|
|
222
|
+
try {
|
|
223
|
+
if (request.resourceType() === 'document') {
|
|
224
|
+
session.documentRequests += 1;
|
|
225
|
+
if (session.documentRequests > maxRedirects + 1) {
|
|
226
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The navigation exceeded the redirect limit.');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
await assertNavigationAllowed(request.url());
|
|
230
|
+
await route.continue();
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
session.navigationBlocked = true;
|
|
234
|
+
await route.abort('blockedbyclient').catch(() => undefined);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
if (cancelled || signal.aborted) {
|
|
238
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
239
|
+
}
|
|
240
|
+
session.page.on('download', (download) => void download.cancel().catch(() => undefined));
|
|
241
|
+
session.page.on('close', () => sessions.delete(id));
|
|
242
|
+
sessions.set(id, session);
|
|
243
|
+
return session;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
await closeContext();
|
|
247
|
+
if (cancelled || signal.aborted) {
|
|
248
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
signal.removeEventListener('abort', onAbort);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export function getSession(value) {
|
|
257
|
+
if (typeof value !== 'string' || !SESSION_ID_PATTERN.test(value)) {
|
|
258
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'A valid sessionId is required.');
|
|
259
|
+
}
|
|
260
|
+
const session = sessions.get(value);
|
|
261
|
+
if (!session || session.page.isClosed()) {
|
|
262
|
+
sessions.delete(value);
|
|
263
|
+
throw new BrowserMcpError('NOT_FOUND', 'The browser session does not exist or has expired.');
|
|
264
|
+
}
|
|
265
|
+
session.touchedAt = Date.now();
|
|
266
|
+
return session;
|
|
267
|
+
}
|
|
268
|
+
export async function closeSession(value) {
|
|
269
|
+
if (typeof value !== 'string' || !SESSION_ID_PATTERN.test(value)) {
|
|
270
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'A valid sessionId is required.');
|
|
271
|
+
}
|
|
272
|
+
return closeSessionInternal(value);
|
|
273
|
+
}
|
|
274
|
+
export async function runCancellable(session, signal, operation) {
|
|
275
|
+
if (signal.aborted) {
|
|
276
|
+
await closeSessionInternal(session.id);
|
|
277
|
+
throw new BrowserMcpError('CANCELLED', 'The browser request was cancelled.');
|
|
278
|
+
}
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
let settled = false;
|
|
281
|
+
const onAbort = () => {
|
|
282
|
+
if (settled)
|
|
283
|
+
return;
|
|
284
|
+
settled = true;
|
|
285
|
+
void closeSessionInternal(session.id).finally(() => {
|
|
286
|
+
reject(new BrowserMcpError('CANCELLED', 'The browser request was cancelled.'));
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
290
|
+
operation().then((value) => {
|
|
291
|
+
if (settled)
|
|
292
|
+
return;
|
|
293
|
+
settled = true;
|
|
294
|
+
signal.removeEventListener('abort', onAbort);
|
|
295
|
+
resolve(value);
|
|
296
|
+
}, (error) => {
|
|
297
|
+
if (settled)
|
|
298
|
+
return;
|
|
299
|
+
settled = true;
|
|
300
|
+
signal.removeEventListener('abort', onAbort);
|
|
301
|
+
reject(error);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
export function resetNavigationCounter(session) {
|
|
306
|
+
session.documentRequests = 0;
|
|
307
|
+
session.navigationBlocked = false;
|
|
308
|
+
}
|
|
309
|
+
export function publicPageState(session) {
|
|
310
|
+
let url = session.page.url();
|
|
311
|
+
try {
|
|
312
|
+
const parsed = new URL(url);
|
|
313
|
+
parsed.search = '';
|
|
314
|
+
parsed.hash = '';
|
|
315
|
+
url = parsed.href;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
url = url === 'about:blank' ? url : '';
|
|
319
|
+
}
|
|
320
|
+
return { sessionId: session.id, url };
|
|
321
|
+
}
|
|
322
|
+
export async function shutdownBrowserRuntime() {
|
|
323
|
+
const ids = [...sessions.keys()];
|
|
324
|
+
await Promise.all(ids.map((id) => closeSessionInternal(id)));
|
|
325
|
+
const active = browser;
|
|
326
|
+
browser = undefined;
|
|
327
|
+
await active?.close().catch(() => undefined);
|
|
328
|
+
if (runtimeRoot) {
|
|
329
|
+
const root = runtimeRoot;
|
|
330
|
+
runtimeRoot = undefined;
|
|
331
|
+
await fs.rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const idleTimer = setInterval(() => {
|
|
335
|
+
const idleMs = integerEnv('FLUJO_BROWSER_IDLE_TIMEOUT_MS', DEFAULT_IDLE_MS, 10_000, 24 * 60 * 60_000);
|
|
336
|
+
const cutoff = Date.now() - idleMs;
|
|
337
|
+
for (const session of sessions.values()) {
|
|
338
|
+
if (session.touchedAt < cutoff)
|
|
339
|
+
void closeSessionInternal(session.id);
|
|
340
|
+
}
|
|
341
|
+
}, 30_000);
|
|
342
|
+
idleTimer.unref();
|
|
343
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
package/dist/tools.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { BrowserMcpError, assertNavigationAllowed, closeSession, getSession, openSession, publicPageState, resetNavigationCounter, runCancellable, timeoutMs, } from './runtime.js';
|
|
2
|
+
import { BROWSER_APP_URI } from './resources.js';
|
|
3
|
+
const MAX_TEXT_CHARS = 50_000;
|
|
4
|
+
const MAX_SELECTOR_CHARS = 2_000;
|
|
5
|
+
const MAX_SCREENSHOT_BYTES = 5_000_000;
|
|
6
|
+
const READ_ANNOTATIONS = {
|
|
7
|
+
readOnlyHint: true,
|
|
8
|
+
destructiveHint: false,
|
|
9
|
+
idempotentHint: true,
|
|
10
|
+
openWorldHint: true,
|
|
11
|
+
};
|
|
12
|
+
const INTERACTION_ANNOTATIONS = {
|
|
13
|
+
readOnlyHint: false,
|
|
14
|
+
destructiveHint: false,
|
|
15
|
+
openWorldHint: true,
|
|
16
|
+
};
|
|
17
|
+
const APP_META = {
|
|
18
|
+
ui: {
|
|
19
|
+
resourceUri: BROWSER_APP_URI,
|
|
20
|
+
visibility: ['model', 'app'],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
const SESSION_PROPERTY = {
|
|
24
|
+
type: 'string',
|
|
25
|
+
pattern: '^[A-Za-z0-9_-]{1,64}$',
|
|
26
|
+
description: 'Opaque browser session identifier returned by browser_open.',
|
|
27
|
+
};
|
|
28
|
+
const TIMEOUT_PROPERTY = {
|
|
29
|
+
type: 'number',
|
|
30
|
+
minimum: 1000,
|
|
31
|
+
maximum: 60000,
|
|
32
|
+
description: 'Operation timeout in milliseconds (default 30000).',
|
|
33
|
+
};
|
|
34
|
+
export function browserToolDefinitions() {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
name: 'browser_open',
|
|
38
|
+
description: 'Open an isolated incognito browser session, optionally navigating to an allowed HTTP(S) URL.',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
sessionId: { ...SESSION_PROPERTY, description: 'Optional stable id for bounded session reuse.' },
|
|
43
|
+
url: { type: 'string', description: 'Optional initial HTTP(S) URL.' },
|
|
44
|
+
timeoutMs: TIMEOUT_PROPERTY,
|
|
45
|
+
},
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
},
|
|
48
|
+
annotations: INTERACTION_ANNOTATIONS,
|
|
49
|
+
_meta: APP_META,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'browser_navigate',
|
|
53
|
+
description: 'Navigate an existing isolated browser session to an allowed HTTP(S) URL.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: { sessionId: SESSION_PROPERTY, url: { type: 'string' }, timeoutMs: TIMEOUT_PROPERTY },
|
|
57
|
+
required: ['sessionId', 'url'],
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
},
|
|
60
|
+
annotations: INTERACTION_ANNOTATIONS,
|
|
61
|
+
_meta: APP_META,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'browser_snapshot',
|
|
65
|
+
description: 'Read the current page title, URL, and bounded visible body text without exposing cookies or storage.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: { sessionId: SESSION_PROPERTY, timeoutMs: TIMEOUT_PROPERTY },
|
|
69
|
+
required: ['sessionId'],
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
},
|
|
72
|
+
annotations: READ_ANNOTATIONS,
|
|
73
|
+
_meta: APP_META,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'browser_click',
|
|
77
|
+
description: 'Click the first element matching an explicit selector in an existing browser session.',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
sessionId: SESSION_PROPERTY,
|
|
82
|
+
selector: { type: 'string', minLength: 1, maxLength: MAX_SELECTOR_CHARS },
|
|
83
|
+
timeoutMs: TIMEOUT_PROPERTY,
|
|
84
|
+
},
|
|
85
|
+
required: ['sessionId', 'selector'],
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
},
|
|
88
|
+
annotations: INTERACTION_ANNOTATIONS,
|
|
89
|
+
_meta: APP_META,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'browser_type',
|
|
93
|
+
description: 'Fill the first element matching an explicit selector; optionally press Enter afterward.',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
sessionId: SESSION_PROPERTY,
|
|
98
|
+
selector: { type: 'string', minLength: 1, maxLength: MAX_SELECTOR_CHARS },
|
|
99
|
+
text: { type: 'string', maxLength: 100000 },
|
|
100
|
+
submit: { type: 'boolean', default: false },
|
|
101
|
+
timeoutMs: TIMEOUT_PROPERTY,
|
|
102
|
+
},
|
|
103
|
+
required: ['sessionId', 'selector', 'text'],
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
},
|
|
106
|
+
annotations: INTERACTION_ANNOTATIONS,
|
|
107
|
+
_meta: APP_META,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'browser_screenshot',
|
|
111
|
+
description: 'Capture a PNG screenshot of the current page in memory; no host filesystem path is exposed.',
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
sessionId: SESSION_PROPERTY,
|
|
116
|
+
fullPage: { type: 'boolean', default: false },
|
|
117
|
+
timeoutMs: TIMEOUT_PROPERTY,
|
|
118
|
+
},
|
|
119
|
+
required: ['sessionId'],
|
|
120
|
+
additionalProperties: false,
|
|
121
|
+
},
|
|
122
|
+
annotations: READ_ANNOTATIONS,
|
|
123
|
+
_meta: APP_META,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'browser_close',
|
|
127
|
+
description: 'Close an isolated browser session and discard its cookies, storage, and temporary state.',
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: { sessionId: SESSION_PROPERTY },
|
|
131
|
+
required: ['sessionId'],
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
},
|
|
134
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
135
|
+
_meta: APP_META,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
function success(data, extraContent = []) {
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: 'text', text: JSON.stringify(data) }, ...extraContent],
|
|
142
|
+
structuredContent: data,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function failure(code, message) {
|
|
146
|
+
const data = { success: false, error: { code, message } };
|
|
147
|
+
return {
|
|
148
|
+
isError: true,
|
|
149
|
+
content: [{ type: 'text', text: JSON.stringify(data) }],
|
|
150
|
+
structuredContent: data,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function normalizedError(error) {
|
|
154
|
+
if (error instanceof BrowserMcpError)
|
|
155
|
+
return error;
|
|
156
|
+
const name = error instanceof Error ? error.name : '';
|
|
157
|
+
const message = error instanceof Error ? error.message : '';
|
|
158
|
+
if (name === 'TimeoutError' || /timeout/i.test(name)) {
|
|
159
|
+
return new BrowserMcpError('TIMEOUT', 'The browser operation timed out.');
|
|
160
|
+
}
|
|
161
|
+
if (/ERR_BLOCKED_BY_CLIENT|blockedbyclient/i.test(message)) {
|
|
162
|
+
return new BrowserMcpError('NAVIGATION_BLOCKED', 'The navigation was blocked by browser policy.');
|
|
163
|
+
}
|
|
164
|
+
if (/Target page, context or browser has been closed|browser has disconnected/i.test(message)) {
|
|
165
|
+
return new BrowserMcpError('BROWSER_UNAVAILABLE', 'The browser process became unavailable; open a new session.');
|
|
166
|
+
}
|
|
167
|
+
return new BrowserMcpError('UNEXPECTED', 'The browser operation failed.');
|
|
168
|
+
}
|
|
169
|
+
function objectArgs(value) {
|
|
170
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
171
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'Tool arguments must be an object.');
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function stringArg(args, key, maxLength = 100_000) {
|
|
176
|
+
const value = args[key];
|
|
177
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > maxLength) {
|
|
178
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', `${key} must be a non-empty string no longer than ${maxLength} characters.`);
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
async function pageState(session, timeout) {
|
|
183
|
+
const [title, bodyText] = await Promise.all([
|
|
184
|
+
session.page.title(),
|
|
185
|
+
session.page.locator('body').innerText({ timeout }).catch(() => ''),
|
|
186
|
+
]);
|
|
187
|
+
const text = bodyText.length > MAX_TEXT_CHARS
|
|
188
|
+
? `${bodyText.slice(0, MAX_TEXT_CHARS)}\n…[truncated]`
|
|
189
|
+
: bodyText;
|
|
190
|
+
return { success: true, ...publicPageState(session), title, text };
|
|
191
|
+
}
|
|
192
|
+
async function navigate(session, rawUrl, timeout, signal) {
|
|
193
|
+
const url = await assertNavigationAllowed(rawUrl);
|
|
194
|
+
resetNavigationCounter(session);
|
|
195
|
+
return runCancellable(session, signal, async () => {
|
|
196
|
+
try {
|
|
197
|
+
await session.page.goto(url.href, { waitUntil: 'domcontentloaded', timeout });
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (session.navigationBlocked) {
|
|
201
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The navigation or one of its redirects was blocked by browser policy.');
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
return pageState(session, timeout);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
export async function browserCallTool(name, rawArgs, signal) {
|
|
209
|
+
try {
|
|
210
|
+
const args = objectArgs(rawArgs);
|
|
211
|
+
if (name === 'browser_open') {
|
|
212
|
+
const session = await openSession(args.sessionId, signal);
|
|
213
|
+
const timeout = timeoutMs(args.timeoutMs);
|
|
214
|
+
const data = typeof args.url === 'string' && args.url.length > 0
|
|
215
|
+
? await navigate(session, args.url, timeout, signal)
|
|
216
|
+
: { success: true, ...publicPageState(session) };
|
|
217
|
+
return success(data);
|
|
218
|
+
}
|
|
219
|
+
if (name === 'browser_close') {
|
|
220
|
+
const sessionId = stringArg(args, 'sessionId', 64);
|
|
221
|
+
const closed = await closeSession(sessionId);
|
|
222
|
+
return success({ success: true, sessionId, closed });
|
|
223
|
+
}
|
|
224
|
+
const session = getSession(args.sessionId);
|
|
225
|
+
const timeout = timeoutMs(args.timeoutMs);
|
|
226
|
+
if (name === 'browser_navigate') {
|
|
227
|
+
return success(await navigate(session, stringArg(args, 'url', 8_192), timeout, signal));
|
|
228
|
+
}
|
|
229
|
+
if (name === 'browser_snapshot') {
|
|
230
|
+
return success(await runCancellable(session, signal, () => pageState(session, timeout)));
|
|
231
|
+
}
|
|
232
|
+
if (name === 'browser_click') {
|
|
233
|
+
const selector = stringArg(args, 'selector', MAX_SELECTOR_CHARS);
|
|
234
|
+
resetNavigationCounter(session);
|
|
235
|
+
const data = await runCancellable(session, signal, async () => {
|
|
236
|
+
await session.page.locator(selector).first().click({ timeout });
|
|
237
|
+
await session.page.waitForLoadState('domcontentloaded', { timeout }).catch(() => undefined);
|
|
238
|
+
if (session.navigationBlocked) {
|
|
239
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The interaction attempted a navigation blocked by browser policy.');
|
|
240
|
+
}
|
|
241
|
+
return pageState(session, timeout);
|
|
242
|
+
});
|
|
243
|
+
return success(data);
|
|
244
|
+
}
|
|
245
|
+
if (name === 'browser_type') {
|
|
246
|
+
const selector = stringArg(args, 'selector', MAX_SELECTOR_CHARS);
|
|
247
|
+
const text = args.text;
|
|
248
|
+
if (typeof text !== 'string' || text.length > 100_000) {
|
|
249
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'text must be a string no longer than 100000 characters.');
|
|
250
|
+
}
|
|
251
|
+
resetNavigationCounter(session);
|
|
252
|
+
const data = await runCancellable(session, signal, async () => {
|
|
253
|
+
const locator = session.page.locator(selector).first();
|
|
254
|
+
await locator.fill(text, { timeout });
|
|
255
|
+
if (args.submit === true)
|
|
256
|
+
await locator.press('Enter', { timeout });
|
|
257
|
+
if (session.navigationBlocked) {
|
|
258
|
+
throw new BrowserMcpError('NAVIGATION_BLOCKED', 'The interaction attempted a navigation blocked by browser policy.');
|
|
259
|
+
}
|
|
260
|
+
return pageState(session, timeout);
|
|
261
|
+
});
|
|
262
|
+
return success(data);
|
|
263
|
+
}
|
|
264
|
+
if (name === 'browser_screenshot') {
|
|
265
|
+
const png = await runCancellable(session, signal, () => session.page.screenshot({
|
|
266
|
+
type: 'png',
|
|
267
|
+
fullPage: args.fullPage === true,
|
|
268
|
+
timeout,
|
|
269
|
+
}));
|
|
270
|
+
if (png.length > MAX_SCREENSHOT_BYTES) {
|
|
271
|
+
throw new BrowserMcpError('INVALID_ARGUMENT', 'The screenshot exceeded the 5 MB artifact limit.');
|
|
272
|
+
}
|
|
273
|
+
const data = { success: true, ...publicPageState(session), mimeType: 'image/png', bytes: png.length };
|
|
274
|
+
return success(data, [{ type: 'image', data: png.toString('base64'), mimeType: 'image/png' }]);
|
|
275
|
+
}
|
|
276
|
+
return failure('NOT_FOUND', `Unknown browser tool: ${name}`);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
const normalized = normalizedError(error);
|
|
280
|
+
return failure(normalized.code, normalized.message);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mario.andreschak/mcp-browser",
|
|
3
|
+
"version": "3.39.0",
|
|
4
|
+
"description": "FLUJO's isolated Patchright browser MCP server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"flujo-mcp-browser": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"install": "patchright install chromium",
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
18
|
+
"test": "node --test"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
25
|
+
"patchright": "1.61.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.17.30",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
}
|
|
31
|
+
}
|