#
tokens: 8043/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
APIFY_TOKEN=

```

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

```
# .npmignore
# Exclude everything by default
*

# Include specific files and folders
!dist/
!README.md
!LICENSE

```

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

```
# This file tells Git which files shouldn't be added to source control

.DS_Store
.idea
dist
build
node_modules
apify_storage
storage


.env

```

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

```
root = true

[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = 120

```

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

```markdown
# MCP Server for the RAG Web Browser Actor 🌐

Implementation of an MCP server for the [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser).
This Actor serves as a web browser for large language models (LLMs) and RAG pipelines, similar to a web search in ChatGPT.

> **This MCP server is deprecated in favor of [mcp.apify.com](https://mcp.apify.com)**
>
> For the same functionality and much more, please use one of these alternatives:

## 🚀 Recommended: use mcp.apify.com

The easiest way to get the same web browsing capabilities is to use **[mcp.apify.com](https://mcp.apify.com)** with default settings.

**Benefits:**
- ✅ No local setup required
- ✅ Always up-to-date
- ✅ Access to 6,000+ Apify Actors (including RAG Web Browser)
- ✅ OAuth support for easy connection
- ✅ Dynamic tool discovery

**Quick Setup:**
1. Go to https://mcp.apify.com
2. Authorize the client (Claude, VS Code, etc.)
3. Copy the generated MCP server configuration (or use OAuth flow if supported)
4. Start using browsing & other tools immediately

## 🌐 Alternative: direct RAG Web Browser integration

You can also call the [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser) directly via its HTTP/SSE interface.

**Benefits:**
- ✅ Direct integration without mcp.apify.com
- ✅ Real-time streaming via Server-Sent Events
- ✅ Full control over the integration
- ✅ No additional dependencies

**Docs:** [Actor Documentation](https://apify.com/apify/rag-web-browser#anthropic-model-context-protocol-mcp-server)

---

## 🎯 What does this MCP server do?

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.
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),
sending search queries and receiving extracted web content in response.

- **Web Search**: Query Google Search, scrape top N URLs, and return cleaned content as Markdown
- **Single URL Fetching**: Fetch a specific URL and return its content as Markdown
- **Local MCP Integration**: Standard input/output (stdio) communication with AI clients

## 🧱 Components

### Tools

- name: `search`
  description: Query Google Search OR fetch a direct URL and return cleaned page contents.
  arguments:
  - `query` (string, required): Search keywords or a full URL. Advanced Google operators supported.
  - `maxResults` (number, optional, default: 1): Max organic results to fetch (ignored when `query` is a URL).
  - `scrapingTool` (string, optional, default: `raw-http`): One of `browser-playwright` | `raw-http`.
    - `raw-http`: Fast (no JS execution) – good for static pages.
    - `browser-playwright`: Handles JS-heavy sites – slower, more robust.
  - `outputFormats` (array of strings, optional, default: [`markdown`]): One or more of `text`, `markdown`, `html`.
  - `requestTimeoutSecs` (number, optional, default: 40, min 1 max 300): Total server-side AND client wait budget. A local abort is enforced.


## 🔄 Migration Guide

### From Local MCP Server to mcp.apify.com

**Before (Deprecated local server):**
```json
{
  "mcpServers": {
    "rag-web-browser": {
      "command": "npx",
      "args": ["@apify/mcp-server-rag-web-browser"],
      "env": {
        "APIFY_TOKEN": "your-apify-api-token"
      }
    }
  }
}
```

**After (Recommended Apify server):**
```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["@apify/actors-mcp-server"],
      "env": {
        "APIFY_TOKEN": "your-apify-api-token"
      }
    }
  }
}
```
Or use the hosted endpoint: `https://mcp.apify.com` (when your client supports HTTP transport / remote MCP).

### MCP clients
- Claude Desktop: https://claude.ai/download
- Visual Studio Code
- Apify Tester MCP Client: https://apify.com/jiri.spilka/tester-mcp-client

## 🛠️ Development

### Prerequisites
- Node.js (v18 or higher)
- Apify API Token (`APIFY_TOKEN`)

Clone & install:
```bash
git clone https://github.com/apify/mcp-server-rag-web-browser.git
cd mcp-server-rag-web-browser
npm install
```

### Build
```bash
npm install
npm run build
```

### Debugging

Since MCP servers operate over standard input/output (stdio), debugging can be challenging.
For the best debugging experience, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).

You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:

```bash
export APIFY_TOKEN=your-apify-api-token
npx @modelcontextprotocol/inspector node dist/index.js
```
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.

# 📖 Learn more

- [Model Context Protocol](https://modelcontextprotocol.org/)
- [RAG Web Browser Actor](https://apify.com/apify/rag-web-browser)
- [What are AI Agents?](https://blog.apify.com/what-are-ai-agents/)
- [What is MCP and why does it matter?](https://blog.apify.com/what-is-model-context-protocol/)
- [How to use MCP with Apify Actors](https://blog.apify.com/how-to-use-mcp/)
- [Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client)
- [Webinar: Building and Monetizing MCP Servers on Apify](https://www.youtube.com/watch?v=w3AH3jIrXXo)
- [How to build and monetize an AI agent on Apify](https://blog.apify.com/how-to-build-an-ai-agent/)
- [Build and deploy MCP servers in minutes with a TypeScript template](https://blog.apify.com/build-and-deploy-mcp-servers-typescript/)

*This repository is maintained for archival purposes only. Please use the recommended alternatives above for active development.*

```

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

```json
{
	"extends": "./tsconfig.json",
	"include": [
	  "src/**/*.ts",
	  "tests/**/*.ts",
	  "*.ts",
	  "*.js",
	  ".eslintrc.js"
	],
	"exclude": [
	  "node_modules",
	  "dist"
	]
  }
  
```

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

```json
{
    "extends": "@apify/tsconfig",
    "compilerOptions": {
      "module": "ESNext",
      "target": "ESNext",
      "outDir": "dist",
      "moduleResolution": "node",
      "noUnusedLocals": false,
      "lib": ["ES2022"],
      "skipLibCheck": true,
      "typeRoots": ["./types", "./node_modules/@types"],
      "strict": true
    },
    "include": ["./src/**/*"],
    "exclude": ["node_modules"]
  }
  
```

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

```
import apifyConfig from '@apify/eslint-config';

// eslint-disable-next-line import/no-default-export
export default [
    { ignores: ['**/dist'] }, // Ignores need to happen first
    ...(Array.isArray(apifyConfig) ? apifyConfig : [apifyConfig]),
    {
        languageOptions: {
            sourceType: 'module',

            parserOptions: {
                project: 'tsconfig.eslint.json',
            },
        },
    },
];

```

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

```typescript
#!/usr/bin/env node
/**
 * This script initializes and starts the MCP server for the Apify RAG Web Browser using the Stdio transport.
 *
 * Usage:
 *   node <script_name>
 *
 * Example:
 *   node index.js
 */
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

import { RagWebBrowserServer } from './server.js';

async function main() {
    const server = new RagWebBrowserServer();
    const transport = new StdioServerTransport();
    await server.connect(transport);
}

main().catch((error) => {
    console.error('Server error:', error); // eslint-disable-line no-console
    process.exit(1);
});

```

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

```yaml
# This workflow runs for every pull request to lint and test the proposed changes.

name: Check

on:
    pull_request:

    # Push to main will trigger code checks
    push:
        branches:
            - main
        tags-ignore:
            - "**" # Ignore all tags to prevent duplicate builds when tags are pushed.

jobs:
    lint_and_test:
        name: Lint
        runs-on: ubuntu-latest

        steps:
            -   uses: actions/checkout@v4
            -   name: Use Node.js 22
                uses: actions/setup-node@v4
                with:
                    node-version: 22
                    cache: 'npm'
                    cache-dependency-path: 'package-lock.json'
            -   name: Install Dependencies
                run: npm ci

            -   name: Lint
                run: npm run lint

```

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

```
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json');

const pkgJson = require(PKG_JSON_PATH); // eslint-disable-line import/no-dynamic-require

const PACKAGE_NAME = pkgJson.name;
const VERSION = pkgJson.version;

const nextVersion = getNextVersion(VERSION);
console.log(`before-deploy: Setting version to ${nextVersion}`); // eslint-disable-line no-console
pkgJson.version = nextVersion;

fs.writeFileSync(PKG_JSON_PATH, `${JSON.stringify(pkgJson, null, 2)}\n`);

function getNextVersion(version) {
    const versionString = execSync(`npm show ${PACKAGE_NAME} versions --json`, { encoding: 'utf8' });
    const versions = JSON.parse(versionString);

    if (versions.some((v) => v === VERSION)) {
        console.error(`before-deploy: A release with version ${VERSION} already exists. Please increment version accordingly.`); // eslint-disable-line no-console
        process.exit(1);
    }

    const prereleaseNumbers = versions
        .filter((v) => (v.startsWith(VERSION) && v.includes('-')))
        .map((v) => Number(v.match(/\.(\d+)$/)[1]));
    const lastPrereleaseNumber = Math.max(-1, ...prereleaseNumbers);
    return `${version}-beta.${lastPrereleaseNumber + 1}`;
}

```

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

```typescript
/**
 * @fileoverview Example how to call the RAG Web Browser Actor in a standby mode.
 * @module src/example_call_web_browser
 */

/* eslint-disable no-console */
import dotenv from 'dotenv';
import fetch from 'node-fetch';

dotenv.config({ path: '../.env' });

const QUERY = 'MCP Server for Anthropic';
const MAX_RESULTS = 1; // Limit the number of results to decrease response size
const ACTOR_BASE_URL = 'https://rag-web-browser.apify.actor/search'; // Base URL from OpenAPI schema

const { APIFY_TOKEN } = process.env;

if (!APIFY_TOKEN) {
    throw new Error('APIFY_TOKEN environment variable is not set.');
}

const queryParams = new URLSearchParams({
    query: QUERY,
    maxResults: MAX_RESULTS.toString(),
});

const headers = {
    Authorization: `Bearer ${APIFY_TOKEN}`,
};

// eslint-disable-next-line no-void
void (async () => {
    const url = `${ACTOR_BASE_URL}?${queryParams.toString()}`;
    console.info(`GET request to ${url}`);

    try {
        const response = await fetch(url, { method: 'GET', headers });

        if (!response.ok) {
            console.log(`Error: Failed to fetch data: ${response.statusText}`);
        }

        const responseBody = await response.json();
        console.info('Received response from RAG Web Browser:', responseBody);

        // Optional: Further process or display the response
        console.log('Response:', responseBody);
    } catch (error: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
        console.error('Error occurred:', error.message);
    }
})();

```

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

```json
{
  "name": "@apify/mcp-server-rag-web-browser",
  "version": "0.1.4",
  "type": "module",
  "description": "An MCP Server for RAG Web Browser Actor",
  "engines": {
    "node": ">=18.0.0"
  },
  "main": "dist/index.js",
  "bin": {
    "mcp-rag-web-browser": "./dist/index.js"
  },
  "files": [
    "dist",
    "LICENSE"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/apify/mcp-server-rag-web-browser"
  },
  "bugs": {
    "url": "https://github.com/apify/mcp-server-rag-web-browser/issues"
  },
  "homepage": "https://github.com/apify/mcp-server-rag-web-browser",
  "keywords": [
    "apify",
    "mcp",
    "server",
    "actors",
    "model context protocol",
    "rag",
    "web browser"
  ],
  "scripts": {
    "start": "npm run start:dev",
    "start:prod": "node dist/index.js",
    "start:dev": "tsx src/index.ts",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "build": "tsc",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.4",
    "express": "^4.21.2",
    "node-fetch": "^3.3.2",
    "zod": "^3.24.1",
    "zod-to-json-schema": "^3.24.1"
  },
  "devDependencies": {
    "@apify/eslint-config": "^0.5.0-beta.7",
    "@apify/eslint-config-ts": "^0.4.1",
    "@apify/tsconfig": "^0.1.0",
    "@types/express": "^5.0.0",
    "@types/node": "^20.11.24",
    "dotenv": "^16.4.7",
    "eslint": "^9.22.0",
    "tsx": "^4.6.2",
    "typescript": "^5.3.3",
    "typescript-eslint": "^8.18.2"
  }
}

```

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

```typescript
/* eslint-disable no-console */
/**
 * Connect to the MCP server using stdio transport and call a tool.
 * You need provide a path to MCP server and APIFY_TOKEN in .env file.
 */

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
import dotenv from 'dotenv';

dotenv.config({ path: '../.env' });

const { APIFY_TOKEN } = process.env;

if (!APIFY_TOKEN) {
    throw new Error('APIFY_TOKEN environment variable is not set.');
}

const SERVER_PATH = '../dist/index.js';

// Create server parameters for stdio connection
const transport = new StdioClientTransport({
    command: 'node', // Executable
    args: [
        SERVER_PATH,
    ],
    env: {
        'APIFY_TOKEN': APIFY_TOKEN
    }
});

// Create a new client instance
const client = new Client(
    { name: 'example-client', version: '1.0.0' },
    { capabilities: {} },
);

// Main function to run the example client
async function run() {
    try {
        // Connect to the MCP server
        await client.connect(transport);

        // List available tools
        const tools = await client.listTools();
        console.log('Available tools:', tools);

        // Call a tool
        console.log('Calling rag web browser ...');
        const result = await client.callTool(
            { name: 'search', arguments: { query: 'web browser for Anthropic' } },
            CallToolResultSchema,
        );
        console.log('Tool result:', JSON.stringify(result));
    } catch (error) {
        console.error('Error:', error);
    }
}

// Execute the main function
await run();

```

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

```markdown
# Changelog

All notable changes to this project will be documented in this file.

## [0.1.4](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.4) (2025-08-21)

### 🚀 Features

- 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)


## [0.1.3](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.3) (2025-03-17)

### 🐛 Bug Fixes

- 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)


## [0.1.1](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.1) (2025-03-17)

### 🚀 Features
- 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)
- 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)
- Change env variable name from APIFY_API_TOKEN to APIFY_TOKEN
- Add extra query parameters
- Remove unnecessary example clients (SSE and chat client)
- Create NPM package @apify/mcp-server-rag-web-browser
- Update readme

### 🐛 Bug Fixes

- 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)

## [0.1.0](https://github.com/apify/mcp-server-rag-web-browser/releases/tag/v0.1.0) (2025-03-17)

### 🚀 Features

- Initial release
- 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)

### 🐛 Bug Fixes

- 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
name: Create a pre-release

on:
    # Push to main will deploy a beta version
    push:
        branches:
            - main
        tags-ignore:
            - "**" # Ignore all tags to prevent duplicate builds when tags are pushed.

concurrency:
    group: release
    cancel-in-progress: false

jobs:
    release_metadata:
        if: "!startsWith(github.event.head_commit.message, 'docs') && !startsWith(github.event.head_commit.message, 'ci') && startsWith(github.repository, 'apify/')"
        name: Prepare release metadata
        runs-on: ubuntu-latest
        outputs:
            version_number: ${{ steps.release_metadata.outputs.version_number }}
            changelog: ${{ steps.release_metadata.outputs.changelog }}
        steps:
            -   uses: apify/workflows/git-cliff-release@main
                name: Prepare release metadata
                id: release_metadata
                with:
                    release_type: prerelease
                    existing_changelog_path: CHANGELOG.md

    wait_for_checks:
        name: Wait for code checks to pass
        runs-on: ubuntu-latest
        steps:
            -   uses: lewagon/[email protected]
                with:
                    ref: ${{ github.ref }}
                    repo-token: ${{ secrets.GITHUB_TOKEN }}
                    check-regexp: (Build & Test .*|Lint|Docs build)
                    wait-interval: 5

    update_changelog:
        needs: [ release_metadata, wait_for_checks ]
        name: Update changelog
        runs-on: ubuntu-latest
        outputs:
            changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }}

        steps:
            -   name: Checkout repository
                uses: actions/checkout@v4
                with:
                    token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}

            -   name: Use Node.js 22
                uses: actions/setup-node@v4
                with:
                    node-version: 22

            -   name: Update package version in package.json
                run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }}

            -   name: Update CHANGELOG.md
                uses: DamianReeves/write-file-action@master
                with:
                    path: CHANGELOG.md
                    write-mode: overwrite
                    contents: ${{ needs.release_metadata.outputs.changelog }}

            -   name: Commit changes
                id: commit
                uses: EndBug/add-and-commit@v9
                with:
                    author_name: Apify Release Bot
                    author_email: [email protected]
                    message: "chore(release): Update changelog and package version [skip ci]"

    publish_to_npm:
        name: Publish to NPM
        needs: [ release_metadata, wait_for_checks ]
        runs-on: ubuntu-latest
        steps:
            -   uses: actions/checkout@v4
                with:
                    ref: ${{ needs.update_changelog.changelog_commitish }}
            -   name: Use Node.js 22
                uses: actions/setup-node@v4
                with:
                    node-version: 22
                    cache: 'npm'
                    cache-dependency-path: 'package-lock.json'
            -   name: Install dependencies
                run: |
                    echo "access=public" >> .npmrc
                    echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
                    npm ci
            - # Check version consistency and increment pre-release version number for beta only.
                name: Bump pre-release version
                run: node ./.github/scripts/before-beta-release.cjs
            -   name: Build module
                run: npm run build
            -   name: Publish to NPM
                run: npm publish --tag beta

env:
    NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
    NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}

```

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

```yaml
name: Create a release

on:
    # Trigger a stable version release via GitHub's UI, with the ability to specify the type of release.
    workflow_dispatch:
        inputs:
            release_type:
                description: Release type
                required: true
                type: choice
                default: auto
                options:
                    - auto
                    - custom
                    - patch
                    - minor
                    - major
            custom_version:
                description: The custom version to bump to (only for "custom" type)
                required: false
                type: string
                default: ""

concurrency:
    group: release
    cancel-in-progress: false

jobs:
    release_metadata:
        name: Prepare release metadata
        runs-on: ubuntu-latest
        outputs:
            version_number: ${{ steps.release_metadata.outputs.version_number }}
            tag_name: ${{ steps.release_metadata.outputs.tag_name }}
            changelog: ${{ steps.release_metadata.outputs.changelog }}
            release_notes: ${{ steps.release_metadata.outputs.release_notes }}
        steps:
            -   uses: apify/workflows/git-cliff-release@main
                name: Prepare release metadata
                id: release_metadata
                with:
                    release_type: ${{ inputs.release_type }}
                    custom_version: ${{ inputs.custom_version }}
                    existing_changelog_path: CHANGELOG.md

    update_changelog:
        needs: [ release_metadata ]
        name: Update changelog
        runs-on: ubuntu-latest
        outputs:
            changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }}

        steps:
            -   name: Checkout repository
                uses: actions/checkout@v4
                with:
                    token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}

            -   name: Use Node.js 22
                uses: actions/setup-node@v4
                with:
                    node-version: 22

            -   name: Update package version in package.json
                run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }}

            -   name: Update CHANGELOG.md
                uses: DamianReeves/write-file-action@master
                with:
                    path: CHANGELOG.md
                    write-mode: overwrite
                    contents: ${{ needs.release_metadata.outputs.changelog }}

            -   name: Commit changes
                id: commit
                uses: EndBug/add-and-commit@v9
                with:
                    author_name: Apify Release Bot
                    author_email: [email protected]
                    message: "chore(release): Update changelog and package version [skip ci]"

    create_github_release:
        name: Create github release
        needs: [release_metadata, update_changelog]
        runs-on: ubuntu-latest
        env:
            GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        steps:
            -   name: Create release
                uses: softprops/action-gh-release@v2
                with:
                    tag_name: ${{ needs.release_metadata.outputs.tag_name }}
                    name: ${{ needs.release_metadata.outputs.version_number }}
                    target_commitish: ${{ needs.update_changelog.outputs.changelog_commitish }}
                    body: ${{ needs.release_metadata.outputs.release_notes }}

    publish_to_npm:
        name: Publish to NPM
        needs: [ update_changelog ]
        runs-on: ubuntu-latest
        steps:
            -   uses: actions/checkout@v4
                with:
                    ref: ${{ needs.update_changelog.changelog_commitish }}
            -   name: Use Node.js 22
                uses: actions/setup-node@v4
                with:
                    node-version: 22
                    cache: 'npm'
                    cache-dependency-path: 'package-lock.json'
            -   name: Install dependencies
                run: |
                    echo "access=public" >> .npmrc
                    echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
                    npm ci
            -   name: Build module
                run: npm run build
            -   name: Publish to NPM
                run: npm publish --tag latest

env:
    NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
    NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}

```

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

```typescript
#!/usr/bin/env node

/**
 * MCP server that allows to call the RAG Web Browser Actor
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import {
    CallToolRequestSchema,
    ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';


const { APIFY_TOKEN } = process.env;

const MAX_RESULTS = 1;
const TOOL_SEARCH = 'search';
const ACTOR_BASE_URL = 'https://rag-web-browser.apify.actor/search';

const WebBrowserArgsSchema = z.object({
    query: z.string()
        .describe('Enter Google Search keywords or a URL of a specific web page. The keywords might include the'
            + 'advanced search operators. Examples: "san francisco weather", "https://www.cnn.com", '
            + '"function calling site:openai.com"')
        .regex(/[^\s]+/, { message: "Search term or URL cannot be empty" }),
    maxResults: z.number().int().positive().min(1).max(100).default(MAX_RESULTS)
        .describe(
            'The maximum number of top organic Google Search results whose web pages will be extracted. '
            + 'If query is a URL, then this field is ignored and the Actor only fetches the specific web page.',
        ),
    scrapingTool: z.enum(['browser-playwright', 'raw-http'])
        .describe('Select a scraping tool for extracting the target web pages. '
        + 'The Browser tool is more powerful and can handle JavaScript heavy websites, while the '
        + 'Plain HTML tool can not handle JavaScript but is about two times faster.')
        .default('raw-http'),
    outputFormats: z.array(z.enum(['text', 'markdown', 'html']))
        .describe('Select one or more formats to which the target web pages will be extracted.')
        .default(['markdown']),
    requestTimeoutSecs: z.number().int().min(1).max(300).default(40)
        .describe('The maximum time in seconds available for the request, including querying Google Search '
            + 'and scraping the target web pages.'),
});

/**
 * Create an MCP server with a tool to call RAG Web Browser Actor
 */
export class RagWebBrowserServer {
    private server: Server;

    constructor() {
        this.server = new Server(
            {
                name: 'mcp-server-rag-web-browser',
                version: '0.1.0',
            },
            {
                capabilities: {
                    tools: {},
                },
            },
        );
        this.setupErrorHandling();
        this.setupToolHandlers();
    }

    private async callRagWebBrowser(args: z.infer<typeof WebBrowserArgsSchema>): Promise<string> {
        if (!APIFY_TOKEN) {
            throw new Error('APIFY_TOKEN is required but not set. '
                + 'Please set it in your environment variables or pass it as a command-line argument.');
        }

        const queryParams = new URLSearchParams({
            query: args.query,
            maxResults: args.maxResults.toString(),
            scrapingTool: args.scrapingTool,
        });

        // Add all other parameters if provided
        if (args.outputFormats) {
            args.outputFormats.forEach((format) => {
                queryParams.append('outputFormats', format);
            });
        }
        if (args.requestTimeoutSecs) {
            queryParams.append('requestTimeoutSecs', args.requestTimeoutSecs.toString());
        }

        const url = `${ACTOR_BASE_URL}?${queryParams.toString()}`;
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${APIFY_TOKEN}`,
            },
        });

        if (!response.ok) {
            throw new Error(`Failed to call RAG Web Browser: ${response.status} ${response.statusText}`);
        }

        const responseBody = await response.json();
        return JSON.stringify(responseBody);
    }

    private setupErrorHandling(): void {
        this.server.onerror = (error) => {
            console.error('[MCP Error]', error); // eslint-disable-line no-console
        };
        process.on('SIGINT', async () => {
            await this.server.close();
            process.exit(0);
        });
    }

    private setupToolHandlers(): void {
        this.server.setRequestHandler(ListToolsRequestSchema, async () => {
            return {
                tools: [
                    {
                        name: TOOL_SEARCH,
                        description: 'Search phrase or a URL at Google and return crawled web pages as text or Markdown. '
                            + 'Prefer HTTP raw client for speed and browser-playwright for reliability.',
                        inputSchema: zodToJsonSchema(WebBrowserArgsSchema),
                    },
                ],
            };
        });
        this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
            const { name, arguments: args } = request.params;
            switch (name) {
                case TOOL_SEARCH: {
                    try {
                        const parsed = WebBrowserArgsSchema.parse(args);
                        const content = await this.callRagWebBrowser(parsed);
                        return {
                            content: [{ type: 'text', text: content }],
                        };
                    } catch (error) {
                        console.error('[MCP Error]', error);
                        throw new Error(`Failed to call RAG Web Browser: ${error}`);
                    }
                }
                default: {
                    throw new Error(`Unknown tool: ${name}`);
                }
            }
        });
    }

    async connect(transport: Transport): Promise<void> {
        await this.server.connect(transport);
    }
}

```