关于 UMich Dining
UMich Dining 是面向 University of Michigan 餐厅信息的 MCP 服务,供查询菜单、菜品属性、过敏原、营养和开放时间。它实时读取网站内容,并以内存短暂缓存页面,不将菜单镜像到磁盘。
主要功能
- 01查询十家餐厅的菜单和供应时段
- 02按菜名、属性和过敏原筛选菜品
- 03查询营养信息、热量和份量
- 04查看营业时间及当前开放餐厅
- 05提供本地 stdio 与托管 HTTP 传输
适用场景
- 01查找指定餐厅当餐供应的菜品
- 02筛选符合饮食属性且排除特定过敏原的菜品
- 03比较菜品的热量、钠或其他营养指标
- 04确认餐厅当前是否营业
MCP 相关
向 MCP 客户端提供菜单、菜品搜索与筛选、营养和营业时间工具,并提供餐厅注册表与分类资源。支持本地 stdio 和托管 Streamable HTTP。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
7收录的工具列表 · 更新于 1 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| date | string | 否 | YYYY-MM-DD 格式的日历日期。该网站发布今天前 3 天至今天后 14 天的菜单。 |
详细介绍
umich-dining-mcp
为密歇根大学安娜堡校区的餐厅提供服务的 MCP 服务。它可以回答当前供应哪些菜品、哪些菜品是纯素或清真食品、菜品包含什么、热量是多少,以及各餐厅的开放时间。
所有回答均实时读取自 dining.umich.edu。页面会在内存中缓存几分钟,因此十个问题只需访问一次;但不会将任何内容写入磁盘,也不会镜像菜单。
| 传输方式 | 本地 stdio 和托管的 Streamable HTTP |
| 运行环境 | Node.js 20.18.1 或更高版本 |
| 餐厅 | 十家餐厅 |
| 注册表 | io.github.esreddy2/umich-dining-mcp |
安装
npm install -g umich-dining-mcp
或者无需安装任何内容即可运行:
npx -y umich-dining-mcp
Claude Code
claude mcp add umich-dining -- npx -y umich-dining-mcp
Cursor
~/.cursor/mcp.json:
{
"mcpServers": {
"umich-dining": {
"command": "npx",
"args": ["-y", "umich-dining-mcp"]
}
}
}
VS Code
.vscode/mcp.json:
{
"servers": {
"umich-dining": {
"type": "stdio",
"command": "npx",
"args": ["-y", "umich-dining-mcp"]
}
}
}
任何其他通过 stdio 启动 MCP 服务的客户端,都可以使用与 Cursor 相同的 mcpServers 配置项,并将 npx -y umich-dining-mcp 设为命令。
工具
| 工具 | 功能 |
|---|---|
list_locations | 列出十家餐厅的地址、电话、电子邮箱、地图链接、当天的供餐时段、当前是否营业,以及是否设有无麸质食品储藏室。 |
get_menu | 获取一家餐厅当前供餐时段的菜单:餐次、餐台和菜品,以及编码的特征和过敏原、营养密度和碳排放等级、热量和份量。也可以指定一个或多个餐次来获取对应菜单。 |
search_dishes | 按菜名搜索菜品,可指定一家或多家餐厅,也可搜索所有餐厅(不区分大小写的子字符串匹配);支持搜索单日或日期范围。 |
filter_dishes | 按过敏原、特征、营养密度、碳足迹、营养素、热量和钠含量筛选菜单,可指定一家或多家餐厅,也可筛选所有餐厅;支持筛选单日或日期范围。 |
list_nutrition_fields | 列出支持的营养素名称、单位,以及微量营养素所依据的每日摄入量。 |
get_hours | 获取某天的供餐时段和常规每周时间表。无法读取的餐厅会列在 failures 中;其他餐厅仍会照常返回。 |
find_open_now | 查询在指定时刻供餐的餐厅。 |
get_item_nutrition | 获取单道菜品的完整营养成分表。 |
资源
| 资源 | 内容 |
|---|---|
umich://dining-halls | 以 JSON 格式列出餐厅注册信息,并包含每家餐厅的代码。 |
umich://taxonomy | 15 种过敏原和 14 种特征及其代码和显示标签、特征蕴含关系闭包,以及过敏原注意事项。 |
示例问题
What is serving at Bursley for dinner tonight?
Which halls are open right now?
Find vegan dishes at South Quad that do not contain soy.
What are the ingredients of the Oatmeal at Bursley? How many calories?
When is Markley open on Saturday?
Show me dishes with more than 500 mg of sodium.
Which halls serve tiramisu between the 24th and the 26th?
响应约定
search_dishes 和 filter_dishes 的行中使用小整数而非名称,响应顶部的 key 块会将每个代码映射回对应的 slug。键只列出该响应实际使用的内容。代码在整个约定的生命周期内固定不变。
{
"key": { "halls": { "1": "bursley" }, "meals": { "3": "Lunch" },
"stations": { "7": "Grill" }, "traits": { "5": "vegan" },
"allergens": { "7": "milk" } },
"units": { "calories": "kcal", "protein": "g" },
"note": "Allergen codes: check key.allergens before reporting; site data is advisory.",
"date": "2026-10-21",
"matches": [
[1, 3, 7, "Grilled Chicken", [5], [7], 3, 320, "1 Each (113g)", 113, 32]
]
}
一行是一个数组;其固定位置在每个工具描述中都有说明,可变的营养素尾部则在每个响应中只携带一次,放在 nutrients 中。命名行的成员名称占用的数据量是行数据的 2.5 倍,而且会在每一行重复:命名行占 216 字节,数组行占 72 字节。
- 餐厅楼的代码为 1..10,顺序与下方注册表一致;餐次代码为 1(早餐)、2(早午餐)、3(午餐)和 4(晚餐);过敏原代码为 1..15;六个布尔特征的代码为 1..6(无麸质、清真、犹太洁食、辛辣、纯素、素食)。网站自创的餐次标签会使用下一个可用代码,从 5 开始。餐台也有固定的词汇表:同一餐台在每个响应中都使用相同代码,因此可以记住某个代码。
mhealthyTier的范围为 1(营养密度低)至 5(营养密度高)。该等级固定不变,并在工具描述和umich://taxonomy中说明。- 只有当调用通过
max_carbon限制碳排放等级,或按carbonFootprint排序时,行才会携带碳排放等级(carbonFootprint);否则会省略,以保持行内容精简。 traits数组只包含六个布尔特征;密度等级会提取出来,放入单独的数值字段。units映射会给出响应所返回的每种营养素的单位。微量营养素和其他营养素一样都是含量:网站发布每日摄入量百分比,而行中携带的是该百分比对应的含量——例如,铁的每日摄入量为 18 mg 时,15 %DV 对应2.7mg。阈值中仍可通过其自身名称%DV获取百分比。- 搜索或筛选会指定其覆盖的餐厅楼:每个餐厅楼可以指定一个、多个,也可以不指定。行会按照请求指定的顺序返回,
limit会选取其中前若干行:默认返回 50 行,响应会说明匹配数量以及如何查看其余结果。 - 行始终携带卡路里和份量大小。其他营养素(包括钠)都需要通过名称用
include_nutrients请求——钠并没有比蛋白质更特殊。筛选条件涉及的营养素始终会返回,无论是否请求了它们。 - 单日响应包含
date;日期范围响应包含dateRange,且每行都包含其出现日期dates。 - 菜品没有已发布数值时会报告为
null,绝不会报告为零。trace或<值不算数值。
get_menu 和扫描工具会返回相同的紧凑行,因此无论在哪里读取,一行的含义都相同。get_item_nutrition 是返回单个菜品原始营养成分面板的工具。
这样一来,完整菜单会小得多:South Quad 的菜单从 60,266 字节缩减到 8,817 字节,Bursley 的菜单从 42,141 字节缩减到 6,573 字节。单个菜品的营养成分面板可通过一次 get_item_nutrition 调用获取。
餐厅楼
| id | 餐厅楼 | id | 餐厅楼 |
|---|---|---|---|
bursley | Bursley | south-quad | South Quad |
east-quad | East Quad | twigs-at-oxford | Twigs at Oxford |
markley | Markley | wolverine-village | Wolverine Village |
mosher-jordan | Mosher-Jordan | lawyers-club | Lawyers Club |
north-quad | North Quad | martha-cook | Martha Cook |
get_menu、get_hours 和 search_dishes 使用的是 id。餐厅楼的代码就是它在此列表中的位置,从 1 开始计数。
日期
该网站会发布从 3 天前到 14 天后的菜单。超出此范围时,网站不会返回错误,而是会返回其他日期的菜单,因此在获取任何内容之前,会拒绝范围外的日期,并提示网站提供的日期范围。如果页面说明了它呈现的是哪一天,系统会将该日期与请求的日期进行比较;如果不一致,就会报错,而不是返回错误答案。
使用 from 和 to 指定日期范围;省略 to 表示截至最近发布的日期。不能同时指定日期范围和 date。如果一个调用无法涵盖整个范围,系统会使用 offset 分段读取;答案中的 coverage 块会注明下一段的偏移量。
时间
服务器报告的所有时间都是校园时间:即安娜堡所在的时区 America/Detroit,冬季为东部标准时间,夏季为东部夏令时间。网站发布的日期、用餐时间以及缓存条目中的“今天”均按此时区读取。
服务器运行所在机器的时区无关紧要。芝加哥和 UTC 上的服务器对同一问题会给出相同答案;测试套件会通过在多个主机时区下运行餐食筛选来验证这一点。只有在要将服务器指向位于其他时区的网站镜像时,才设置 UMICH_DINING_TIMEZONE。
筛选条件
| 参数 | 含义 |
|---|---|
exclude_allergens | 仅保留不含任何指定过敏原的菜品。 |
require_traits | 仅保留具备所有指定特征的菜品。 |
min_mhealthy | 最低营养密度等级,1..5(3 = “中等或更高营养密度”)。 |
max_carbon | 碳足迹上限:1(低)、2(中)或 3(高)。 |
nutrients | 为每种营养素指定范围,例如 {"protein": {"min": 20, "unit": "g"}}。单位默认为该营养素自身的单位,并会进行检查。 |
include_nutrients | 要返回的营养素,可按规范键名或发布名称指定,也可指定 all。默认为筛选条件使用的营养素;受约束的营养素始终会返回。 |
query | 菜品名称中不区分大小写的子字符串。 |
meal、station | 将检索范围缩小到一种餐食或一个站点。 |
sort_by、sort_order | 排序字段及排序方向。 |
对营养素设置约束时,如果某道菜未发布该营养素的数值,就会将其排除:未知值不等于零。
范围查询的成本
范围查询每天会读取每个餐厅的一页内容,而网站每秒最多抓取两页。若查询所需的上游页面数量超过 UMICH_DINING_MAX_RANGE_FETCHES(默认值为 12),则会返回当前能够提供的行,并附带说明已覆盖范围的 coverage 块:
"coverage": { "requested": 30, "cached": 18, "fetchedNow": 12, "pending": 0, "failed": 0,
"stale": 0, "offset": 0, "pages": 30, "nextOffset": null }
各项数量相加后相符:查询请求的每一页都处于已缓存、已抓取、待处理或失败状态。nextOffset 是范围剩余部分的起点,因此宽范围会分段读取:使用该数字设置 offset 后再次查询。
持久化缓存正是为此而设:已发布日期范围内的页面会保存在磁盘上,并在后台预热,因此范围查询通常无需访问网站即可完成。
过敏原
过敏原数据的准确性取决于网站本身。菜单页面会标注厨房报告的菜品过敏原,但不会区分“含有”和“可能含有”;没有配料清单,且未标注并不代表不含该过敏原。网站自己的指引是:“请查看每个食品供应区附近张贴的标识,了解哪些菜单选项标红了过敏原。”“无麸质”表示这道菜不含麸质,并不代表乳糜泻患者可以安全食用。South Quad、East Quad、Mosher-Jordan 和 Bursley 均设有专用的无麸质食品储藏室。
每个返回过敏原数据的工具的说明中都包含这项注意事项,umich://taxonomy 中也有说明。包含过敏原信息的响应会重复一行提示,提醒读者查阅代码说明;不会在每个响应正文中重复完整的注意事项。任何工具都不会声称某道菜是安全的。
关于饮食特征,还有两点需要说明,因为网站对此处理得很谨慎:由于网站将素食和纯素分别标注,纯素菜品也会标注为素食;其他特征则互不推断(清真不代表素食,犹太洁食也不代表素食)。
配置
| 环境变量 | 默认值 | 含义 |
|---|---|---|
UMICH_DINING_USER_AGENT | python-requests/2.32.3 | 使用的 User-Agent。 |
UMICH_DINING_BASE_URL | https://dining.umich.edu | 要读取的网站。 |
UMICH_DINING_TIMEZONE | America/Detroit | 校园所在时区:网站发布日期和用餐时间均以此时区读取。 |
UMICH_DINING_TIMEOUT_MS | 20000 | 每个请求的超时时间。 |
UMICH_DINING_MAX_RETRIES | 3 | 首次尝试后最多重试次数。 |
UMICH_DINING_MAX_CONCURRENCY | 2 | 同时进行的请求数。 |
UMICH_DINING_MIN_GAP_MS | 500 | 请求之间的最短间隔。 |
UMICH_DINING_TTL_TODAY_MS | 900000 | 今日菜单的缓存时长。 |
UMICH_DINING_TTL_FUTURE_MS | 21600000 | 未来菜单的缓存时长。 |
UMICH_DINING_TTL_PAST_MS | 86400000 | 过去菜单的缓存时长。 |
UMICH_DINING_TTL_REGULAR_HOURS_MS | 86400000 | 每周营业时间表的缓存时长。 |
UMICH_DINING_MAX_RANGE_FETCHES | 12 | 单次日期范围查询最多可获取的上游页面数。 |
UMICH_DINING_CACHE_DIR | ~/.cache/umich-dining-mcp | 持久化缓存目录。设置为 off 可关闭缓存。 |
UMICH_DINING_CACHE_MAX_AGE_DAYS | 30 | 缓存条目的保留时长。 |
UMICH_DINING_CACHE_MAX_SIZE_MB | 200 | 持久化缓存允许达到的最大容量。 |
UMICH_DINING_CACHE_PRUNE_INTERVAL_MS | 3600000 | 清理持久化缓存的间隔。 |
如何访问网站
dining.umich.edu 是第三方网站,因此服务会尽量减少对它的访问:
- 最多同时进行两个请求,请求间隔至少为 500 ms。 此限制对整个进程生效。
- 缓存并对正在进行的请求去重。 针对同一餐厅的十个问题只会产生一个请求。今日菜单缓存 15 分钟,未来菜单缓存 6 小时,过去菜单缓存 24 小时,每周营业时间缓存 24 小时。
- 出错时使用过期缓存。 如果无法访问网站但存在缓存副本,服务会返回该副本并标记为过期,而不是让查询失败。
- 使用单一 User-Agent。 网站只会向少数客户端返回 HTTP 200;经测试,
python-requests/2.32.3可正常使用,而通用抓取 Agent 和无头浏览器都会收到 403。它并非服务的真实身份,因此可以配置。
人流量
get_dining_occupancy 会报告配有传感器的餐厅实时满员情况(Bursley、East Quad、South Quad、Mosher-Jordan、Oxford 的 Twigs):
{ "hall": "bursley" }
它会返回当前人数、每日顾客流量、总容量、满员百分比和读数时间。没有传感器的餐厅不会出现在结果中。
简写
食堂名称、营养素和特征支持简写别名,且不区分大小写:
- 食堂:
mojo、south、east、lawyers、cook、twigs、wovlerine,…… - 营养素:
cals、kcals、carbs、fiber、protein/prot、na/salt,…… - 特征:
gf、veg。
描述保持不变;服务会在验证前解析别名。
数据来源
菜单、营业时间、地点、营养信息和就餐人数均从 U-M 餐饮移动 API 读取,并保留公共网站抓取器作为备用方案。答案仍为实时读取,并缓存几分钟。
贡献
欢迎提交拉取请求。测试套件、固定数据和发布流程详见 CONTRIBUTING.md。
许可证
MIT,详见 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。