# eSpeakers MCP Server

A remote **Model Context Protocol (MCP)** server for finding keynote speakers, checking their availability, and requesting bookings from the eSpeakers marketplace. Any MCP-compatible AI client can connect and use the tools — **no account or API key required**.

---

## Endpoint

```
POST https://balboa.espeakers.com/mcp
```

- **Transport:** Streamable HTTP, JSON-RPC 2.0
- **Authentication:** none (public)
- **Rate limiting:** per client IP (see [Limits](#limits))

The server implements the standard MCP methods: `initialize`, `tools/list`, `tools/call`, and `ping`.

---

## Connecting

### MCP-native clients (Claude Desktop, Cursor, etc.)

Add it as a custom **remote** MCP server (Streamable HTTP transport) pointing at the endpoint above. A typical client config looks like:

```json
{
  "mcpServers": {
    "espeakers": {
      "url": "https://balboa.espeakers.com/mcp"
    }
  }
}
```

### Claude API (MCP connector)

```python
import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    betas=["mcp-client-2025-11-20"],
    mcp_servers=[{
        "type": "url",
        "name": "espeakers",
        "url": "https://balboa.espeakers.com/mcp",
    }],
    messages=[{
        "role": "user",
        "content": "Find me a leadership speaker under $10,000 and check if they're free on 2026-09-20.",
    }],
)
print(response)
```

### Quick test with the MCP Inspector

```bash
npx @modelcontextprotocol/inspector --cli https://balboa.espeakers.com/mcp --method tools/list
```

---

## Tools

The server exposes three tools. Every `tools/call` response returns its payload as a JSON **string** inside `result.content[0].text` (see [Response format](#response-format)).

### 1. `searchSpeakers`

Search the marketplace for speakers. All arguments are optional; omit an argument to leave that filter unconstrained.

| Argument | Type | Description |
|----------|------|-------------|
| `topic` | string | A topic/expertise keyword, e.g. `"Leadership"` |
| `budget_max` | number | Maximum speaking fee in USD |
| `event_date` | string | Event date in `YYYY-MM-DD` — results are filtered to speakers available that day |

Results are ordered by relevance and **capped at 12 speakers**. When `event_date` is supplied, every returned speaker is already confirmed available that day (no need to also call `checkSpeakerAvailability` on them).

**Example call**
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "searchSpeakers",
    "arguments": { "topic": "Leadership", "budget_max": 10000 }
  }
}
```

**Example result** (the `text` field parsed as JSON — truncated to two speakers):
```json
{
  "count": 11,
  "speakers": [
    {
      "id": 5160,
      "name": "Shawna Schuh",
      "fee_low": 7500,
      "fee_high": 10000,
      "currency": "USD",
      "topics": ["Presentation Skills", "Etiquette", "Leadership", "Communication", "Associations", "Sales", "Motivation"],
      "location": "Gaston, OR, US",
      "thumbnail_url": "https://streamer.espeakers.com/assets/0/5160/160344.jpg",
      "profile_slug": "shawna-schuh"
    },
    {
      "id": 24901,
      "name": "Gobinder Gill",
      "fee_low": 2500,
      "fee_high": 5000,
      "currency": "USD",
      "topics": ["Workplace Respect", "Diversity", "Leadership", "Human Resources"],
      "location": "Vancouver, BC, CANADA",
      "thumbnail_url": "https://streamer.espeakers.com/assets/1/24901/238622.jpg",
      "profile_slug": "gobinder-gill"
    }
  ]
}
```

#### Speaker object fields

| Field | Type | Notes |
|-------|------|-------|
| `id` | number | Speaker ID — pass to `checkSpeakerAvailability` / `initiateBookingRequest` |
| `name` | string | Full name |
| `fee_low` | number | Low end of the fee range, USD. `0` means unspecified |
| `fee_high` | number | High end of the fee range, USD. `0` means unspecified / no published ceiling |
| `currency` | string \| null | Currency code, e.g. `"USD"`. **May be `null`** — default to USD |
| `topics` | string[] | Up to 10 topic labels. May be empty |
| `location` | string | `"City, State, Country"`. **May be an empty string** |
| `thumbnail_url` | string \| null | Absolute headshot URL. May be `null` |
| `profile_slug` | string | Slug identifying the speaker's marketplace profile |

### 2. `checkSpeakerAvailability`

Check whether a speaker is free on a specific date.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `speaker_id` | string | Yes | Speaker ID |
| `date` | string | Yes | Date to check, `YYYY-MM-DD` |

**Example call**
```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "checkSpeakerAvailability",
    "arguments": { "speaker_id": "9426", "date": "2026-09-20" }
  }
}
```

**Example result** (parsed):
```json
{
  "speaker_id": 9426,
  "date": "2026-09-20",
  "available": true,
  "conflicts": []
}
```

When the speaker has a conflict, `available` is `false` and `conflicts` contains the conflicting event(s).

### 3. `initiateBookingRequest`

Submit a booking request. This **sends an email to the eSpeakers sales team**, who follow up directly — it does not confirm a booking automatically. Subject to a stricter rate limit than the other tools.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `speaker_id` | string | Yes | Speaker ID to book |
| `event_date` | string | Yes | Event date, `YYYY-MM-DD` |
| `contact_name` | string | Yes | Full name of the contact person |
| `contact_email` | string | Yes | Email for the eSpeakers team to reply to |
| `contact_phone` | string | No | Contact phone number |
| `delivery_method` | string | Yes | `"onsite"` or `"virtual"` |
| `location` | string | Yes | City and state/country of the event |

**Example result** (parsed):
```json
{
  "success": true,
  "message": "Booking request sent successfully.",
  "speaker_id": 1234,
  "speaker_name": "Jane Smith"
}
```

---

## Response format

Tool payloads follow the MCP convention: the result is a `content` array whose first item is a text block containing the payload as a **JSON-encoded string**. Parse `content[0].text` to get the object documented above. A full envelope looks like:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      { "type": "text", "text": "{\"speaker_id\":9426,\"date\":\"2026-09-20\",\"available\":true,\"conflicts\":[]}" }
    ]
  }
}
```

**Tool errors** (e.g. bad input) return a normal result with `"isError": true` and a human-readable message in the text block, rather than a JSON-RPC error. Protocol-level problems (unknown method, malformed request) return a standard JSON-RPC `error` object.

---

## Limits

The server is public and rate-limited **per client IP** to keep it healthy:

- General tool calls: roughly **30 per minute**.
- `initiateBookingRequest`: a small number **per hour**.

Exceeding a limit returns a tool result with `"isError": true` and a "Rate limit exceeded" message. Limits are subject to change.

---

## Legacy REST API

Before the MCP server existed, these capabilities were exposed as plain REST endpoints, described by `https://balboa.espeakers.com/mcp/context.json`:

- `GET /mcp/search-speakers`
- `GET /mcp/speaker/{id}`
- `GET /mcp/speaker-availability/{id}?date=YYYY-MM-DD`
- `POST /mcp/request-booking`

These still work for anyone who integrated against them directly, but new integrations should use the MCP server above.

---

## Support

Questions or issues: [tech@espeakers.com](mailto:sales@espeakers.com)
