Skip to main content
Glama
README.md
# ๐Ÿœ Gurume

[![PyPI](https://img.shields.io/pypi/v/gurume)](https://pypi.org/project/gurume/)
[![Python](https://img.shields.io/pypi/pyversions/gurume)](https://pypi.org/project/gurume/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/narumiruna/gurume/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-mkdocs--material-blue)](https://narumiruna.github.io/gurume/)

๐Ÿ“š **Documentation:** <https://narumiruna.github.io/gurume/>

**Gurume** is a Python library, CLI, TUI, and MCP server for discovering Japanese restaurants on [Tabelog](https://tabelog.com) โ€” Japan's largest restaurant review platform.

Search by area, cuisine, date, and party size; parse structured detail pages; and plug the same workflows directly into AI assistants via a FastMCP server.

## โœจ Features

- ๐Ÿฑ **Tri-Source Unified Search**: Concurrently query and normalize results across three industry-standard platforms: **Tabelog (้ฃŸในใƒญใ‚ฐ)**, **Recruit Hot Pepper Gourmet (ใƒ›ใƒƒใƒˆใƒšใƒƒใƒ‘ใƒผ ใ‚ฐใƒซใƒก)**, and **Google Maps Platform (Places API New)**.
- ๐ŸŽฏ **Filter by Amenities**: Filter for private dining rooms (ๅ€‹ๅฎค), all-you-can-drink (้ฃฒใฟๆ”พ้กŒ), all-you-can-eat (้ฃŸในๆ”พ้กŒ), non-smoking, and parking via Hot Pepper official API.
- ๐Ÿ” Search restaurants by area, keyword, cuisine, date, time, and party size
- ๐Ÿฃ Filter by 29 supported Japanese cuisine categories with stable Tabelog genre codes
- ๐Ÿ“„ Parse restaurant detail pages into structured review, menu, and course data
- โšก Use synchronous and asynchronous Python APIs
- ๐Ÿ–ฅ๏ธ Run an interactive TUI with area suggestions, keyword suggestions, and cuisine auto-detection
- ๐Ÿค– Start an MCP server with schema-first inputs and structured outputs for AI assistant integrations
- ๐Ÿ”’ Keep MCP responses typed with Pydantic models and Python APIs type-annotated

## ๐Ÿ“ฆ Installation

```bash
uv add gurume
```

Or with `pip`:

```bash
pip install gurume
```

For local development:

```bash
uv sync --dev
```

## ๐ŸŒ HTTP Configuration

No additional HTTP setup is required for normal use.
Gurume uses `curl_cffi` with Safari impersonation by default so its TLS fingerprint and generated browser headers remain consistent.

If Tabelog starts returning HTTP 403 for the default profile, set `GURUME_IMPERSONATE` before starting the CLI, TUI, MCP server, or Python process:

```bash
GURUME_IMPERSONATE=firefox gurume search --area ไธ‰้‡ --cuisine ใ™ใ็„ผใ
```

PowerShell users can set the same process environment variable before running Gurume:

```powershell
$env:GURUME_IMPERSONATE = "firefox"
gurume search --area ไธ‰้‡ --cuisine ใ™ใ็„ผใ
```

The value must be an impersonation profile supported by the installed `curl_cffi` version.
An unsupported value is rejected by `curl_cffi`.

## ๐Ÿš€ Quick Start

### ๐Ÿ’ป CLI

The built-in CLI currently exposes five commands:

- `gurume search`
- `gurume map-search` (experimental geographic yakitori search)
- `gurume list-cuisines`
- `gurume tui`
- `gurume mcp`

Examples:

```bash
# Search by area and keyword
gurume search --area ๆฑไบฌ --keyword ๅฏฟๅธ

# Search with a precise cuisine filter
gurume search --area ไธ‰้‡ --cuisine ใ™ใ็„ผใ

# Change sort order and output format
gurume search --area ๅคง้˜ช --cuisine ใƒฉใƒผใƒกใƒณ --sort ranking --output json-list

# Return a structured JSON envelope for scripts and agents
gurume search --area ๅคง้˜ช --cuisine ใƒฉใƒผใƒกใƒณ --sort ranking --output json

# List all supported cuisines
gurume list-cuisines

# Start the TUI
gurume tui

# Start the MCP server
gurume mcp
```

Current `gurume search` options:

- `--area`, `-a`
- `--keyword`, `-k`
- `--cuisine`, `-c`
- `--sort`, `-s`: `ranking`, `review-count`, `new-open`, `standard`
- `--limit`, `-n`
- `--output`, `-o`: `table`, `json`, `json-envelope`, `json-list`, `simple`

Notes:

- For natural-language input, use the [`gurume-cli` agent skill](skills/gurume-cli/) with an AI assistant โ€” it decomposes free-form text into the structured flags above.
- `--output json` returns a structured envelope (`status`, `items`, `meta`, `applied_filters`, `warnings`, `error`). Use `--output json-list` for the legacy list-only JSON shape.
- CLI, TUI, and MCP diagnostics are in English; user-entered search terms and Tabelog restaurant data may remain in Japanese.
- Reservation filters, detail fetching, and page selection for `gurume search` are available in the Python API and MCP tools, but are not currently exposed as CLI flags.

Experimental map search uses explicit geographic bounds, not a prefecture ranking:

```bash
gurume map-search --min-lat 34.36 --max-lat 35.15 \
  --min-lon 135.85 --max-lon 137.25 --cuisine ็„ผใ้ณฅ --page 1 --limit 20 -o json
```

Only yakitori and upstream rating order are currently supported. Results can cross prefecture boundaries;
raw budget fields are not interpreted as lunch/dinner prices. See [Map search usage](docs/site/usage/map-search.md)
for pagination, structured outputs, and limitations.

### ๐Ÿ Python Library

#### Simple search

```python
from gurume import SortType
from gurume import query_restaurants

restaurants = query_restaurants(
    area="้Š€ๅบง",
    keyword="ๅฏฟๅธ",
    party_size=2,
    sort_type=SortType.RANKING,
)

for restaurant in restaurants[:3]:
    print(restaurant.name, restaurant.rating, restaurant.url)
```

#### Advanced search with filters

```python
from gurume import PriceRange
from gurume import RestaurantSearchRequest
from gurume import SortType

request = RestaurantSearchRequest(
    area="ๆธ‹่ฐท",
    keyword="็„ผ่‚‰",
    reservation_date="20250715",
    reservation_time="1900",
    party_size=4,
    sort_type=SortType.RANKING,
    price_range=PriceRange.DINNER_4000_5000,
    online_booking_only=True,
    has_private_room=True,
)

restaurants = request.search_sync()
print(f"Found {len(restaurants)} restaurants")
```

#### Async search with metadata

```python
import asyncio

from gurume import SearchRequest


async def main() -> None:
    request = SearchRequest(
        area="ๆ–ฐๅฎฟ",
        keyword="ๅฑ…้…’ๅฑ‹",
        max_pages=2,
        include_meta=True,
    )

    response = await request.search()
    print(response.status)
    print(response.meta.total_count if response.meta else None)

    for restaurant in response.restaurants[:5]:
        print(restaurant.name, restaurant.review_count)


asyncio.run(main())
```

#### Restaurant detail scraping

```python
from gurume import RestaurantDetailRequest

detail = RestaurantDetailRequest(
    restaurant_url="https://tabelog.com/tokyo/A1307/A130704/13053564/",
    fetch_reviews=True,
    fetch_menu=True,
    fetch_courses=True,
    max_review_pages=2,
).fetch_sync()

print(detail.restaurant.name)
print(len(detail.reviews), len(detail.menu_items), len(detail.courses))
```

Set `fetch_reviews=False`, `fetch_menu=False`, and `fetch_courses=False` to fetch only the base restaurant page
for address, station, phone, hours, closed days, and budget fields.

#### Cuisine helpers

```python
from gurume import get_all_genres
from gurume import get_genre_code

print(get_genre_code("ใ™ใ็„ผใ"))
print(get_all_genres()[:5])
```

## ๐Ÿค– MCP Server

Gurume ships a FastMCP server for AI assistants and other MCP-compatible clients.

### โš™๏ธ MCP Configuration

GitHub development version:

```json
{
  "mcpServers": {
    "gurume": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/narumiruna/gurume",
        "gurume",
        "mcp"
      ]
    }
  }
}
```

PyPI release:

```json
{
  "mcpServers": {
    "gurume": {
      "command": "uvx",
      "args": ["gurume", "mcp"]
    }
  }
}
```

Local development:

```json
{
  "mcpServers": {
    "gurume": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/home/<user>/workspace/gurume",
        "gurume",
        "mcp"
      ]
    }
  }
}
```

### ๐Ÿ› ๏ธ MCP Tools

0. 
   Unified dual-source search across both Tabelog (้ฃŸในใƒญใ‚ฐ) and Hot Pepper (ใƒ›ใƒƒใƒˆใƒšใƒƒใƒ‘ใƒผ ใ‚ฐใƒซใƒก). Concurrently retrieves, normalizes, and compares restaurant data.

   Parameters:
   - : required dish, cuisine, or restaurant name (e.g. '้ชจไป˜ใใ‚ซใƒซใƒ“', '็„ผ่‚‰')
   - : optional area, prefecture, city, or station name (e.g. 'ๅคง้˜ช', '้ถดๆฉ‹', 'ๆ–ฐๅฎฟ')
   - : optional list of sources, defaults to 
   - : filter for private dining rooms (ๅ€‹ๅฎคใ‚ใ‚Š)
   - : filter for all-you-can-drink plans (้ฃฒใฟๆ”พ้กŒใ‚ใ‚Š)
   - : filter for all-you-can-eat plans (้ฃŸในๆ”พ้กŒใ‚ใ‚Š)
   - : filter for non-smoking dining (็ฆ็…™ๅธญใ‚ใ‚Š)
   - : maximum results per platform (1 to 20, default )

6. 
   Search restaurants globally or in Japan via Google Maps Platform Places API (New). Provides worldwide location accuracy, Google user ratings, and direct Google Maps navigation links.

7. 
   Direct search on Recruit Hot Pepper Gourmet API with official amenities, budget ranges, and coupons.

8. 
   Fetch comprehensive shop details and coupon information by Hot Pepper shop ID.

1. `tabelog_search_restaurants`
   Search by area, keyword, or cuisine with structured pagination metadata.

   Parameters:

   - `area`: optional area, prefecture, city, or station name
   - `keyword`: optional free-text keyword for restaurant names or general matching
   - `cuisine`: optional cuisine name in Japanese for precise genre filtering
   - `sort`: `ranking`, `review-count`, `new-open`, `standard`
   - `limit`: 1 to 60, default `20`
   - `page`: 1-based page number, default `1`
   - `reservation_date`: `YYYYMMDD`
   - `reservation_time`: `HHMM`
   - `party_size`: positive integer

   Returns a structured `RestaurantSearchOutput` envelope with:

   - `status`
   - `items`
   - `returned_count`
   - `limit`
   - `has_more`
   - `meta`
   - `applied_filters`
   - `warnings`
   - `error`

2. `tabelog_list_cuisines`
   Return the supported cuisine list as structured data.

3. `tabelog_get_restaurant_details`
   Fetch base restaurant information and optionally collect reviews, menu items, and courses.

   Parameters:

   - `restaurant_url`: required Tabelog restaurant URL
   - `fetch_reviews`: default `true`; set `false` to skip review pages
   - `fetch_menu`: default `true`; set `false` to skip menu pages
   - `fetch_courses`: default `true`; set `false` to skip course pages
   - `max_review_pages`: minimum `1`, default `1`

   All optional fetch flags may be `false` when only basic restaurant information is needed.

4. `tabelog_get_area_suggestions`
   Return structured area and station suggestions from Tabelog.

5. `tabelog_get_keyword_suggestions`
   Return structured keyword suggestions for cuisines, restaurant names, and combined terms.

6. `tabelog_search_map_restaurants`
   Experimental yakitori search within explicit `min_lat`, `max_lat`, `min_lon`, and `max_lon` bounds.
   Optional `cuisine` (only `็„ผใ้ณฅ`), `page` (1-based), and `limit` (1โ€“20).
   Returns a separate map envelope with coordinates, raw budgets, and rectangle-scoped metadata.
   This is not an exact prefecture/national ranking or an automatic fallback for HTTP 403.

### ๐Ÿ“‹ Recommended MCP Workflow

1. Validate the area with `tabelog_get_area_suggestions` when the user input is ambiguous.
2. Validate cuisines or restaurant names with `tabelog_get_keyword_suggestions`.
3. Call `tabelog_search_restaurants` with normalized values.
4. Continue with `page + 1` when `meta.has_next_page` is true.
5. Call `tabelog_get_restaurant_details` for shortlisted restaurants.

### โš ๏ธ Error Model

All MCP tools return structured error data instead of relying on free-form exception text.

Current stable error codes:

- `invalid_parameters`
- `unsupported_cuisine`
- `upstream_unavailable`
- `internal_error`

When `status="error"`, inspect `error.error_code`, `error.retryable`, and `error.suggested_action` first.

### ๐Ÿงช Testing the MCP Server

```bash
# Run the MCP test suite (mocked upstream)
uv run pytest -q tests/test_server.py

# Opt-in live stdio check of cuisines, suggestions, search, and details
uv run python scripts/check_mcp_live.py

# Start the server locally (stdio, default)
uv run gurume mcp

# Inspect tools and schemas interactively
npx @modelcontextprotocol/inspector uv run gurume mcp
```

The live check exits nonzero when any tool fails and reports each result as JSON.
Supply a known current Tabelog restaurant URL for an independent detail check;
without one, details use the first search result or are marked `skipped`.
HTTP 403 on search or detail means Tabelog denied page access; repeated retries
or different search filters will not fix it. Browser impersonation may not
resolve upstream restrictions.

### ๐ŸŒ HTTP transport

`gurume mcp` can also run as an HTTP server for clients that speak streamable
HTTP (or SSE):

```bash
# Streamable HTTP (recommended; endpoint at http://127.0.0.1:8000/mcp)
uv run gurume mcp --transport streamable-http

# Custom bind address, port, and path
uv run gurume mcp --transport streamable-http --host 0.0.0.0 --port 9001 --path /api/mcp

# Server-Sent Events transport
uv run gurume mcp --transport sse --port 8765 --path /events
```

Security note: the default bind is `127.0.0.1`. Use `--host 0.0.0.0` only on
trusted networks; the MCP endpoint has no built-in authentication.

## ๐Ÿ–ฅ๏ธ TUI

Start the Textual TUI with:

```bash
uv run gurume tui
```

Or:

```bash
python -m gurume.tui
```

The TUI includes:

- a two-column layout with search results and a detail panel
- area suggestions with `F2`
- keyword and cuisine suggestions with `F3`
- automatic cuisine detection for direct cuisine-name input
- visible sort controls and keyboard navigation

Detailed TUI documentation lives in [`docs/site/usage/tui.md`](docs/site/usage/tui.md).

## ๐Ÿงฉ Agent Skill

This repo ships an agent skill at [`skills/gurume-cli/`](skills/gurume-cli/) that teaches AI coding assistants (Claude Code, Codex CLI, etc.) when and how to call the `gurume` CLI for restaurant search on Tabelog.

Install it with [`skills`](https://www.npmjs.com/package/skills):

```bash
npx skills add narumiruna/gurume
```

This pulls `skills/gurume-cli/` into your local agent skills directory. After installing, your agent will reach for `gurume` automatically whenever you ask it to find restaurants in Japan โ€” by area, cuisine, or a vague "where should I eat" prompt.

## ๐Ÿ“ Examples

See the `examples/` directory for runnable scripts:

- `examples/basic_search.py`: simple, advanced, and async search examples
- `examples/restaurant_detail.py`: restaurant detail scraping examples
- `examples/cli_example.py`: standalone example CLI built on the Python API

## ๐Ÿ“ Notes and Limitations

- Gurume scrapes Tabelog pages and internal suggestion endpoints. Upstream HTML or API changes may break parsing.
- Tabelog data is primarily Japanese. Even when user input is multilingual, normalized search values and many results are Japanese.
- Natural-language input is no longer parsed inside the CLI/TUI. Use the [`gurume-cli` agent skill](skills/gurume-cli/) with an AI assistant to translate free-form text into structured flags.
- Reservation-related search results reflect Tabelog availability data and may change over time.
- Use the library responsibly and avoid excessive request volume.

## โš–๏ธ Legal and Ethical Use

This project is intended for educational and research use.

- Respect Tabelog terms of service and robots policies.
- Do not send excessive or abusive traffic.
- Add your own rate limiting and operational safeguards for production use.

## ๐Ÿ“„ License

MIT License

## ๐Ÿค Contributing

Contributions are welcome. Please open an issue or submit a pull request.