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
MCPCallOp[In, Out] for a specific remote tool with typed In / Outtransport="http" against a streamable-HTTP MCP server with init_timeout_ms and call_timeout_msheaders param injects Authorization or other static headers for authenticated remote MCP serversEmbed 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.
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]()
}
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)
interface SearchInput {
query: string;
}
class MCPCloudflareDocsSearchOp extends library.MCPCallOp<SearchInput, string> {}
operator.registerOpFactory("search_input_const",
builtin.ContextValFactory<SearchInput>(searchInputKey));
operator.registerOp(MCPCloudflareDocsSearchOp);
buildGraphSet 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.
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()
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()
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();
Just --query. The remote MCP server is public,
so no API keys are required.
go run ./examples/remote-mcp-server --query "workers durable objects"
python examples/remote_mcp_server/main.py --query "workers durable objects"
npx tsx examples/remote_mcp_server/index.ts --query "workers durable objects"
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.
# Speak MCP over stdin/stdout instead of running once
go run ./examples/remote-mcp-server --mcp
# Speak MCP over stdin/stdout instead of running once
python examples/remote_mcp_server/main.py --mcp
# 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:
{
"mcpServers": {
"remote-mcp-server": {
"command": "go",
"args": ["run", "./examples/remote-mcp-server", "--mcp"]
}
}
}
{
"mcpServers": {
"remote-mcp-server": {
"command": "python",
"args": ["examples/remote_mcp_server/main.py", "--mcp"]
}
}
}
{
"mcpServers": {
"remote-mcp-server": {
"command": "npx",
"args": ["tsx", "examples/remote_mcp_server/index.ts", "--mcp"]
}
}
}