Skip to main content
Glama
senzg
by senzg
README.md
# mcp-tellus-search

A Model Context Protocol (MCP) server that exposes Skyscanner's Tellus (Travel API v2) to Claude.

---

## What is MCP?

MCP (Model Context Protocol) is a protocol that lets Claude call external tools. This project packages Tellus API functions as MCP tools so Claude can search geo entities (cities, airports, hotels, etc.) directly during a conversation.

When Claude is connected to this MCP server, it can:
- Look up entities by ID
- Search entities by type or location
- Fetch child entities of a parent (e.g. all hotels in a city)
- Find nearby entities by radius
- Get suggested airports for a city

---

## How it works

### Architecture

```
Claude Code
    │
    │  stdio (JSON messages)
    ▼
Docker Container  ←── mcp.run() blocks here, waiting for requests
    │
    └── python src/server.py
            │
            └── FastMCP registers all @mcp.tool() functions
                    │
                    └── requests → Tellus API (gateway.skyscanner.net)
```

### Key concepts

- `@mcp.tool()` decorator registers a function as an MCP tool. Claude sees the function name, docstring, and type annotations — it never sees the raw source code.
- `mcp.run()` starts the server and blocks, listening on stdin. The container stays alive for the entire Claude session (one container per session).
- The container is started automatically by Claude Code when it reads `.mcp.json`, and destroyed on exit (`--rm`).

---

## Project structure

```
.
├── Dockerfile          # Packages the server into a Docker image
├── .mcp.json           # Tells Claude Code how to launch the server
├── requirements.txt    # Python dependencies installed inside the image
└── src/
    └── server.py       # MCP tools + Tellus API helper functions
```

---

## Available tools

| Tool | Description |
|---|---|
| `get_entities` | Fetch entities by ID list (up to 200) via GET /v2/entities |
| `search_entities` | Search entities by type/ID with optional name filter via POST /v2/search |
| `fetch_children` | Fetch child entities of a parent, auto-selects filter strategy by type |
| `fetch_nearby_entities` | Fetch entities within a radius of an entity or lat/lon |
| `get_entity_type` | Resolve the type of a single entity |
| `get_suggested_airports` | Get suggested airports for a city (deprecated endpoint, limited coverage) |
| `fetch_children_parallel` | `fetch_children` for multiple parents in parallel |
| `fetch_nearby_entities_parallel` | `fetch_nearby_entities` for multiple locations in parallel |
| `search_entities_parallel` | `search_entities` for multiple queries in parallel |

### Filter strategy by entity type

| Entity type | Filter used |
|---|---|
| Hotel, Airport, CarHireOffice | `relations.geopolitical_parents:containsAny` |
| TouristAttraction, MetroStation, District, City | `hierarchy:isChildOf` |

---

## Build and deploy

### Prerequisites

- Docker Desktop installed and running
- Claude Code

### 1. Build the image

```bash
cd /path/to/this/project
docker build -t mcp-tellus-search .
```

`docker build` executes all `RUN` instructions in the Dockerfile (installs dependencies, copies source). The resulting image is stored in your local Docker registry.

`docker run` is what actually starts the container and triggers `CMD ["python", "src/server.py"]`.

### 2. Configure Claude Code

**Project-level** (only active when Claude Code is opened in this directory):

`.mcp.json` is already configured:
```json
{
  "mcpServers": {
    "mcp-tellus-search": {
      "command": "docker",
      "args": ["run", "--rm", "-i", "mcp-tellus-search"]
    }
  }
}
```

**Global** (active in any directory):

Add to `~/.claude.json` under `mcpServers`:
```json
"mcp-tellus-search": {
  "type": "stdio",
  "command": "docker",
  "args": ["run", "--rm", "-i", "mcp-tellus-search"]
}
```

### 3. Restart Claude Code

Run `/mcp` to verify the server shows as connected.

---

## How Claude Code finds .mcp.json

Claude Code scans upward from the current working directory for `.mcp.json`, the same way git looks for `.git`. It also loads global MCP servers from `~/.claude.json`. Project-level config takes precedence over global config for servers with the same name.

---

## Distributing the image

To share this server with others without requiring them to build from source:

```bash
# Push to Docker Hub
docker push your-username/mcp-tellus-search

# Others only need .mcp.json pointing to the image name
# Docker pulls it automatically on first run
```

This is the key advantage of Docker packaging: the recipient needs no Python, no pip install — just Docker and the `.mcp.json` config file.

---

## Tellus API reference

- Base URL: `https://gateway.skyscanner.net/travel-api/v2`
- Docs: [Travel API v2 User Guide](https://skyscanner.atlassian.net/wiki/spaces/AS/pages/154286038/Travel+API+v2+User+Guide)
- Use `geometry_centroid` instead of `geometry` (per API guidelines)
- Parallel fetch functions use `ThreadPoolExecutor` with `MAX_WORKERS=10` (~8x speedup)
- Do not use `requests.Session()` in parallel code — use plain `requests.post()` which is thread-safe