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]()
}

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

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"

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

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"]
    }
  }
}
← Local MCP Next: Stock Analyzer →