#
tokens: 10632/50000 18/18 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .editorconfig
├── .env.example
├── .github
│   ├── scripts
│   │   └── before-beta-release.cjs
│   └── workflows
│       ├── check.yaml
│       ├── pre_release.yaml
│       └── release.yaml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── eslint.config.mjs
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── example_call_web_browser.ts
│   ├── example_client_stdio.ts
│   ├── index.ts
│   └── server.ts
├── tsconfig.eslint.json
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
1 | APIFY_TOKEN=
2 | 
```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
1 | # .npmignore
2 | # Exclude everything by default
3 | *
4 | 
5 | # Include specific files and folders
6 | !dist/
7 | !README.md
8 | !LICENSE
9 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # This file tells Git which files shouldn't be added to source control
 2 | 
 3 | .DS_Store
 4 | .idea
 5 | dist
 6 | build
 7 | node_modules
 8 | apify_storage
 9 | storage
10 | 
11 | 
12 | .env
13 | 
```

--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------

```
 1 | root = true
 2 | 
 3 | [*]
 4 | indent_style = space
 5 | indent_size = 4
 6 | charset = utf-8
 7 | trim_trailing_whitespace = true
 8 | insert_final_newline = true
 9 | end_of_line = lf
10 | max_line_length = 120
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Server for the RAG Web Browser Actor 🌐
  2 | 
  3 | Implementation of an MCP server for the [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser).
  4 | This Actor serves as a web browser for large language models (LLMs) and RAG pipelines, similar to a web search in ChatGPT.
  5 | 
  6 | > **This MCP server is deprecated in favor of [mcp.apify.com](https://mcp.apify.com)**
  7 | >
  8 | > For the same functionality and much more, please use one of these alternatives:
  9 | 
 10 | ## 🚀 Recommended: use mcp.apify.com
 11 | 
 12 | The easiest way to get the same web browsing capabilities is to use **[mcp.apify.com](https://mcp.apify.com)** with default settings.
 13 | 
 14 | **Benefits:**
 15 | - ✅ No local setup required
 16 | - ✅ Always up-to-date
 17 | - ✅ Access to 6,000+ Apify Actors (including RAG Web Browser)
 18 | - ✅ OAuth support for easy connection
 19 | - ✅ Dynamic tool discovery
 20 | 
 21 | **Quick Setup:**
 22 | 1. Go to https://mcp.apify.com
 23 | 2. Authorize the client (Claude, VS Code, etc.)
 24 | 3. Copy the generated MCP server configuration (or use OAuth flow if supported)
 25 | 4. Start using browsing & other tools immediately
 26 | 
 27 | ## 🌐 Alternative: direct RAG Web Browser integration
 28 | 
 29 | You can also call the [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser) directly via its HTTP/SSE interface.
 30 | 
 31 | **Benefits:**
 32 | - ✅ Direct integration without mcp.apify.com
 33 | - ✅ Real-time streaming via Server-Sent Events
 34 | - ✅ Full control over the integration
 35 | - ✅ No additional dependencies
 36 | 
 37 | **Docs:** [Actor Documentation](https://apify.com/apify/rag-web-browser#anthropic-model-context-protocol-mcp-server)
 38 | 
 39 | ---
 40 | 
 41 | ## 🎯 What does this MCP server do?
 42 | 
 43 | This server is specifically designed to provide fast responses to AI agents and LLMs, allowing them to interact with the web and extract information from web pages.
 44 | It runs locally and communicates with the [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser) in [**Standby mode**](https://docs.apify.com/platform/actors/running/standby),
 45 | sending search queries and receiving extracted web content in response.
 46 | 
 47 | - **Web Search**: Query Google Search, scrape top N URLs, and return cleaned content as Markdown
 48 | - **Single URL Fetching**: Fetch a specific URL and return its content as Markdown
 49 | - **Local MCP Integration**: Standard input/output (stdio) communication with AI clients
 50 | 
 51 | ## 🧱 Components
 52 | 
 53 | ### Tools
 54 | 
 55 | - name: `search`
 56 |   description: Query Google Search OR fetch a direct URL and return cleaned page contents.
 57 |   arguments:
 58 |   - `query` (string, required): Search keywords or a full URL. Advanced Google operators supported.
 59 |   - `maxResults` (number, optional, default: 1): Max organic results to fetch (ignored when `query` is a URL).
 60 |   - `scrapingTool` (string, optional, default: `raw-http`): One of `browser-playwright` | `raw-http`.
 61 |     - `raw-http`: Fast (no JS execution) – good for static pages.
 62 |     - `browser-playwright`: Handles JS-heavy sites – slower, more robust.
 63 |   - `outputFormats` (array of strings, optional, default: [`markdown`]): One or more of `text`, `markdown`, `html`.
 64 |   - `requestTimeoutSecs` (number, optional, default: 40, min 1 max 300): Total server-side AND client wait budget. A local abort is enforced.
 65 | 
 66 | 
 67 | ## 🔄 Migration Guide
 68 | 
 69 | ### From Local MCP Server to mcp.apify.com
 70 | 
 71 | **Before (Deprecated local server):**
 72 | ```json
 73 | {
 74 |   "mcpServers": {
 75 |     "rag-web-browser": {
 76 |       "command": "npx",
 77 |       "args": ["@apify/mcp-server-rag-web-browser"],
 78 |       "env": {
 79 |         "APIFY_TOKEN": "your-apify-api-token"
 80 |       }
 81 |     }
 82 |   }
 83 | }
 84 | ```
 85 | 
 86 | **After (Recommended Apify server):**
 87 | ```json
 88 | {
 89 |   "mcpServers": {
 90 |     "apify": {
 91 |       "command": "npx",
 92 |       "args": ["@apify/actors-mcp-server"],
 93 |       "env": {
 94 |         "APIFY_TOKEN": "your-apify-api-token"
 95 |       }
 96 |     }
 97 |   }
 98 | }
 99 | ```
100 | Or use the hosted endpoint: `https://mcp.apify.com` (when your client supports HTTP transport / remote MCP).
101 | 
102 | ### MCP clients
103 | - Claude Desktop: https://claude.ai/download
104 | - Visual Studio Code
105 | - Apify Tester MCP Client: https://apify.com/jiri.spilka/tester-mcp-client
106 | 
107 | ## 🛠️ Development
108 | 
109 | ### Prerequisites
110 | - Node.js (v18 or higher)
111 | - Apify API Token (`APIFY_TOKEN`)
112 | 
113 | Clone & install:
114 | ```bash
115 | git clone https://github.com/apify/mcp-server-rag-web-browser.git
116 | cd mcp-server-rag-web-browser
117 | npm install
118 | ```
119 | 
120 | ### Build
121 | ```bash
122 | npm install
123 | npm run build
124 | ```
125 | 
126 | ### Debugging
127 | 
128 | Since MCP servers operate over standard input/output (stdio), debugging can be challenging.
129 | For the best debugging experience, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
130 | 
131 | You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
132 | 
133 | ```bash
134 | export APIFY_TOKEN=your-apify-api-token
135 | npx @modelcontextprotocol/inspector node dist/index.js
136 | ```
137 | Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
138 | 
139 | # 📖 Learn more
140 | 
141 | - [Model Context Protocol](https://modelcontextprotocol.org/)
142 | - [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser)
143 | - [What are AI Agents?](https://blog.apify.com/what-are-ai-agents/)
144 | - [What is MCP and why does it matter?](https://blog.apify.com/what-is-model-context-protocol/)
145 | - [How to use MCP with Apify Actors](https://blog.apify.com/how-to-use-mcp/)
146 | - [Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client)
147 | - [Webinar: Building and Monetizing MCP Servers on Apify](https://www.youtube.com/watch?v=w3AH3jIrXXo)
148 | - [How to build and monetize an AI agent on Apify](https://blog.apify.com/how-to-build-an-ai-agent/)
149 | - [Build and deploy MCP servers in minutes with a TypeScript template](https://blog.apify.com/build-and-deploy-mcp-servers-typescript/)
150 | 
151 | *This repository is maintained for archival purposes only. Please use the recommended alternatives above for active development.*
152 | 
```

--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"extends": "./tsconfig.json",
 3 | 	"include": [
 4 | 	  "src/**/*.ts",
 5 | 	  "tests/**/*.ts",
 6 | 	  "*.ts",
 7 | 	  "*.js",
 8 | 	  ".eslintrc.js"
 9 | 	],
10 | 	"exclude": [
11 | 	  "node_modules",
12 | 	  "dist"
13 | 	]
14 |   }
15 |   
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "extends": "@apify/tsconfig",
 3 |     "compilerOptions": {
 4 |       "module": "ESNext",
 5 |       "target": "ESNext",
 6 |       "outDir": "dist",
 7 |       "moduleResolution": "node",
 8 |       "noUnusedLocals": false,
 9 |       "lib": ["ES2022"],
10 |       "skipLibCheck": true,
11 |       "typeRoots": ["./types", "./node_modules/@types"],
12 |       "strict": true
13 |     },
14 |     "include": ["./src/**/*"],
15 |     "exclude": ["node_modules"]
16 |   }
17 |   
```

--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------

```
 1 | import apifyConfig from '@apify/eslint-config';
 2 | 
 3 | // eslint-disable-next-line import/no-default-export
 4 | export default [
 5 |     { ignores: ['**/dist'] }, // Ignores need to happen first
 6 |     ...(Array.isArray(apifyConfig) ? apifyConfig : [apifyConfig]),
 7 |     {
 8 |         languageOptions: {
 9 |             sourceType: 'module',
10 | 
11 |             parserOptions: {
12 |                 project: 'tsconfig.eslint.json',
13 |             },
14 |         },
15 |     },
16 | ];
17 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | /**
 3 |  * This script initializes and starts the MCP server for the Apify RAG Web Browser using the Stdio transport.
 4 |  *
 5 |  * Usage:
 6 |  *   node <script_name>
 7 |  *
 8 |  * Example:
 9 |  *   node index.js
10 |  */
11 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12 | 
13 | import { RagWebBrowserServer } from './server.js';
14 | 
15 | async function main() {
16 |     const server = new RagWebBrowserServer();
17 |     const transport = new StdioServerTransport();
18 |     await server.connect(transport);
19 | }
20 | 
21 | main().catch((error) => {
22 |     console.error('Server error:', error); // eslint-disable-line no-console
23 |     process.exit(1);
24 | });
25 | 
```

--------------------------------------------------------------------------------
/.github/workflows/check.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # This workflow runs for every pull request to lint and test the proposed changes.
 2 | 
 3 | name: Check
 4 | 
 5 | on:
 6 |     pull_request:
 7 | 
 8 |     # Push to main will trigger code checks
 9 |     push:
10 |         branches:
11 |             - main
12 |         tags-ignore:
13 |             - "**" # Ignore all tags to prevent duplicate builds when tags are pushed.
14 | 
15 | jobs:
16 |     lint_and_test:
17 |         name: Lint
18 |         runs-on: ubuntu-latest
19 | 
20 |         steps:
21 |             -   uses: actions/checkout@v4
22 |             -   name: Use Node.js 22
23 |                 uses: actions/setup-node@v4
24 |                 with:
25 |                     node-version: 22
26 |                     cache: 'npm'
27 |                     cache-dependency-path: 'package-lock.json'
28 |             -   name: Install Dependencies
29 |                 run: npm ci
30 | 
31 |             -   name: Lint
32 |                 run: npm run lint
33 | 
```

--------------------------------------------------------------------------------
/.github/scripts/before-beta-release.cjs:
--------------------------------------------------------------------------------

```
 1 | const { execSync } = require('child_process');
 2 | const fs = require('fs');
 3 | const path = require('path');
 4 | 
 5 | const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json');
 6 | 
 7 | const pkgJson = require(PKG_JSON_PATH); // eslint-disable-line import/no-dynamic-require
 8 | 
 9 | const PACKAGE_NAME = pkgJson.name;
10 | const VERSION = pkgJson.version;
11 | 
12 | const nextVersion = getNextVersion(VERSION);
13 | console.log(`before-deploy: Setting version to ${nextVersion}`); // eslint-disable-line no-console
14 | pkgJson.version = nextVersion;
15 | 
16 | fs.writeFileSync(PKG_JSON_PATH, `${JSON.stringify(pkgJson, null, 2)}\n`);
17 | 
18 | function getNextVersion(version) {
19 |     const versionString = execSync(`npm show ${PACKAGE_NAME} versions --json`, { encoding: 'utf8' });
20 |     const versions = JSON.parse(versionString);
21 | 
22 |     if (versions.some((v) => v === VERSION)) {
23 |         console.error(`before-deploy: A release with version ${VERSION} already exists. Please increment version accordingly.`); // eslint-disable-line no-console
24 |         process.exit(1);
25 |     }
26 | 
27 |     const prereleaseNumbers = versions
28 |         .filter((v) => (v.startsWith(VERSION) && v.includes('-')))
29 |         .map((v) => Number(v.match(/\.(\d+)$/)[1]));
30 |     const lastPrereleaseNumber = Math.max(-1, ...prereleaseNumbers);
31 |     return `${version}-beta.${lastPrereleaseNumber + 1}`;
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/example_call_web_browser.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * @fileoverview Example how to call the RAG Web Browser Actor in a standby mode.
 3 |  * @module src/example_call_web_browser
 4 |  */
 5 | 
 6 | /* eslint-disable no-console */
 7 | import dotenv from 'dotenv';
 8 | import fetch from 'node-fetch';
 9 | 
10 | dotenv.config({ path: '../.env' });
11 | 
12 | const QUERY = 'MCP Server for Anthropic';
13 | const MAX_RESULTS = 1; // Limit the number of results to decrease response size
14 | const ACTOR_BASE_URL = 'https://rag-web-browser.apify.actor/search'; // Base URL from OpenAPI schema
15 | 
16 | const { APIFY_TOKEN } = process.env;
17 | 
18 | if (!APIFY_TOKEN) {
19 |     throw new Error('APIFY_TOKEN environment variable is not set.');
20 | }
21 | 
22 | const queryParams = new URLSearchParams({
23 |     query: QUERY,
24 |     maxResults: MAX_RESULTS.toString(),
25 | });
26 | 
27 | const headers = {
28 |     Authorization: `Bearer ${APIFY_TOKEN}`,
29 | };
30 | 
31 | // eslint-disable-next-line no-void
32 | void (async () => {
33 |     const url = `${ACTOR_BASE_URL}?${queryParams.toString()}`;
34 |     console.info(`GET request to ${url}`);
35 | 
36 |     try {
37 |         const response = await fetch(url, { method: 'GET', headers });
38 | 
39 |         if (!response.ok) {
40 |             console.log(`Error: Failed to fetch data: ${response.statusText}`);
41 |         }
42 | 
43 |         const responseBody = await response.json();
44 |         console.info('Received response from RAG Web Browser:', responseBody);
45 | 
46 |         // Optional: Further process or display the response
47 |         console.log('Response:', responseBody);
48 |     } catch (error: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
49 |         console.error('Error occurred:', error.message);
50 |     }
51 | })();
52 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@apify/mcp-server-rag-web-browser",
 3 |   "version": "0.1.4",
 4 |   "type": "module",
 5 |   "description": "An MCP Server for RAG Web Browser Actor",
 6 |   "engines": {
 7 |     "node": ">=18.0.0"
 8 |   },
 9 |   "main": "dist/index.js",
10 |   "bin": {
11 |     "mcp-rag-web-browser": "./dist/index.js"
12 |   },
13 |   "files": [
14 |     "dist",
15 |     "LICENSE"
16 |   ],
17 |   "repository": {
18 |     "type": "git",
19 |     "url": "https://github.com/apify/mcp-server-rag-web-browser"
20 |   },
21 |   "bugs": {
22 |     "url": "https://github.com/apify/mcp-server-rag-web-browser/issues"
23 |   },
24 |   "homepage": "https://github.com/apify/mcp-server-rag-web-browser",
25 |   "keywords": [
26 |     "apify",
27 |     "mcp",
28 |     "server",
29 |     "actors",
30 |     "model context protocol",
31 |     "rag",
32 |     "web browser"
33 |   ],
34 |   "scripts": {
35 |     "start": "npm run start:dev",
36 |     "start:prod": "node dist/index.js",
37 |     "start:dev": "tsx src/index.ts",
38 |     "lint": "eslint .",
39 |     "lint:fix": "eslint . --fix",
40 |     "build": "tsc",
41 |     "watch": "tsc --watch",
42 |     "inspector": "npx @modelcontextprotocol/inspector dist/index.js"
43 |   },
44 |   "dependencies": {
45 |     "@modelcontextprotocol/sdk": "^1.0.4",
46 |     "express": "^4.21.2",
47 |     "node-fetch": "^3.3.2",
48 |     "zod": "^3.24.1",
49 |     "zod-to-json-schema": "^3.24.1"
50 |   },
51 |   "devDependencies": {
52 |     "@apify/eslint-config": "^0.5.0-beta.7",
53 |     "@apify/eslint-config-ts": "^0.4.1",
54 |     "@apify/tsconfig": "^0.1.0",
55 |     "@types/express": "^5.0.0",
56 |     "@types/node": "^20.11.24",
57 |     "dotenv": "^16.4.7",
58 |     "eslint": "^9.22.0",
59 |     "tsx": "^4.6.2",
60 |     "typescript": "^5.3.3",
61 |     "typescript-eslint": "^8.18.2"
62 |   }
63 | }
64 | 
```

--------------------------------------------------------------------------------
/src/example_client_stdio.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /* eslint-disable no-console */
 2 | /**
 3 |  * Connect to the MCP server using stdio transport and call a tool.
 4 |  * You need provide a path to MCP server and APIFY_TOKEN in .env file.
 5 |  */
 6 | 
 7 | import { Client } from '@modelcontextprotocol/sdk/client/index.js';
 8 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
 9 | import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
10 | import dotenv from 'dotenv';
11 | 
12 | dotenv.config({ path: '../.env' });
13 | 
14 | const { APIFY_TOKEN } = process.env;
15 | 
16 | if (!APIFY_TOKEN) {
17 |     throw new Error('APIFY_TOKEN environment variable is not set.');
18 | }
19 | 
20 | const SERVER_PATH = '../dist/index.js';
21 | 
22 | // Create server parameters for stdio connection
23 | const transport = new StdioClientTransport({
24 |     command: 'node', // Executable
25 |     args: [
26 |         SERVER_PATH,
27 |     ],
28 |     env: {
29 |         'APIFY_TOKEN': APIFY_TOKEN
30 |     }
31 | });
32 | 
33 | // Create a new client instance
34 | const client = new Client(
35 |     { name: 'example-client', version: '1.0.0' },
36 |     { capabilities: {} },
37 | );
38 | 
39 | // Main function to run the example client
40 | async function run() {
41 |     try {
42 |         // Connect to the MCP server
43 |         await client.connect(transport);
44 | 
45 |         // List available tools
46 |         const tools = await client.listTools();
47 |         console.log('Available tools:', tools);
48 | 
49 |         // Call a tool
50 |         console.log('Calling rag web browser ...');
51 |         const result = await client.callTool(
52 |             { name: 'search', arguments: { query: 'web browser for Anthropic' } },
53 |             CallToolResultSchema,
54 |         );
55 |         console.log('Tool result:', JSON.stringify(result));
56 |     } catch (error) {
57 |         console.error('Error:', error);
58 |     }
59 | }
60 | 
61 | // Execute the main function
62 | await run();
63 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Changelog
 2 | 
 3 | All notable changes to this project will be documented in this file.
 4 | 
 5 | ## [0.1.4](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.4) (2025-08-21)
 6 | 
 7 | ### 🚀 Features
 8 | 
 9 | - Update readme ([#22](https://github.com/apify/mcp-server-rag-web-browser/pull/22)) ([57218e5](https://github.com/apify/mcp-server-rag-web-browser/commit/57218e50b7378b45713097d61615da71d9740a3e)) by [@jirispilka](https://github.com/jirispilka)
10 | 
11 | 
12 | ## [0.1.3](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.3) (2025-03-17)
13 | 
14 | ### 🐛 Bug Fixes
15 | 
16 | - Changelog, package ([#17](https://github.com/apify/mcp-server-rag-web-browser/pull/17)) ([9067509](https://github.com/apify/mcp-server-rag-web-browser/commit/9067509a12fda837456899b22a3432c8ff172f4f)) by [@jirispilka](https://github.com/jirispilka)
17 | 
18 | 
19 | ## [0.1.1](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.1) (2025-03-17)
20 | 
21 | ### 🚀 Features
22 | - Add scraping tool option ([#13](https://github.com/apify/mcp-server-rag-web-browser/pull/13)) ([0dc796c](https://github.com/apify/mcp-server-rag-web-browser/commit/0dc796cea98e02e276fcc03e43514fa156a3018d)) by [@jirispilka](https://github.com/jirispilka)
23 | - Refactor server to simplify it ([#14](https://github.com/apify/mcp-server-rag-web-browser/pull/14)) ([bbd063b](https://github.com/apify/mcp-server-rag-web-browser/commit/bbd063b2b4fc58e1fd25c07908ec1e8355955c59)) by [@jirispilka](https://github.com/jirispilka)
24 | - Change env variable name from APIFY_API_TOKEN to APIFY_TOKEN
25 | - Add extra query parameters
26 | - Remove unnecessary example clients (SSE and chat client)
27 | - Create NPM package @apify/mcp-server-rag-web-browser
28 | - Update readme
29 | 
30 | ### 🐛 Bug Fixes
31 | 
32 | - Workflow ([#16](https://github.com/apify/mcp-server-rag-web-browser/pull/16)) ([dea430d](https://github.com/apify/mcp-server-rag-web-browser/commit/dea430d793e924821b3eebc99ed4996333af99b8)) by [@jirispilka](https://github.com/jirispilka)
33 | 
34 | ## [0.1.0](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.0) (2025-03-17)
35 | 
36 | ### 🚀 Features
37 | 
38 | - Initial release
39 | - Example clients ([#10](https://github.com/apify/mcp-server-rag-web-browser/pull/10)) ([ad31f34](https://github.com/apify/mcp-server-rag-web-browser/commit/ad31f34045e3d5a01b41073af06bae33e89b1f32)) by [@jirispilka](https://github.com/jirispilka), closes [#8](https://github.com/apify/mcp-server-rag-web-browser/issues/8)
40 | 
41 | ### 🐛 Bug Fixes
42 | 
43 | - Update version ([#15](https://github.com/apify/mcp-server-rag-web-browser/pull/15)) ([1a05865](https://github.com/apify/mcp-server-rag-web-browser/commit/1a05865b4052b080a0dd758ec0e90ea1f7bdd14c)) by [@jirispilka](https://github.com/jirispilka)
```

--------------------------------------------------------------------------------
/.github/workflows/pre_release.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Create a pre-release
  2 | 
  3 | on:
  4 |     # Push to main will deploy a beta version
  5 |     push:
  6 |         branches:
  7 |             - main
  8 |         tags-ignore:
  9 |             - "**" # Ignore all tags to prevent duplicate builds when tags are pushed.
 10 | 
 11 | concurrency:
 12 |     group: release
 13 |     cancel-in-progress: false
 14 | 
 15 | jobs:
 16 |     release_metadata:
 17 |         if: "!startsWith(github.event.head_commit.message, 'docs') && !startsWith(github.event.head_commit.message, 'ci') && startsWith(github.repository, 'apify/')"
 18 |         name: Prepare release metadata
 19 |         runs-on: ubuntu-latest
 20 |         outputs:
 21 |             version_number: ${{ steps.release_metadata.outputs.version_number }}
 22 |             changelog: ${{ steps.release_metadata.outputs.changelog }}
 23 |         steps:
 24 |             -   uses: apify/workflows/git-cliff-release@main
 25 |                 name: Prepare release metadata
 26 |                 id: release_metadata
 27 |                 with:
 28 |                     release_type: prerelease
 29 |                     existing_changelog_path: CHANGELOG.md
 30 | 
 31 |     wait_for_checks:
 32 |         name: Wait for code checks to pass
 33 |         runs-on: ubuntu-latest
 34 |         steps:
 35 |             -   uses: lewagon/[email protected]
 36 |                 with:
 37 |                     ref: ${{ github.ref }}
 38 |                     repo-token: ${{ secrets.GITHUB_TOKEN }}
 39 |                     check-regexp: (Build & Test .*|Lint|Docs build)
 40 |                     wait-interval: 5
 41 | 
 42 |     update_changelog:
 43 |         needs: [ release_metadata, wait_for_checks ]
 44 |         name: Update changelog
 45 |         runs-on: ubuntu-latest
 46 |         outputs:
 47 |             changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }}
 48 | 
 49 |         steps:
 50 |             -   name: Checkout repository
 51 |                 uses: actions/checkout@v4
 52 |                 with:
 53 |                     token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
 54 | 
 55 |             -   name: Use Node.js 22
 56 |                 uses: actions/setup-node@v4
 57 |                 with:
 58 |                     node-version: 22
 59 | 
 60 |             -   name: Update package version in package.json
 61 |                 run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }}
 62 | 
 63 |             -   name: Update CHANGELOG.md
 64 |                 uses: DamianReeves/write-file-action@master
 65 |                 with:
 66 |                     path: CHANGELOG.md
 67 |                     write-mode: overwrite
 68 |                     contents: ${{ needs.release_metadata.outputs.changelog }}
 69 | 
 70 |             -   name: Commit changes
 71 |                 id: commit
 72 |                 uses: EndBug/add-and-commit@v9
 73 |                 with:
 74 |                     author_name: Apify Release Bot
 75 |                     author_email: [email protected]
 76 |                     message: "chore(release): Update changelog and package version [skip ci]"
 77 | 
 78 |     publish_to_npm:
 79 |         name: Publish to NPM
 80 |         needs: [ release_metadata, wait_for_checks ]
 81 |         runs-on: ubuntu-latest
 82 |         steps:
 83 |             -   uses: actions/checkout@v4
 84 |                 with:
 85 |                     ref: ${{ needs.update_changelog.changelog_commitish }}
 86 |             -   name: Use Node.js 22
 87 |                 uses: actions/setup-node@v4
 88 |                 with:
 89 |                     node-version: 22
 90 |                     cache: 'npm'
 91 |                     cache-dependency-path: 'package-lock.json'
 92 |             -   name: Install dependencies
 93 |                 run: |
 94 |                     echo "access=public" >> .npmrc
 95 |                     echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
 96 |                     npm ci
 97 |             - # Check version consistency and increment pre-release version number for beta only.
 98 |                 name: Bump pre-release version
 99 |                 run: node ./.github/scripts/before-beta-release.cjs
100 |             -   name: Build module
101 |                 run: npm run build
102 |             -   name: Publish to NPM
103 |                 run: npm publish --tag beta
104 | 
105 | env:
106 |     NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
107 |     NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
108 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Create a release
  2 | 
  3 | on:
  4 |     # Trigger a stable version release via GitHub's UI, with the ability to specify the type of release.
  5 |     workflow_dispatch:
  6 |         inputs:
  7 |             release_type:
  8 |                 description: Release type
  9 |                 required: true
 10 |                 type: choice
 11 |                 default: auto
 12 |                 options:
 13 |                     - auto
 14 |                     - custom
 15 |                     - patch
 16 |                     - minor
 17 |                     - major
 18 |             custom_version:
 19 |                 description: The custom version to bump to (only for "custom" type)
 20 |                 required: false
 21 |                 type: string
 22 |                 default: ""
 23 | 
 24 | concurrency:
 25 |     group: release
 26 |     cancel-in-progress: false
 27 | 
 28 | jobs:
 29 |     release_metadata:
 30 |         name: Prepare release metadata
 31 |         runs-on: ubuntu-latest
 32 |         outputs:
 33 |             version_number: ${{ steps.release_metadata.outputs.version_number }}
 34 |             tag_name: ${{ steps.release_metadata.outputs.tag_name }}
 35 |             changelog: ${{ steps.release_metadata.outputs.changelog }}
 36 |             release_notes: ${{ steps.release_metadata.outputs.release_notes }}
 37 |         steps:
 38 |             -   uses: apify/workflows/git-cliff-release@main
 39 |                 name: Prepare release metadata
 40 |                 id: release_metadata
 41 |                 with:
 42 |                     release_type: ${{ inputs.release_type }}
 43 |                     custom_version: ${{ inputs.custom_version }}
 44 |                     existing_changelog_path: CHANGELOG.md
 45 | 
 46 |     update_changelog:
 47 |         needs: [ release_metadata ]
 48 |         name: Update changelog
 49 |         runs-on: ubuntu-latest
 50 |         outputs:
 51 |             changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }}
 52 | 
 53 |         steps:
 54 |             -   name: Checkout repository
 55 |                 uses: actions/checkout@v4
 56 |                 with:
 57 |                     token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
 58 | 
 59 |             -   name: Use Node.js 22
 60 |                 uses: actions/setup-node@v4
 61 |                 with:
 62 |                     node-version: 22
 63 | 
 64 |             -   name: Update package version in package.json
 65 |                 run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }}
 66 | 
 67 |             -   name: Update CHANGELOG.md
 68 |                 uses: DamianReeves/write-file-action@master
 69 |                 with:
 70 |                     path: CHANGELOG.md
 71 |                     write-mode: overwrite
 72 |                     contents: ${{ needs.release_metadata.outputs.changelog }}
 73 | 
 74 |             -   name: Commit changes
 75 |                 id: commit
 76 |                 uses: EndBug/add-and-commit@v9
 77 |                 with:
 78 |                     author_name: Apify Release Bot
 79 |                     author_email: [email protected]
 80 |                     message: "chore(release): Update changelog and package version [skip ci]"
 81 | 
 82 |     create_github_release:
 83 |         name: Create github release
 84 |         needs: [release_metadata, update_changelog]
 85 |         runs-on: ubuntu-latest
 86 |         env:
 87 |             GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 88 |         steps:
 89 |             -   name: Create release
 90 |                 uses: softprops/action-gh-release@v2
 91 |                 with:
 92 |                     tag_name: ${{ needs.release_metadata.outputs.tag_name }}
 93 |                     name: ${{ needs.release_metadata.outputs.version_number }}
 94 |                     target_commitish: ${{ needs.update_changelog.outputs.changelog_commitish }}
 95 |                     body: ${{ needs.release_metadata.outputs.release_notes }}
 96 | 
 97 |     publish_to_npm:
 98 |         name: Publish to NPM
 99 |         needs: [ update_changelog ]
100 |         runs-on: ubuntu-latest
101 |         steps:
102 |             -   uses: actions/checkout@v4
103 |                 with:
104 |                     ref: ${{ needs.update_changelog.changelog_commitish }}
105 |             -   name: Use Node.js 22
106 |                 uses: actions/setup-node@v4
107 |                 with:
108 |                     node-version: 22
109 |                     cache: 'npm'
110 |                     cache-dependency-path: 'package-lock.json'
111 |             -   name: Install dependencies
112 |                 run: |
113 |                     echo "access=public" >> .npmrc
114 |                     echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
115 |                     npm ci
116 |             -   name: Build module
117 |                 run: npm run build
118 |             -   name: Publish to NPM
119 |                 run: npm publish --tag latest
120 | 
121 | env:
122 |     NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
123 |     NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
124 | 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * MCP server that allows to call the RAG Web Browser Actor
  5 |  */
  6 | 
  7 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  8 | import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
  9 | import {
 10 |     CallToolRequestSchema,
 11 |     ListToolsRequestSchema,
 12 | } from '@modelcontextprotocol/sdk/types.js';
 13 | import { z } from 'zod';
 14 | import { zodToJsonSchema } from 'zod-to-json-schema';
 15 | 
 16 | 
 17 | const { APIFY_TOKEN } = process.env;
 18 | 
 19 | const MAX_RESULTS = 1;
 20 | const TOOL_SEARCH = 'search';
 21 | const ACTOR_BASE_URL = 'https://rag-web-browser.apify.actor/search';
 22 | 
 23 | const WebBrowserArgsSchema = z.object({
 24 |     query: z.string()
 25 |         .describe('Enter Google Search keywords or a URL of a specific web page. The keywords might include the'
 26 |             + 'advanced search operators. Examples: "san francisco weather", "https://www.cnn.com", '
 27 |             + '"function calling site:openai.com"')
 28 |         .regex(/[^\s]+/, { message: "Search term or URL cannot be empty" }),
 29 |     maxResults: z.number().int().positive().min(1).max(100).default(MAX_RESULTS)
 30 |         .describe(
 31 |             'The maximum number of top organic Google Search results whose web pages will be extracted. '
 32 |             + 'If query is a URL, then this field is ignored and the Actor only fetches the specific web page.',
 33 |         ),
 34 |     scrapingTool: z.enum(['browser-playwright', 'raw-http'])
 35 |         .describe('Select a scraping tool for extracting the target web pages. '
 36 |         + 'The Browser tool is more powerful and can handle JavaScript heavy websites, while the '
 37 |         + 'Plain HTML tool can not handle JavaScript but is about two times faster.')
 38 |         .default('raw-http'),
 39 |     outputFormats: z.array(z.enum(['text', 'markdown', 'html']))
 40 |         .describe('Select one or more formats to which the target web pages will be extracted.')
 41 |         .default(['markdown']),
 42 |     requestTimeoutSecs: z.number().int().min(1).max(300).default(40)
 43 |         .describe('The maximum time in seconds available for the request, including querying Google Search '
 44 |             + 'and scraping the target web pages.'),
 45 | });
 46 | 
 47 | /**
 48 |  * Create an MCP server with a tool to call RAG Web Browser Actor
 49 |  */
 50 | export class RagWebBrowserServer {
 51 |     private server: Server;
 52 | 
 53 |     constructor() {
 54 |         this.server = new Server(
 55 |             {
 56 |                 name: 'mcp-server-rag-web-browser',
 57 |                 version: '0.1.0',
 58 |             },
 59 |             {
 60 |                 capabilities: {
 61 |                     tools: {},
 62 |                 },
 63 |             },
 64 |         );
 65 |         this.setupErrorHandling();
 66 |         this.setupToolHandlers();
 67 |     }
 68 | 
 69 |     private async callRagWebBrowser(args: z.infer<typeof WebBrowserArgsSchema>): Promise<string> {
 70 |         if (!APIFY_TOKEN) {
 71 |             throw new Error('APIFY_TOKEN is required but not set. '
 72 |                 + 'Please set it in your environment variables or pass it as a command-line argument.');
 73 |         }
 74 | 
 75 |         const queryParams = new URLSearchParams({
 76 |             query: args.query,
 77 |             maxResults: args.maxResults.toString(),
 78 |             scrapingTool: args.scrapingTool,
 79 |         });
 80 | 
 81 |         // Add all other parameters if provided
 82 |         if (args.outputFormats) {
 83 |             args.outputFormats.forEach((format) => {
 84 |                 queryParams.append('outputFormats', format);
 85 |             });
 86 |         }
 87 |         if (args.requestTimeoutSecs) {
 88 |             queryParams.append('requestTimeoutSecs', args.requestTimeoutSecs.toString());
 89 |         }
 90 | 
 91 |         const url = `${ACTOR_BASE_URL}?${queryParams.toString()}`;
 92 |         const response = await fetch(url, {
 93 |             method: 'GET',
 94 |             headers: {
 95 |                 Authorization: `Bearer ${APIFY_TOKEN}`,
 96 |             },
 97 |         });
 98 | 
 99 |         if (!response.ok) {
100 |             throw new Error(`Failed to call RAG Web Browser: ${response.status} ${response.statusText}`);
101 |         }
102 | 
103 |         const responseBody = await response.json();
104 |         return JSON.stringify(responseBody);
105 |     }
106 | 
107 |     private setupErrorHandling(): void {
108 |         this.server.onerror = (error) => {
109 |             console.error('[MCP Error]', error); // eslint-disable-line no-console
110 |         };
111 |         process.on('SIGINT', async () => {
112 |             await this.server.close();
113 |             process.exit(0);
114 |         });
115 |     }
116 | 
117 |     private setupToolHandlers(): void {
118 |         this.server.setRequestHandler(ListToolsRequestSchema, async () => {
119 |             return {
120 |                 tools: [
121 |                     {
122 |                         name: TOOL_SEARCH,
123 |                         description: 'Search phrase or a URL at Google and return crawled web pages as text or Markdown. '
124 |                             + 'Prefer HTTP raw client for speed and browser-playwright for reliability.',
125 |                         inputSchema: zodToJsonSchema(WebBrowserArgsSchema),
126 |                     },
127 |                 ],
128 |             };
129 |         });
130 |         this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
131 |             const { name, arguments: args } = request.params;
132 |             switch (name) {
133 |                 case TOOL_SEARCH: {
134 |                     try {
135 |                         const parsed = WebBrowserArgsSchema.parse(args);
136 |                         const content = await this.callRagWebBrowser(parsed);
137 |                         return {
138 |                             content: [{ type: 'text', text: content }],
139 |                         };
140 |                     } catch (error) {
141 |                         console.error('[MCP Error]', error);
142 |                         throw new Error(`Failed to call RAG Web Browser: ${error}`);
143 |                     }
144 |                 }
145 |                 default: {
146 |                     throw new Error(`Unknown tool: ${name}`);
147 |                 }
148 |             }
149 |         });
150 |     }
151 | 
152 |     async connect(transport: Transport): Promise<void> {
153 |         await this.server.connect(transport);
154 |     }
155 | }
156 | 
```