umich-dining-mcp
An MCP server for live University of Michigan dining-hall data: menus, hours, open status, dish filtering, and nutrition.
List all ten dining halls with contact/address info, gluten-free pantry status, today’s service periods, open-now status, and per-hall failures.
Fetch one hall’s menu by date, optionally narrowed by meal(s) or station, returning dishes with traits, allergens, nutrient-density tier, calories, serving size, and requested nutrients.
Filter dishes across one, several, or all halls by name substring, meal, station, excluded allergens, required traits, nutrient-density tier, carbon footprint, nutrient ranges, calories/sodium, over a single date or date range with sorting, limits, offsets, and coverage metadata.
List supported nutrition fields, units, accepted threshold units, sort fields, micronutrient Daily Value bases, and notes.
Get a hall’s daily service periods plus weekly schedule, with partial results and failures when a hall cannot be read.
Find which halls are open at a specified campus-local time/day, or now.
Retrieve a single dish’s full nutrition panel plus coded traits/allergens and carbon footprint.
Use compact coded rows with key/units maps, stale/asOf metadata, and advisory allergen caveats.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@umich-dining-mcpWhich dining halls are open right now?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
umich-dining-mcp
An MCP server for the dining halls of the University of Michigan in Ann Arbor. It answers what is being served, which dishes are vegan or halal, what a dish contains, how many calories it has, and when each hall is open.
Every answer is read live from dining.umich.edu. Pages are kept in memory for a few minutes so that ten questions cost one visit, but nothing is written to disk and no menu is mirrored.
Transports | Local stdio and hosted Streamable HTTP |
Runtime | Node.js 20.18.1 or newer |
Halls | the ten dining halls |
Registry |
Hosted MCP (no installation required)
The public Streamable HTTP endpoint is:
https://umich-dining-mcp.duckdns.org/mcp
Connect a compatible MCP client directly to this URL. No npm installation or GitHub download is needed.
The existing npm package remains available for users who prefer running the MCP server locally through stdio.
Related MCP server: nutrition-mcp-server
Install
npm install -g umich-dining-mcpor run it without installing anything:
npx -y umich-dining-mcpClaude Code
claude mcp add umich-dining -- npx -y umich-dining-mcpCursor
~/.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"]
}
}
}Any other client that starts MCP servers over stdio takes the same mcpServers entry as
Cursor, with npx -y umich-dining-mcp as the command.
Tools
Tool | What it does |
| The ten halls with address, phone, e-mail, map link, today's service periods, whether they are open right now, and whether they have a gluten-free pantry. |
| The menu of one hall for the meal it is serving right now: meals, stations and dishes, with the coded traits and allergens, the density and carbon tiers, the calories and the serving size. Name one meal or several to get them instead. |
| Find dishes by name, in the halls that are named or across all of them (case-insensitive substring match), on one day or over a range. |
| Filter the menus by allergen, trait, nutrient density, carbon footprint, nutrients, calories and sodium, in the halls that are named or across all of them, on one day or over a range. |
| The supported nutrient names, their units and the Daily Value basis of the micronutrients. |
| The service periods of a day plus the regular weekly schedule. A hall that cannot be read is reported in |
| Which halls are serving at a given time of day. |
| The full nutrition panel of a single dish. |
Resources
Resource | Content |
| The hall registry as JSON, with the code of every hall. |
| The 15 allergens and 14 traits with their codes and display labels, the trait implication closure and the allergen caveat. |
Example questions
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?The response contract
The rows of search_dishes and filter_dishes carry small integers instead of names, and a
key block at the top of the response maps every code back to its slug. The key lists only
what that response uses. The codes are frozen for the life of the contract.
{
"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]
]
}A row is an array; its fixed positions are named in each tool description and the
variable nutrient tail is carried once per response as nutrients. The member names of a
named row are two and a half times the data of the row, repeated on every row: a named row
cost 216 bytes, an array row costs 72.
The halls are 1..10 in the order of the registry below, the meals are 1 (Breakfast), 2 (Brunch), 3 (Lunch) and 4 (Dinner), the allergens are 1..15 and the six boolean traits are 1..6 (glutenfree, halal, kosher, spicy, vegan, vegetarian). A meal label the site invents takes the next free code, from 5. The stations have a frozen vocabulary too: the same station carries the same code in every response, so a code can be remembered.
mhealthyTieris 1 (Nutrient Dense Low) to 5 (Nutrient Dense High). The scale is fixed and described in the tool descriptions and inumich://taxonomy.A row carries the carbon tier (
carbonFootprint) only when the call constrains it withmax_carbonor sorts bycarbonFootprint; otherwise it is left out to keep the row small.The
traitsarray carries the six boolean traits only; the density tier is lifted into its own numeric field.The
unitsmap gives the unit of every nutrient the response returns. A micronutrient is an amount like every other nutrient: the site publishes the percent of the Daily Value, and the row carries the amount that percent stands for — 15 %DV of an 18 mg Daily Value of iron is2.7in mg. The percent is still available under its own name,%DV, in a threshold.A search or a filter takes the halls it covers: one hall, several halls, or no hall at all for every one of them. The rows come back in the order that was asked for, and
limittakes the first of them: the default is 50 rows, and the answer says how many matched and how to see the rest.The row always carries the calories and the serving size. Every other nutrient, the sodium included, is asked for by name with
include_nutrients— the sodium has nothing that makes it more interesting than the protein. A nutrient a filter constrained is always returned, whether or not it was asked for.A single-day response carries
date; a range response carriesdateRangeand each row carries thedatesit appeared on.A dish without a published value is reported as
null, never as zero. Atraceor a<value is not a value at all.
get_menu and the sweep tools return the same compact row, so a row means the same thing
wherever it is read. get_item_nutrition is the tool that returns the raw nutrition panel,
of one dish.
A whole menu is far smaller this way: the menu of South Quad went from 60,266 bytes to
8,817, and the menu of Bursley from 42,141 to 6,573. The panel of a single dish is one
call to get_item_nutrition.
The halls
id | Hall | id | Hall |
| Bursley |
| South Quad |
| East Quad |
| Twigs at Oxford |
| Markley |
| Wolverine Village |
| Mosher-Jordan |
| Lawyers Club |
| North Quad |
| Martha Cook |
The id is what get_menu, get_hours and search_dishes expect. The code of a hall is
its position in this list, counted from one.
Dates
The site publishes menus from three days ago to fourteen days ahead. Outside that range the site does not answer with an error, it answers with a different day's menu, so a date outside the range is refused before anything is fetched, with a message that names the range the site offers. When the page states which day it rendered, that day is compared with the one you asked for and a mismatch is an error rather than a wrong answer.
A range is asked for with from and to; to omitted means the last published day. The
two cannot be combined with date. A range that does not fit in one call is read in
windows with offset; the coverage block of the answer names the offset of the next
window.
Time
Every time the server reports is campus time: the time zone of Ann Arbor,
America/Detroit, which is Eastern Standard Time and Eastern Daylight Time in the summer.
The day the site publishes, the mealtimes, and the "today" of a cache entry are all read in
that zone.
The zone of the machine the server runs on does not matter. A server in Chicago and a server
in UTC answer the same question the same way, and the test suite checks that by running the
meal selection under several host zones. Set UMICH_DINING_TIMEZONE only to point the
server at a mirror of the site in another zone.
Filters
Parameter | Meaning |
| Keeps only the dishes that do not carry any of the listed allergens. |
| Keeps only the dishes that carry every listed trait. |
| Minimum nutrient-density tier, 1..5 (3 = "Nutrient Dense Medium or better"). |
| Maximum carbon footprint: |
| A range per nutrient, e.g. |
| The nutrients to return, by canonical key or by published name, or |
| A case-insensitive substring of the dish name. |
| Narrow the sweep to one meal or one station. |
| The field to sort on and the direction. |
A constraint on a nutrient excludes the dishes that publish no value for it: an unknown value is never a zero.
The cost of a range
A range query reads one page per hall per day, and the site is fetched at most two pages a
second. A query that needs more upstream pages than UMICH_DINING_MAX_RANGE_FETCHES
(default 12) returns the rows it could serve and a coverage block that says how much was
served:
"coverage": { "requested": 30, "cached": 18, "fetchedNow": 12, "pending": 0, "failed": 0,
"stale": 0, "offset": 0, "pages": 30, "nextOffset": null }The numbers add up: every page the query asked for was cached, fetched, left pending or
failed. nextOffset is where the rest of the range begins, so a wide range is read in
windows: ask again with offset set to that number.
The persistent cache exists for this: the pages of the published window are kept on disk and warmed in the background, so a range query is usually served without touching the site.
Allergens
Allergen data is only as good as the site. The menu page flags the allergens the kitchen reported for a dish, but it does not distinguish "contains" from "may contain", there are no ingredient lists, and a missing flag is not an absence of the allergen. The site's own guidance is: "Look for the signs posted near each food serving area for a list of menu options with allergens highlighted in red." "Gluten Free" means the dish is made without gluten, not that it is safe for someone with coeliac disease. Dedicated gluten-free pantries exist at South Quad, East Quad, Mosher-Jordan and Bursley.
The caveat is in the description of every tool that returns allergen data and in
umich://taxonomy. A response that carries allergens repeats one line reminding the reader
to resolve the codes; the long caveat is not repeated in every body. No tool ever claims
that a dish is safe.
Two further points about the traits, because the site is careful about them: a vegan dish is reported as vegetarian as well, since the site keeps the two apart, and no other trait implies another (halal does not imply vegetarian, and neither does kosher).
Configuration
Environment variable | Default | Meaning |
|
| The User-Agent to use. |
|
| The site to read. |
|
| The time zone of the campus: the day the site publishes and the mealtimes are read in it. |
|
| Per-request timeout. |
|
| Retries after the first attempt. |
|
| Requests in flight. |
|
| Minimum gap between requests. |
|
| Cache lifetime of today's menu. |
|
| Cache lifetime of a future menu. |
|
| Cache lifetime of a past menu. |
|
| Cache lifetime of the weekly schedule. |
|
| Upstream pages a single range query may fetch. |
|
| The persistent cache. |
|
| How long a cache entry is kept. |
|
| The largest the persistent cache may grow. |
|
| How often the persistent cache is pruned. |
How it treats the site
dining.umich.edu is somebody else's website, so the server stays gentle with it:
Two requests in flight, 500 ms apart at most. The limit is global to the process.
A cache with in-flight de-duplication. Ten questions about the same hall cost one request. Today's menu is cached for 15 minutes, a future menu for 6 hours, a past menu for 24 hours and the weekly hours for 24 hours.
Stale on error. If the site cannot be reached and a cached copy exists, the copy is served and marked as stale instead of failing the question.
One User-Agent. The site answers HTTP 200 to a small set of clients only;
python-requests/2.32.3is the one that has been measured to work, while a generic fetch agent and a headless browser both get 403. It is not the server's real identity, which is why it is configurable.
Contributing
Pull requests are welcome. The test suite, the fixtures and the release process are described in CONTRIBUTING.md.
License
MIT, see LICENSE.
Maintainer hosting and updates
The Oracle deployment is separate from the npm package. Its NGINX configuration, systemd service and updater are maintainer infrastructure, not requirements for MCP users.
See Oracle hosting and automatic updates.
The optional Oracle updater checks published GitHub tags against npm releases, builds and tests new versions, and rolls back if the updated service fails its health check. Ordinary GitHub commits do not trigger deployments.
Available Tools
7 toolsfilter_dishesFilter dishesARead-onlyIdempotent
Filter dining hall menus by allergen, trait, nutrient density, carbon, calories, sodium and name. All params optional; no params lists every dish for the day. Allergens: alcohol, beef, coconut, eggs, fish, item-is-deep-fried, milk, oats, peanuts, pork, sesame-seed, shellfish, soy, tree-nuts, wheat_barley_rye. Traits: glutenfree, halal, kosher, spicy, vegan, vegetarian. min_mhealthy: keep tiers >= (1-5). max_carbon: keep tiers <= (1 low, 2 medium, 3 high). Untagged dropped by both. nutrients: range per nutrient, e.g. {"protein":{"min":20,"unit":"g"}}. query: case-insensitive substring. Rows are arrays: [hall, meal, station, name, traits, allergens, mhealthyTier] plus carbonFootprint when max_carbon or sort_by carbon, then calories, servingSize, servingSizeGrams, dates in a range, then nutrients named by include_nutrients. traits are codes 1-6; allergens are codes; read the key. Station codes in each row resolve via key.stations. Every row already includes calories, servingSize, and servingSizeGrams. Use include_nutrients to get full nutrition data in the same call - no need to call get_item_nutrition per dish. stale means the data is from cache and may be slightly old, but is still the best available; asOf is the age. Re-call only if you need guaranteed-fresh data. Allergens advisory.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | The last day of a range to filter. Defaults to the last published day. | |
| date | No | A single day to filter. Cannot be combined with from/to. | |
| from | No | The first day of a range to filter. | |
| hall | No | Halls: one, a list, or none for all. | |
| meal | No | Keeps only that meal. | |
| limit | No | Rows to return (default 50, max 500). | |
| query | No | A case-insensitive substring of the dish name. | |
| offset | No | Pages of the range to skip. | |
| sort_by | No | Sort field: a nutrient key, carbonFootprint, a micronutrient name, servingSize, mhealthyTier, or a ratio "numerator:denominator" (e.g. "protein:calories", "vitaminA:iron"). Legacy: proteinToCalorie, fiberToCalorie. | |
| station | No | Keeps only that station. | |
| nutrients | No | Range per nutrient, e.g. {"protein":{"min":20,"unit":"g"}}. A constraint drops dishes with no value. | |
| max_carbon | No | The highest acceptable carbon footprint: 1 (low), 2 (medium) or 3 (high). The dishes the site did not tag are dropped. | |
| sort_order | No | The direction of the sort. It defaults to desc, so the biggest value comes first. | |
| min_mhealthy | No | The lowest acceptable nutrient-density tier. The dishes the site did not tag are dropped. | |
| require_traits | No | Keeps only the dishes that carry every one of these traits. | |
| exclude_allergens | No | Keeps only the dishes that carry none of these allergens. | |
| include_nutrients | No | Nutrients to return, by key/name, or ["all"]. |
Output Schema
| Name | Required | Description |
|---|---|---|
| key | Yes | |
| asOf | No | |
| date | No | |
| note | No | |
| flags | No | |
| stale | Yes | |
| units | No | |
| filter | No | |
| noMenu | No | |
| matches | Yes | |
| coverage | No | |
| failures | Yes | |
| dateRange | No | |
| nutrients | No | |
| truncated | Yes | |
| totalMatches | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description adds substantial context on top: the exact row layout, when carbonFootprint and nutrient columns appear, that trait/allergen values are numeric codes resolvable via the returned key, and that 'stale' data is cached. The 'Allergens advisory' caveat is a meaningful safety disclosure no annotation carries.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The filtering capability is front-loaded and nearly every sentence carries operational detail. The long row-ordering sentence is dense and partially overlaps the output schema, keeping it short of a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 17-parameter tool with nested objects and no required fields, the description covers defaults, constraint side effects, output row structure, cache freshness, and the alternative nutrition call. Combined with the output schema and annotations, an agent has everything needed to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond it: a concrete nutrients syntax example, tier mappings for min_mhealthy and max_carbon, the rule that a constraint drops dishes with no value, and that query is a case-insensitive substring. The enumerated allergen/trait vocabularies duplicate the schema enums, which caps it below 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource ('Filter dining hall menus') plus the exact dimension set it filters on. It also distinguishes itself from the closest sibling by stating 'Use include_nutrients to get full nutrition data in the same call - no need to call get_item_nutrition per dish.'
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear default behavior ('no params lists every dish for the day'), explains the untagged-drop side effect, and tells the agent when to re-call versus accept cached data ('Re-call only if you need guaranteed-fresh data'). It does not explicitly contrast itself with get_menu, so routing between those two is left implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_open_nowFind halls that are open nowARead-onlyIdempotent
The dining halls that are serving at the given time of day, and the ids of the ones that are not, with the service periods of the day for the halls that are open. Without arguments the current campus-local time is used, and the response says which time it resolved. open carries the hall details and today's periods; closed carries hall ids only. stale means a page was served from an expired cache entry; asOf is the age of the data.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The campus-local time of day to look at. Defaults to the current time. | |
| date | No | The day to look at. Defaults to today. |
Output Schema
| Name | Required | Description |
|---|---|---|
| at | Yes | |
| asOf | No | |
| date | Yes | |
| open | Yes | |
| closed | Yes | |
| failures | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare this as a read-only, idempotent, non-destructive open-world operation, and the description adds useful behavior beyond that: default time resolution, open vs. closed response contents, stale-cache meaning, and asOf age. It does not contradict the annotations and gives strong context for interpreting the response.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and then supplies response-field context. It is dense but avoids obvious repetition, and every clause appears to serve interpretation or default behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the moderate complexity, annotations, complete input schema, and output schema, the description is complete enough to call correctly. It covers default time handling, response partitioning into open and closed, and cache-related fields.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents both `at` and `date`. The description adds the important default behavior when no arguments are supplied, but otherwise does not substantially extend parameter semantics beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the resource and scope: dining halls serving at a given time, plus ids of closed halls. It is specific enough for an agent to understand the operation, though it does not explicitly contrast itself with siblings such as get_hours.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the phrase 'dining halls that are serving at the given time of day,' and the default to current campus-local time is helpful. However, there is no explicit when-to-use guidance, no exclusions, and no named alternative such as get_hours.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_hoursGet dining hall hoursARead-onlyIdempotent
Get the service periods published for a dining hall on the requested day, plus the regular weekly schedule. Accepts one hall, a list, or none for all (like filter_dishes). today is the day's published service periods and regular is the weekly schedule. A hall that cannot be read is reported in the failures and the other halls are still returned. The response carries the weekday name of the requested date. stale means the data is from cache and may be slightly old, but is still the best available; asOf is the age. Re-call only if you need guaranteed-fresh data.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | The day to read. Defaults to today. | |
| hall | No | Halls: one, a list, or none for all. |
Output Schema
| Name | Required | Description |
|---|---|---|
| asOf | No | |
| date | Yes | |
| halls | Yes | |
| weekly | No | |
| weekday | No | |
| failures | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: partial-failure semantics ('A hall that cannot be read is reported in the failures and the other halls are still returned'), cache staleness semantics ('stale means the data is from cache... asOf is the age'), and a response detail the agent would otherwise have to derive (weekday name). Annotations already cover safety, and this fills in the operation-specific context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded and the density is high with little filler; each sentence carries operational meaning (failures, stale/asOf, weekday name). It is slightly run-on, mixing output details and cache semantics in a single block, but remains readable and efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema and full annotation coverage, the description supplies the non-obvious pieces: failure behavior, cache staleness interpretation, and the today-vs-regular distinction. The one gap is the missing routing guidance relative to 'find_open_now', which matters for a tool whose answer overlaps with 'what is open now'.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already explains 'date' (defaults to today) and 'hall' (one, a list, or none for all). The description mostly repeats those semantics rather than adding format or edge-case detail, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Uses a specific verb and resource ('Get the service periods published for a dining hall on the requested day, plus the regular weekly schedule') and clearly separates the 'today' view from the 'regular' weekly view. It does not, however, differentiate itself from the sibling 'find_open_now', which an agent could easily confuse with a hours lookup.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for the hall parameter ('like filter_dishes') and explicit retry guidance ('Re-call only if you need guaranteed-fresh data'), which is actionable. It stops short of stating when to prefer this over 'find_open_now' or 'get_menu', so it lacks alternatives/exclusions that a 5 would require.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_item_nutritionGet a dish nutrition panelARead-onlyIdempotent
Get the full nutrition panel of one dish. Name matched case-insensitively; exact match wins, else unique substring. Dish row uses the same coded layout as the sweep tools. Panel values are numbers with one units map; micronutrient amount is the derived amount, with the published percent behind it. asOf data age. Allergens advisory. Use this only when you need the full panel for a single dish. For multiple dishes, use filter_dishes with include_nutrients instead.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | Date YYYY-MM-DD. Site publishes today-3..+14 days. | |
| hall | Yes | The dining hall to read. | |
| item | Yes | The dish name. | |
| meal | No | Narrow the search to one meal. | |
| station | No | Narrow the search to one station. |
Output Schema
| Name | Required | Description |
|---|---|---|
| key | Yes | |
| asOf | No | |
| date | Yes | |
| hall | Yes | |
| meal | Yes | |
| name | Yes | |
| units | No | |
| traits | Yes | |
| station | Yes | |
| warning | Yes | |
| allergens | Yes | |
| nutrition | Yes | |
| mhealthyTier | Yes | |
| carbonFootprint | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the safe-read profile (readOnly, idempotent, non-destructive), and the description adds real behavioral context the annotations can't: name-matching rules (case-insensitive, exact wins, else unique substring), what panel values look like (numbers with a units map, derived micronutrient amount plus published percent), asOf data age, and that allergens are advisory. It stops short of saying what happens when a substring matches nothing or matches ambiguously, which is the main remaining gap.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose and the routing rule, then terse notes on the result shape. Efficient, but several clauses are telegraphic to the point of being hard to parse on first read ('Dish row uses the same coded layout as the sweep tools', 'asOf data age').
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values needn't be enumerated, yet the description still gives the agent enough about panel structure and the advisory nature of allergens to interpret results. Combined with full schema coverage and explicit sibling routing, nothing essential for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so date, hall, item, meal, and station are all documented in the schema itself, making 3 the baseline. The description does add one useful semantic beyond the schema — how the item name is resolved (exact match beats substring) — but does not explain how meal/station narrowing interacts with an ambiguous name.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'Get the full nutrition panel of one dish' — and immediately frames it against the sibling that handles the many-dish case. An agent can tell this apart from filter_dishes and get_menu without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly scopes use: 'Use this only when you need the full panel for a single dish. For multiple dishes, use filter_dishes with include_nutrients instead.' Both the when and the named alternative are stated, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_locationsList dining hallsARead-onlyIdempotent
List the dining halls and their service periods for the day, with whether they are open now. openNow is evaluated at the current campus-local time. The entries are the halls and their service periods for the day. stale means a page was served from an expired cache entry; asOf is the age of the data. Cafes, markets, bakeries, grills, prices and meal plans are out of scope.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | The day to read. Defaults to today. | |
| hall | No | Keep only this hall. |
Output Schema
| Name | Required | Description |
|---|---|---|
| date | Yes | |
| failures | Yes | |
| locations | Yes | |
| mapUrlTemplate | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so the safety burden is light. The description adds genuinely useful behavior: openNow is evaluated at campus-local time, and stale/asOf describe cache-expiry semantics an agent would otherwise misread.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core purpose well, but 'The entries are the halls and their service periods for the day' restates the opening sentence almost verbatim, spending a sentence without adding information. The out-of-scope clause earns its place; the repetition does not.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a rich input schema, an output schema, and full annotations, the description covers what's needed to call it: scope, time basis, and cache semantics. Only a hint about empty-result or error behavior is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for both parameters (date pattern and hall enum), so the baseline of 3 applies. The description adds only the implicit 'for the day' framing and nothing about date format or the hall enum beyond what the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a concrete verb (list) and resource (dining halls) with scope: service periods for the day and open-now status. The out-of-scope sentence separates it from menu/price/meal-plan siblings, but it does not distinguish it from find_open_now, which overlaps on the openNow concept.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear when-not list (cafes, markets, bakeries, grills, prices, meal plans) and clarifies the date default applies to 'the day'. Missing an explicit pointer to the sibling to use for cross-hall open-now filtering (find_open_now), so the alternative routing is only partial.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_nutrition_fieldsList nutrition fieldsARead-onlyIdempotent
List the canonical nutrient identifiers, their units, the alternative units a threshold may be written in, and the Daily Value basis of the published micronutrients. A constraint on a nutrient excludes the dishes that publish no value for it, and a value the site writes as "trace" or "< 1%" is missing rather than zero. sortFields includes servingSize, mhealthyTier and ratio syntax "a:b".
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| notes | Yes | |
| fields | Yes | |
| sortFields | Yes | |
| acceptedUnits | Yes | |
| micronutrients | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is covered; the description instead adds genuine domain behavior — that a constraint excludes dishes publishing no value for that nutrient, and that 'trace' or '< 1%' means missing rather than zero. That is exactly the kind of non-obvious semantics annotations cannot convey. It stops short of describing response ordering or cache/staleness behavior, keeping it at 4.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the actual list contents, zero filler. The closing sortFields sentence is somewhat displaced — it describes a parameter of a different tool — but it is compact and earns its place as domain context.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return values need no explanation, and the description supplies the interpretive layer an agent needs: what the identifiers mean, how units and Daily Value baselines relate, and how absent/trace values behave. Only the explicit 'call this first' workflow link to filter_dishes is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline is 4; the schema is empty and closed. The description does add meaning about fields consumed elsewhere (alternative threshold units, sortFields including servingSize and mhealthyTier with 'a:b' ratio syntax), which is useful even though none of it belongs to this tool's own input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource — 'List the canonical nutrient identifiers, their units, the alternative units a threshold may be written in, and the Daily Value basis' — which is far more precise than the title. It is clearly a reference/metadata lookup, distinguishable in kind from get_item_nutrition, though it never names a sibling to make the contrast explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is the discovery step before writing a nutrient constraint or a sortFields expression. There is no explicit 'use this before calling filter_dishes' statement and no when-not guidance, so the routing must be reconstructed from the constraint and sortFields remarks.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.1.1- Changed
filter_dishes46 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date in YYYY-MM-DD. The site publishes menus for today-3 days to today+14 days."New value: +"A single day to filter. Cannot be combined with from/to." - added
Input schema / properties / exclude_allergens / descriptionAdded value: +"Keeps only the dishes that carry none of these allergens." - added
Input schema / properties / fromAdded value: +{ + "description": "The first day of a range to filter.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" +} - added
Input schema / properties / hall / anyOfAdded value: +[ + { + "enum": [ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" + ], + "type": "string" + }, + { + "items": { + "enum": [ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" + ], + "type": "string" + }, + "maxItems": 10, + "type": "array" + } +] - added
Input schema / properties / hall / descriptionAdded value: +"Halls: one, a list, or none for all." - removed
Input schema / properties / hall / enumRemoved value: -[ - "bursley", - "east-quad", - "markley", - "mosher-jordan", - "north-quad", - "south-quad", - "twigs-at-oxford", - "wolverine-village", - "lawyers-club", - "martha-cook" -] - removed
Input schema / properties / hall / typeRemoved value: -"string" - added
Input schema / properties / include_nutrientsAdded value: +{ + "description": "Nutrients to return, by key/name, or [\"all\"].", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / limit / descriptionAdded value: +"Rows to return (default 50, max 500)." - removed
Input schema / properties / max_caloriesRemoved value: -{ - "minimum": 0, - "type": "number" -} - added
Input schema / properties / max_carbon / descriptionAdded value: +"The highest acceptable carbon footprint: 1 (low), 2 (medium) or 3 (high). The dishes the site did not tag are dropped." - removed
Input schema / properties / max_carbon / enumRemoved value: -[ - "low", - "medium", - "high" -] - added
Input schema / properties / max_carbon / maximumAdded value: +3 - added
Input schema / properties / max_carbon / minimumAdded value: +1 - changed
Input schema / properties / max_carbon / typePrevious value: -"string"New value: +"integer" - removed
Input schema / properties / max_sodium_mgRemoved value: -{ - "minimum": 0, - "type": "number" -} - added
Input schema / properties / mealAdded value: +{ + "description": "Keeps only that meal.", + "enum": [ + "Breakfast", + "Brunch", + "Lunch", + "Dinner" + ], + "type": "string" +} - added
Input schema / properties / min_mhealthy / descriptionAdded value: +"The lowest acceptable nutrient-density tier. The dishes the site did not tag are dropped." - added
Input schema / properties / nutrientsAdded value: +{ + "additionalProperties": { + "properties": { + "max": { + "description": "The highest acceptable value, in the unit below.", + "type": "number" + }, + "min": { + "description": "The lowest acceptable value, in the unit below.", + "type": "number" + }, + "unit": { + "description": "The unit of the bounds. It defaults to the nutrient's own; a micronutrient defaults to the published percent.", + "type": "string" + } + }, + "type": "object" + }, + "description": "Range per nutrient, e.g. {\"protein\":{\"min\":20,\"unit\":\"g\"}}. A constraint drops dishes with no value.", + "propertyNames": { + "type": "string" + }, + "type": "object" +} - added
Input schema / properties / offsetAdded value: +{ + "description": "Pages of the range to skip.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - added
Input schema / properties / queryAdded value: +{ + "description": "A case-insensitive substring of the dish name.", + "type": "string" +} - added
Input schema / properties / require_traits / descriptionAdded value: +"Keeps only the dishes that carry every one of these traits." - added
Input schema / properties / sort_byAdded value: +{ + "description": "Sort field: a nutrient key, carbonFootprint, a micronutrient name, servingSize, mhealthyTier, or a ratio \"numerator:denominator\" (e.g. \"protein:calories\", \"vitaminA:iron\"). Legacy: proteinToCalorie, fiberToCalorie.", + "type": "string" +} - added
Input schema / properties / sort_orderAdded value: +{ + "description": "The direction of the sort. It defaults to desc, so the biggest value comes first.", + "enum": [ + "asc", + "desc" + ], + "type": "string" +} - added
Input schema / properties / stationAdded value: +{ + "description": "Keeps only that station.", + "type": "string" +} - added
Input schema / properties / toAdded value: +{ + "description": "The last day of a range to filter. Defaults to the last published day.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" +} - added
Output schema / properties / asOfAdded value: +{ + "type": "string" +} - added
Output schema / properties / coverageAdded value: +{ + "additionalProperties": false, + "properties": { + "cached": { + "type": "number" + }, + "failed": { + "type": "number" + }, + "fetchedNow": { + "type": "number" + }, + "nextOffset": { + "type": [ + "number", + "null" + ] + }, + "offset": { + "type": "number" + }, + "pages": { + "type": "number" + }, + "pending": { + "type": "number" + }, + "requested": { + "type": "number" + }, + "stale": { + "type": "number" + } + }, + "required": [ + "requested", + "cached", + "fetchedNow", + "pending", + "failed", + "stale", + "offset", + "pages", + "nextOffset" + ], + "type": "object" +} - added
Output schema / properties / dateRangeAdded value: +{ + "additionalProperties": false, + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "from", + "to" + ], + "type": "object" +} - added
Output schema / properties / filterAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / flagsAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / keyAdded value: +{ + "additionalProperties": { + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - removed
Output schema / properties / matches / items / additionalPropertiesRemoved value: -false - added
Output schema / properties / matches / items / itemsAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "additionalProperties": { + "type": "number" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + } + ] +} - removed
Output schema / properties / matches / items / propertiesRemoved value: -{ - "allergens": { - "items": { - "type": "string" - }, - "type": "array" - }, - "calories": { - "type": [ - "number", - "null" - ] - }, - "carbonFootprint": { - "anyOf": [ - { - "enum": [ - "low", - "medium", - "high" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "date": { - "type": "string" - }, - "hall": { - "enum": [ - "bursley", - "east-quad", - "markley", - "mosher-jordan", - "north-quad", - "south-quad", - "twigs-at-oxford", - "wolverine-village", - "lawyers-club", - "martha-cook" - ], - "type": "string" - }, - "hallName": { - "type": "string" - }, - "meal": { - "enum": [ - "Breakfast", - "Lunch", - "Dinner" - ], - "type": "string" - }, - "mhealthyTier": { - "type": [ - "number", - "null" - ] - }, - "name": { - "type": "string" - }, - "sodiumMg": { - "type": [ - "number", - "null" - ] - }, - "station": { - "type": "string" - }, - "traitLabels": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "traits": { - "items": { - "type": "string" - }, - "type": "array" - } -} - removed
Output schema / properties / matches / items / requiredRemoved value: -[ - "hall", - "hallName", - "date", - "meal", - "station", - "name", - "traits", - "traitLabels", - "allergens", - "mhealthyTier", - "carbonFootprint", - "calories", - "sodiumMg" -] - changed
Output schema / properties / matches / items / typePrevious value: -"object"New value: +"array" - added
Output schema / properties / noMenuAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "date": { + "type": "string" + }, + "hall": { + "type": "string" + } + }, + "required": [ + "hall", + "date" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / noteAdded value: +{ + "type": "string" +} - added
Output schema / properties / nutrientsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / staleAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / truncatedAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / unitsAdded value: +{ + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - removed
Output schema / properties / warningRemoved value: -{ - "type": "string" -} - changed
Output schema / requiredPrevious value: -[ - "date", - "totalMatches", - "matches", - "failures", - "warning" -]New value: +[ + "key", + "totalMatches", + "truncated", + "stale", + "matches", + "failures" +]
- Changed
find_open_now22 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / atAdded value: +{ + "description": "The campus-local time of day to look at. Defaults to the current time.", + "pattern": "^\\d{1,2}:\\d{2}(\\s*(am|pm))?$", + "type": "string" +} - added
Input schema / properties / dateAdded value: +{ + "description": "The day to look at. Defaults to today.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" +} - added
Output schema / properties / asOfAdded value: +{ + "type": "string" +} - added
Output schema / properties / atAdded value: +{ + "type": "string" +} - removed
Output schema / properties / closed / items / additionalPropertiesRemoved value: -false - added
Output schema / properties / closed / items / enumAdded value: +[ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" +] - removed
Output schema / properties / closed / items / propertiesRemoved value: -{ - "address": { - "type": [ - "string", - "null" - ] - }, - "date": { - "type": "string" - }, - "email": { - "type": [ - "string", - "null" - ] - }, - "glutenFreePantry": { - "type": "boolean" - }, - "id": { - "enum": [ - "bursley", - "east-quad", - "markley", - "mosher-jordan", - "north-quad", - "south-quad", - "twigs-at-oxford", - "wolverine-village", - "lawyers-club", - "martha-cook" - ], - "type": "string" - }, - "mapUrl": { - "type": [ - "string", - "null" - ] - }, - "meals": { - "items": { - "enum": [ - "Breakfast", - "Lunch", - "Dinner" - ], - "type": "string" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "openNow": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "phone": { - "type": [ - "string", - "null" - ] - }, - "residentsOnly": { - "type": "boolean" - }, - "stale": { - "type": "boolean" - }, - "today": { - "items": { - "additionalProperties": false, - "properties": { - "closed": { - "type": "boolean" - }, - "label": { - "type": "string" - }, - "times": { - "type": "string" - } - }, - "required": [ - "label", - "times", - "closed" - ], - "type": "object" - }, - "type": "array" - }, - "url": { - "type": "string" - } -} - removed
Output schema / properties / closed / items / requiredRemoved value: -[ - "id", - "name", - "path", - "url", - "residentsOnly", - "glutenFreePantry", - "address", - "phone", - "email", - "mapUrl", - "meals", - "date", - "today", - "openNow", - "stale" -] - changed
Output schema / properties / closed / items / typePrevious value: -"object"New value: +"string" - removed
Output schema / properties / open / items / properties / addressRemoved value: -{ - "type": [ - "string", - "null" - ] -} - added
Output schema / properties / open / items / properties / asOfAdded value: +{ + "type": "string" +} - removed
Output schema / properties / open / items / properties / dateRemoved value: -{ - "type": "string" -} - removed
Output schema / properties / open / items / properties / emailRemoved value: -{ - "type": [ - "string", - "null" - ] -} - removed
Output schema / properties / open / items / properties / mapUrlRemoved value: -{ - "type": [ - "string", - "null" - ] -} - removed
Output schema / properties / open / items / properties / mealsRemoved value: -{ - "items": { - "enum": [ - "Breakfast", - "Lunch", - "Dinner" - ], - "type": "string" - }, - "type": "array" -} - removed
Output schema / properties / open / items / properties / pathRemoved value: -{ - "type": "string" -} - removed
Output schema / properties / open / items / properties / phoneRemoved value: -{ - "type": [ - "string", - "null" - ] -} - changed
Output schema / properties / open / items / properties / today / items / requiredPrevious value: -[ - "label", - "times", - "closed" -]New value: +[ + "label", + "times" +] - removed
Output schema / properties / open / items / properties / urlRemoved value: -{ - "type": "string" -} - changed
Output schema / properties / open / items / requiredPrevious value: -[ - "id", - "name", - "path", - "url", - "residentsOnly", - "glutenFreePantry", - "address", - "phone", - "email", - "mapUrl", - "meals", - "date", - "today", - "openNow", - "stale" -]New value: +[ + "id", + "name", + "residentsOnly", + "glutenFreePantry", + "today", + "openNow", + "stale" +] - changed
Output schema / requiredPrevious value: -[ - "date", - "open", - "closed", - "failures" -]New value: +[ + "date", + "at", + "open", + "closed", + "failures" +]
- Changed
get_hours17 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date in YYYY-MM-DD. The site publishes menus for today-3 days to today+14 days."New value: +"The day to read. Defaults to today." - added
Input schema / properties / hall / anyOfAdded value: +[ + { + "enum": [ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" + ], + "type": "string" + }, + { + "items": { + "enum": [ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" + ], + "type": "string" + }, + "maxItems": 10, + "type": "array" + } +] - added
Input schema / properties / hall / descriptionAdded value: +"Halls: one, a list, or none for all." - removed
Input schema / properties / hall / enumRemoved value: -[ - "bursley", - "east-quad", - "markley", - "mosher-jordan", - "north-quad", - "south-quad", - "twigs-at-oxford", - "wolverine-village", - "lawyers-club", - "martha-cook" -] - removed
Input schema / properties / hall / typeRemoved value: -"string" - added
Output schema / properties / asOfAdded value: +{ + "type": "string" +} - added
Output schema / properties / failuresAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "hall": { + "enum": [ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" + ], + "type": "string" + }, + "hallName": { + "type": "string" + } + }, + "required": [ + "hall", + "hallName", + "error" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / halls / items / properties / asOfAdded value: +{ + "type": "string" +} - removed
Output schema / properties / halls / items / properties / regularRemoved value: -{ - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "hall": { - "enum": [ - "bursley", - "east-quad", - "markley", - "mosher-jordan", - "north-quad", - "south-quad", - "twigs-at-oxford", - "wolverine-village", - "lawyers-club", - "martha-cook" - ], - "type": "string" - }, - "name": { - "type": "string" - }, - "url": { - "type": [ - "string", - "null" - ] - }, - "weekly": { - "items": { - "additionalProperties": false, - "properties": { - "closed": { - "type": "boolean" - }, - "days": { - "items": { - "type": "string" - }, - "type": "array" - }, - "periods": { - "items": { - "additionalProperties": false, - "properties": { - "closed": { - "type": "boolean" - }, - "label": { - "type": "string" - }, - "times": { - "type": "string" - } - }, - "required": [ - "label", - "times", - "closed" - ], - "type": "object" - }, - "type": "array" - } - }, - "required": [ - "days", - "periods", - "closed" - ], - "type": "object" - }, - "type": "array" - } - }, - "required": [ - "hall", - "name", - "url", - "weekly" - ], - "type": "object" - }, - { - "type": "null" - } - ] -} - changed
Output schema / properties / halls / items / properties / today / items / requiredPrevious value: -[ - "label", - "times", - "closed" -]New value: +[ + "label", + "times" +] - added
Output schema / properties / halls / items / properties / weekdayAdded value: +{ + "type": "string" +} - added
Output schema / properties / halls / items / properties / weeklyAdded value: +{ + "type": [ + "string", + "null" + ] +} - changed
Output schema / properties / halls / items / requiredPrevious value: -[ - "hall", - "name", - "date", - "today", - "regular", - "stale" -]New value: +[ + "hall", + "name", + "date", + "today", + "weekly", + "stale" +] - added
Output schema / properties / weekdayAdded value: +{ + "type": "string" +} - added
Output schema / properties / weeklyAdded value: +{ + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "closed": { + "type": "boolean" + }, + "days": { + "items": { + "type": "string" + }, + "type": "array" + }, + "periods": { + "items": { + "additionalProperties": false, + "properties": { + "closed": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "times": { + "type": "string" + } + }, + "required": [ + "label", + "times" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "days", + "periods" + ], + "type": "object" + }, + "type": "array" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "date", - "halls" -]New value: +[ + "date", + "halls", + "failures" +]
- Changed
get_item_nutrition22 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date in YYYY-MM-DD. The site publishes menus for today-3 days to today+14 days."New value: +"Date YYYY-MM-DD. Site publishes today-3..+14 days." - added
Input schema / properties / mealAdded value: +{ + "description": "Narrow the search to one meal.", + "enum": [ + "Breakfast", + "Brunch", + "Lunch", + "Dinner" + ], + "type": "string" +} - added
Input schema / properties / stationAdded value: +{ + "description": "Narrow the search to one station.", + "type": "string" +} - changed
Output schema / properties / allergens / items / typePrevious value: -"string"New value: +"number" - added
Output schema / properties / asOfAdded value: +{ + "type": "string" +} - changed
Output schema / properties / carbonFootprint / anyOfPrevious value: -[ - { - "enum": [ - "low", - "medium", - "high" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maximum": 3, + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } +] - removed
Output schema / properties / hall / enumRemoved value: -[ - "bursley", - "east-quad", - "markley", - "mosher-jordan", - "north-quad", - "south-quad", - "twigs-at-oxford", - "wolverine-village", - "lawyers-club", - "martha-cook" -] - changed
Output schema / properties / hall / typePrevious value: -"string"New value: +"number" - removed
Output schema / properties / hallNameRemoved value: -{ - "type": "string" -} - added
Output schema / properties / keyAdded value: +{ + "additionalProperties": { + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - removed
Output schema / properties / meal / enumRemoved value: -[ - "Breakfast", - "Lunch", - "Dinner" -] - changed
Output schema / properties / meal / typePrevious value: -"string"New value: +"number" - changed
Output schema / properties / nutrition / additionalPropertiesPrevious value: -falseNew value: +{} - removed
Output schema / properties / nutrition / propertiesRemoved value: -{ - "calories": { - "type": [ - "number", - "null" - ] - }, - "cholesterol": { - "type": [ - "string", - "null" - ] - }, - "dietaryFiber": { - "type": [ - "string", - "null" - ] - }, - "micronutrients": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "protein": { - "type": [ - "string", - "null" - ] - }, - "saturatedFat": { - "type": [ - "string", - "null" - ] - }, - "servingSize": { - "type": [ - "string", - "null" - ] - }, - "sodium": { - "type": [ - "string", - "null" - ] - }, - "sugars": { - "type": [ - "string", - "null" - ] - }, - "totalCarbohydrate": { - "type": [ - "string", - "null" - ] - }, - "totalFat": { - "type": [ - "string", - "null" - ] - }, - "transFat": { - "type": [ - "string", - "null" - ] - } -} - added
Output schema / properties / nutrition / propertyNamesAdded value: +{ + "type": "string" +} - removed
Output schema / properties / nutrition / requiredRemoved value: -[ - "servingSize", - "calories", - "totalFat", - "saturatedFat", - "transFat", - "cholesterol", - "sodium", - "totalCarbohydrate", - "dietaryFiber", - "sugars", - "protein", - "micronutrients" -] - changed
Output schema / properties / station / typePrevious value: -"string"New value: +"number" - removed
Output schema / properties / traitLabelsRemoved value: -{ - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" -} - changed
Output schema / properties / traits / items / typePrevious value: -"string"New value: +"number" - added
Output schema / properties / unitsAdded value: +{ + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "hall", - "hallName", - "date", - "meal", - "station", - "name", - "traits", - "traitLabels", - "allergens", - "mhealthyTier", - "carbonFootprint", - "nutrition", - "warning" -]New value: +[ + "key", + "hall", + "date", + "meal", + "station", + "name", + "traits", + "allergens", + "mhealthyTier", + "carbonFootprint", + "nutrition", + "warning" +]
- Changed
get_menu23 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date in YYYY-MM-DD. The site publishes menus for today-3 days to today+14 days."New value: +"The day to read. Defaults to today." - added
Input schema / properties / include_nutrientsAdded value: +{ + "description": "Nutrients to return, by key/name, or [\"all\"].", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / meal / anyOfAdded value: +[ + { + "enum": [ + "Breakfast", + "Brunch", + "Lunch", + "Dinner" + ], + "type": "string" + }, + { + "items": { + "enum": [ + "Breakfast", + "Brunch", + "Lunch", + "Dinner" + ], + "type": "string" + }, + "minItems": 1, + "type": "array" + } +] - added
Input schema / properties / meal / descriptionAdded value: +"Keeps only those meals: one name, or a list of them. When it is left out, the meal the hall is serving right now is used." - removed
Input schema / properties / meal / enumRemoved value: -[ - "Breakfast", - "Lunch", - "Dinner" -] - removed
Input schema / properties / meal / typeRemoved value: -"string" - added
Input schema / properties / stationAdded value: +{ + "description": "Keeps only that station.", + "type": "string" +} - added
Output schema / properties / asOfAdded value: +{ + "type": "string" +} - removed
Output schema / properties / hallNameRemoved value: -{ - "type": "string" -} - added
Output schema / properties / keyAdded value: +{ + "additionalProperties": { + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - removed
Output schema / properties / mealLabelsRemoved value: -{ - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" -} - removed
Output schema / properties / meals / additionalPropertiesRemoved value: -false - added
Output schema / properties / meals / itemsAdded value: +{ + "additionalProperties": false, + "properties": { + "meal": { + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" + }, + "stations": { + "items": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "additionalProperties": { + "type": "number" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + } + ] + }, + "type": "array" + }, + "type": "array" + }, + "station": { + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "station", + "items" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "meal", + "stations" + ], + "type": "object" +} - removed
Output schema / properties / meals / propertiesRemoved value: -{ - "Breakfast": { - "items": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "additionalProperties": false, - "properties": { - "allergens": { - "items": { - "type": "string" - }, - "type": "array" - }, - "carbonFootprint": { - "anyOf": [ - { - "enum": [ - "low", - "medium", - "high" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "mhealthyTier": { - "type": [ - "number", - "null" - ] - }, - "name": { - "type": "string" - }, - "nutrition": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "calories": { - "type": [ - "number", - "null" - ] - }, - "cholesterol": { - "type": [ - "string", - "null" - ] - }, - "dietaryFiber": { - "type": [ - "string", - "null" - ] - }, - "micronutrients": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "protein": { - "type": [ - "string", - "null" - ] - }, - "saturatedFat": { - "type": [ - "string", - "null" - ] - }, - "servingSize": { - "type": [ - "string", - "null" - ] - }, - "sodium": { - "type": [ - "string", - "null" - ] - }, - "sugars": { - "type": [ - "string", - "null" - ] - }, - "totalCarbohydrate": { - "type": [ - "string", - "null" - ] - }, - "totalFat": { - "type": [ - "string", - "null" - ] - }, - "transFat": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "servingSize", - "calories", - "totalFat", - "saturatedFat", - "transFat", - "cholesterol", - "sodium", - "totalCarbohydrate", - "dietaryFiber", - "sugars", - "protein", - "micronutrients" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "traitLabels": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "traits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "name", - "traits", - "traitLabels", - "allergens", - "mhealthyTier", - "carbonFootprint", - "nutrition" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - } - }, - "required": [ - "name", - "items" - ], - "type": "object" - }, - "type": "array" - }, - "Dinner": { - "items": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "additionalProperties": false, - "properties": { - "allergens": { - "items": { - "type": "string" - }, - "type": "array" - }, - "carbonFootprint": { - "anyOf": [ - { - "enum": [ - "low", - "medium", - "high" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "mhealthyTier": { - "type": [ - "number", - "null" - ] - }, - "name": { - "type": "string" - }, - "nutrition": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "calories": { - "type": [ - "number", - "null" - ] - }, - "cholesterol": { - "type": [ - "string", - "null" - ] - }, - "dietaryFiber": { - "type": [ - "string", - "null" - ] - }, - "micronutrients": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "protein": { - "type": [ - "string", - "null" - ] - }, - "saturatedFat": { - "type": [ - "string", - "null" - ] - }, - "servingSize": { - "type": [ - "string", - "null" - ] - }, - "sodium": { - "type": [ - "string", - "null" - ] - }, - "sugars": { - "type": [ - "string", - "null" - ] - }, - "totalCarbohydrate": { - "type": [ - "string", - "null" - ] - }, - "totalFat": { - "type": [ - "string", - "null" - ] - }, - "transFat": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "servingSize", - "calories", - "totalFat", - "saturatedFat", - "transFat", - "cholesterol", - "sodium", - "totalCarbohydrate", - "dietaryFiber", - "sugars", - "protein", - "micronutrients" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "traitLabels": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "traits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "name", - "traits", - "traitLabels", - "allergens", - "mhealthyTier", - "carbonFootprint", - "nutrition" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - } - }, - "required": [ - "name", - "items" - ], - "type": "object" - }, - "type": "array" - }, - "Lunch": { - "items": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "additionalProperties": false, - "properties": { - "allergens": { - "items": { - "type": "string" - }, - "type": "array" - }, - "carbonFootprint": { - "anyOf": [ - { - "enum": [ - "low", - "medium", - "high" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "mhealthyTier": { - "type": [ - "number", - "null" - ] - }, - "name": { - "type": "string" - }, - "nutrition": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "calories": { - "type": [ - "number", - "null" - ] - }, - "cholesterol": { - "type": [ - "string", - "null" - ] - }, - "dietaryFiber": { - "type": [ - "string", - "null" - ] - }, - "micronutrients": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "protein": { - "type": [ - "string", - "null" - ] - }, - "saturatedFat": { - "type": [ - "string", - "null" - ] - }, - "servingSize": { - "type": [ - "string", - "null" - ] - }, - "sodium": { - "type": [ - "string", - "null" - ] - }, - "sugars": { - "type": [ - "string", - "null" - ] - }, - "totalCarbohydrate": { - "type": [ - "string", - "null" - ] - }, - "totalFat": { - "type": [ - "string", - "null" - ] - }, - "transFat": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "servingSize", - "calories", - "totalFat", - "saturatedFat", - "transFat", - "cholesterol", - "sodium", - "totalCarbohydrate", - "dietaryFiber", - "sugars", - "protein", - "micronutrients" - ], - "type": "object" - }, - { - "type": "null" - } - ] - }, - "traitLabels": { - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - "traits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "name", - "traits", - "traitLabels", - "allergens", - "mhealthyTier", - "carbonFootprint", - "nutrition" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - } - }, - "required": [ - "name", - "items" - ], - "type": "object" - }, - "type": "array" - } -} - removed
Output schema / properties / meals / requiredRemoved value: -[ - "Breakfast", - "Lunch", - "Dinner" -] - changed
Output schema / properties / meals / typePrevious value: -"object"New value: +"array" - added
Output schema / properties / noteAdded value: +{ + "type": "string" +} - added
Output schema / properties / nutrientsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / renderedDate / typePrevious value: -[ - "string", - "null" -]New value: +"string" - added
Output schema / properties / unitsAdded value: +{ + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / unknownMealLabelsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "hall", - "hallName", - "date", - "mealLabels", - "meals", - "renderedDate", - "totalStations", - "totalItems", - "stale" -]New value: +[ + "hall", + "date", + "key", + "meals", + "unknownMealLabels", + "totalStations", + "totalItems", + "stale" +]
- Changed
list_locations13 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date in YYYY-MM-DD. The site publishes menus for today-3 days to today+14 days."New value: +"The day to read. Defaults to today." - added
Input schema / properties / hallAdded value: +{ + "description": "Keep only this hall.", + "enum": [ + "bursley", + "east-quad", + "markley", + "mosher-jordan", + "north-quad", + "south-quad", + "twigs-at-oxford", + "wolverine-village", + "lawyers-club", + "martha-cook" + ], + "type": "string" +} - added
Output schema / properties / locations / items / properties / asOfAdded value: +{ + "type": "string" +} - removed
Output schema / properties / locations / items / properties / dateRemoved value: -{ - "type": "string" -} - removed
Output schema / properties / locations / items / properties / mapUrlRemoved value: -{ - "type": [ - "string", - "null" - ] -} - removed
Output schema / properties / locations / items / properties / mealsRemoved value: -{ - "items": { - "enum": [ - "Breakfast", - "Lunch", - "Dinner" - ], - "type": "string" - }, - "type": "array" -} - removed
Output schema / properties / locations / items / properties / pathRemoved value: -{ - "type": "string" -} - changed
Output schema / properties / locations / items / properties / today / items / requiredPrevious value: -[ - "label", - "times", - "closed" -]New value: +[ + "label", + "times" +] - changed
Output schema / properties / locations / items / requiredPrevious value: -[ - "id", - "name", - "path", - "url", - "residentsOnly", - "glutenFreePantry", - "address", - "phone", - "email", - "mapUrl", - "meals", - "date", - "today", - "openNow", - "stale" -]New value: +[ + "id", + "name", + "url", + "residentsOnly", + "glutenFreePantry", + "address", + "phone", + "email", + "today", + "openNow", + "stale" +] - added
Output schema / properties / mapUrlTemplateAdded value: +{ + "type": "string" +} - removed
Output schema / properties / warningRemoved value: -{ - "type": "string" -} - changed
Output schema / requiredPrevious value: -[ - "date", - "locations", - "failures", - "warning" -]New value: +[ + "date", + "locations", + "failures" +]
- Added
list_nutrition_fields - Removed
search_dishes
7 tool updates
v0.1.0- First observed
filter_dishes - First observed
find_open_now - First observed
get_hours - First observed
get_item_nutrition - First observed
get_menu - First observed
list_locations - First observed
search_dishes
TDQS
Scored across 7 tools
Three tools (list_locations, get_hours, find_open_now) all touch opening status and service periods, and get_menu/filter_dishes both surface dish data for the day. The descriptions do draw distinctions (openNow vs. periods vs. time-filtered open list; filtering vs. single-hall menu), but an agent could reasonably pick the wrong one for a 'is it open / what's on' question.
All tools are lowercase snake_case and verb-led (list_, get_, filter_, find_), which is predictable. The minor deviation is find_open_now, which is a verb+adverb phrase rather than the verb_noun shape used elsewhere.
Seven tools for a read-only dining domain (locations, menus, filtering, nutrition, hours, open-now, per-item nutrition) is well-scoped, with each tool earning its place.
Core read workflows are covered: locations, hall menus, cross-hall filtering with nutrients, nutrient field reference, hours, open-now, and item nutrition. Coverage is deliberately scoped to dining halls (cafes, prices, meal plans explicitly out), which is reasonable, though there is no direct station-level view beyond what get_menu/filter_dishes return.
Maintenance
Related MCP Connectors
Persistent food journal for AI assistants. Log meals, save recipes, plan meals and track nutrition.
Find restaurants and sourced dietary dish evidence. Free discovery; metered menus need an API key.
Look up a packaged food by barcode, search and compare products, and check listed allergens.
Log meals, check calories and macros, set up a nutrition plan, and search foods.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides real-time access to menus from all six MIT Bon Appétit dining halls. It enables users to query daily or weekly schedules and filter options by specific dietary requirements such as vegan, halal, or gluten-free.25 npm1MIT
- AlicenseAqualityDmaintenanceEnables natural language access to USDA's FoodData Central database with 1M+ foods, supporting search, nutrition facts, food comparison, and daily value calculations.8MIT
- FlicenseNot gradedqualityDmaintenanceEnables logging food into MyFitnessPal diary via natural language, supporting search, log, quick add, and diary retrieval.-
- AlicenseNot gradedqualityCmaintenanceEnables querying Yale Hospitality dining menus, listing residential colleges, fetching meal offerings, and searching dishes across colleges via MCP tools.MIT