Cloudflare Agents SDK
Connect a Cloudflare Agents SDK agent to Edison Watch with addMcpServer over streamable HTTP, using the agent's own Durable Object name as the stable session id so data-leak protection holds across every turn.
The Cloudflare Agents SDK (agents) connects to Edison with its built-in MCP client. Each agent instance is a Durable Object with its own storage, so this.mcp is a long-lived MCPClientManager rather than a client you build per request. Your connection URL carries your API key, so no auth header is needed - and because Edison authenticates from the URL path, you never wire up the SDK's OAuth flow for this connection.
npm install agents ai @ai-sdk/openaiConnect in onStart() and hand the tools to your model. onStart() runs after the SDK has restored any existing MCP connections, so it's the right place for this:
import { Agent, type AgentNamespace, routeAgentRequest } from 'agents';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
interface Env {
EdisonAgent: AgentNamespace<EdisonAgent>;
EDISON_MCP_URL: string;
}
export class EdisonAgent extends Agent<Env> {
async onStart() {
await this.addMcpServer('edison', this.env.EDISON_MCP_URL, {
transport: {
type: 'streamable-http',
// See "Keep a stable session across turns" below - this is not optional.
headers: { 'x-edison-conversation-id': this.name },
},
});
}
async onRequest(_request: Request) {
const { text } = await generateText({
model: openai('gpt-5.6-luna'),
tools: this.mcp.getAITools(),
messages: [{ role: 'user', content: 'List my available tools.' }],
});
return Response.json({ text });
}
}
export default {
async fetch(request: Request, env: Env) {
return (await routeAgentRequest(request, env)) ?? new Response('Not found', { status: 404 });
},
};Set EDISON_MCP_URL to your connection URL, e.g. https://mcp.edison.watch/mcp/<your-api-key>/?client=cloudflare-agents. Keep it in a Worker secret (wrangler secret put EDISON_MCP_URL), not in wrangler.jsonc - the URL is the credential. Agents also need a Durable Object binding and migration entry in your Wrangler config; the agents-starter template ships both.
Pin transport: { type: 'streamable-http' }. Edison serves streamable-HTTP only, so pinning it skips transport probing and avoids a wasted SSE attempt. Use this.mcp.getAITools() rather than caching the tool list at connect time - it returns the full set once background connection restoration finishes after a Durable Object wakes from hibernation.
Keep a stable session across turns to preserve data-leak protection
Send a stable x-edison-conversation-id header on every turn of the same conversation. That header is what keeps Edison's data-leak protection intact across a multi-turn run: Edison tracks lethal-trifecta risk per session, so if each turn looks like a brand-new session, that protection resets - and a later turn can leak data that the accumulated risk should have blocked.
This SDK makes that unusually easy, which is why it's already wired into the snippet above. You normally address one agent instance per conversation - getAgentByName(env.EdisonAgent, conversationId), or the /agents/edison-agent/<conversationId> route that routeAgentRequest resolves - so the agent's own Durable Object name, this.name, is already a stable per-conversation identifier that survives hibernation. Passing it straight through is all it takes.
Without a stable x-edison-conversation-id, each connection is treated as a fresh session that starts with empty risk state - so risk accumulated on an earlier turn won't be there to block a later exfiltration. The ?client= label is only a dashboard tag, not a session key. Use a unique id per conversation (a UUID is ideal); ids are scoped to your API key, so don't reuse one string for two different conversations.
Reuse of one agent instance for two unrelated conversations is the failure mode to watch here. If you route several users or threads into a single Durable Object, this.name no longer identifies a conversation - pass your own per-conversation id instead, and reconnect when it changes.
Optional: the encrypted-secrets header
For servers with zero-knowledge-encrypted secrets, add it alongside the conversation id under transport.headers:
await this.addMcpServer('edison', this.env.EDISON_MCP_URL, {
transport: {
type: 'streamable-http',
headers: {
'x-edison-conversation-id': this.name,
'x-edison-secret-key': this.env.EDISON_SECRET_KEY,
},
},
});Store EDISON_SECRET_KEY as a Worker secret too. Edison never persists it server-side - it derives an in-memory key per request - so a Durable Object that hibernates and wakes simply sends the header again on reconnect.
Governing the other direction
An Agents SDK agent can also expose MCP tools (as an McpAgent), which puts it on the server side of the gateway - Edison can then front your agent's own tools for other clients. That's the Add an MCP server path, and it's independent of everything above. Cloudflare's own hosted server (mcp.cloudflare.com/mcp, for Workers and DNS management) is available in Edison's server marketplace.
Mastra
Connect a Mastra agent to Edison Watch using MCPClient over streamable HTTP, keeping a stable session per conversation so data-leak protection holds across every turn.
DSPy
Connect a DSPy program to Edison Watch using the MCP Python SDK and dspy.Tool.from_mcp_tool, keeping a stable session per conversation so data-leak protection holds across every turn.

