Skip to main content

What is MCP?

Model Context Protocol (MCP) is Anthropic’s open protocol for connecting AI systems to external tools and data sources. With Cloudflare’s announcement of MCP support (November 2024), Conductor implements HTTP-only MCP for secure, scalable tool integration on the edge.

Why MCP?

  • Standardized - Open protocol for tool integration
  • Secure - Built-in auth (bearer, OAuth, HMAC)
  • Scalable - HTTP-based, works on edge
  • Interoperable - Works with any MCP-compatible system

Bidirectional MCP

Conductor supports MCP in both directions:
  1. Outbound (Consumer) - Call external MCP servers using the tools operation
  2. Inbound (Provider) - Expose ensembles as MCP tools via /mcp endpoints
┌─────────────────────────────────────────────────┐
│                                                 │
│  External MCP Servers (GitHub, Brave, Custom)  │
│                                                 │
└────────────────┬────────────────────────────────┘

                 │ HTTP (tools operation)

┌─────────────────────────────────────────────────┐
│                                                 │
│            Conductor Ensembles                  │
│                                                 │
└────────────────┬────────────────────────────────┘

                 │ HTTP (/mcp endpoints)

┌─────────────────────────────────────────────────┐
│                                                 │
│  External Systems (Claude Desktop, AI Apps)     │
│                                                 │
└─────────────────────────────────────────────────┘

Part 1: Using External MCP Tools

Call external MCP servers from your ensembles.

1. Configure MCP Servers

Create conductor.config.ts in your project root:
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
    }
  }
}

export default config

2. Set Environment Variables

# .dev.vars (local development)
GITHUB_MCP_TOKEN=ghp_xxx
BRAVE_API_KEY=BSAxxx
OAUTH_CLIENT_ID=client_xxx
OAUTH_CLIENT_SECRET=secret_xxx
# Production (Cloudflare Workers)
wrangler secret put GITHUB_MCP_TOKEN
wrangler secret put BRAVE_API_KEY
wrangler secret put OAUTH_CLIENT_SECRET

3. Use Tools in Ensembles

ensemble: github-pr-analyzer

agents:
  # Call GitHub MCP tool
  - name: get-pr
    operation: tools
    config:
      mcp: github              # Server name from config
      tool: get_pull_request   # Tool name
      timeout: 10000

  # Analyze with AI
  - name: analyze
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Pull Request Data:
        ${get-pr.output}

        Provide a code review.

inputs:
  owner: anthropics
  repo: anthropic-sdk-typescript
  pull_number: 123

outputs:
  review: ${analyze.output}
  pr_data: ${get-pr.output}

Examples: Common MCP Servers

GitHub MCP

agents:
  - name: get-pr
    operation: tools
    config:
      mcp: github
      tool: get_pull_request

  - name: list-files
    operation: tools
    config:
      mcp: github
      tool: list_pull_request_files

  - name: get-file
    operation: tools
    config:
      mcp: github
      tool: get_file_contents

inputs:
  owner: anthropics
  repo: anthropic-sdk-typescript
  pull_number: 123
  file_path: src/index.ts

Brave Search MCP

agents:
  - name: search
    operation: tools
    config:
      mcp: brave
      tool: web_search

  - name: summarize
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Search Results: ${search.output}
        Question: ${input.question}

        Provide a comprehensive answer.

inputs:
  query: "Model Context Protocol specification"
  count: 10

Authentication Options

Bearer Token:
auth: {
  type: 'bearer',
  token: process.env.API_TOKEN
}
OAuth:
auth: {
  type: 'oauth',
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  tokenUrl: 'https://auth.example.com/oauth/token'
}
HMAC Signature:
auth: {
  type: 'bearer',
  token: process.env.API_TOKEN
},
secret: process.env.API_SECRET  // For signing requests

Part 2: Exposing Ensembles as MCP Tools

Make your ensembles available as MCP tools to external systems.

1. Configure MCP Exposure

Add expose configuration to your ensemble:
ensemble: github-pr-reviewer

trigger:
  - type: mcp
    public: false
    auth:
      type: bearer
      tokens:
        - ${env.MCP_CLIENT_TOKEN_1}
        - ${env.MCP_CLIENT_TOKEN_2}

agents:
  - name: review
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Review this pull request:
        Owner: ${input.owner}
        Repo: ${input.repo}
        PR: ${input.pull_number}

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

outputs:
  review: ${review.output}
  approved: ${review.output.approved}

2. Deploy to Cloudflare Workers

# Deploy your ensemble
wrangler deploy

# Set secrets
wrangler secret put MCP_CLIENT_TOKEN_1
wrangler secret put MCP_CLIENT_TOKEN_2

3. Discover Available Tools

External systems can discover your tools:
# List all available tools
curl https://your-worker.workers.dev/mcp/tools \
  -H "Authorization: Bearer ${MCP_CLIENT_TOKEN}"
Response:
{
  "tools": [
    {
      "name": "github-pr-reviewer",
      "description": "Reviews GitHub pull requests",
      "inputSchema": {
        "type": "object",
        "properties": {
          "owner": {
            "type": "string",
            "description": "GitHub repository owner"
          },
          "repo": {
            "type": "string",
            "description": "GitHub repository name"
          },
          "pull_number": {
            "type": "number",
            "description": "Pull request number"
          }
        },
        "required": ["owner", "repo", "pull_number"]
      }
    }
  ]
}

4. Invoke Tools

# Call the tool
curl https://your-worker.workers.dev/mcp/tools/github-pr-reviewer \
  -H "Authorization: Bearer ${MCP_CLIENT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "github-pr-reviewer",
    "arguments": {
      "owner": "anthropics",
      "repo": "anthropic-sdk-typescript",
      "pull_number": 123
    }
  }'
Response:
{
  "content": [
    {
      "type": "text",
      "text": "{\"review\": \"LGTM\", \"approved\": true}"
    }
  ],
  "isError": false
}

5. Use in Claude Desktop

Configure Claude Desktop to use your MCP server:
{
  "mcpServers": {
    "conductor-tools": {
      "url": "https://your-worker.workers.dev/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_TOKEN"
      }
    }
  }
}
Now Claude can use your ensembles as tools!

Complete Example: Bidirectional Integration

Build a system that both consumes and provides MCP tools:

Setup: conductor.config.ts

import type { ConductorConfig } from '@ensemble-edge/conductor'

const config: ConductorConfig = {
  // Outbound: External MCP servers we consume
  mcpServers: {
    github: {
      url: 'https://github-mcp.example.com',
      auth: {
        type: 'bearer',
        token: process.env.GITHUB_MCP_TOKEN
      }
    },
    brave: {
      url: 'https://brave-search-mcp.example.com',
      auth: {
        type: 'bearer',
        token: process.env.BRAVE_API_KEY
      }
    }
  }
}

export default config

Ensemble 1: Research Assistant (Consumes MCP Tools)

ensemble: research-assistant

# This ensemble uses external MCP tools
agents:
  # Use Brave MCP for web search
  - name: search-web
    operation: tools
    config:
      mcp: brave
      tool: web_search

  # Use GitHub MCP for code search
  - name: search-code
    operation: tools
    config:
      mcp: github
      tool: search_code

  # Synthesize results
  - name: synthesize
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Web Results: ${search-web.output}
        Code Results: ${search-code.output}
        Question: ${input.question}

        Provide a comprehensive answer.

inputs:
  question: string
  repo: string

outputs:
  answer: ${synthesize.output}
  sources:
    web: ${search-web.output}
    code: ${search-code.output}

Ensemble 2: PR Analyzer (Exposed as MCP Tool)

ensemble: pr-analyzer

# This ensemble is exposed as an MCP tool
trigger:
  - type: mcp
    public: false
    auth:
      type: bearer
      tokens:
        - ${env.MCP_CLIENT_TOKEN}

# Uses external GitHub MCP internally
agents:
  - name: get-pr
    operation: tools
    config:
      mcp: github
      tool: get_pull_request

  - name: get-files
    operation: tools
    config:
      mcp: github
      tool: list_pull_request_files

  - name: analyze
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        PR: ${get-pr.output}
        Files: ${get-files.output}

        Analyze this PR and provide feedback.

inputs:
  owner:
    type: string
    required: true
  repo:
    type: string
    required: true
  pull_number:
    type: number
    required: true

outputs:
  analysis: ${analyze.output}
  file_count: ${get-files.output.length}

Usage

1. Use research-assistant ensemble directly:
curl -X POST https://your-worker.workers.dev/api/v1/execute/ensemble/research-assistant \
  -H "X-API-Key: ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "question": "How to implement MCP servers?",
      "repo": "anthropics/model-context-protocol"
    }
  }'
2. Use pr-analyzer as MCP tool from Claude Desktop:
// Claude Desktop config
{
  "mcpServers": {
    "conductor": {
      "url": "https://your-worker.workers.dev/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_TOKEN"
      }
    }
  }
}
Then in Claude Desktop:
Analyze PR #123 in anthropics/anthropic-sdk-typescript
Claude will automatically call your pr-analyzer tool!

Authentication Best Practices

1. Use Default-Deny Security

# GOOD: Explicit auth required
trigger:
  - type: mcp
    auth:
      type: bearer
      tokens: [${env.TOKEN}]

# GOOD: Explicitly public
trigger:
  - type: mcp
    public: true

# BAD: No auth, not marked public (will fail)
trigger:
  - type: mcp  # Error!

2. Rotate Tokens Regularly

# Generate new tokens
wrangler secret put MCP_CLIENT_TOKEN_1

# Update clients
# Remove old tokens after migration

3. Use OAuth for Third-Party Access

trigger:
  - type: mcp
    auth:
      type: oauth
      issuer: https://auth.example.com
      audience: https://api.example.com

4. Monitor Token Usage

notifications:
  - type: webhook
    url: https://api.example.com/webhooks/security
    events:
      - execution.started
      - execution.failed
    secret: ${env.WEBHOOK_SECRET}

Testing MCP Integration

Test Outbound (Consuming Tools)

import { describe, it, expect } from 'vitest'
import { TestConductor } from '@ensemble-edge/conductor/testing'

describe('research-assistant', () => {
  it('should use MCP tools', async () => {
    const conductor = await TestConductor.create({
      mocks: {
        tools: {
          'brave:web_search': {
            results: [
              { title: 'MCP Docs', url: 'https://modelcontextprotocol.io' }
            ]
          },
          'github:search_code': {
            items: [
              { path: 'src/mcp/server.ts', repository: { full_name: 'user/repo' } }
            ]
          }
        }
      }
    })

    const result = await conductor.executeEnsemble('research-assistant', {
      question: 'How to implement MCP?',
      repo: 'anthropics/model-context-protocol'
    })

    expect(result).toBeSuccessful()
    expect(result.output.answer).toBeDefined()
  })
})

Test Inbound (Exposed Tools)

describe('pr-analyzer MCP endpoint', () => {
  it('should list available tools', async () => {
    const response = await fetch('http://localhost:8787/mcp/tools', {
      headers: {
        'Authorization': `Bearer ${process.env.MCP_CLIENT_TOKEN}`
      }
    })

    const data = await response.json()
    expect(data.tools).toContainEqual(
      expect.objectContaining({
        name: 'pr-analyzer',
        description: expect.any(String)
      })
    )
  })

  it('should invoke tool', async () => {
    const response = await fetch('http://localhost:8787/mcp/tools/pr-analyzer', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.MCP_CLIENT_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'pr-analyzer',
        arguments: {
          owner: 'anthropics',
          repo: 'anthropic-sdk-typescript',
          pull_number: 1
        }
      })
    })

    const data = await response.json()
    expect(data.content).toBeDefined()
    expect(data.isError).toBe(false)
  })
})

Common Patterns

Pattern 1: Aggregator Tool

Combine multiple MCP tools into one:
ensemble: multi-source-search

trigger:
  - type: mcp
    auth:
      type: bearer
      tokens: [${env.MCP_TOKEN}]

agents:
  - name: search-brave
    operation: tools
    config:
      mcp: brave
      tool: web_search

  - name: search-github
    operation: tools
    config:
      mcp: github
      tool: search_code

  - name: combine
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Combine these search results:
        Web: ${search-brave.output}
        Code: ${search-github.output}

inputs:
  query: string

outputs:
  combined: ${combine.output}

Pattern 2: Transform Tool

Add AI-powered transformations:
ensemble: smart-pr-reviewer

trigger:
  - type: mcp
    auth:
      type: bearer
      tokens: [${env.MCP_TOKEN}]

agents:
  - name: fetch-pr
    operation: tools
    config:
      mcp: github
      tool: get_pull_request

  - name: analyze-sentiment
    operation: think
    config:
      provider: anthropic
      model: claude-haiku-4
      prompt: "Analyze sentiment: ${fetch-pr.output.body}"

  - name: check-conventions
    operation: think
    config:
      provider: anthropic
      model: claude-haiku-4
      prompt: "Check if follows conventions: ${fetch-pr.output.title}"

  - name: generate-summary
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        PR: ${fetch-pr.output}
        Sentiment: ${analyze-sentiment.output}
        Conventions: ${check-conventions.output}

        Generate comprehensive review.

outputs:
  review: ${generate-summary.output}

Pattern 3: Chain Tools

Create workflows across MCP servers:
ensemble: research-to-pr

trigger:
  - type: mcp
    auth:
      type: bearer
      tokens: [${env.MCP_TOKEN}]

agents:
  # Research phase
  - name: search
    operation: tools
    config:
      mcp: brave
      tool: web_search

  - name: synthesize
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: "Summarize: ${search.output}"

  # Create PR phase
  - name: create-branch
    operation: tools
    config:
      mcp: github
      tool: create_branch

  - name: create-pr
    operation: tools
    config:
      mcp: github
      tool: create_pull_request

outputs:
  research: ${synthesize.output}
  pr_url: ${create-pr.output.html_url}

Troubleshooting

Tool Discovery Fails

# Enable caching to reduce discovery calls
agents:
  - name: tool-call
    operation: tools
    config:
      mcp: github
      tool: get_repo
      cacheDiscovery: true
      cacheTTL: 3600

Authentication Errors

# Verify token is set
wrangler secret list

# Check token format
echo $GITHUB_MCP_TOKEN | wc -c  # Should be non-zero

# Test auth
curl https://github-mcp.example.com/tools \
  -H "Authorization: Bearer $GITHUB_MCP_TOKEN"

Timeout Issues

# Increase timeout for slow tools
config:
  mcp: external
  tool: slow_operation
  timeout: 60000  # 60 seconds

OAuth Token Refresh

OAuth tokens are automatically refreshed. Check logs:
wrangler tail --format pretty

Next Steps