Skip to main content
Glama
README.md
# odh-mcp-server

A [FastMCP](https://github.com/jlowin/fastmcp) server that exposes hospitality data — accommodations, events, and gastronomy — through a unified MCP interface. Part of the **GaiaWM ecosystem**, it is open source, self-hostable, and designed to work both locally (stdio) and remotely (SSE / streamable-http).

The server maintains a **"current world"** in session context. A `set_world` tool switches the active world. All other tools query the current world transparently. The same `find_accommodations` call returns real hotel data from South Tyrol or fictional inn listings from a Spelljammer asteroid city.

---

## Quickstart

```bash
# Install with uv (recommended)
pip install uv

# Run locally via stdio (Claude Desktop, etc.)
uvx odh-mcp-server --stdio

# Or clone and run directly
git clone https://github.com/openfantasymap/odh-mcp-server
cd odh-mcp-server
uv run server.py --stdio
```

---

## Available worlds

| World ID    | Name               | Data source                        | Notes                          |
|-------------|--------------------|------------------------------------|--------------------------------|
| `earth-313` | South Tyrol        | Open Data Hub (`opendatahub.com`)  | Real hotels, events, restaurants. WGS84 coordinates. |
| `bral`      | The Rock of Bral   | JSON fixtures                      | Spelljammer city-asteroid. Mock data for demo/roleplay. |

---

## Tools

| Tool                  | Description                                                         |
|-----------------------|---------------------------------------------------------------------|
| `set_world`           | Switch the active world. Returns world description on success.      |
| `describe_world`      | Describe the current world and how to query it.                     |
| `find_accommodations` | Search for hotels / inns with optional geo, feature, and date filters. |
| `find_events`         | Search for events with optional geo, date, and topic filters.       |
| `find_gastronomy`     | Search for restaurants and food places with geo and cuisine filters. |

---

## Example session

```
# 1. Start in South Tyrol
set_world("earth-313")
→ World set to: South Tyrol (earth-313)
  Real hospitality data from the Open Data Hub...

# 2. Find hotels near Bolzano with a pool
find_accommodations(near="Bolzano centro", features=["pool"], max_results=3)
→ Found 2 accommodations in South Tyrol (earth-313), near Bolzano centro...

# 3. Switch to the Rock of Bral
set_world("bral")
→ World set to: The Rock of Bral
  A city-asteroid drifting through Wildspace...

# 4. Find an inn near the Great Market
find_accommodations(near="great market", radius_m=800)
→ Found 1 inn in the Rock of Bral, near great market...
  1. The Raised Cup [inn] (budget) — A lively taproom in the lower city...
```

---

## Claude Desktop config

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "mcpServers": {
    "odh-world": {
      "command": "uv",
      "args": ["run", "/path/to/odh-mcp-server/server.py", "--stdio"]
    }
  }
}
```

Or, once published to PyPI:

```json
{
  "mcpServers": {
    "odh-world": {
      "command": "uvx",
      "args": ["odh-mcp-server", "--stdio"]
    }
  }
}
```

---

## Session context notes

The server uses `contextvars.ContextVar` to track the current world per session.

- **SSE / streamable-http transport**: each client connection gets its own context — multiple users can be in different worlds simultaneously.
- **stdio transport**: single process, single context — fine for local single-user use (Claude Desktop, CLI).

---

## Adding a new world

1. Create `worlds/myworld.py` and implement the `WorldAdapter` abstract base class:

```python
from worlds.base import WorldAdapter, Accommodation, Event, GastronomyPlace, WorldInfo

class MyWorldAdapter(WorldAdapter):
    async def find_accommodations(self, ...) -> list[Accommodation]: ...
    async def find_events(self, ...) -> list[Event]: ...
    async def find_gastronomy(self, ...) -> list[GastronomyPlace]: ...
    async def describe(self) -> WorldInfo: ...
```

2. Register it in `worlds/__init__.py`:

```python
from .myworld import MyWorldAdapter

WORLD_REGISTRY = {
    "earth-313": Earth313Adapter(),
    "bral": BralAdapter(),
    "myworld": MyWorldAdapter(),   # add this line
}
```

3. That's it. All tools will automatically support your new world once it's in the registry.

---

## Configuration

Copy `.env.example` to `.env` and adjust as needed:

```env
DEFAULT_WORLD=earth-313
ODH_BASE_URL=https://tourism.api.opendatahub.com/v1
ODH_TIMEOUT=10
NOMINATIM_URL=https://nominatim.openstreetmap.org
NOMINATIM_USER_AGENT=odh-mcp-server/0.1 (opensource hospitality MCP)
```

---

## License

MIT

TDQS

A4.4/5.0

Scored across 5 tools

Disambiguation5/5

Each tool targets a distinct operation: set_world establishes context, describe_world explains it, and the three find_* tools query for accommodations, events, and gastronomy separately. Even though they share parameters like near and radius_m, the entity types are clearly different, so an agent can easily select the right tool.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case: set_world, describe_world, find_accommodations, find_events, find_gastronomy. The verbs (set, describe, find) are clear and the nouns are specific, making the pattern predictable.

Tool Count5/5

With 5 tools, the server is well-scoped for its purpose of exploring hospitality and tourism data in a world context. Each tool earns its place: world management (set, describe) and three distinct search types (accommodation, events, gastronomy). No redundancy or bloat.

Completeness5/5

The server's domain is read-only exploration of world-based hospitality data, and it covers the core workflow: set the world to establish context, understand what data is available via describe_world, and then search for the three primary POI types. There are no obvious dead ends, and all CRUD operations beyond reading are outside the stated purpose.

Maintenance

ActivityInactive
ResponsivenessNo issues