Skip to main content
Glama
rjp44

emf-bar-mcp

by rjp44

emf-bar-mcp

A small, public MCP server over the EMF Camp bar API, built for a low‑context voice model: fuzzy drink discovery by one or two keywords, the bar each drink is served at, and live stock — while touching the upstream API as little as possible.

  • Discovery is free. The whole menu (124 drinks) and which bar carries each one is pulled in one call to /api/stocktypes.json at container start, then refreshed slowly in the background. find_drinks and list_bars never hit the network.

  • Live checks are cheap and targeted. check_stock reads one drink via /api/stocktype/<id>.json; whats_on_tap reads the two on‑tap endpoints. Both sit behind short TTL caches, so a burst of voice traffic collapses to one upstream request.

The paste‑into‑a‑prompt guide for the voice model is PROMPT.md.

Live instance: https://emf-bar-mcp-37viybmxjq-ew.a.run.app/mcp — Cloud Run (llm-voice, europe‑west1), public, scale‑to‑zero. Health: /health.

Quick start

npm install
npm start                 # HTTP server on http://0.0.0.0:8787/mcp  (default)
# or
npm run stdio             # stdio transport, for desktop MCP clients

Health check: curl localhost:8787/health

Docker

docker build -t emf-bar-mcp .
docker run -p 8787:8787 emf-bar-mcp

Related MCP server: bar-assistant-mcp

Connecting a client

Streamable HTTP (remote / public) — point your MCP client at http://<host>:8787/mcp.

stdio (e.g. Claude Desktop claude_desktop_config.json):

{
  "mcpServers": {
    "emf-bar": { "command": "node", "args": ["/abs/path/to/emf-bar-mcp/src/index.js"], "env": { "MCP_TRANSPORT": "stdio" } }
  }
}

Tools

Tool

Args

Live call?

Returns

list_bars

no

The 3 bars (slug, name, drink count, map link)

find_drinks

query (1–2 keywords), bar?, category?, include_unavailable?, limit?

no

Ranked drinks: id, name, abv, price, category, bars, dietary flags

check_stock

drink (name or id), bar?

1

status (on_sale/in_stock_not_on_sale/out_of_stock), onSale, inStock, level, servingsRemaining+servingUnit, percentRemaining, containerPercentRemaining (cask/keg), price, bars, plus source/live/checkedAt freshness

whats_on_tap

bar?

1 (cached)

Casks/kegs/ciders pouring now, each with remainingPct / level

opening_hours

bar?

1 (cached)

open, closesAt/nextOpen, upcoming schedule. Site-wide schedule (EMF publishes no per-bar hours)

Every tool returns a short spoken‑style content string and machine‑readable structuredContent. check_stock returns a candidates list when a name is ambiguous.

On sale vs in stock. EMF only serves some draught beers/ciders at a time ("not all draught beers and ciders are on sale at the same time"). The API models this: a product is on sale when it has a stockline (a place to buy it), and in stock when base_units_remaining > 0. So a cask in the cellar can be in stock but not on sale. check_stock.status surfaces the three cases — on_sale, in_stock_not_on_sale, out_of_stock — and find_drinks hides not‑on‑sale drinks unless include_unavailable: true.

Bar names are fuzzy: robotarms/"Robot Arms"/"main bar", cybar/"Cybar"/"Null Sector", spacebar/"SpaceBAR"/"space bar" all resolve.

Configuration (env vars)

Var

Default

Purpose

MCP_TRANSPORT

http

http or stdio

PORT / HOST

8787 / 0.0.0.0

HTTP bind

EMF_BAR_BASE

https://bar.emf.camp

Upstream API base

CATALOG_REFRESH_MS

900000 (15 min)

Background catalog refresh interval

STOCK_TTL_MS

20000

Per‑drink live‑stock cache TTL

ONTAP_TTL_MS

30000

On‑tap cache TTL

SESSIONS_TTL_MS

300000

Opening‑times cache TTL

HTTP_TIMEOUT_MS

8000

Upstream request timeout

How it works

                 startup + every CATALOG_REFRESH_MS
  /api/stocktypes.json ───────────────► catalog cache (drinks, tags, bar placement, Fuse index)
                                              │
  find_drinks / list_bars  ◄──────────────────┘   (0 upstream calls)

  check_stock   ──► /api/stocktype/<id>.json      (TTL STOCK_TTL_MS)
  whats_on_tap  ──► /api/on-tap.json + /api/cybar-on-tap.json  (TTL ONTAP_TTL_MS)

Discovery matching is deterministic (keyword/synonym/style/flavour tags derived from each drink, including "hoppy"/"gluten free"/"alcohol free"), with a Fuse.js fuzzy fallback for typos and mis‑transcriptions. The HTTP transport is stateless (a fresh MCP server per request; the catalog cache is a shared singleton), so it scales and hosts simply.

The upstream /api/stocktypes.json is flagged expensive — do not poll; this server honours that by pulling it only at start‑up and on the slow refresh timer, never per request.

Notes

  • Not affiliated with EMF; uses the public read‑only bar API. Data © EMF.

  • Node ≥ 18 (uses global fetch). MIT licensed.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    MCP server for Bar Assistant that enables searching cocktails, managing ingredients, shelves, shopping lists, and collections via natural language.
    41
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    An MCP server that interfaces with Untappd's mobile API to search beers, breweries, venues, and user data, and to post check-ins, toasts, and comments using your own account.
    45
    590 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for Untappd connectivity, enabling beer search with optional limit, offset, and sorting.
    MIT