Connect MCP servers¶
DeepAgents ships a built-in MCP client that connects to any number of stdio or HTTP MCP servers concurrently, wraps their tools as AgentTool conformers, and feeds them through the same middleware pipeline as every other tool in the agent.
Overview¶
The connection flow has four steps:
- Build one
MCPServerConfigper server. - Create a
MultiServerMCPClientwith those configs. - Fetch the tools and wrap them in
MCPMiddleware. - Pass the middleware to
createAgentorcreateDeepAgent. - Call
disconnectAll()when the agent is done.
MCPServerConfig¶
MCPServerConfig is a Codable, Sendable struct. Two transport kinds are supported:
var config = MCPServerConfig()
config.name = "filesystem-mcp"
config.kind = .stdio
config.command = "/usr/local/bin/mcp-server-filesystem"
config.args = ["--root", "/tmp/workspace"]
config.env = ["LOG_LEVEL": "warn"]
config.isEnabled = true
config.approvalMode = .ask // .approve / .ask / .deny
The client launches the command as a subprocess and communicates over stdin/stdout. env is merged into the subprocess environment.
var config = MCPServerConfig()
config.name = "remote-tools"
config.kind = .http
config.url = "https://tools.example.com/mcp"
config.headers = ["Authorization": "Bearer \(apiKey)"]
config.auth = .none // .oauth for OAuth 2 flow
config.isEnabled = true
config.approvalMode = .approve
HTTP transport sends requests to the given URL. For OAuth-protected servers set auth = .oauth and supply an openURL callback to MultiServerMCPClient so the framework can open the authorization URL.
MultiServerMCPClient¶
public actor MultiServerMCPClient {
public init(configs: [MCPServerConfig], openURL: @escaping (URL) -> Void = { _ in })
public func tools() async -> [any AgentTool]
public func load() async -> (tools: [any AgentTool], statuses: [MCPServerStatus])
public func disconnectAll() async
}
tools()connects to all enabled servers and returns the union of their tools. Per-server failures are isolated: a server that fails to start or authenticate is skipped and logged; other servers continue normally.load()does the same but also returns a[MCPServerStatus]array so you can surface connection errors in your UI.disconnectAll()closes all open connections. Call this when the agent's lifetime ends (e.g. in adeferblock ordeinit).
MCPMiddleware¶
public struct MCPMiddleware: AgentMiddleware {
public init(tools: [any AgentTool])
public var tools: [any AgentTool]
}
MCPMiddleware is a thin wrapper that contributes the fetched MCP tools to the agent. It has no lifecycle hooks of its own - all routing logic lives in the MultiServerMCPClient.
End-to-end example¶
import DeepAgents
import DeepAgentsAnthropic
// 1. Configure servers
var fsConfig = MCPServerConfig()
fsConfig.name = "filesystem-mcp"
fsConfig.kind = .stdio
fsConfig.command = "/usr/local/bin/mcp-server-filesystem"
fsConfig.args = ["--root", FileManager.default.temporaryDirectory.path]
fsConfig.isEnabled = true
fsConfig.approvalMode = .ask
var webConfig = MCPServerConfig()
webConfig.name = "brave-search"
webConfig.kind = .http
webConfig.url = "https://api.search.example.com/mcp"
webConfig.headers = ["Authorization": "Bearer \(braveAPIKey)"]
webConfig.isEnabled = true
webConfig.approvalMode = .approve
// 2. Create the client
let mcpClient = MultiServerMCPClient(configs: [fsConfig, webConfig])
// 3. Fetch tools and build the middleware
let mcpTools = await mcpClient.tools()
let mcpMiddleware = MCPMiddleware(tools: mcpTools)
// 4. Create the agent
let model = AnthropicChatModel(
baseURL: URL(string: "https://api.anthropic.com")!,
model: "claude-opus-4-8",
apiKey: ProcessInfo.processInfo.environment["ANTHROPIC_API_KEY"],
supportsVision: false
)
let agent = createDeepAgent(
model: model,
middleware: [mcpMiddleware],
systemPrompt: "You are a research assistant."
)
// 5. Run the agent
defer { Task { await mcpClient.disconnectAll() } }
let ok = await agent.run([.human("Summarize the latest news on Swift concurrency.")]) { event in
// handle events
}
Tool namespacing¶
MCP tools are namespaced with their server name using double underscores: server__tool_name. For example, a read_file tool from a server named filesystem-mcp becomes filesystem-mcp__read_file in the agent's tool list. This prevents collisions when multiple servers expose tools with the same name.
Approval mode per server¶
Each MCPServerConfig has an approvalMode: ToolApprovalMode property:
| Mode | Behavior |
|---|---|
.approve |
All tools from this server are auto-approved (no prompt) |
.ask |
Every call pauses for human approval |
.deny |
Tools from this server are never executed |
approvalMode is applied on top of any approvalHandler you pass to createDeepAgent. If you pass an approvalHandler, it gates all tools; the per-server approvalMode provides an additional coarse-grained filter. See Gate tools with approvals for the full approval model.
Warning
.ask requires an approvalHandler to be passed to createDeepAgent, otherwise the agent has no mechanism to pause and surface the approval request.
Checking connection status¶
Use load() instead of tools() when you need to surface server errors:
let (tools, statuses) = await mcpClient.load()
for status in statuses {
if case .failed(let error) = status.state {
print("Server \(status.name) failed: \(error)")
}
}
Related pages¶
- MCP client - Architecture of
MultiServerMCPClient, tool lifecycle, and isolation model