> ## 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.

# MCP Integration Guide

> Build bidirectional MCP integrations - consume external tools and expose ensembles as MCP tools. Complete guide to Conductor's Model Context Protocol integration.

## 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:

```typescript theme={null}
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

```bash theme={null}
# .dev.vars (local development)
GITHUB_MCP_TOKEN=ghp_xxx
BRAVE_API_KEY=BSAxxx
OAUTH_CLIENT_ID=client_xxx
OAUTH_CLIENT_SECRET=secret_xxx
```

```bash theme={null}
# 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

```yaml theme={null}
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

```yaml theme={null}
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

```yaml theme={null}
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**:

```typescript theme={null}
auth: {
  type: 'bearer',
  token: process.env.API_TOKEN
}
```

**OAuth**:

```typescript theme={null}
auth: {
  type: 'oauth',
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  tokenUrl: 'https://auth.example.com/oauth/token'
}
```

**HMAC Signature**:

```typescript theme={null}
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:

```yaml theme={null}
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

```bash theme={null}
# 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:

```bash theme={null}
# List all available tools
curl https://your-worker.workers.dev/mcp/tools \
  -H "Authorization: Bearer ${MCP_CLIENT_TOKEN}"
```

Response:

```json theme={null}
{
  "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

```bash theme={null}
# 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:

```json theme={null}
{
  "content": [
    {
      "type": "text",
      "text": "{\"review\": \"LGTM\", \"approved\": true}"
    }
  ],
  "isError": false
}
```

### 5. Use in Claude Desktop

Configure Claude Desktop to use your MCP server:

```json theme={null}
{
  "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

```typescript theme={null}
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)

```yaml theme={null}
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)

```yaml theme={null}
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:**

```bash theme={null}
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:**

```json theme={null}
// 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

```yaml theme={null}
# 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

```bash theme={null}
# Generate new tokens
wrangler secret put MCP_CLIENT_TOKEN_1

# Update clients
# Remove old tokens after migration
```

### 3. Use OAuth for Third-Party Access

```yaml theme={null}
trigger:
  - type: mcp
    auth:
      type: oauth
      issuer: https://auth.example.com
      audience: https://api.example.com
```

### 4. Monitor Token Usage

```yaml theme={null}
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)

```typescript theme={null}
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)

```typescript theme={null}
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:

```yaml theme={null}
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:

```yaml theme={null}
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:

```yaml theme={null}
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

```yaml theme={null}
# Enable caching to reduce discovery calls
agents:
  - name: tool-call
    operation: tools
    config:
      mcp: github
      tool: get_repo
      cacheDiscovery: true
      cacheTTL: 3600
```

### Authentication Errors

```bash theme={null}
# 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

```yaml theme={null}
# 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:

```bash theme={null}
wrangler tail --format pretty
```

## Next Steps

<CardGroup cols={2}>
  <Card title="tools Operation" icon="wrench" href="/conductor/operations/tools">
    Detailed tools operation reference
  </Card>

  <Card title="Webhooks" icon="webhook" href="/api/http/webhooks">
    Inbound webhook integration
  </Card>

  <Card title="Notifications" icon="bell" href="/conductor/core-concepts/notifications">
    Outbound webhook notifications
  </Card>

  <Card title="Security" icon="shield" href="/api/http/authentication">
    Authentication best practices
  </Card>
</CardGroup>
