Skip to main content
Glama

cf-control-mcp

Private infrastructure control plane for MCP clients

Cloudflare Workers · OAuth + PKCE · Cloudflare · Hugging Face · ProxyHarvest · Internet Intelligence · Sandboxed Execution

Version Cloudflare Workers MCP OAuth TypeScript

A private remote MCP server that turns a Cloudflare Worker into an authenticated control surface for infrastructure, provider APIs, web access, and controlled code execution.

Production MCP · Source · OAuth Wrapper · Plugin · Verification

Developed by DreamWorker
THE FUTURE IS CREATED TODAY


✦ What this project is

cf-control-mcp is a private, owner-approved remote MCP server deployed on Cloudflare Workers. It exposes a single MCP endpoint that can inspect and manage Cloudflare resources, work with Hugging Face repositories, inspect ProxyHarvest gateway reachability, fetch/search the public web, run short-lived code in Piston, and dispatch real ephemeral Ubuntu jobs through GitHub Actions.

The server is intentionally stateless at the MCP transport layer: one authenticated HTTP request produces one JSON-RPC response, without requiring Durable Objects or a long-lived SSE session.

MCP Client
   │
   │  OAuth + PKCE  /  legacy bearer
   ▼
Cloudflare Worker ───────────────► Cloudflare API
   │                              Hugging Face Hub API
   │                              ProxyHarvest Gateway
   │                              Public Web
   │                              Piston Sandbox
   └─────────────────────────────► GitHub Actions Ubuntu Runner

Related MCP server: Remote MCP Server on Cloudflare

✦ Capability map

Surface

What it provides

Representative tools

☁️ Cloudflare

Zones, DNS, cache, Workers, routes, KV, token verification, direct API passthrough

cf_list_zones, cf_list_dns_records, cf_purge_cache, cf_list_workers, cf_deploy_worker_module, cf_api_request

🤗 Hugging Face

Identity, model search, repo metadata, file commits/deletes, generic Hub API access

hf_whoami, hf_search_models, hf_repo_info, hf_commit_file, hf_api_request

🌐 Internet Intelligence

Unified/multi-provider search, news & image search, hardened fetch, browser rendering, markdown/extract/links, bounded crawl, evidence-based deep research

web_search, web_search_multi, web_news_search, web_image_search, web_fetch, web_render, web_markdown, web_extract, web_links, web_snapshot, web_crawl, web_deep_research

⚙️ Execution

Fast Piston snippets and full ephemeral GitHub Actions Ubuntu runners

run_code, list_code_runtimes, gh_run_code, gh_get_run_result

🛰️ ProxyHarvest

Gateway health, source reachability, TCP/TLS transport reachability

proxyharvest_gateway_health, proxyharvest_source_check, proxyharvest_transport_probe

🔐 Auth

OAuth discovery, DCR, PKCE S256, owner approval, access/refresh tokens, legacy bearer

/.well-known/*, /register, /authorize, /token, /mcp

Boundary: ProxyHarvest Cloudflare checks are reachability signals only. They are never tunnel/protocol/WireGuard verification. Real VERIFIED status remains exclusive to the Local Real Test Bridge using sing-box + curl.


✦ Architecture

flowchart LR
    C["MCP Client<br/>ChatGPT · Claude · Codex · Desktop"]
    O["OAuth 2.1-style flow<br/>PKCE S256 + Owner Approval"]
    L["Legacy Bearer Path"]
    W["Cloudflare Worker<br/>cf-control-mcp"]

    CF["Cloudflare API v4"]
    HF["Hugging Face Hub"]
    PH["ProxyHarvest Gateway"]
    WEB["Public Web"]
    P["Piston Sandbox"]
    GH["GitHub Actions<br/>Ephemeral Ubuntu"]

    C --> O --> W
    C --> L --> W
    W --> CF
    W --> HF
    W --> PH
    W --> WEB
    W --> P
    W --> GH

Design principles

  • Private by default — /mcp requires an accepted OAuth access token or the legacy owner bearer token.

  • Provider secrets stay server-side — Cloudflare, Hugging Face, and GitHub credentials remain Worker secrets.

  • Read first, write deliberately — focused destructive tools expose destructive annotations and, where implemented, require explicit confirmation flags.

  • No fake verification — reachability, deployment, and execution claims are returned from the actual provider/tool path.

  • Stateless transport — simple Streamable HTTP JSON-RPC without per-client server sessions.


✦ Authentication & OAuth

There are two credential layers:

Layer

Credential

Purpose

Client → MCP Worker

OAuth access token or MCP_AUTH_TOKEN

Protects access to the MCP control surface

MCP Worker → Providers

CLOUDFLARE_API_TOKEN, optional HUGGINGFACE_TOKEN, optional GITHUB_PAT

Authenticates server-side provider calls

The OAuth wrapper supports:

  • protected-resource discovery

  • authorization-server discovery

  • Dynamic Client Registration

  • Authorization Code flow

  • PKCE with S256

  • explicit owner approval page

  • access tokens

  • refresh tokens through offline_access

  • OAuth resource/audience binding

  • legacy owner-token compatibility

OAuth endpoints

Endpoint

Purpose

/.well-known/oauth-protected-resource

Protected-resource metadata

/.well-known/oauth-authorization-server

Authorization-server metadata

/register

Dynamic client registration

/authorize

PKCE authorization + owner approval

/token

Authorization-code / refresh-token exchange

/mcp

Streamable HTTP MCP endpoint

Current authorization behavior

After the owner explicitly approves an OAuth client, the current implementation exposes the same owner-approved tool catalog as the legacy bearer path. This includes tools capable of writes and destructive operations.

The advertised OAuth scopes are currently:

mcp:read
offline_access

offline_access enables refresh tokens. The current implementation does not use separate fine-grained OAuth scopes to hide individual write-capable tools after owner approval.

Generic passthrough tools such as cf_api_request and hf_api_request should be treated as privileged interfaces because their effective power follows the underlying provider token permissions.

Emergency revocation

Rotating MCP_AUTH_TOKEN invalidates the stateless client-side authorization artifacts derived from it, including registered client IDs, authorization codes, access tokens, refresh tokens, and the legacy bearer credential.


✦ Quick start

1. Install

npm install
wrangler login

2. Configure required Worker secrets

openssl rand -hex 32
wrangler secret put MCP_AUTH_TOKEN
wrangler secret put CLOUDFLARE_API_TOKEN
wrangler secret put CLOUDFLARE_ACCOUNT_ID

3. Optional provider integrations

# Hugging Face control tools
wrangler secret put HUGGINGFACE_TOKEN

# GitHub Actions real-runner execution
wrangler secret put GITHUB_PAT

# Optional; defaults to nimazasinich/cf-control-mcp
wrangler secret put GITHUB_REPO

# Internet Intelligence search providers (all optional)
wrangler secret put BRAVE_SEARCH_API_KEY
wrangler secret put TAVILY_API_KEY
wrangler secret put EXA_API_KEY

4. Deploy

npm run deploy

Production endpoint:

https://cf-control-mcp.amin-chinisaz-edu.workers.dev/mcp

✦ Connecting an MCP client

The repository includes a direct MCP configuration at:

plugins/cf-control/.mcp.json

It points to the production endpoint:

{
  "mcpServers": {
    "cf_control": {
      "type": "http",
      "url": "https://cf-control-mcp.amin-chinisaz-edu.workers.dev/mcp"
    }
  }
}

OAuth-capable clients can discover the authorization server from the 401 challenge and the .well-known metadata, dynamically register, complete PKCE, and continue with access/refresh tokens after owner approval.

Trusted legacy/desktop clients may use the owner bearer path directly:

curl -X POST \
  https://cf-control-mcp.amin-chinisaz-edu.workers.dev/mcp \
  -H "Authorization: Bearer <MCP_AUTH_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

✦ Cloudflare control surface

Focused operations

Tool

Purpose

Type

cf_verify_api_token

Verify the configured Cloudflare token

Read

cf_list_zones

List/filter zones

Read

cf_list_dns_records

Inspect DNS records

Read

cf_create_dns_record

Create DNS record

Write

cf_delete_dns_record

Delete DNS record

Destructive

cf_purge_cache

Purge selected URLs or entire zone cache

Destructive

cf_list_workers

List deployed Worker scripts

Read

cf_get_worker_metadata

Inspect Worker/service metadata

Read

cf_get_workers_subdomain

Resolve the workers.dev subdomain

Read

cf_list_worker_routes

Inspect Worker routes for a zone

Read

cf_deploy_worker_module

Deploy a single-module ES Worker

Destructive / Write

cf_delete_worker

Delete Worker script

Destructive

cf_kv_list_namespaces

List KV namespaces

Read

cf_kv_get_value

Read KV value

Read

cf_kv_put_value

Write KV value

Write

cf_api_request

Generic Cloudflare API v4 passthrough

Privileged

cf_deploy_worker_module sends source directly to Cloudflare, does not persist that source inside the MCP Worker, validates script/module names, caps source size, and requires confirm_destructive=true.

cf_api_request exists for Cloudflare APIs that do not yet have focused tools, including areas such as zone settings, SSL/TLS, WAF/firewall, Access, R2, D1, Pages, Stream, Images, Load Balancing, and other account/zone endpoints allowed by the configured Cloudflare token.


✦ Hugging Face control surface

Enable with:

wrangler secret put HUGGINGFACE_TOKEN

Tool

Purpose

hf_whoami

Verify token and return account metadata

hf_search_models

Search public or author-filtered models

hf_repo_info

Read model/dataset/space metadata

hf_list_repo_files

List repo tree at a revision

hf_create_repo

Create model/dataset/space repo

hf_delete_repo

Delete a repo

hf_commit_file

Create/update one non-LFS file through the Commit API

hf_delete_file

Delete a repo file through the Commit API

hf_api_request

Generic Hugging Face Hub API passthrough

If HUGGINGFACE_TOKEN is not configured, hf_* tools return an explicit configuration error instead of silently falling back.


✦ Internet Intelligence (v1.6.0)

Version 1.6.0 turns the Worker into a private internet-capable MCP server. Twelve tools cover search, reading, rendering, link discovery, bounded crawling, and evidence-based research — all behind the same owner-approved auth, with the same server-side secret model.

Search providers

Search is provider-agnostic. Results from every provider are normalized to a single shape ({title, url, snippet, provider, published_at?, score?}) before they reach the client.

Provider

Secret

Strengths

Configured when

Brave

BRAVE_SEARCH_API_KEY

General web, news, images

secret present

Tavily

TAVILY_API_KEY

Recent/news/research queries

secret present

Exa

EXA_API_KEY

Semantic/neural retrieval

secret present

DuckDuckGo

(none)

Keyless best-effort fallback

always

All three paid keys are optional. The server starts and runs with zero, one, two, or all three present. web_search with provider="auto" picks the best configured provider and falls back to the next only on genuine failure; with no paid keys it still answers via the keyless DuckDuckGo fallback. A provider-specific request (e.g. provider="brave") returns a CONFIGURATION_ERROR when that provider's secret is absent — it never silently degrades.

# All optional — enable the providers you want
wrangler secret put BRAVE_SEARCH_API_KEY
wrangler secret put TAVILY_API_KEY
wrangler secret put EXA_API_KEY

Tools

Tool

Purpose

Type

web_search

Unified search; provider=auto|brave|tavily|exa|ddg with auto-fallback

Read

web_search_multi

Query several providers concurrently, merge + dedupe, per-provider errors isolated

Read

web_news_search

Recency-oriented search (Tavily → Brave → Exa → DDG)

Read

web_image_search

Image metadata search (Brave); no image bytes downloaded

Read

web_fetch

Hardened public fetch: GET/HEAD/POST/PUT/PATCH/DELETE, bounded body, redirect re-validation

Read/Write*

web_render

Cloudflare Browser Rendering of JS-heavy public pages

Read

web_markdown

Clean Markdown of a page (Browser Rendering, with fetch fallback)

Read

web_extract

CSS-selector content extraction (scrape endpoint, with fetch fallback)

Read

web_links

Absolute-resolved, deduped outbound/internal links

Read

web_snapshot

One-page inspection: final URL, status, title, markdown, text, optional links

Read

web_crawl

Strictly bounded multi-page crawl (default 10 / max 50 pages, max depth 5); robots.txt-aware by default

Read

web_deep_research

Query expansion → multi-provider search → fetch → evidence aggregation

Read

*web_fetch is deliberately not annotated readOnlyHint because it can issue POST/PUT/PATCH/DELETE. Every other web tool is readOnlyHint: true. All web tools are openWorldHint: true.

SSRF hardening

Every URL-taking tool validates its target before and after each redirect hop, so a public-looking URL cannot 30x-redirect into a private target. The guard rejects:

  • localhost, loopback (127.0.0.0/8), and the unspecified address (0.0.0.0)

  • RFC1918 private ranges (10/8, 172.16/12, 192.168/16), carrier-grade NAT (100.64/10)

  • link-local (169.254/16) and cloud metadata endpoints (e.g. 169.254.169.254, metadata.google.internal)

  • obfuscated IPv4 (decimal / octal / hex / short) forms of the above

  • IPv6 loopback (::1), unique-local (fc00::/7), link-local (fe80::/10), and IPv4-mapped equivalents

  • *.internal, *.local, and other internal hostname patterns

The robots.txt fetch used by the crawler goes through this same hardened path — it is never a bypass.

HTML processing layer

web_markdown, web_extract, web_links, web_snapshot, web_crawl, and web_deep_research share a single, dependency-free HTML layer (src/internet/html.ts) built on the Workers-native HTMLRewriter. There is no second, ad-hoc parser. It streams the markup rather than building a DOM, so it stays light and predictable:

  • htmlToText — page title + readable text, with <script>/<style>/<noscript> removed

  • htmlToMarkdown — headings, paragraphs, list items, blockquotes, and preformatted blocks, with a nesting-aware block stack so inner blocks don't clobber outer ones

  • extractLinks — href/src resolved to absolute URLs, normalized and deduped, with an optional same-origin filter

  • extractBySelectors — per-selector text extraction that returns exactly what the DOM contained (no model-generated fields)

Output size limits are measured in actual UTF-8 bytes (via TextEncoder), not JavaScript character count, and truncation never splits a multi-byte character.

Robots layer

web_crawl and web_deep_research are polite by default. Before fetching a page they consult the origin's /robots.txt through the robots layer (src/internet/robots.ts):

  • a conservative subset parser — User-agent, Allow, Disallow, Crawl-delay — with longest-prefix rule matching and simple * wildcard support (this is not a full RFC 9309 implementation)

  • one fetch per origin, cached in memory (10-minute TTL); it does not re-fetch /robots.txt for every page

  • crawler identity is cf-control-mcp/1.6 — it does not impersonate Chrome, Googlebot, or Bingbot, and robots matching uses that same identity

  • pages disallowed by robots are reported with skippedReason: "ROBOTS_DENIED" and status: null — a policy decision, not a network failure

  • a site-declared Crawl-delay is honored but clamped to MAX_ROBOTS_CRAWL_DELAY_SECONDS (10 s); an origin asking for more is dropped with ROBOTS_CRAWL_DELAY_EXCEEDED so execution stays bounded

  • failure policy: a 2xx robots.txt is parsed and applied; a 404/non-2xx is treated as "allow all"; a timeout or error never crashes the crawl. The result reports whether a policy was actually fetched (robotsFetched) — it never pretends to have evaluated one it couldn't retrieve

Set respect_robots: false on web_crawl/web_deep_research to disable this gating. web_fetch and web_render are single-target tools and do not consult robots.txt.

Limits & error model

Results are capped (search ≤ 50), fetch bodies bounded (default 200 KB, max 1 MB), renders time-boxed (default 30 s, max 60 s), crawls bounded (≤ 10 default / ≤ 50 hard pages), and research bounded by source count, total bytes, and duration. Failures map to a structured code — CONFIGURATION_ERROR, PROVIDER_ERROR, RATE_LIMITED, TIMEOUT, BLOCKED_TARGET, INVALID_ARGUMENT, NETWORK_ERROR, RENDER_ERROR — and provider API keys are never included in error text.

web_deep_research deliberately returns structured evidence (ranked sources plus extracted supporting passages), not an invented narrative or conclusion; synthesis is left to the calling model.

web_render, web_markdown, and web_extract use Cloudflare Browser Rendering via the existing CLOUDFLARE_API_TOKEN — no new secret. If that token lacks the Browser Rendering permission, those tools return a clear RENDER_ERROR and (where possible) fall back to a plain fetch.


✦ Execution tools

Fast sandbox: Piston

run_code sends short snippets to the public Piston service and returns execution output. Use list_code_runtimes to discover currently available runtime versions.

This path is:

  • ephemeral

  • stateless

  • external to the Cloudflare account

  • not suitable for secrets or private data

Real runner: GitHub Actions

gh_run_code dispatches .github/workflows/mcp-exec.yml and returns a run_key.

gh_get_run_result polls that key until the Actions run is visible/completed, then returns status and job logs.

The runner is a real ephemeral Ubuntu VM with internet access and can run optional setup commands such as pip install, npm install, or apt operations within the workflow’s limits.

Required Worker secret:

wrangler secret put GITHUB_PAT

The token should be scoped to the target repository with the permissions required by the workflow dispatch/read path.


✦ ProxyHarvest boundary

The three ProxyHarvest tools are intentionally read-only:

Tool

Meaning

proxyharvest_gateway_health

Gateway / edge / optional AI-provider health

proxyharvest_source_check

Public source reachability

proxyharvest_transport_probe

TCP/TLS transport reachability

They explicitly return classifications that distinguish gateway/source/transport reachability from real proxy verification.

Reachable ≠ Verified.


✦ Verification

Local static checks

npm ci
npx tsc --noEmit
python3 -m py_compile scripts/oauth_smoke.py scripts/apex_cf_bridge.py

Live OAuth/MCP smoke test

The deployment workflow executes scripts/oauth_smoke.py against the live Worker. The current smoke test validates:

  • protected-resource discovery

  • authorization-server discovery

  • Dynamic Client Registration

  • PKCE S256

  • owner approval UI

  • authorization-code exchange

  • access + refresh tokens

  • OAuth tools/list

  • presence of the owner-approved write-capable catalog

  • a harmless GET /zones through cf_api_request using OAuth

  • refresh-token grant

  • legacy bearer compatibility

  • optional Hugging Face live check

  • unauthenticated 401 + WWW-Authenticate resource metadata

The smoke test does not print the owner secret or issued OAuth tokens.


✦ Repository layout

.
├── src/
│   ├── index.ts              # MCP server + tool handlers
│   ├── oauth-worker.ts       # OAuth discovery, PKCE, tokens, MCP wrapper
│   └── internet/             # v1.6 Internet Intelligence layer
│       ├── types.ts          # normalized result + structured error model
│       ├── util.ts           # limits, URL normalize/dedupe, bounded fetch
│       ├── ssrf.ts           # SSRF guard + redirect-revalidating safeFetch
│       ├── fetch.ts          # hardened fetch / fetchText + bounded stream reader
│       ├── html.ts           # shared HTMLRewriter text/markdown/links/extract
│       ├── robots.ts         # robots.txt policy layer (crawl politeness)
│       ├── browser.ts        # Cloudflare Browser Rendering client
│       ├── crawl.ts          # bounded, robots-aware crawl + snapshot
│       ├── research.ts       # evidence-based deep research pipeline
│       ├── search-router.ts  # provider selection, fallback, multi-search
│       ├── providers/        # brave / tavily / exa / ddg adapters
│       └── tools.ts          # the 12 web_* MCP tool definitions
├── scripts/
│   └── oauth_smoke.py        # live end-to-end OAuth/MCP verification
├── plugins/cf-control/
│   ├── .mcp.json             # remote MCP connection descriptor
│   └── skills/               # OpenAI/Codex skill package
├── .github/workflows/
│   ├── ci.yml                # static verification
│   ├── deploy.yml            # deploy + live smoke verification
│   └── mcp-exec.yml          # ephemeral real-runner execution backend
├── assets/
│   └── dreamworker-logo.webp # DreamWorker developer brand
├── wrangler.jsonc
├── package.json
└── README.md

✦ Operational notes

  • Keep all provider credentials in Worker/GitHub secret stores, never in source control.

  • Scope CLOUDFLARE_API_TOKEN to the minimum provider permissions needed for the enabled tools.

  • Treat generic API passthroughs as privileged interfaces.

  • Treat gh_run_code as intentional arbitrary code execution on disposable Actions runners.

  • Treat Piston as a public third-party sandbox; never send secrets through it.

  • Verify state after writes using focused read tools when practical.

  • Workers observability remains enabled through wrangler.jsonc.


✦ Roadmap direction

The current 1.6.0 architecture behaves as a multi-provider infrastructure and internet-intelligence MCP gateway. Natural next upgrades include:

  • fine-grained OAuth scopes per capability family

  • schema-first argument validation

  • provider-aware pagination helpers

  • rate limiting / abuse controls

  • structured audit events

  • safer generic passthrough policies

  • richer MCP resources/prompts alongside tools

  • explicit capability discovery and health reporting

  • DNS-pinned SSRF protection once the Workers runtime exposes resolved IPs


Built for operators who want real control, not simulated success.

DreamWorker
THE FUTURE IS CREATED TODAY

cf-control-mcp · private remote infrastructure tooling through MCP

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables deploying a Model Context Protocol (MCP) server on Cloudflare Workers with built-in OAuth authentication. It allows local clients like Claude Desktop to securely connect to and use remote tools through an HTTP/SSE transport.
    -
  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables deploying and connecting to MCP servers on Cloudflare Workers with OAuth login, allowing remote access to tools via MCP clients like Claude Desktop.
    529 npm
    MIT