First Class MCP Agents and Tools
Context
Agent SDK is leveraging AI SDK’s Tools to enable LLMs to do custom tasks or operations. The Model Context Protocol (MCPs) is becoming a standard for how LLMs can communicate with each other for the same use cases that tools enable LLMs to discover functionality that it can invoke. We should consider enabling Agents created with the Agent SDK to both:
- Expose itself as an MCP so that others can utilize the agent through this protocol.
- Define a standard for bootstrapping MCPs as Tools for the agent to connect to and leverage in its operations.
Definition of a tool in MCP
{
name: string; // Unique identifier for the tool
description?: string; // Human-readable description
inputSchema: { // JSON Schema for the tool's parameters
type: "object",
properties: { ... } // Tool-specific parameters
}
}
Definition of an Tools using AI SDK
tools: {
name: tool({
description: 'Get the weather in a location',
parameters: // Zod or JSON Schema of tool's parameters
execute: // Args from params above + History of messages
}),
...
}
Standard Server Sent Events MCP Example We already have a solution from cloudflare with Workers MCP which allows servers to serve API endpoints as standard MCP Servers via JSDdoc standard which makes it intuitive for users to not have to handle bootstrapping MCP Servers.
export class ExampleWorkerMCP extends WorkerEntrypoint<Env> {
/**
* Generates a random number. This is extra random because it had to travel all the way to
* your nearest Cloudflare PoP to be calculated which... something something lava lamps?
*
* @return {string} A message containing a super duper random number
* */
async getRandomNumber() {
return `Your random number is ${Math.random()}`
}
// ...etc
}
Problem
Agent SDK needs a way to bootstrap itself as an MCP Server and easily depend on existing MCP Servers as Tools to perform its desired functionality. This will enable Cloudflare Agents to participate in the growing MCP standard, decouple reusable Agent Tools that can be reused across multiple agents, help support multi-tenancy and the separation/security of state.
Coupling agents with mandatory tools, such as Email or Scheduling, may increase the complexity of each feature to consider a stateful component and multi-tenancy. Additionally, these features may not need to be used at all and we should consider how we want optional tools to be considered, configured and leveraged in the ecosystem of Cloudflare AI Agents. Cloudflare Agent developers should be able to easily configure what functionality is needed by registering and configuring MCPs as Tools. Contributors to this project should have a clear pathway on how to contribute custom functionality for the ecosytem.
Possible solutions
The following options are unbiased in their order of importance and represent potential solutions that may help solve the above problem.
MCP Server Bootstrapping
Leveraging the available tools array created from the server and setup a Stdio and/or SSE Transport service to accept incoming messages and their responses.
Assuming MCP typescript-sdk potentially can work on Cloudflare Workers, a standard service can iterate through the given tools of the Agent and register them as follows:
// Create an MCP server sudo code
const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
// Register all tools that are used within the agent sdk.
for (tool : tools) {
server.tool(tool);
}
// Should really be a fetch example for cloudflare workers...
app.get("/sse", (req, res) => {
transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
MCP Client Bootstrapping
Create a standard Tool that takes a live MCP Server SSE Endpoint for indexing and generating its available commands. MCP servers are providing three different types of potential endpoints to help understand how to use it:
- Prompts: A list of example templates of messages that the LLM can use
- Resources: The ability to query data (ie: a database, PDF, images, etc.) to acquire information.
- Tools: Is functionality (Create, Update, Delete) that the MCP server does for the LLM.
Due to AI SDK that we're using today does not supporting Prompts and Resources, we can convert Prompts to help enrich descriptions for the Tools and Resources. The Resources can be applied as Tools in the AI SDK standard, and Tools will convert seamlessly since they share the same schema today.
Optimizing the description size is debatable to save costs and adhere to context windows.
export class McpToolFactory {
function async new(url: String): Tools[] {
// Query & Index MCP server Prompts,
// Query & Index Resource Endpoints
// Query & Index Tools
return // Generate tools for reading Resources &
}
}
export class McpTool implements Tool {
private tool: Tool
constructor(tool: Tool) {
this.tool = tool;
}
// Wrap Tool Defintions
function async execute (... args} => ({
result: client.callTool(tool.Name, args);
})
}
Configuration Option A: Config File
Create a configuration file that gets parsed that includes a list of MCP servers
- Pros:
- Clear file of mcp server definitions that exposes the feature and functionality to developers in a clear way
- Allow for future proofing flexibility and options of how the MCP servers are used
- Able to support Stdio, Sse and potentially custom transport handlers.
- Cons
- Separate file that needs to be considered and parsed.
- Multiple flavors (ts, jsonc, toml) formats to be considered
Example mcpconfig.json:
{
"servers": [
{"url":"http://localhost:8080/sse"},
{"command":"./server","args":["--option","value"]}
}
}
Configuration Option B: Environment Variables
To keep things simple and use existing wrangler and .env files, we could consider a comma separated array for a list of MCP servers to bootstrap.
- Pro
- Uses standard files that exist already
- Con
- Not able to easily support Stdio Transport
Example .env file:
MCP_SERVERS=http://localhost:8080/sse,http://agent.coop.com/sse
Configuration Option C: Statically Typed
You can consider statically registering MCP Servers in Typescript. This may make it harder to quickly update
- Pros:
- Flexibility in dynamically creating tools for MCP servers on demand
- Cons:
- Not easily able to config per-environment setups.
const mcpToolFactory = new McpToolFactory();
const tool = mcpToolFactory.createTool(“http://localhost:8080/sse”)
Summary
Allowing MCP servers to enable the flexibility of potentially stateless agents and decoupling state for multi-tenancy. Users or folks who leverage Cloudflare Platform for Platforms can consider deploying individually configured and contextually bound agents per tenant which improves overall security and potentially even optimizes LLMs to perform better for their owner’s needs. D1, Vectorize, KV and other serverless cloud native primitives that Cloudflare provides us allows this solution to scale cost effectively versus having to consider optimizing tenants and states within single stores or instances.
Thoughts?
All is welcomed!
- Feedback on the problem itself - is its something we all care about?
- Feedback or questions about the options listed here.
- New proposed solutions.
A couple of early pieces of feedback:
Due to AI SDK that we're using today does not supporting Prompts and Resources…
The agents SDK doesn’t depend on the AI SDK directly: you can use MCP directly or write your own. The example application agents-starter does because it’s meant to be a practical example and the AI SDK is widely used.
Assuming MCP typescript-sdk potentially can work on Cloudflare Workers
It does! You should try it!
Agent SDK needs a way to bootstrap itself as an MCP Server and easily depend on existing MCP Servers as Tools to perform its desired functionality
I don’t quite get that in this RFC. Can you show a more complete example of the following? Specifically taking into account:
- Not all Agents will want to expose APIs over MCP, since not all Agents may need to communicate over MCP
- Some Agents will want to just act as an MCP client - how will this work? How do we make a client (that may talk to multiple servers!) feel part of the framework without adding a ton of default complexity?
- MCP server: some Agents may wish to expose themselves over MCP, and/or as a “multiplexer” (an MCP server that combined others/chains calls).
What’s in here right now isn’t really fleshed out and I would suggest prototyping something to test it out. Start narrow!
I'll get to prototyping it for more real code examples @elithrar
But it would be helpful, as a project, to align on the vision of how MCPs or even functionality should exist. For example, in the roadmap we have email, and we already have things like schedules baked into the agent. I would imagine those as MCPs / Tools themselves and my interests is most definitely helping create an OSS serverless tool catalog as things continue to move forward.
Next steps for me would be:
- Show example option above on how to configure MCPs
- Show how to optionally expose agent as an MCP
- Showcase how something like the "Schedular" can be an MCP or Tool (both options - ideally configurable by choice)
@Disturbing how is this coming along for you? I just managed to create an extension of the base Agent class with our own HTTPTransport for handling both regular RESTful/non-MCP requests and MCP-requests under the prefix of /mcp.
If you'd want to collaborate a bit on this, or at least bounce ideas, I'd love to contribute to moving this further along.
Here's an example implementation of a (very simple) agent with an RNG tool that responds to on regular GET requests on anything but /mcp/* and to RPC calls via the /mcp prefix:
export class RandomAgent extends MCPAgent<{}, {}> {
async onRequest(request: Request): Promise<Response> {
if (request.method !== 'GET') {
return new Response('Unsupported method', { status: 400 })
}
return new Response(JSON.stringify({ data: await this.random() }))
}
createServerParams(): [Implementation, ServerOptions] {
return [
{ name: 'random-example', version: '1.0.0' },
{
capabilities: {
resources: { subscribe: false, listChanged: true },
tools: { listChanged: true, random: true },
},
},
]
}
configureServer(server: Server): void {
server.setRequestHandler(ListToolsRequestSchema, () => {
return {
tools: [{ name: 'random', description: 'Random number generator' }],
}
})
server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
if (params.name !== 'random') throw new Error('Unknown tool')
return {
content: [{ type: 'text', text: JSON.stringify(await this.random()) }],
}
})
}
async random() {
return Math.floor(Math.random() * 1_000)
}
}
And here's the agent class with MCP capabilities:
type MCPState = {
config: [Implementation, ServerOptions]
}
export default abstract class MCPAgent<Env, State = unknown> extends Agent<Env, State & MCPState> {
messages!: WeakMap<Request, Response>
server!: Server
onStart(): void | Promise<void> {
this.messages = new WeakMap<Request, Response>()
this.initMCPServer()
}
/**
* Initializes the MCP server instance using stored or newly created configuration.
* This method is called during object construction and blocks concurrent access.
*/
async initMCPServer() {
const [impl, opts] = this.state.config ?? (await this.createServerParams())
this.server = new Server(impl, opts)
this.configureServer(this.server)
}
/**
* Creates a new HTTP transport instance for handling server communications.
* @returns A promise that resolves to a new HTTPServerTransport instance
*/
async createTransport(request: Request): Promise<Transport> {
return new HTTPServerTransport({
receive: () => request,
transmit: (response: Response) => {
this.messages.set(request, response)
},
})
}
/**
* Abstract method that must be implemented by subclasses to provide server configuration.
* @returns A tuple containing the server implementation and options, or a Promise of such
*/
abstract createServerParams():
| [Implementation, ServerOptions]
| Promise<[Implementation, ServerOptions]>
/**
* Abstract method that must be implemented by subclasses to configure the server instance.
* Called after server initialization to set up any additional server configuration, e.g., handlers of incoming RPC calls.
* @param server - The MCP server instance to configure
*/
abstract configureServer(server: Server): void
async onMCPRequest(request: Request) {
const transport = await this.createTransport(request)
this.server.connect(transport)
return await this.#transport(request)
}
/**
* Handles incoming HTTP requests by creating a transport, connecting it to the server,
* and processing the request through the MCP protocol.
* @param request - The incoming HTTP request to proces
* @returns A promise that resolves to the HTTP response
*/
async fetch(request: Request) {
const url = new URL(request.url)
if (url.pathname.startsWith('/mcp')) {
return this.onMCPRequest(request)
}
return super.fetch(request)
}
async #transport(request: Request, max = 100) {
let tries = 0
while (!this.messages.has(request) && tries < max) {
await new Promise((resolve) =>
setTimeout(() => {
++tries
resolve(undefined)
}, 0)
)
}
const response = this.messages.get(request)
if (!response) {
return new Response("Server didn't respond in time", { status: 503 })
}
return response
}
}
Thanks for jumping in here @felixnorden - Happy to collab, I have some pressing work on another project to unblock someone and hoping to hop onto this next week (31st - March).
You on the community discord, maybe we can do a call and discuss some ideas and see what can be split.
Regarding the above impl. - I was thinking that SSE would be the std transport to integrate. Is HTTP Transport part of the standard? Mind doing a fork / committing the code so I can see the full imports. I was thinking of using the MCP typscript sdk.
Maybe you are integrating the std from scratch and then I'd have to cross check as I'm not 100% sure with the code snippits lacking imports.
Sounds good to me! I just jumped back into the CF discord today, so I'd be happy to discuss further in there if you'd like.
HTTP isn't currently part of it, but there was recently an announcement about generalizing the SSE transport to support HTTP with promotion out of the box. I just happened to do this one at the same time as I didn't like the leaky abstraction that the SSE transport has. I add in some additional lifecycle hooks in the constructor so that the interface remains intact and so we could add in similar promotion logic if we'd want to!
Here's the amendment to the specification: https://github.com/modelcontextprotocol/specification/pull/206
I'll make a fork and drop some context later this week -- it uses the internal types and everything from the MCP Typescript SDK as well!
Just found some work got merged in here: https://github.com/cloudflare/agents/pull/106
Nice find, let's see if we can help that further along with the code we have here (including the imports, lol)
Would love feedback on an initial version of adding MCP client capabilities https://github.com/cloudflare/agents/pull/125
Lots more to do here and iterate on, ideas welcome
Sure thing! It's a great application area, I'll be using it as a reference, making agents act as both clients and servers in MCP. Is this something you want as an external POC, or do you want more examples in PRs? @irvinebroque
Would welcome examples! We're focused on getting auth bits all wired up for agent as MCP client, and wrangling with transport and few other things.
Is there a standard auth bit we can follow, looks like MCP and a2e's are pushing for auth standards @irvinebroque ?
Went ahead and created some examples in a separate repository as there is a lot of parallel work going on regarding MCPs.
https://github.com/xava-labs/typescript-agent-framework/tree/main/examples/simple-prompt-agent
The above has an example of what I was proposing around defining mcp.json files that can be bootstrapped as agent tools:
- It serializes mcp.json into .env file as json or base64 (base64 as default)
- It leverages the MCPClientConnector in the agents SDK to create connections to all MCPs available with "SSE"
- It injects "tools" into AI SDK calls when messages are released.
Since MCP servers and MCPClientManager has been merged it seems there are already enough tools in the Agent SDK to do this manually.
What I would ask to review if you'd like me to work on it is the following:
1st Class MCP Package
I believe MCPs and Agents should not be tightly coupled. Right now there's "Agent Context" and "Task" logic in every MCP. I would suggest MCP have its own 1st class package in Agents SDK similar to the following:
https://github.com/xava-labs/typescript-agent-framework/blob/main/packages/mcp/src/mcp/server.ts
Then Agents should be focused on providing business logic around LLMs.
1st Class Websocket support
Right now the agents sdk is using websockets from Worker => Durable Object and managing them to maintain state. Streamable Http handles reconnection and resume session logic: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/streamableHttp.ts
I would suggest that we allow connections to disconnect, and stay strictly using the Stream Response that will timeout after workers / DOs go offline and all connections are forwarded to the DO to manage version having an extra worker in between.
Correct me if I'm reading the following wrong, but that's the approach I read here which feels like a websocket <> sse translation versus letting the client connect directly.
https://github.com/cloudflare/agents/blob/067cd1a8e172f6d35e1230b3a673fb21ed6b1b0c/packages/agents/src/mcp/index.ts#L768
With that said, MCPs have an official websocket Client, but not Server here which we can use for websocket related work and attempt to propose a std server (with resumeability) functionality to the MCP protocal, together.
https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/client/websocket.ts
Standardize MCP Files
Introduce a tool and example that can serialize MCPs and be used to inject them into tools
https://github.com/xava-labs/typescript-agent-framework/blob/main/packages/agent/src/services/toolbox.ts
Happy to port this example over for the agent SDK and consider a separate "ticket" for decoupling Agent State and Functionality to be modular services/plugins that folks may be able to develop and contribute.
Feedback
If any of these ideas are interesting, happy to do work and contribute. It looks like there is a lot of internal discussions and movement of features / vision for Agents SDK being early as it is today and without a cadence / meeting, doing larger work as a community member - I think showcasing examples and seeing if anything fits the bill is what I have to offer on the above.
I do think Services/Plugins of some sort would be a big win and worth a discussion and I'm keen to hear what ya'll think on decoupling the logic in Agents so that each agent doesn't have schema/functionality that doesn't fit a particular use case in the agent ecosystem as exemplified above.
Closing due to lack of interest.