Skip to the page

Cloudflare Agents

Give an agent built with the Cloudflare Agents SDK live web search. It calls SpecStream's search_web and read_pages tools over MCP, or the Search API from an AI SDK tool, and gets an answer with the page each value came from.

SpecStream for Cloudflare AgentsAgent frameworks & builders

What you need

  • A Worker project with the Agents SDK, the AI SDK and Wrangler (npm i agents ai zod), and a model that supports tool calls. The example uses a Workers AI model through createAI.
  • A SpecStream account. New accounts start with $10 of credit, no card. Create one.
  • A SpecStream API key with a spending limit.
  1. 1. Create an API key

    In your SpecStream workspace, open Agents & API, choose API keys, then Create API key, and set a spending limit. Store the key as a Worker secret, and in .dev.vars for wrangler dev:

    Terminal
    npx wrangler secret put SPECSTREAM_API_KEY
    echo 'SPECSTREAM_API_KEY=YOUR_SPECSTREAM_API_KEY' >> .dev.vars
  2. 2. Add the agent to wrangler.jsonc

    An agent is a Durable Object, so it needs a binding and a SQLite migration. The ai binding gives it a Workers AI model. Then run npx wrangler types so Env knows AI, SearchAgent and SPECSTREAM_API_KEY:

    wrangler.jsonc
    {
    "name": "search-agent",
    "main": "src/server.ts",
    "compatibility_date": "2026-06-11",
    "compatibility_flags": ["nodejs_compat"],
    "ai": { "binding": "AI" },
    "durable_objects": {
    "bindings": [{ "name": "SearchAgent", "class_name": "SearchAgent" }]
    },
    "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SearchAgent"] }]
    }
  3. 3. Connect SpecStream as an MCP server

    addMcpServer() connects the agent to https://api.specstream.ai/mcp and keeps the server in the agent's SQL storage, so it reconnects after the agent wakes. The key goes in the transport headers, so there is no OAuth step. The id option names the server specstream, and the Agents SDK keys each tool tool_<serverId>_<toolName>, so the agent can pass the model only tool_specstream_search_web and tool_specstream_read_pages:

    src/server.ts
    import { Agent, routeAgentRequest } from "agents";
    import { createAI } from "agents/models/ai-sdk";
    import { generateText, isStepCount } from "ai";
    export class SearchAgent extends Agent<Env> {
    async onStart() {
    await this.addMcpServer("SpecStream", "https://api.specstream.ai/mcp", {
    id: "specstream",
    transport: {
    type: "streamable-http",
    headers: { Authorization: `Bearer ${this.env.SPECSTREAM_API_KEY}` },
    },
    });
    }
    async onRequest(request: Request) {
    await this.mcp.waitForConnections({ timeout: 10_000 });
    const all = this.mcp.getAITools();
    const tools = {
    search_web: all.tool_specstream_search_web,
    read_pages: all.tool_specstream_read_pages,
    };
    const ai = createAI({ binding: this.env.AI });
    const { text } = await generateText({
    model: ai("@cf/zai-org/glm-4.7-flash"),
    system:
    "For anything recent or factual, call search_web with the question in plain words. " +
    "Call read_pages to read a page it found. Name the page each fact came from.",
    prompt: await request.text(),
    tools,
    stopWhen: isStepCount(5),
    });
    return new Response(text);
    }
    }
    export default {
    async fetch(request: Request, env: Env) {
    return (
    (await routeAgentRequest(request, env)) ??
    new Response("Not found", { status: 404 })
    );
    },
    } satisfies ExportedHandler<Env>;
  4. 4. Run it and ask

    Start the Worker with npx wrangler dev. routeAgentRequest serves SearchAgent at /agents/search-agent/<name>, so post a question to it:

    Terminal
    curl -X POST http://localhost:8787/agents/search-agent/default \
    -d "Which Wrangler version added web search to the Workers AI binding?"

What comes back

search_web takes {"q": "<the question>"}. The reply holds answer, the value as the pages print it, with the page it was read from, and sources, the results read in rank order with their url, title, site and snippet. When no page prints a value that answers the question, answer is null and the sources are still there, so the model can say what it found instead of guessing.

read_pages takes {"urls": ["<url>", ...]}, up to 20 at once, and returns each page's text as Markdown.

Or call the Search API from an AI SDK tool

To skip the MCP connection, wrap the Search API in an AI SDK tool() and pass it in tools. A POST keeps the question out of URL logs, and a failed search returns a sentence the model can read instead of throwing:

Or keep the Agents SDK web_search tool

The SDK's own webSearchTool takes a source in place of the Workers AI binding. A source backed by SpecStream keeps the tool's name, description and trimmed results for the model, and the search runs on your SpecStream key. speed: "fast" asks for the fast tier, which costs $1 per 1,000 searches of up to 10 results:

Cost

Each search_web call is one Search API call at $5 per 1,000 searches of up to 10 results, from your balance, with no quote and no approval. A fast search is $1 per 1,000. A search that returns no results costs nothing. Each page read_pages reads is $0.80 per 1,000 pages, and a page with no text is not charged. The reply's price_usd says what each call cost. Search API

Limits on the key

The key's spending limit holds on every call the agent makes, so a loop cannot spend past it. Give each Worker its own key, and revoke it in Agents & API to stop it at once. Every search is kept in your account's history with its answer and sources. The other SpecStream tools, which build and price larger lists, stay out of reach until you add them to tools. The agent guide describes each one.

Remove

Call this.removeMcpServer("specstream") to close the connection and delete the stored server, take the tool out of tools, and revoke the key in Agents & API. To put SpecStream on a Cloudflare MCP server portal for your team instead, see Cloudflare. Questions go to [email protected].

More

SpecStream for Cloudflare Agents covers what it does in Cloudflare Agents on one page. Every other tool has its guide in Connect your tools. You see the most a request can cost before it starts, and the matches are paid from your balance. Billing has the details. For help, email [email protected].