Documentation Index Fetch the complete documentation index at: https://docs.ensemble.ai/llms.txt
Use this file to discover all available pages before exploring further.
Invoke external Model Context Protocol (MCP) tools over HTTP to extend Conductor with third-party capabilities.
The tools operation allows ensembles to call external MCP servers (like GitHub MCP, Brave Search MCP, or custom MCP servers) via HTTP. This enables integration with any MCP-compatible service without requiring subprocess execution.
Overview
With Cloudflare’s announcement of MCP support (November 2024), Conductor implements HTTP-only MCP transport for secure, scalable tool integration on the edge. This approach:
Works in Cloudflare Workers (no Node.js required)
Scales horizontally with zero configuration
Provides built-in auth (bearer tokens, OAuth)
Supports HMAC signature verification
Integrates seamlessly with existing infrastructure
Basic Usage
ensemble : github-pr-analyzer
agents :
- name : get-pr
operation : tools
config :
mcp : github # MCP server name
tool : get_pull_request # Tool to invoke
timeout : 10000
inputs :
owner : anthropics
repo : anthropic-sdk-typescript
pull_number : 123
outputs :
pr_data : ${get-pr.output}
Configuration
Agent Config
config :
mcp : string # MCP server name from conductor.config.ts
tool : string # Tool name to invoke
timeout : number # Request timeout in ms (default: 30000)
cacheDiscovery : boolean # Cache tool discovery (default: false)
cacheTTL : number # Discovery cache TTL in seconds (default: 300)
The agent input becomes the tool’s arguments:
inputs :
owner : anthropics
repo : anthropic-sdk-typescript
pull_number : 123
Becomes:
{
"name" : "get_pull_request" ,
"arguments" : {
"owner" : "anthropics" ,
"repo" : "anthropic-sdk-typescript" ,
"pull_number" : 123
}
}
Output
{
tool : string // Tool name that was invoked
server : string // MCP server name
content : unknown // Tool output (format varies by tool)
duration : number // Execution time in ms
isError : boolean // Whether tool returned an error
}
Setup MCP Servers
Create conductor.config.ts:
import type { ConductorConfig } from '@ensemble-edge/conductor'
const config : ConductorConfig = {
mcpServers: {
// GitHub MCP Server
github: {
url: 'https://github-mcp.example.com' ,
auth: {
type: 'bearer' ,
token: process . env . GITHUB_MCP_TOKEN
},
timeout: 15000
},
// Brave Search MCP Server
brave: {
url: 'https://brave-search-mcp.example.com' ,
auth: {
type: 'bearer' ,
token: process . env . BRAVE_API_KEY
}
},
// Custom MCP Server with OAuth
custom: {
url: 'https://custom-mcp.example.com' ,
auth: {
type: 'oauth' ,
clientId: process . env . OAUTH_CLIENT_ID ,
clientSecret: process . env . OAUTH_CLIENT_SECRET ,
tokenUrl: 'https://auth.example.com/oauth/token'
},
timeout: 20000
},
// Public MCP Server (no auth)
public: {
url: 'https://public-mcp.example.com'
}
}
}
export default config
2. Set Environment Variables
# .dev.vars (for local development)
GITHUB_MCP_TOKEN = ghp_xxx
BRAVE_API_KEY = BSAxxx
OAUTH_CLIENT_ID = client_xxx
OAUTH_CLIENT_SECRET = secret_xxx
# wrangler.toml (for production)
[ vars ]
# Public variables (non-sensitive)
[[ env . production . vars ]]
# Production secrets via wrangler secret
# Set secrets in production
wrangler secret put GITHUB_MCP_TOKEN
wrangler secret put BRAVE_API_KEY
wrangler secret put OAUTH_CLIENT_SECRET
MCP Server Examples
GitHub MCP
Interact with GitHub repositories, pull requests, and issues:
ensemble : github-pr-review
agents :
# Get pull request data
- name : get-pr
operation : tools
config :
mcp : github
tool : get_pull_request
# List PR files
- name : list-files
operation : tools
config :
mcp : github
tool : list_pull_request_files
# Get file contents
- name : get-file
operation : tools
config :
mcp : github
tool : get_file_contents
# Analyze with AI
- name : analyze
operation : think
config :
provider : anthropic
model : claude-sonnet-4
prompt : |
Pull Request: ${get-pr.output}
Files Changed: ${list-files.output}
File Contents: ${get-file.output}
Review this PR and provide feedback.
inputs :
owner : anthropics
repo : anthropic-sdk-typescript
pull_number : 123
file_path : src/index.ts
outputs :
review : ${analyze.output}
pr_data : ${get-pr.output}
Brave Search MCP
Web search with Brave Search API:
ensemble : web-research
agents :
# Web search
- name : search
operation : tools
config :
mcp : brave
tool : web_search
# Summarize results with AI
- name : summarize
operation : think
config :
provider : anthropic
model : claude-sonnet-4
prompt : |
Search Results:
${search.output}
Question: ${input.question}
Provide a comprehensive answer with citations.
inputs :
query : "Model Context Protocol specification"
count : 10
outputs :
answer : ${summarize.output}
sources : ${search.output}
Custom MCP Server
Call your own MCP server:
ensemble : data-processor
agents :
# Call custom tool
- name : process
operation : tools
config :
mcp : custom
tool : process_data
timeout : 30000
inputs :
data : ${input.raw_data}
format : json
outputs :
processed : ${process.output}
Common Patterns
ensemble : github-analysis
agents :
# Step 1: Get repository
- name : get-repo
operation : tools
config :
mcp : github
tool : get_repo
# Step 2: List issues
- name : list-issues
operation : tools
config :
mcp : github
tool : list_issues
# Step 3: Analyze issues
- name : analyze
operation : think
config :
provider : anthropic
model : claude-sonnet-4
prompt : |
Repository: ${get-repo.output}
Issues: ${list-issues.output}
Analyze the project's issue trends.
inputs :
owner : anthropics
repo : anthropic-sdk-typescript
state : open
outputs :
analysis : ${analyze.output}
issue_count : ${list-issues.output.length}
ensemble : multi-source-search
agents :
# These run in parallel
- name : search-brave
operation : tools
config :
mcp : brave
tool : web_search
- name : search-github
operation : tools
config :
mcp : github
tool : search_code
# Combine results
- name : synthesize
operation : think
config :
provider : anthropic
model : claude-sonnet-4
prompt : |
Brave Results: ${search-brave.output}
GitHub Results: ${search-github.output}
Question: ${input.question}
Synthesize a comprehensive answer.
inputs :
question : "How to implement MCP servers"
brave_query : "MCP server implementation guide"
github_query : "mcp server"
github_repo : "anthropics/model-context-protocol"
outputs :
answer : ${synthesize.output}
sources :
brave : ${search-brave.output}
github : ${search-github.output}
agents :
# Try primary tool
- name : search-primary
operation : tools
config :
mcp : brave
tool : web_search
# Fallback if primary fails
- name : search-fallback
condition : ${search-primary.failed}
operation : api
config :
url : https://api.duckduckgo.com/
method : GET
params :
q : ${input.query}
outputs :
results : ${search-primary.output || search-fallback.output}
source : ${search-primary.executed ? 'brave' : 'duckduckgo' }
agents :
- name : get-pr
operation : tools
config :
mcp : github
tool : get_pull_request
cacheDiscovery : true # Cache tool list from server
cacheTTL : 3600 # Cache for 1 hour
This is useful when:
MCP server’s tool list doesn’t change often
You want to reduce latency
You’re making many tool calls to the same server
Authentication
Bearer Token
// conductor.config.ts
mcpServers : {
github : {
url : 'https://github-mcp.example.com' ,
auth : {
type : 'bearer' ,
token : process . env . GITHUB_MCP_TOKEN
}
}
}
HTTP Request:
POST /tools/get_pull_request
Authorization : Bearer ghp_xxx
Content-Type : application/json
{
"name" : "get_pull_request" ,
"arguments" : { "owner" : "..." , "repo" : "..." , "pull_number" : 123 }
}
OAuth
// conductor.config.ts
mcpServers : {
custom : {
url : 'https://custom-mcp.example.com' ,
auth : {
type : 'oauth' ,
clientId : process . env . OAUTH_CLIENT_ID ,
clientSecret : process . env . OAUTH_CLIENT_SECRET ,
tokenUrl : 'https://auth.example.com/oauth/token'
}
}
}
OAuth flow:
Conductor requests token from tokenUrl using client credentials
Token is cached and reused for subsequent requests
Token is automatically refreshed when expired
HMAC Signature
For MCP servers that require signature verification:
mcpServers : {
secure : {
url : 'https://secure-mcp.example.com' ,
auth : {
type : 'bearer' ,
token : process . env . MCP_TOKEN
},
secret : process . env . MCP_SECRET // For signing requests
}
}
Request includes:
POST /tools/my_tool
Authorization : Bearer token_xxx
X-Conductor-Signature : sha256=abc123...
X-Conductor-Timestamp : 1705315200
Error Handling
agents :
- name : risky-tool
operation : tools
config :
mcp : external
tool : flaky_api
- name : handle-error
condition : ${risky-tool.failed}
operation : code
config :
script : scripts/handle-tool-error
// scripts/handle-tool-error.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function handleToolError ( context : AgentExecutionContext ) {
return {
error: true ,
message: 'Tool failed' ,
fallback_value: null
}
}
outputs :
result : ${risky-tool.output || handle-error.output}
success : ${risky-tool.success}
Tools can return isError: true even if the HTTP request succeeds:
agents :
- name : call-tool
operation : tools
config :
mcp : github
tool : get_file_contents
- name : check-result
operation : code
config :
script : scripts/check-tool-result
input :
tool_output : ${call-tool.output}
// scripts/check-tool-result.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function checkToolResult ( context : AgentExecutionContext ) {
const { tool_output } = context . input
if ( tool_output . isError ) {
throw new Error ( 'Tool returned error: ' + tool_output . content )
}
return tool_output . content
}
Timeout Handling
agents :
- name : slow-tool
operation : tools
config :
mcp : external
tool : slow_operation
timeout : 15000 # 15 second timeout
- name : use-cache
condition : ${slow-tool.failed}
operation : storage
config :
type : kv
action : get
key : cached-result
Testing
import { describe , it , expect } from 'vitest'
import { TestConductor } from '@ensemble-edge/conductor/testing'
describe ( 'github-pr-analyzer' , () => {
it ( 'should analyze pull request' , async () => {
const conductor = await TestConductor . create ({
mocks: {
tools: {
'github:get_pull_request' : {
number: 123 ,
title: 'Add new feature' ,
state: 'open' ,
user: { login: 'octocat' }
}
}
}
})
const result = await conductor . executeEnsemble ( 'github-pr-analyzer' , {
owner: 'anthropics' ,
repo: 'anthropic-sdk-typescript' ,
pull_number: 123
})
expect ( result ). toBeSuccessful ()
expect ( result . output . pr_data . number ). toBe ( 123 )
})
})
Integration Testing
// Test against real MCP server
describe ( 'github MCP integration' , () => {
it ( 'should call real GitHub MCP' , async () => {
const conductor = await TestConductor . create ({
projectPath: './conductor'
})
const result = await conductor . executeEnsemble ( 'github-pr-analyzer' , {
owner: 'anthropics' ,
repo: 'anthropic-sdk-typescript' ,
pull_number: 1
})
expect ( result ). toBeSuccessful ()
expect ( result . output . pr_data ). toHaveProperty ( 'number' )
})
})
Best Practices
1. Set Appropriate Timeouts
# Short timeout for fast tools
- name : cache-lookup
operation : tools
config :
mcp : cache-server
tool : get
timeout : 2000 # 2 seconds
# Long timeout for slow tools
- name : ai-analysis
operation : tools
config :
mcp : ai-server
tool : analyze
timeout : 60000 # 60 seconds
# Enable caching for stable tool lists
- name : frequent-tool
operation : tools
config :
mcp : github
tool : get_repo
cacheDiscovery : true
cacheTTL : 3600 # 1 hour
# Always check for failures
- name : tool-call
operation : tools
config :
mcp : external
tool : api_call
- name : verify
operation : code
config :
script : scripts/verify-tool-call
input :
tool_call_failed : ${tool-call.failed}
tool_output : ${tool-call.output}
// scripts/verify-tool-call.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function verifyToolCall ( context : AgentExecutionContext ) {
const { tool_call_failed , tool_output } = context . input
if ( tool_call_failed || tool_output . isError ) {
throw new Error ( 'Tool failed' )
}
return tool_output . content
}
# Define clear input schema
inputs :
owner :
type : string
required : true
description : GitHub repository owner
repo :
type : string
required : true
description : GitHub repository name
pull_number :
type : number
required : true
description : Pull request number
ensemble : github-pr-analyzer
description : Analyzes GitHub pull requests using MCP
agents :
- name : get-pr
description : Fetch pull request data from GitHub
operation : tools
config :
mcp : github
tool : get_pull_request
Limitations
HTTP Only : Conductor only supports HTTP transport for MCP. Stdio/subprocess MCP servers are not supported in Cloudflare Workers.
No Streaming : The current implementation doesn’t support streaming responses from MCP servers.
Tool Discovery : Tool discovery is performed on-demand. Use cacheDiscovery: true to cache the tool list.
OAuth Refresh : OAuth token refresh is automatic but requires the MCP server to support token refresh.
Next Steps
MCP Integration Complete MCP integration guide
Expose as MCP Expose ensembles as MCP tools
think Operation Combine tools with AI
Testing Test tool integrations