Examples Remote MCP — Cloudflare docs search
MCP example

Remote MCP — Cloudflare docs search

A two-vertex graph: a context-val op lifts the user's query, and a single MCPCallOp over streamable HTTP calls Cloudflare's search_cloudflare_documentation tool. No subprocess, no API keys, no per-server SDK.

What this example teaches

  • Defining a concrete MCPCallOp[In, Out] for a specific remote tool with typed In / Out
  • transport="http" against a streamable-HTTP MCP server with init_timeout_ms and call_timeout_ms
  • How a headers param injects Authorization or other static headers for authenticated remote MCP servers
  • The minimal end-to-end shape of an MCP workflow: one context-val vertex and one tool-call vertex
View source on GitHub
The workflow

Two vertices

ContextValOp
SearchInput from --query
MCPCallOp
transport=http · search_cloudflare_documentation
Result
string
Implementation

Walkthrough

1. Pin the tool's wire shape with a typed concrete op

Embed library.MCPCallOp[In, Out] in a named struct so each tool gets a registered op type. The SearchInput JSON tag aligns with the Cloudflare tool's input schema; Out is just string — the default dispatch returns the concatenated text content.

main.go
type SearchInput struct {
    Query string `json:"query"`
}

type MCPCloudflareDocsSearchOp struct {
    library.MCPCallOp[SearchInput, string]
}

func init() {
    operator.RegisterOpFactory("search_input_const",
        builtin.ContextValFactory[SearchInput](searchInputKey{}))
    operator.RegisterOp[MCPCloudflareDocsSearchOp]()
}
main.py
from pydantic import BaseModel

class SearchInput(BaseModel):
    query: str

class MCPCloudflareDocsSearchOp(library.MCPCallOp[SearchInput, str]):
    pass

operator.register_op_factory("search_input_const",
    builtin.ContextValFactory(SearchInput, searchInputKey()))
operator.register_op(MCPCloudflareDocsSearchOp)
index.ts
interface SearchInput {
    query: string;
}

class MCPCloudflareDocsSearchOp extends library.MCPCallOp<SearchInput, string> {}

operator.registerOpFactory("search_input_const",
    builtin.ContextValFactory<SearchInput>(searchInputKey));
operator.registerOp(MCPCloudflareDocsSearchOp);

2. Wire the HTTP transport in buildGraph

Set transport=http and point url at the MCP endpoint. init_timeout_ms covers the protocol handshake; call_timeout_ms covers the actual tool call. For private endpoints, add a Bearer token (or any other static header) via the headers param.

main.go — buildGraph()
cfParams := map[string]string{
    "transport":       "http",
    "url":             "https://docs.mcp.cloudflare.com/mcp",
    "tool_name":       "search_cloudflare_documentation",
    "init_timeout_ms": "30000",
    "call_timeout_ms": "60000",
    "max_retries":     "2",
    // For authenticated remote MCP servers:
    // "headers": "Authorization=Bearer ${TOKEN}",
}

graph.NewBuilder("mcp_cloudflare_docs_search").
    Vertex("search_input").Op("search_input_const").
        Output("Result", "search_input").
    Vertex("cf_search").Op("MCPCloudflareDocsSearchOp").
        Params(cfParams).
        Input("Input", "search_input").
        Output("Result", "search_results").
    Build()
main.py — buildGraph()
cf_params = {
    "transport":       "http",
    "url":             "https://docs.mcp.cloudflare.com/mcp",
    "tool_name":       "search_cloudflare_documentation",
    "init_timeout_ms": "30000",
    "call_timeout_ms": "60000",
    "max_retries":     "2",
    # For authenticated remote MCP servers:
    # "headers": "Authorization=Bearer ${TOKEN}",
}

graph.new_builder("mcp_cloudflare_docs_search") \
    .vertex("search_input").op("search_input_const") \
        .output("Result", "search_input") \
    .vertex("cf_search").op("MCPCloudflareDocsSearchOp") \
        .params(cf_params) \
        .input("Input", "search_input") \
        .output("Result", "search_results") \
    .build()
index.ts — buildGraph()
const cfParams = {
    transport:       "http",
    url:             "https://docs.mcp.cloudflare.com/mcp",
    tool_name:       "search_cloudflare_documentation",
    init_timeout_ms: "30000",
    call_timeout_ms: "60000",
    max_retries:     "2",
    // For authenticated remote MCP servers:
    // "headers": "Authorization=Bearer ${TOKEN}",
};

graph.newBuilder("mcp_cloudflare_docs_search")
    .vertex("search_input").op("search_input_const")
        .output("Result", "search_input")
    .vertex("cf_search").op("MCPCloudflareDocsSearchOp")
        .params(cfParams)
        .input("Input", "search_input")
        .output("Result", "search_results")
    .build();

Run it

Just --query. The remote MCP server is public, so no API keys are required.

shell
go run ./examples/remote-mcp-server --query "workers durable objects"
shell
python examples/remote_mcp_server/main.py --query "workers durable objects"
shell
npx tsx examples/remote_mcp_server/index.ts --query "workers durable objects"

Run it as an MCP server

The same binary is dual-mode. Pass --mcp and instead of running once and exiting it speaks the Model Context Protocol over stdin/stdout, exposing the workflow as a single MCP tool, search_cloudflare_docs. The result is a local stdio MCP server that proxies the public remote (HTTP) Cloudflare docs server — a thin, typed front door an agent can register without dealing with the streamable-HTTP transport itself.

shell
# Speak MCP over stdin/stdout instead of running once
go run ./examples/remote-mcp-server --mcp
shell
# Speak MCP over stdin/stdout instead of running once
python examples/remote_mcp_server/main.py --mcp
shell
# Speak MCP over stdin/stdout instead of running once
npx tsx examples/remote_mcp_server/index.ts --mcp

Register it with any MCP client by pointing the client at that command:

.mcp.json
{
  "mcpServers": {
    "remote-mcp-server": {
      "command": "go",
      "args": ["run", "./examples/remote-mcp-server", "--mcp"]
    }
  }
}
.mcp.json
{
  "mcpServers": {
    "remote-mcp-server": {
      "command": "python",
      "args": ["examples/remote_mcp_server/main.py", "--mcp"]
    }
  }
}
.mcp.json
{
  "mcpServers": {
    "remote-mcp-server": {
      "command": "npx",
      "args": ["tsx", "examples/remote_mcp_server/index.ts", "--mcp"]
    }
  }
}
← Local MCP Next: Stock Analyzer →