Skip to content

Conversation

@mattzcarey
Copy link
Contributor

@mattzcarey mattzcarey commented Nov 14, 2025

part one in a long saga. This moves all the mcp storage functions to the client manager, with an interface to try and make it less platform specific.

also cleans up the _connectMcpServersInternal and mcp.connect into..

  • mcp.registerServer()
  • mcp.connectToServer()

tested with: https://search-mcp.parallel.ai/mcp on https://workers-ai-playground.mattzcarey.workers.dev/

Other fun stuff here:

  • reasoning card for playground
  • updating to qwen 3 as default

@changeset-bot
Copy link

changeset-bot bot commented Nov 14, 2025

⚠️ No Changeset found

Latest commit: d5b672e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@claude
Copy link

claude bot commented Nov 14, 2025

Claude Code Review

Critical Issues

1. Missing await in removeMcpServer (packages/agents/src/index.ts:1465-1468)

async removeMcpServer(id: string) {
  this.mcp.closeConnection(id);  // Missing await
  this.mcp.removeServer(id);
  this.broadcastMcpServers();
}

closeConnection() is async but not awaited. This creates a race condition where the server record could be deleted from storage before the connection is fully closed, potentially leaving orphaned resources.

Fix: await this.mcp.closeConnection(id);


Architecture Issues

2. Storage adapter passed to OAuth provider is too broad (client-manager.ts:134-138)
The entire storage adapter (with SQL and KV access) is passed to DurableObjectOAuthClientProvider. OAuth providers should only need KV access for tokens, not full storage control.

Consider creating a narrower interface for OAuth providers that only exposes KV operations.

3. Connection state race condition in restore (client-manager.ts:164-187)
The code checks existingConn state and decides whether to skip/continue, but between the check and the registerServer call, another async operation could change the state. The restoration logic would be clearer with explicit state machine transitions.


Testing Gaps

4. No integration tests for OAuth flow
The test file (client-manager.test.ts) mocks the OAuth flow but doesn't actually test the full sequence: register → connect → callback → establish connection. This leaves OAuth edge cases (duplicate callbacks, state mismatches, concurrent OAuth attempts) untested.

5. Cache invalidation not tested
The callback URL cache mechanism (_callbackUrlCache, _invalidateCallbackUrlCache) is implemented but has no test coverage verifying the cache is properly invalidated when servers are added/removed.


Minor Issues

6. Inconsistent connection disposal (client-manager.ts:687-688)
closeConnection deletes the connection before disposing event subscriptions, while closeAllConnections disposes after deletion. Should follow consistent ordering.

7. Silent connection recreation (client-manager.ts:239-242)
registerServer silently skips if connection exists. This hides potential bugs where callers expect registration to succeed but the old connection is reused with potentially stale config.


Summary: Address the missing await (#1) before merging. Consider #2 for security boundaries. Others are improvements for future PRs.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 14, 2025

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@652

commit: d5b672e

threepointone pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Nov 14, 2025
This PR introduces a storage adapter architecture for the MCP client manager,
allowing custom storage backends for MCP server configurations.

Changes:
- Created advanced-client-api.mdx documenting the MCPStorageAdapter interface
- Added security section to oauth-mcp-client.mdx about callback URL clearing
- Updated mcp-client-api.mdx to link to advanced configuration guide

These docs explain the new storage adapter pattern, OAuth security improvements,
and provide examples for implementing custom storage backends.

Related: cloudflare/agents#652
threepointone pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Nov 14, 2025
Documents the MCP client manager refactoring that introduces a storage
adapter interface and implements automatic OAuth credential cleanup for
enhanced security.

Changes:
- Add new mcp-storage.mdx explaining storage adapter architecture
- Document automatic OAuth credential cleanup after authentication
- Add security notes about replay attack prevention
- Document automatic connection restoration after hibernation
- Update OAuth guide with security information

Related PR: cloudflare/agents#652

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@threepointone
Copy link
Contributor

📚 Documentation Sync

I've created a documentation PR to sync these changes to cloudflare-docs:

Docs PR: cloudflare/cloudflare-docs#26535

Documentation Changes

This PR introduces a storage adapter architecture for the MCP client manager. The documentation includes:

  1. New Advanced Configuration Guide (advanced-client-api.mdx)

    • Explains the MCPStorageAdapter interface
    • Provides examples for implementing custom storage backends (KV, R2, external databases)
    • Documents the connection restoration process after hibernation
    • Details performance considerations and caching strategies
  2. OAuth Security Section (updated oauth-mcp-client.mdx)

    • Documents the security improvement where callback URLs are cleared after successful OAuth
    • Explains state parameter validation
    • Notes on secure token storage
  3. API Reference Updates (updated mcp-client-api.mdx)

    • Added link to advanced configuration guide
    • Noted the modular storage adapter architecture

Why These Docs Matter

This refactoring introduces a breaking change in how MCPClientManager is initialized (now requires a storage adapter in options). While the Agent class handles this transparently for most users, advanced users who might want custom storage backends (e.g., storing MCP configs in KV instead of SQL) now have a documented path forward.

The OAuth security improvements are also worth documenting for user awareness.


This is an automated sync from the intelligent documentation workflow.

@threepointone
Copy link
Contributor

📚 Documentation sync

Documentation has been updated in cloudflare-docs to reflect these changes:

Docs PR: cloudflare/cloudflare-docs#26535

Changes documented

  • New page: mcp-storage.mdx - Explains the storage adapter architecture and security considerations
  • Updated: mcp-client-api.mdx - Added security notes about OAuth credential cleanup
  • Updated: oauth-mcp-client.mdx - Added security information about automatic credential cleanup

The documentation covers:

  • Storage adapter interface for flexible MCP server configuration persistence
  • Automatic OAuth credential cleanup after successful authentication (security improvement)
  • Connection restoration behavior after Agent hibernation

🤖 Auto-generated comment

threepointone pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Nov 14, 2025
- Add new storage-adapter.mdx page documenting the MCPStorageAdapter interface
- Update mcp-client-api.mdx to reference storage adapter pattern
- Document breaking change: MCPClientManager now requires storage option
- Include migration guide for direct MCPClientManager usage
- Document security improvements in OAuth credential handling

Related to cloudflare/agents#652
@threepointone
Copy link
Contributor

Documentation Updated

The documentation for this PR has been updated in cloudflare-docs#26535.

Documentation changes:

  • Added new Storage Adapter page documenting the MCPStorageAdapter interface
  • Updated MCP Client API reference to reference storage adapter pattern
  • Documented breaking change: MCPClientManager now requires storage option
  • Included migration guide for direct MCPClientManager usage
  • Documented security improvements in OAuth credential handling

Please review the documentation PR when you have a chance.

@mattzcarey mattzcarey marked this pull request as draft November 14, 2025 18:48
@threepointone
Copy link
Contributor

📚 Documentation sync completed

The documentation for this PR has been updated in the cloudflare-docs repository.

Documentation PR: cloudflare/cloudflare-docs#26535

Updated documentation:

Key changes documented:

  • Storage adapter pattern for decoupling MCP connection management from storage
  • Automatic connection restoration after Agent hibernation
  • OAuth security improvements (callback URL clearing to prevent replay attacks)
  • Custom storage backend implementation guide
  • Connection state management and efficient callback routing

The documentation is ready for review alongside this PR.

@mattzcarey mattzcarey marked this pull request as ready for review November 14, 2025 23:29
Copy link
Contributor

@deathbyknowledge deathbyknowledge left a comment

Choose a reason for hiding this comment

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

🔥

this._ParentClass.name,
"0.0.1"
);
readonly mcp!: MCPClientManager;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: ! is not needed if you set it in the constructor

await this.mcp.ensureJsonSchema();
}

await this._initializeMcpConnectionsFromStorage();
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to move this._mcpConnectionsInitialized and this._initializeMcpConnectionsFromStorage inside the ClientManager constructor?

Ideally in the Agent constructor we'd just do this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", ...) and forget about it right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe the call to ensureJsonSchema() too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is super annoying. We need the name to make the called to the d-o-OauthClient. We dont have that in the constructor where this is defined :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can clean this up a bit tho

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.

3 participants