emf-bar-mcp
MCP server for the EMF Camp bar API that enables fuzzy drink discovery by one or two keywords, showing the bar and live stock for each drink.
README
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.jsonat container start, then refreshed slowly in the background.find_drinksandlist_barsnever hit the network. - Live checks are cheap and targeted.
check_stockreads one drink via/api/stocktype/<id>.json;whats_on_tapreads 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
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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。