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

# tools Operation

> Invoke external MCP tools over HTTP

**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

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

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

### Input

The agent input becomes the tool's `arguments`:

```yaml theme={null}
inputs:
  owner: anthropics
  repo: anthropic-sdk-typescript
  pull_number: 123
```

Becomes:

```json theme={null}
{
  "name": "get_pull_request",
  "arguments": {
    "owner": "anthropics",
    "repo": "anthropic-sdk-typescript",
    "pull_number": 123
  }
}
```

### Output

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

### 1. Configure MCP Servers

Create `conductor.config.ts`:

```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
    },

    // Public MCP Server (no auth)
    public: {
      url: 'https://public-mcp.example.com'
    }
  }
}

export default config
```

### 2. Set Environment Variables

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

```toml theme={null}
# wrangler.toml (for production)
[vars]
# Public variables (non-sensitive)

[[env.production.vars]]
# Production secrets via wrangler secret
```

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

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

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

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

### Sequential Tool Calls

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

### Parallel Tool Calls

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

### Tool with Fallback

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

### Caching Tool Discovery

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

```typescript theme={null}
// conductor.config.ts
mcpServers: {
  github: {
    url: 'https://github-mcp.example.com',
    auth: {
      type: 'bearer',
      token: process.env.GITHUB_MCP_TOKEN
    }
  }
}
```

HTTP Request:

```http theme={null}
POST /tools/get_pull_request
Authorization: Bearer ghp_xxx
Content-Type: application/json

{
  "name": "get_pull_request",
  "arguments": { "owner": "...", "repo": "...", "pull_number": 123 }
}
```

### OAuth

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

1. Conductor requests token from `tokenUrl` using client credentials
2. Token is cached and reused for subsequent requests
3. Token is automatically refreshed when expired

### HMAC Signature

For MCP servers that require signature verification:

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

```http theme={null}
POST /tools/my_tool
Authorization: Bearer token_xxx
X-Conductor-Signature: sha256=abc123...
X-Conductor-Timestamp: 1705315200
```

## Error Handling

### Tool Invocation Errors

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

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

```yaml theme={null}

outputs:
  result: ${risky-tool.output || handle-error.output}
  success: ${risky-tool.success}
```

### Check Tool Errors

Tools can return `isError: true` even if the HTTP request succeeds:

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

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

```yaml theme={null}
```

### Timeout Handling

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

### Mock MCP Tools

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

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

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

### 2. Cache Tool Discovery

```yaml theme={null}
# Enable caching for stable tool lists
- name: frequent-tool
  operation: tools
  config:
    mcp: github
    tool: get_repo
    cacheDiscovery: true
    cacheTTL: 3600  # 1 hour
```

### 3. Handle Tool Failures

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

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

```yaml theme={null}
```

### 4. Use Typed Inputs

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

### 5. Document Tool Usage

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

<CardGroup cols={2}>
  <Card title="MCP Integration" icon="plug" href="/conductor/building/tools-mcp-integration">
    Complete MCP integration guide
  </Card>

  <Card title="Expose as MCP" icon="share" href="/conductor/building/tools-mcp-integration#part-2-exposing-ensembles-as-mcp-tools">
    Expose ensembles as MCP tools
  </Card>

  <Card title="think Operation" icon="brain" href="/conductor/operations/think">
    Combine tools with AI
  </Card>

  <Card title="Testing" icon="vial" href="/conductor/building/testing-observability">
    Test tool integrations
  </Card>
</CardGroup>
