Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Cf Worker

БесплатноНе проверен

A MCP server deployed on Cloudflare Workers with Durable Objects, exposing a suite of web-search and web-fetch tools over the Streamable HTTP transport

GitHubEmbed

Описание

A MCP server deployed on Cloudflare Workers with Durable Objects, exposing a suite of web-search and web-fetch tools over the Streamable HTTP transport

README

A Model Context Protocol (MCP) server deployed on Cloudflare Workers with Durable Objects, exposing a suite of web-search and web-fetch tools over the Streamable HTTP transport.

Live endpoint: https://your-worker.workers.dev/mcp


Tools

Tool Description
web_search Neural web search via Exa AI — returns titles, URLs, dates, and highlights
web_fetch_html Fetch a URL and return raw HTML
web_fetch_markdown Fetch a URL via Exa /contents and return clean extracted text; falls back to a direct fetch + local conversion if Exa is unavailable
web_fetch_json Fetch one or more URLs via Exa /contents and return structured JSON
web_fetch_txt Fetch a URL and return plain text (strips HTML tags)
web_github_search Search code across public GitHub repos via grep.app
web_github_readme Fetch any file (default: README.md) from a public GitHub repo
web_google_search Google search grounded answer via Gemini API with inline citations

Shared conventions

  • maxLength — every fetch tool takes one. Omit it for the tool's default; pass 0 for no limit. Truncated output ends with an explicit …[truncated: showing N of M chars] marker so the model knows it is looking at a fragment and can re-request with a larger bound.
  • onlyMainContent (web_fetch_txt, default true) — drops nav/header/footer/sidebar/forms and prefers the page's <main>/<article> body. Set false for the whole document.
  • Errors set isError: true so clients can distinguish a tool failure from a successful call that happened to return an error-shaped string.
  • web_fetch_json also returns structuredContent, so clients that understand it get the parsed object instead of re-parsing the text block.

Fetch safety

Tools that fetch a user-supplied URL (web_fetch_html, web_fetch_txt, and the web_fetch_markdown fallback) go through a guard first:

  • http/https onlyfile:, ftp:, javascript: and friends are refused.
  • Internal addresses blocked — loopback, private, link-local, and CGNAT ranges in both IPv4 and IPv6, plus localhost, *.internal and the cloud metadata host. This is why fetching http://localhost:3000 from a local wrangler dev session is refused; point the tool at a public URL instead.
  • Every redirect hop is re-checked — redirects are followed manually (max 5), so a public URL cannot bounce the Worker to 169.254.169.254.
  • 15-second timeout on every outbound request, including the Exa, Gemini and grep.app calls.

Requests to the fixed API endpoints keep the timeout but skip the URL guard, since their hosts are not user-controlled.


Requirements

Both API keys are per-tool, not server-wide — the worker boots and serves without them:

  • An Exa API key — needed by web_search and web_fetch_json; web_fetch_markdown degrades to local extraction instead
  • A Gemini API key — needed by web_google_search

The remaining six tools need no credentials at all.


Setup

1. Clone and install

git clone https://github.com/hypnguyen1209/cf-worker-mcp.git
cd cf-worker-mcp
npm install

2. Configure

Copy the example config:

cp wrangler.example.jsonc wrangler.jsonc

Set "name" in wrangler.jsonc (it becomes <name>.workers.dev). vars holds only non-secret values such as GEMINI_MODEL.

API keys are secrets, not vars — values in vars are committed to the repo and visible in the Cloudflare dashboard:

npx wrangler secret put EXA_API_KEY
npx wrangler secret put GEMINI_API_KEY

If you set your keys as plain-text variables in the dashboard, move them before your next deploy. wrangler deploy replaces the Worker's bindings with whatever the config declares, and this config declares only GEMINI_MODEL. Dashboard-set variables are wiped by that; secrets survive it. The failure is quiet — the deploy succeeds and reports no problem, and you find out when web_search starts answering Exa API error 401.

npx wrangler secret list                    # [] means nothing is set

If a deploy already removed them, the old values are still readable from the previous version, so you can put them back as secrets:

npx wrangler versions list
npx wrangler versions view <old-version-id>  # prints var values in full

Treat any key recovered this way as compromised and rotate it. It sat unencrypted in the version history, which anyone with dashboard read access can inspect, and deleting the old versions is not an option.

For wrangler dev, put the same names in an untracked .dev.vars (both it and wrangler.jsonc are already in .gitignore):

EXA_API_KEY=your-exa-key
GEMINI_API_KEY=your-gemini-key

Either key may be left empty — see Tests for what still works without them.

Optional: require an auth token

Without a token the endpoint is public — anyone who finds the URL can spend your Exa and Gemini quota. Set MCP_AUTH_TOKEN to gate /mcp and /sse:

node -e "console.log(crypto.randomUUID())"   # generate one
npx wrangler secret put MCP_AUTH_TOKEN

The token is accepted in either header, whichever your client can send:

Authorization: Bearer <token>
x-api-key: <token>

Authorization is the MCP-standard form and the one to prefer. x-api-key is there for clients that only let you attach a flat custom header. Sending both is fine — each is checked, so a stale Authorization doesn't veto a correct x-api-key. The Bearer prefix is required on Authorization; a bare token in that header is rejected.

Add the same name to .dev.vars to exercise the gate under wrangler dev. The comparison is constant-time and doesn't short-circuit across the two headers, so a rejected request gets a 401 with WWW-Authenticate: Bearer realm="mcp" and no hint about how far the token matched or which header came closer.

Leaving MCP_AUTH_TOKEN unset keeps the server open, so existing deployments keep working unchanged. GET / stays public either way and reports "authRequired": true when the gate is on.

Usage with MCP clients covers passing the token from Claude Code and Codex.

3. Generate types

npm run cf-typegen

4. Run locally

npm run dev
# MCP endpoint: http://localhost:8787/mcp

5. Deploy

npx wrangler secret list   # check this first — see the warning in step 2
npm run deploy
# MCP endpoint: https://<name>.workers.dev/mcp

If your Cloudflare login has more than one account, pin the target rather than letting Wrangler pick — it can't prompt you from a non-interactive shell:

CLOUDFLARE_ACCOUNT_ID=<account-id> npm run deploy

Then confirm the running build is the one you just pushed:

curl -s https://<name>.workers.dev/ | jq '.version, .authRequired'

Usage with MCP clients

The endpoint speaks Streamable HTTP at /mcp, which is what every current client wants. The legacy SSE transport at /sse stays available for older ones, but reach for it only when a client cannot do HTTP.

Two things to have ready:

  • the URL — https://<name>.workers.dev/mcp
  • the token, if you set MCP_AUTH_TOKEN. GET / tells you whether the gate is on: "authRequired": true. It goes in either Authorization: Bearer <token> or x-api-key: <token> — see Optional: require an auth token.

Claude Code

# Public worker
claude mcp add --transport http web https://your-worker.workers.dev/mcp

# Gated worker
claude mcp add --transport http web https://your-worker.workers.dev/mcp \
  --header "Authorization: Bearer your-token"

Options go before the server name. Add --scope user to make it available in every project instead of just the current one. Verify with claude mcp list or /mcp inside a session.

To share the server with a repo without committing the token, put it in .mcp.json and let Claude Code expand the variable at load time — ${VAR} expansion works in both url and headers:

{
  "mcpServers": {
    "web": {
      "type": "http",
      "url": "https://your-worker.workers.dev/mcp",
      "headers": { "Authorization": "Bearer ${MCP_AUTH_TOKEN}" }
    }
  }
}

Keep MCP_AUTH_TOKEN in your shell environment. If the variable is unset the entry still loads, but the literal ${MCP_AUTH_TOKEN} text goes out as the token and the worker answers 401; claude mcp list flags the missing variable, so check there before suspecting the deploy. The type field is not optional either — an entry with a url and no type is read as a stdio server and skipped.

--header "x-api-key: your-token" and "headers": { "x-api-key": "${MCP_AUTH_TOKEN}" } work identically if you prefer that header.

One gotcha specific to a gated worker: when an Authorization header is present and rejected, Claude Code reports a hard connection failure rather than falling back to OAuth. A wrong token therefore looks exactly like a broken server.

Codex CLI

# Public worker
codex mcp add web --url https://your-worker.workers.dev/mcp

# Gated worker — pass the variable NAME, not the token
codex mcp add web --url https://your-worker.workers.dev/mcp \
  --bearer-token-env-var MCP_AUTH_TOKEN

--bearer-token-env-var takes the name of an environment variable. Codex reads it at connect time and sends Authorization: Bearer <value>, so the variable has to be exported in the shell that launches Codex — a token pasted there directly will be looked up as a variable name and resolve to nothing.

codex mcp add writes to ~/.codex/config.toml. The equivalent hand-written entry, with url and no command selecting the HTTP transport:

[mcp_servers.web]
url = "https://your-worker.workers.dev/mcp"
bearer_token_env_var = "MCP_AUTH_TOKEN"

For the x-api-key form, swap bearer_token_env_var for env_http_headers, which also maps a header to a variable name rather than a value:

[mcp_servers.web]
url = "https://your-worker.workers.dev/mcp"
env_http_headers = { "x-api-key" = "MCP_AUTH_TOKEN" }

Codex's tool_timeout_sec default of 60 is already well clear of this worker, which caps every outbound request at 15s, so leave it alone unless you raise that cap too.

A project-scoped .codex/config.toml works too, but only in a trusted directory, and user config wins on conflict. Verify with codex mcp list (it prints a Status column) or /mcp in a session.

Anything else

Most other clients take the same shape as the .mcp.json block above — a type/url pair plus optional headers. Some spell the transport streamable-http instead of http; both name the same thing.


Health check

GET / returns a JSON discovery document:

{
  "name": "mcp-worker",
  "version": "1.3.0",
  "endpoint": "https://your-worker.workers.dev/mcp",
  "authRequired": false,
  "tools": ["web_search", "web_fetch_html", "..."]
}

Tests

npm test           # typecheck + tools/list token budget

scripts/tools-budget.mjs bundles the tool registrars, serialises the real tools/list payload and fails if it exceeds the token ceiling. Clients pay for that manifest in every conversation before the model does anything, so description growth is caught here rather than in review. It prints a per-tool breakdown showing where to trim when a new tool pushes the total over.

test-tools.mjs is a separate end-to-end smoke test that calls every tool on a live worker. It spends real Exa and Gemini quota, so it is not part of npm test:

MCP_URL=https://<name>.workers.dev/mcp node test-tools.mjs
MCP_AUTH_TOKEN=<token> MCP_URL=... node test-tools.mjs   # if the worker is gated
MCP_AUTH_HEADER=x-api-key MCP_AUTH_TOKEN=<token> MCP_URL=... node test-tools.mjs

MCP_AUTH_HEADER picks which of the two accepted headers carries the token, so the same suite covers both auth paths; it defaults to authorization.

It reports three states, and exits non-zero only on ❌:

Meaning
tool returned usable content
⚠️ tool succeeded but returned nothing usable — an empty-result string
transport error, or a result with isError: true

An upstream that refuses the request throws, so it lands in ❌ rather than ⚠️ — the run is red even though the worker itself is healthy. Read the reason before treating a ❌ as a regression: 429 Too Many Requests from grep.app is an upstream condition, not a worker bug.

Running it against wrangler dev

The suite works against a local worker too, which is the cheapest way to check that a dependency bump still boots:

# terminal 1
npm run dev

# terminal 2
MCP_URL=http://localhost:8787/mcp node test-tools.mjs
MCP_AUTH_TOKEN=<token> MCP_URL=http://localhost:8787/mcp node test-tools.mjs

MCP_AUTH_TOKEN in .dev.vars gates the local worker exactly like the deployed one, so running without the header is a quick way to confirm the gate still returns 401 rather than serving the tools.

Leaving EXA_API_KEY and GEMINI_API_KEY empty in .dev.vars is a useful run in itself — it isolates the three credential-dependent tools from everything else:

Empty-key result Tools
✅ unaffected web_fetch_html, web_fetch_txt, web_github_search, web_github_readme, plus the SSRF-guard case
✅ via fallback web_fetch_markdown — Exa 401s and the output carries the Extracted locally note, which is what proves the fallback is wired up
❌ needs a key web_search, web_fetch_json (Exa), web_google_search (Gemini)

Project structure

src/
  index.ts            # Worker fetch handler + health endpoint
  server.ts           # MyMCP Durable Object (registers tools)
  types.ts            # Env, ToolContext
  lib/                # Shared helpers
    auth.ts           #   Optional Bearer gate (constant-time compare)
    fetch.ts          #   Timeouts, redirect re-validation, UA constants
    html.ts           #   HTML → text / Markdown, boilerplate stripping
    mcp.ts            #   Tool response, error, truncation helpers
    url.ts            #   SSRF guard (scheme + private-IP blocklist)
  services/           # External API clients
    exa.ts            #   Exa AI search + contents
    gemini.ts         #   Gemini w/ Google Search grounding
    grep-app.ts       #   grep.app code search + snippet parsing
  tools/              # One file per MCP tool
    index.ts          #   registerAllTools() + TOOL_NAMES
    web-search.ts web-fetch-html.ts web-fetch-markdown.ts
    web-fetch-json.ts web-fetch-txt.ts web-github-search.ts
    web-github-readme.ts web-google-search.ts
scripts/
  tools-budget.mjs  # tools/list token-budget regression test
tsconfig.json       # Workers types + strict mode
wrangler.example.jsonc  # Template to commit
wrangler.jsonc      # Your config (gitignored)
.dev.vars           # Local secrets for wrangler dev (gitignored)
test-tools.mjs      # End-to-end smoke test (node test-tools.mjs)

License

MIT

from github.com/hypnguyen1209/cf-worker-mcp

Установка Cf Worker

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/hypnguyen1209/cf-worker-mcp

FAQ

Cf Worker MCP бесплатный?

Да, Cf Worker MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Cf Worker?

Нет, Cf Worker работает без API-ключей и переменных окружения.

Cf Worker — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Cf Worker в Claude Desktop, Claude Code или Cursor?

Открой Cf Worker на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Fetch

Web content fetching and conversion for efficient LLM usage.

автор: Community

Roblox Studio

Enables AI coding tools to control Roblox Studio for workspace exploration, instance manipulation, and script management. It provides tools for playtesting, sce

paralovавтор: paralov

Opencode Omniroute Plugin

OpenCode plugin for the OmniRoute AI Gateway. Drives dynamic model discovery, /connect auth flow, and multi-instance OmniRoute providers via the official @openc

GitHub Actionsавтор: GitHub Actions

AWS KB Retrieval

Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.

modelcontextprotocolавтор: modelcontextprotocol

Spring AI MCP Server

Provides auto-configuration for setting up an MCP server in Spring Boot applications.

автор: Community

llm-analysis-assistant

A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also

xuzexin-hzавтор: xuzexin-hz

MCP-Agent

A simple, composable framework to build agents using Model Context Protocol by [LastMile AI](https://www.lastmileai.dev)

lastmile-aiавтор: lastmile-ai

Spring AI MCP Client

Provides auto-configuration for MCP client functionality in Spring Boot applications.

автор: Community

mcp.natoma.ai

A Hosted MCP Platform to discover, install, manage and deploy MCP servers by [Natoma Labs](https://www.natoma.ai)

автор: Community

MCPHub

Website to list high quality MCP servers and reviews by real users. Also provide online chatbot for popular LLM models with MCP server support.

автор: Community

Compare Cf Worker with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории ai