@solvvn/mcp-simple-browser 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # MCP Simple Browser
2
+
3
+ An MCP (Model Context Protocol) server that provides browser automation tools powered by CloakBrowser.
4
+
5
+ ## Features
6
+
7
+ - **Headless Browser**: Automatically launches a stealth browser via CloakBrowser
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ # Add to Claude Desktop
13
+ claude mcp add simple-browser npx @solvvn/mcp-simple-browser
14
+ ```
15
+
16
+ ## Installation (Manual)
17
+
18
+ ```bash
19
+ npm install
20
+ # or
21
+ yarn install
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### As MCP Server
27
+
28
+ ```bash
29
+ # Development
30
+ yarn dev
31
+
32
+ # Production
33
+ yarn build
34
+ yarn start
35
+ ```
36
+
37
+ The server runs on stdio and can be connected to any MCP-compatible client (Claude Desktop, etc.).
38
+
39
+ ### Claude Desktop Configuration
40
+
41
+ Add to your Claude Desktop config:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "simple-browser": {
47
+ "command": "node",
48
+ "args": ["/path/to/mcp-simple-browser/dist/index.js"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Available Tools
55
+
56
+ | Tool | Description |
57
+ |------|-------------|
58
+ | `browser_navigate` | Navigate to a URL and wait for it to load |
59
+ | `browser_screenshot` | Take a screenshot (returns base64) |
60
+ | `browser_save_screenshot` | Take a screenshot and save to file |
61
+ | `browser_click` | Click an element by CSS selector |
62
+ | `browser_type` | Type text into an input field |
63
+ | `browser_get_content` | Get HTML content of the page |
64
+ | `browser_get_text` | Get text content from page or element |
65
+ | `browser_evaluate` | Execute JavaScript in page context |
66
+ | `browser_search` | Search the web (Google, DuckDuckGo, Bing) |
67
+ | `browser_wait` | Wait for specified milliseconds |
68
+ | `browser_print_to_pdf` | Print page to PDF |
69
+ | `browser_close` | Close browser and cleanup |
70
+
71
+ ## Examples
72
+
73
+ ### Navigate and Screenshot
74
+
75
+ ```javascript
76
+ // Navigate to a page
77
+ await browser_navigate({ url: "https://example.com" })
78
+
79
+ // Take screenshot
80
+ await browser_screenshot({ fullPage: true })
81
+ ```
82
+
83
+ ### Search
84
+
85
+ ```javascript
86
+ // Search with default engine (DuckDuckGo)
87
+ await browser_search({ query: "TypeScript MCP" })
88
+
89
+ // Search with specific engine
90
+ await browser_search({ query: "CloakBrowser", engine: "google" })
91
+ ```
92
+
93
+ ### Interact with Elements
94
+
95
+ ```javascript
96
+ // Type in search box
97
+ await browser_type({ selector: "input[name='q']", text: "hello" })
98
+
99
+ // Click a button
100
+ await browser_click({ selector: "button[type='submit']" })
101
+ ```
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ # Type check
107
+ yarn typecheck
108
+
109
+ # Build
110
+ yarn build
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,7 @@
1
+ import { launch } from "cloakbrowser";
2
+ type Browser = Awaited<ReturnType<typeof launch>>;
3
+ type Page = Awaited<ReturnType<Browser["newPage"]>>;
4
+ export declare function getPage(): Promise<Page>;
5
+ export declare function closeBrowser(): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,KAAK,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC;AAClD,KAAK,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AASpD,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ7C;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAKlD"}
@@ -0,0 +1,22 @@
1
+ import { launch } from "cloakbrowser";
2
+ let browserInstance = null;
3
+ let pageInstance = null;
4
+ function createBrowser() {
5
+ return launch({ headless: true, humanize: true });
6
+ }
7
+ export async function getPage() {
8
+ if (!browserInstance) {
9
+ browserInstance = await createBrowser();
10
+ }
11
+ if (!pageInstance) {
12
+ pageInstance = await browserInstance.newPage();
13
+ }
14
+ return pageInstance;
15
+ }
16
+ export async function closeBrowser() {
17
+ await pageInstance?.close().catch(() => { });
18
+ await browserInstance?.close().catch(() => { });
19
+ pageInstance = null;
20
+ browserInstance = null;
21
+ }
22
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAKtC,IAAI,eAAe,GAAmB,IAAI,CAAC;AAC3C,IAAI,YAAY,GAAgB,IAAI,CAAC;AAErC,SAAS,aAAa;IACpB,OAAO,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,eAAe,GAAG,MAAM,aAAa,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,YAAY,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC5C,MAAM,eAAe,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC/C,YAAY,GAAG,IAAI,CAAC;IACpB,eAAe,GAAG,IAAI,CAAC;AACzB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import * as dotenv from "dotenv";
2
+ dotenv.config();
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { registerTools } from "./tools.js";
6
+ async function main() {
7
+ const server = new McpServer({ name: "simple-browser", version: "1.0.0" });
8
+ registerTools(server);
9
+ await server.connect(new StdioServerTransport());
10
+ console.error("SimpleBrowser MCP Server running on stdio");
11
+ }
12
+ main().catch((err) => {
13
+ console.error(err);
14
+ process.exit(1);
15
+ });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3E,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;AAC7D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTools(server: McpServer): void;
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8LzE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiBrD"}
package/dist/tools.js ADDED
@@ -0,0 +1,187 @@
1
+ import { z } from "zod";
2
+ import { writeFile } from "fs/promises";
3
+ import { closeBrowser, getPage } from "./browser.js";
4
+ function createTool(def) {
5
+ return def;
6
+ }
7
+ function success(extra) {
8
+ return { success: true, ...extra };
9
+ }
10
+ const SearchEngines = {
11
+ google: (q) => `https://www.google.com/search?q=${q}`,
12
+ duckduckgo: (q) => `https://duckduckgo.com/?q=${q}`,
13
+ bing: (q) => `https://www.bing.com/search?q=${q}`,
14
+ };
15
+ const tools = [
16
+ createTool({
17
+ name: "browser_navigate",
18
+ description: "Navigate to a URL and wait for it to load.",
19
+ schema: {
20
+ url: z.string().describe("The URL to navigate to"),
21
+ waitUntil: z
22
+ .enum(["load", "domcontentloaded", "networkidle", "commit"])
23
+ .default("networkidle"),
24
+ },
25
+ handler: async ({ url, waitUntil }) => {
26
+ const page = await getPage();
27
+ await page.goto(url, { waitUntil });
28
+ return success({ url: page.url(), title: await page.title() });
29
+ },
30
+ }),
31
+ createTool({
32
+ name: "browser_screenshot",
33
+ description: "Take a screenshot of the current page.",
34
+ schema: {
35
+ fullPage: z.boolean().default(false).describe("Capture full scrollable page"),
36
+ },
37
+ handler: async ({ fullPage }) => {
38
+ const buffer = await (await getPage()).screenshot({ fullPage });
39
+ return success({ format: "png", data: buffer.toString("base64") });
40
+ },
41
+ }),
42
+ createTool({
43
+ name: "browser_click",
44
+ description: "Click an element by CSS selector.",
45
+ schema: { selector: z.string().describe("CSS selector") },
46
+ handler: async ({ selector }) => {
47
+ await (await getPage()).click(selector);
48
+ return success({});
49
+ },
50
+ }),
51
+ createTool({
52
+ name: "browser_type",
53
+ description: "Type text into an input field.",
54
+ schema: {
55
+ selector: z.string(),
56
+ text: z.string(),
57
+ delay: z.number().default(50).describe("Delay between keystrokes (ms)"),
58
+ },
59
+ handler: async ({ selector, text, delay }) => {
60
+ await (await getPage()).type(selector, text, { delay });
61
+ return success({});
62
+ },
63
+ }),
64
+ createTool({
65
+ name: "browser_get_content",
66
+ description: "Get the HTML content of the page.",
67
+ schema: {},
68
+ handler: async () => ({ html: await (await getPage()).content() }),
69
+ }),
70
+ createTool({
71
+ name: "browser_get_text",
72
+ description: "Get text content from the page or a specific element.",
73
+ schema: {
74
+ selector: z.string().optional().describe("CSS selector (defaults to body)"),
75
+ },
76
+ handler: async ({ selector }) => {
77
+ const el = selector || "body";
78
+ const text = await (await getPage()).$eval(el, (el) => el.textContent);
79
+ return { text: text ?? "" };
80
+ },
81
+ }),
82
+ createTool({
83
+ name: "browser_evaluate",
84
+ description: "Execute JavaScript in the page context.",
85
+ schema: { script: z.string().describe("JavaScript code to execute") },
86
+ handler: async ({ script }) => ({ result: await (await getPage()).evaluate(script) }),
87
+ }),
88
+ createTool({
89
+ name: "browser_search",
90
+ description: "Search the web using a search engine.",
91
+ schema: {
92
+ query: z.string(),
93
+ engine: z.enum(["google", "duckduckgo", "bing"]).default("duckduckgo"),
94
+ },
95
+ handler: async ({ query, engine }) => {
96
+ const page = await getPage();
97
+ const searchUrl = SearchEngines[engine](encodeURIComponent(query));
98
+ await page.goto(searchUrl, { waitUntil: "networkidle" });
99
+ const results = await page.evaluate(() => {
100
+ const out = [];
101
+ const selectors = ["div.g a[href^='http']", "div[data-srg] a[href^='http']", ".result a[href^='http']"];
102
+ for (const sel of selectors) {
103
+ document.querySelectorAll(sel).forEach((a) => {
104
+ if (!a.href || /google|bing|duckduckgo/.test(a.href))
105
+ return;
106
+ const titleEl = a.querySelector("h3") || a;
107
+ out.push({
108
+ title: titleEl.textContent?.trim() ?? "",
109
+ url: a.href,
110
+ snippet: a.textContent?.trim() ?? "",
111
+ });
112
+ });
113
+ if (out.length)
114
+ break;
115
+ }
116
+ return out.slice(0, 10);
117
+ });
118
+ return success({ engine, query, results });
119
+ },
120
+ }),
121
+ createTool({
122
+ name: "browser_wait",
123
+ description: "Wait for a specified time.",
124
+ schema: { milliseconds: z.number().describe("Time to wait in ms") },
125
+ handler: async ({ milliseconds }) => {
126
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
127
+ return success({ waited: milliseconds });
128
+ },
129
+ }),
130
+ createTool({
131
+ name: "browser_close",
132
+ description: "Close the browser and clean up resources.",
133
+ schema: {},
134
+ handler: async () => {
135
+ await closeBrowser();
136
+ return success({});
137
+ },
138
+ }),
139
+ createTool({
140
+ name: "browser_save_screenshot",
141
+ description: "Take a screenshot and save it to a file.",
142
+ schema: {
143
+ filepath: z.string().describe("Path where to save the screenshot"),
144
+ fullPage: z.boolean().default(false).describe("Capture full scrollable page"),
145
+ },
146
+ handler: async ({ filepath, fullPage }) => {
147
+ const buffer = await (await getPage()).screenshot({ fullPage });
148
+ await writeFile(filepath, buffer);
149
+ return success({ filepath, size: buffer.length });
150
+ },
151
+ }),
152
+ createTool({
153
+ name: "browser_print_to_pdf",
154
+ description: "Print the current page to PDF and save to a file.",
155
+ schema: {
156
+ filepath: z.string().describe("Path where to save the PDF"),
157
+ landscape: z.boolean().default(false).describe("Use landscape orientation"),
158
+ printBackground: z.boolean().default(true).describe("Print background graphics"),
159
+ },
160
+ handler: async ({ filepath, landscape, printBackground }) => {
161
+ const pdf = await (await getPage()).pdf({
162
+ landscape,
163
+ printBackground,
164
+ format: "A4",
165
+ });
166
+ await writeFile(filepath, pdf);
167
+ return success({ filepath, size: pdf.length });
168
+ },
169
+ }),
170
+ ];
171
+ function toResult(value, isError = false) {
172
+ const text = typeof value === "string" ? value : JSON.stringify(value);
173
+ return { content: [{ type: "text", text }], ...(isError && { isError }) };
174
+ }
175
+ export function registerTools(server) {
176
+ for (const tool of tools) {
177
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.schema }, async (args) => {
178
+ try {
179
+ return toResult(await tool.handler(args));
180
+ }
181
+ catch (err) {
182
+ return toResult({ error: err instanceof Error ? err.message : String(err) }, true);
183
+ }
184
+ });
185
+ }
186
+ }
187
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ services:
2
+ cloakbrowser-manager:
3
+ image: cloakhq/cloakbrowser-manager
4
+ ports:
5
+ - "8081:8080"
6
+ volumes:
7
+ - cloakprofiles:/data
8
+
9
+ volumes:
10
+ cloakprofiles:
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@solvvn/mcp-simple-browser",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "bin": {
13
+ "mcp-simple-browser": "dist/index.js"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "start": "node dist/index.js",
18
+ "dev": "tsx watch src/index.ts",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "author": "Huy Le Tien <huy@macbook.local>",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.29.0",
25
+ "cloakbrowser": "^0.3.30",
26
+ "dotenv": "^17.4.2",
27
+ "zod": "^4.4.3"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^25.9.1",
31
+ "tsx": "^4.22.3",
32
+ "typescript": "^6.0.3"
33
+ }
34
+ }
package/src/browser.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { launch } from "cloakbrowser";
2
+
3
+ type Browser = Awaited<ReturnType<typeof launch>>;
4
+ type Page = Awaited<ReturnType<Browser["newPage"]>>;
5
+
6
+ let browserInstance: Browser | null = null;
7
+ let pageInstance: Page | null = null;
8
+
9
+ function createBrowser(): Promise<Browser> {
10
+ return launch({ headless: true, humanize: true });
11
+ }
12
+
13
+ export async function getPage(): Promise<Page> {
14
+ if (!browserInstance) {
15
+ browserInstance = await createBrowser();
16
+ }
17
+ if (!pageInstance) {
18
+ pageInstance = await browserInstance.newPage();
19
+ }
20
+ return pageInstance;
21
+ }
22
+
23
+ export async function closeBrowser(): Promise<void> {
24
+ await pageInstance?.close().catch(() => {});
25
+ await browserInstance?.close().catch(() => {});
26
+ pageInstance = null;
27
+ browserInstance = null;
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ import * as dotenv from "dotenv";
2
+ dotenv.config();
3
+
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { registerTools } from "./tools.js";
7
+
8
+ async function main() {
9
+ const server = new McpServer({ name: "simple-browser", version: "1.0.0" });
10
+ registerTools(server);
11
+ await server.connect(new StdioServerTransport());
12
+ console.error("SimpleBrowser MCP Server running on stdio");
13
+ }
14
+
15
+ main().catch((err) => {
16
+ console.error(err);
17
+ process.exit(1);
18
+ });
package/src/tools.ts ADDED
@@ -0,0 +1,210 @@
1
+ import { z } from "zod";
2
+ import { writeFile } from "fs/promises";
3
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
5
+ import { closeBrowser, getPage } from "./browser.js";
6
+
7
+ type ToolHandler<T extends z.ZodRawShape> = (args: z.infer<z.ZodObject<T>>) => Promise<unknown>;
8
+
9
+ interface ToolDefinition<S extends z.ZodRawShape> {
10
+ name: string;
11
+ description: string;
12
+ schema: S;
13
+ handler: ToolHandler<S>;
14
+ }
15
+
16
+ function createTool<S extends z.ZodRawShape>(def: ToolDefinition<S>) {
17
+ return def as unknown as ToolDefinition<z.ZodRawShape>;
18
+ }
19
+
20
+ function success<T extends Record<string, unknown>>(extra: T) {
21
+ return { success: true, ...extra };
22
+ }
23
+
24
+ const SearchEngines = {
25
+ google: (q: string) => `https://www.google.com/search?q=${q}`,
26
+ duckduckgo: (q: string) => `https://duckduckgo.com/?q=${q}`,
27
+ bing: (q: string) => `https://www.bing.com/search?q=${q}`,
28
+ } as const;
29
+
30
+ const tools = [
31
+ createTool({
32
+ name: "browser_navigate",
33
+ description: "Navigate to a URL and wait for it to load.",
34
+ schema: {
35
+ url: z.string().describe("The URL to navigate to"),
36
+ waitUntil: z
37
+ .enum(["load", "domcontentloaded", "networkidle", "commit"])
38
+ .default("networkidle"),
39
+ },
40
+ handler: async ({ url, waitUntil }) => {
41
+ const page = await getPage();
42
+ await page.goto(url, { waitUntil });
43
+ return success({ url: page.url(), title: await page.title() });
44
+ },
45
+ }),
46
+ createTool({
47
+ name: "browser_screenshot",
48
+ description: "Take a screenshot of the current page.",
49
+ schema: {
50
+ fullPage: z.boolean().default(false).describe("Capture full scrollable page"),
51
+ },
52
+ handler: async ({ fullPage }) => {
53
+ const buffer = await (await getPage()).screenshot({ fullPage });
54
+ return success({ format: "png", data: buffer.toString("base64") });
55
+ },
56
+ }),
57
+ createTool({
58
+ name: "browser_click",
59
+ description: "Click an element by CSS selector.",
60
+ schema: { selector: z.string().describe("CSS selector") },
61
+ handler: async ({ selector }) => {
62
+ await (await getPage()).click(selector);
63
+ return success({});
64
+ },
65
+ }),
66
+ createTool({
67
+ name: "browser_type",
68
+ description: "Type text into an input field.",
69
+ schema: {
70
+ selector: z.string(),
71
+ text: z.string(),
72
+ delay: z.number().default(50).describe("Delay between keystrokes (ms)"),
73
+ },
74
+ handler: async ({ selector, text, delay }) => {
75
+ await (await getPage()).type(selector, text, { delay });
76
+ return success({});
77
+ },
78
+ }),
79
+ createTool({
80
+ name: "browser_get_content",
81
+ description: "Get the HTML content of the page.",
82
+ schema: {},
83
+ handler: async () => ({ html: await (await getPage()).content() }),
84
+ }),
85
+ createTool({
86
+ name: "browser_get_text",
87
+ description: "Get text content from the page or a specific element.",
88
+ schema: {
89
+ selector: z.string().optional().describe("CSS selector (defaults to body)"),
90
+ },
91
+ handler: async ({ selector }) => {
92
+ const el = selector || "body";
93
+ const text = await (await getPage()).$eval(el, (el: HTMLElement) => el.textContent);
94
+ return { text: text ?? "" };
95
+ },
96
+ }),
97
+ createTool({
98
+ name: "browser_evaluate",
99
+ description: "Execute JavaScript in the page context.",
100
+ schema: { script: z.string().describe("JavaScript code to execute") },
101
+ handler: async ({ script }) => ({ result: await (await getPage()).evaluate(script) }),
102
+ }),
103
+ createTool({
104
+ name: "browser_search",
105
+ description: "Search the web using a search engine.",
106
+ schema: {
107
+ query: z.string(),
108
+ engine: z.enum(["google", "duckduckgo", "bing"]).default("duckduckgo"),
109
+ },
110
+ handler: async ({ query, engine }) => {
111
+ const page = await getPage();
112
+ const searchUrl = SearchEngines[engine](encodeURIComponent(query));
113
+ await page.goto(searchUrl, { waitUntil: "networkidle" });
114
+
115
+ const results = await page.evaluate(() => {
116
+ const out: Array<{ title: string; url: string; snippet: string }> = [];
117
+ const selectors = ["div.g a[href^='http']", "div[data-srg] a[href^='http']", ".result a[href^='http']"];
118
+
119
+ for (const sel of selectors) {
120
+ document.querySelectorAll<HTMLAnchorElement>(sel).forEach((a) => {
121
+ if (!a.href || /google|bing|duckduckgo/.test(a.href)) return;
122
+ const titleEl = a.querySelector("h3") || a;
123
+ out.push({
124
+ title: titleEl.textContent?.trim() ?? "",
125
+ url: a.href,
126
+ snippet: a.textContent?.trim() ?? "",
127
+ });
128
+ });
129
+ if (out.length) break;
130
+ }
131
+ return out.slice(0, 10);
132
+ });
133
+
134
+ return success({ engine, query, results });
135
+ },
136
+ }),
137
+ createTool({
138
+ name: "browser_wait",
139
+ description: "Wait for a specified time.",
140
+ schema: { milliseconds: z.number().describe("Time to wait in ms") },
141
+ handler: async ({ milliseconds }) => {
142
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
143
+ return success({ waited: milliseconds });
144
+ },
145
+ }),
146
+ createTool({
147
+ name: "browser_close",
148
+ description: "Close the browser and clean up resources.",
149
+ schema: {},
150
+ handler: async () => {
151
+ await closeBrowser();
152
+ return success({});
153
+ },
154
+ }),
155
+ createTool({
156
+ name: "browser_save_screenshot",
157
+ description: "Take a screenshot and save it to a file.",
158
+ schema: {
159
+ filepath: z.string().describe("Path where to save the screenshot"),
160
+ fullPage: z.boolean().default(false).describe("Capture full scrollable page"),
161
+ },
162
+ handler: async ({ filepath, fullPage }) => {
163
+ const buffer = await (await getPage()).screenshot({ fullPage });
164
+ await writeFile(filepath, buffer);
165
+ return success({ filepath, size: buffer.length });
166
+ },
167
+ }),
168
+ createTool({
169
+ name: "browser_print_to_pdf",
170
+ description: "Print the current page to PDF and save to a file.",
171
+ schema: {
172
+ filepath: z.string().describe("Path where to save the PDF"),
173
+ landscape: z.boolean().default(false).describe("Use landscape orientation"),
174
+ printBackground: z.boolean().default(true).describe("Print background graphics"),
175
+ },
176
+ handler: async ({ filepath, landscape, printBackground }) => {
177
+ const pdf = await (await getPage()).pdf({
178
+ landscape,
179
+ printBackground,
180
+ format: "A4",
181
+ });
182
+ await writeFile(filepath, pdf);
183
+ return success({ filepath, size: pdf.length });
184
+ },
185
+ }),
186
+ ];
187
+
188
+ function toResult(value: unknown, isError = false): CallToolResult {
189
+ const text = typeof value === "string" ? value : JSON.stringify(value);
190
+ return { content: [{ type: "text", text }], ...(isError && { isError }) };
191
+ }
192
+
193
+ export function registerTools(server: McpServer): void {
194
+ for (const tool of tools) {
195
+ server.registerTool(
196
+ tool.name,
197
+ { description: tool.description, inputSchema: tool.schema },
198
+ async (args) => {
199
+ try {
200
+ return toResult(await tool.handler(args as never));
201
+ } catch (err) {
202
+ return toResult(
203
+ { error: err instanceof Error ? err.message : String(err) },
204
+ true,
205
+ );
206
+ }
207
+ },
208
+ );
209
+ }
210
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022", "DOM"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }