Skip to content

refactor(mcp): client manager hooks into standard AgentLifecycle#1895

Draft
mattzcarey wants to merge 1 commit into
mainfrom
feat/standardise-agent-lifecycle
Draft

refactor(mcp): client manager hooks into standard AgentLifecycle#1895
mattzcarey wants to merge 1 commit into
mainfrom
feat/standardise-agent-lifecycle

Conversation

@mattzcarey

Copy link
Copy Markdown
Contributor

Summary

  • add an internal, ordered Agent component lifecycle with onStart, onRequest, onTurn, and onDestroy
  • make MCPClientManager own its Agent integration: schema setup, HTTP/RPC restoration, OAuth callback interception, tool contribution, state publication, observability forwarding, and cleanup
  • reduce Agent's MCP implementation to lifecycle dispatch plus compatibility delegators for addMcpServer(), removeMcpServer(), and getMcpServers()
  • route Think's MCP tools and AIChatAgent's MCP readiness through the standard turn lifecycle
  • preserve existing MCP storage rows, Agent APIs, protocol frames, React onMcpUpdate, OAuth-provider overrides, and connection behavior

API

Most Agents continue using the default this.mcp without configuration. A custom manager is now installed as a normal Agent component field:

import { Agent } from "agents";
import { MCPClientManager } from "agents/mcp/client";

export class MyAgent extends Agent<Env> {
  override mcp = new MCPClientManager(this, {
    name: "my-agent",
    version: "1.0.0"
  });
}

The previous standalone storage-only constructor, new MCPClientManager(name, version, { storage }), is removed. Keeping one Agent-owned construction mode avoids constructor discrimination, manual host attachment, fallback OAuth wiring, and a second lifecycle model.

Lifecycle behavior

  • components are resolved lazily so subclass field initialization can replace the default manager
  • startup runs components in order
  • request handling stops at the first component response
  • turn tools merge in component order
  • explicit destruction runs components in reverse order
  • a component failure stops the current lifecycle phase

The lifecycle remains internal in this first stage. This PR does not add a raw Durable Object harness, a new MCP package, or a public lifecycle export.

Verification

  • pnpm --filter agents test:workers
  • pnpm --filter @cloudflare/ai-chat test
  • pnpm --filter @cloudflare/think test
  • pnpm run build
  • pnpm run check
  • CI=true pnpm exec nx affected -t test

All checks pass on top of origin/main at a43df13b.

@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 294ef27

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Minor
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1895

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1895

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1895

create-think

npm i https://pkg.pr.new/create-think@1895

hono-agents

npm i https://pkg.pr.new/hono-agents@1895

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1895

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1895

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1895

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1895

commit: 294ef27

@mattzcarey mattzcarey changed the title mcp client manager hooking into standard agent lifecycle refactor(mcp): client manager hooks into standard AgentLifecycle Jul 8, 2026

@cjol cjol left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the lifecycle hooks are great and work well here. I'm not entirely sure what each represents yet (what's a request vs a turn? Doesn't matter right now, and I know we'll obviously document them prominently eventually) but it feels a good shape.

I'm not so sure about the interface of MCPClientManager but -- a bit like my state PR from yesterday, maybe it's still progress even if it's not the best shape yet.

Comment thread docs/agents/mcp-client.md

The manager is resolved when Agent startup begins, after subclass field initializers run. Replacing `mcp` therefore replaces the default component rather than registering both.

`MCPClientManager` no longer has a standalone storage-only constructor. It must be attached to an `Agent` through the host-first form above so startup, OAuth callbacks, turn tools, protocol updates, observability, and destruction all use the same lifecycle.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to think about this for a little bit. Maybe it's the right move, but it makes the interface contract between MCPClientManager and host agent implicit and invites tighter coupling than I'd like.


// Initialize MCPClientManager AFTER tables are created
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", {
const mcpHost: MCPClientManagerHost = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same point as my other comment, really) This is effectively the object that we're requiring non-Agent hosts to implement in order to use our MCP client, which is quite extensive and also non-specific (e.g. what bindings need to be returned by getEnv?)

Equally I know from experience trying it with Channels (and then State, where I ended up with a similar situation), that this is the bit where extraction from the current spaghetti gets very hairy, and it's what made me want to go in the opposite direction with Channels -- build a neat, isolated interface for the module, and then put all the adapter code inside Agent rather than bending the module to suit the current Agent implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants