Skip to main content
Glama
esreddy2

umich-dining-mcp

by esreddy2

umich-dining-mcp

npm CI Live site

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

io.github.esreddy2/umich-dining-mcp

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-mcp

or run it without installing anything:

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"]
    }
  }
}

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

list_locations

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.

get_menu

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.

search_dishes

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_dishes

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.

list_nutrition_fields

The supported nutrient names, their units and the Daily Value basis of the micronutrients.

get_hours

The service periods of a day plus the regular weekly schedule. A hall that cannot be read is reported in failures; the others are still returned.

find_open_now

Which halls are serving at a given time of day.

get_item_nutrition

The full nutrition panel of a single dish.

Resources

Resource

Content

umich://dining-halls

The hall registry as JSON, with the code of every hall.

umich://taxonomy

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.

  • mhealthyTier is 1 (Nutrient Dense Low) to 5 (Nutrient Dense High). The scale is fixed and described in the tool descriptions and in umich://taxonomy.

  • A row carries the carbon tier (carbonFootprint) only when the call constrains it with max_carbon or sorts by carbonFootprint; otherwise it is left out to keep the row small.

  • The traits array carries the six boolean traits only; the density tier is lifted into its own numeric field.

  • The units map 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 is 2.7 in 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 limit takes 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 carries dateRange and each row carries the dates it appeared on.

  • A dish without a published value is reported as null, never as zero. A trace or 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

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

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

exclude_allergens

Keeps only the dishes that do not carry any of the listed allergens.

require_traits

Keeps only the dishes that carry every listed trait.

min_mhealthy

Minimum nutrient-density tier, 1..5 (3 = "Nutrient Dense Medium or better").

max_carbon

Maximum carbon footprint: 1 (low), 2 (medium) or 3 (high).

nutrients

A range per nutrient, e.g. {"protein": {"min": 20, "unit": "g"}}. The unit defaults to the nutrient's own and is checked.

include_nutrients

The nutrients to return, by canonical key or by published name, or all. Defaults to the ones the filter used; a constrained nutrient is always returned.

query

A case-insensitive substring of the dish name.

meal, station

Narrow the sweep to one meal or one station.

sort_by, sort_order

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

UMICH_DINING_USER_AGENT

python-requests/2.32.3

The User-Agent to use.

UMICH_DINING_BASE_URL

https://dining.umich.edu

The site to read.

UMICH_DINING_TIMEZONE

America/Detroit

The time zone of the campus: the day the site publishes and the mealtimes are read in it.

UMICH_DINING_TIMEOUT_MS

20000

Per-request timeout.

UMICH_DINING_MAX_RETRIES

3

Retries after the first attempt.

UMICH_DINING_MAX_CONCURRENCY

2

Requests in flight.

UMICH_DINING_MIN_GAP_MS

500

Minimum gap between requests.

UMICH_DINING_TTL_TODAY_MS

900000

Cache lifetime of today's menu.

UMICH_DINING_TTL_FUTURE_MS

21600000

Cache lifetime of a future menu.

UMICH_DINING_TTL_PAST_MS

86400000

Cache lifetime of a past menu.

UMICH_DINING_TTL_REGULAR_HOURS_MS

86400000

Cache lifetime of the weekly schedule.

UMICH_DINING_MAX_RANGE_FETCHES

12

Upstream pages a single range query may fetch.

UMICH_DINING_CACHE_DIR

~/.cache/umich-dining-mcp

The persistent cache. off switches it off.

UMICH_DINING_CACHE_MAX_AGE_DAYS

30

How long a cache entry is kept.

UMICH_DINING_CACHE_MAX_SIZE_MB

200

The largest the persistent cache may grow.

UMICH_DINING_CACHE_PRUNE_INTERVAL_MS

3600000

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.3 is 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 tools
filter_dishesFilter dishesA
Read-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.

NameRequiredDescriptionDefault
toNoThe last day of a range to filter. Defaults to the last published day.
dateNoA single day to filter. Cannot be combined with from/to.
fromNoThe first day of a range to filter.
hallNoHalls: one, a list, or none for all.
mealNoKeeps only that meal.
limitNoRows to return (default 50, max 500).
queryNoA case-insensitive substring of the dish name.
offsetNoPages of the range to skip.
sort_byNoSort field: a nutrient key, carbonFootprint, a micronutrient name, servingSize, mhealthyTier, or a ratio "numerator:denominator" (e.g. "protein:calories", "vitaminA:iron"). Legacy: proteinToCalorie, fiberToCalorie.
stationNoKeeps only that station.
nutrientsNoRange per nutrient, e.g. {"protein":{"min":20,"unit":"g"}}. A constraint drops dishes with no value.
max_carbonNoThe highest acceptable carbon footprint: 1 (low), 2 (medium) or 3 (high). The dishes the site did not tag are dropped.
sort_orderNoThe direction of the sort. It defaults to desc, so the biggest value comes first.
min_mhealthyNoThe lowest acceptable nutrient-density tier. The dishes the site did not tag are dropped.
require_traitsNoKeeps only the dishes that carry every one of these traits.
exclude_allergensNoKeeps only the dishes that carry none of these allergens.
include_nutrientsNoNutrients to return, by key/name, or ["all"].

Output Schema

NameRequiredDescription
keyYes
asOfNo
dateNo
noteNo
flagsNo
staleYes
unitsNo
filterNo
noMenuNo
matchesYes
coverageNo
failuresYes
dateRangeNo
nutrientsNo
truncatedYes
totalMatchesYes

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 nowA
Read-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.

NameRequiredDescriptionDefault
atNoThe campus-local time of day to look at. Defaults to the current time.
dateNoThe day to look at. Defaults to today.

Output Schema

NameRequiredDescription
atYes
asOfNo
dateYes
openYes
closedYes
failuresYes

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 hoursA
Read-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.

NameRequiredDescriptionDefault
dateNoThe day to read. Defaults to today.
hallNoHalls: one, a list, or none for all.

Output Schema

NameRequiredDescription
asOfNo
dateYes
hallsYes
weeklyNo
weekdayNo
failuresYes

TDQS

A4.1/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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 panelA
Read-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.

NameRequiredDescriptionDefault
dateNoDate YYYY-MM-DD. Site publishes today-3..+14 days.
hallYesThe dining hall to read.
itemYesThe dish name.
mealNoNarrow the search to one meal.
stationNoNarrow the search to one station.

Output Schema

NameRequiredDescription
keyYes
asOfNo
dateYes
hallYes
mealYes
nameYes
unitsNo
traitsYes
stationYes
warningYes
allergensYes
nutritionYes
mhealthyTierYes
carbonFootprintYes

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

get_menuGet a dining hall menuA
Read-onlyIdempotent

Get the menu of one dining hall: meals, stations and dishes. Dish rows are arrays: [name, traits, allergens, mhealthyTier, calories, servingSize, servingSizeGrams] plus nutrients named by include_nutrients (a get_menu row never carries carbonFootprint). traits are codes 1-6 (glutenfree, halal, kosher, spicy, vegan, vegetarian); allergens are codes; read the key block. mhealthyTier 1-5. Calories kcal. With no meal, the meal being served now is returned (or the next one if closed); the response says which and why. Date defaults to today; site publishes today-3..+14. 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.

NameRequiredDescriptionDefault
dateNoThe day to read. Defaults to today.
hallYesThe dining hall to read.
mealNoKeeps 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.
stationNoKeeps only that station.
include_nutrientsNoNutrients to return, by key/name, or ["all"].

Output Schema

NameRequiredDescription
keyYes
asOfNo
dateYes
hallYes
noteNo
mealsYes
staleYes
unitsNo
nutrientsNo
totalItemsYes
renderedDateNo
totalStationsYes
unknownMealLabelsYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety as read-only, idempotent, non-destructive, and open-world. The description adds substantial behavioral context: dish-row array structure, trait and allergen code meanings, mhealthyTier scale, calorie units, no carbonFootprint in get_menu rows, default meal fallback behavior, cache staleness and asOf age, and allergen advisory status.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and has no filler, but it is a dense single paragraph with compressed code explanations. It remains usable, though slightly more structure would improve scanability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the existing output schema and annotations, the description covers what an agent needs to call it correctly: scope, date window, meal fallback, station filtering context, nutrient inclusion, cache behavior, and allergen caveat. No critical behavioral gap is apparent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description goes beyond the schema by adding the date publication window, the meal fallback to the next serving if closed, the effect of include_nutrients on returned row contents, and the fact that get_menu rows never include carbonFootprint.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: "Get the menu of one dining hall: meals, stations and dishes." It is clear what the tool returns, but it does not explicitly distinguish itself from siblings such as filter_dishes or get_item_nutrition.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear usage context for defaults and freshness: meal defaults to the current or next serving, date defaults to today with a today-3..+14 publication window, and stale data means cached but best-available. It does not explicitly say when to choose this tool over alternatives like filter_dishes.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_locationsList dining hallsA
Read-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.

NameRequiredDescriptionDefault
dateNoThe day to read. Defaults to today.
hallNoKeep only this hall.

Output Schema

NameRequiredDescription
dateYes
failuresYes
locationsYes
mapUrlTemplateNo

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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 fieldsA
Read-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".

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
notesYes
fieldsYes
sortFieldsYes
acceptedUnitsYes
micronutrientsYes

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

  1. 8 tool updatesv0.1.1
    • Changedfilter_dishes46 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / date / description
        Previous 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."
      • addedInput schema / properties / exclude_allergens / description
        Added value: +"Keeps only the dishes that carry none of these allergens."
      • addedInput schema / properties / from
        Added value: +{
        +  "description": "The first day of a range to filter.",
        +  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        +  "type": "string"
        +}
      • addedInput schema / properties / hall / anyOf
        Added 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"
        +  }
        +]
      • addedInput schema / properties / hall / description
        Added value: +"Halls: one, a list, or none for all."
      • removedInput schema / properties / hall / enum
        Removed value: -[
        -  "bursley",
        -  "east-quad",
        -  "markley",
        -  "mosher-jordan",
        -  "north-quad",
        -  "south-quad",
        -  "twigs-at-oxford",
        -  "wolverine-village",
        -  "lawyers-club",
        -  "martha-cook"
        -]
      • removedInput schema / properties / hall / type
        Removed value: -"string"
      • addedInput schema / properties / include_nutrients
        Added value: +{
        +  "description": "Nutrients to return, by key/name, or [\"all\"].",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedInput schema / properties / limit / description
        Added value: +"Rows to return (default 50, max 500)."
      • removedInput schema / properties / max_calories
        Removed value: -{
        -  "minimum": 0,
        -  "type": "number"
        -}
      • addedInput schema / properties / max_carbon / description
        Added value: +"The highest acceptable carbon footprint: 1 (low), 2 (medium) or 3 (high). The dishes the site did not tag are dropped."
      • removedInput schema / properties / max_carbon / enum
        Removed value: -[
        -  "low",
        -  "medium",
        -  "high"
        -]
      • addedInput schema / properties / max_carbon / maximum
        Added value: +3
      • addedInput schema / properties / max_carbon / minimum
        Added value: +1
      • changedInput schema / properties / max_carbon / type
        Previous value: -"string"New value: +"integer"
      • removedInput schema / properties / max_sodium_mg
        Removed value: -{
        -  "minimum": 0,
        -  "type": "number"
        -}
      • addedInput schema / properties / meal
        Added value: +{
        +  "description": "Keeps only that meal.",
        +  "enum": [
        +    "Breakfast",
        +    "Brunch",
        +    "Lunch",
        +    "Dinner"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / min_mhealthy / description
        Added value: +"The lowest acceptable nutrient-density tier. The dishes the site did not tag are dropped."
      • addedInput schema / properties / nutrients
        Added 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"
        +}
      • addedInput schema / properties / offset
        Added value: +{
        +  "description": "Pages of the range to skip.",
        +  "maximum": 9007199254740991,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / query
        Added value: +{
        +  "description": "A case-insensitive substring of the dish name.",
        +  "type": "string"
        +}
      • addedInput schema / properties / require_traits / description
        Added value: +"Keeps only the dishes that carry every one of these traits."
      • addedInput schema / properties / sort_by
        Added 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"
        +}
      • addedInput schema / properties / sort_order
        Added value: +{
        +  "description": "The direction of the sort. It defaults to desc, so the biggest value comes first.",
        +  "enum": [
        +    "asc",
        +    "desc"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / station
        Added value: +{
        +  "description": "Keeps only that station.",
        +  "type": "string"
        +}
      • addedInput schema / properties / to
        Added 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"
        +}
      • addedOutput schema / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / coverage
        Added 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"
        +}
      • addedOutput schema / properties / dateRange
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "from": {
        +      "type": "string"
        +    },
        +    "to": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "from",
        +    "to"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / filter
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • addedOutput schema / properties / flags
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • addedOutput schema / properties / key
        Added value: +{
        +  "additionalProperties": {
        +    "additionalProperties": {
        +      "type": "string"
        +    },
        +    "propertyNames": {
        +      "type": "string"
        +    },
        +    "type": "object"
        +  },
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • removedOutput schema / properties / matches / items / additionalProperties
        Removed value: -false
      • addedOutput schema / properties / matches / items / items
        Added 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"
        +    }
        +  ]
        +}
      • removedOutput schema / properties / matches / items / properties
        Removed 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"
        -  }
        -}
      • removedOutput schema / properties / matches / items / required
        Removed value: -[
        -  "hall",
        -  "hallName",
        -  "date",
        -  "meal",
        -  "station",
        -  "name",
        -  "traits",
        -  "traitLabels",
        -  "allergens",
        -  "mhealthyTier",
        -  "carbonFootprint",
        -  "calories",
        -  "sodiumMg"
        -]
      • changedOutput schema / properties / matches / items / type
        Previous value: -"object"New value: +"array"
      • addedOutput schema / properties / noMenu
        Added value: +{
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "date": {
        +        "type": "string"
        +      },
        +      "hall": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "hall",
        +      "date"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / note
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / nutrients
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / units
        Added value: +{
        +  "additionalProperties": {
        +    "type": "string"
        +  },
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • removedOutput schema / properties / warning
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "date",
        -  "totalMatches",
        -  "matches",
        -  "failures",
        -  "warning"
        -]New value: +[
        +  "key",
        +  "totalMatches",
        +  "truncated",
        +  "stale",
        +  "matches",
        +  "failures"
        +]
    • Changedfind_open_now22 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / at
        Added 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"
        +}
      • addedInput schema / properties / date
        Added value: +{
        +  "description": "The day to look at. Defaults to today.",
        +  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        +  "type": "string"
        +}
      • addedOutput schema / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / at
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / closed / items / additionalProperties
        Removed value: -false
      • addedOutput schema / properties / closed / items / enum
        Added value: +[
        +  "bursley",
        +  "east-quad",
        +  "markley",
        +  "mosher-jordan",
        +  "north-quad",
        +  "south-quad",
        +  "twigs-at-oxford",
        +  "wolverine-village",
        +  "lawyers-club",
        +  "martha-cook"
        +]
      • removedOutput schema / properties / closed / items / properties
        Removed 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"
        -  }
        -}
      • removedOutput schema / properties / closed / items / required
        Removed value: -[
        -  "id",
        -  "name",
        -  "path",
        -  "url",
        -  "residentsOnly",
        -  "glutenFreePantry",
        -  "address",
        -  "phone",
        -  "email",
        -  "mapUrl",
        -  "meals",
        -  "date",
        -  "today",
        -  "openNow",
        -  "stale"
        -]
      • changedOutput schema / properties / closed / items / type
        Previous value: -"object"New value: +"string"
      • removedOutput schema / properties / open / items / properties / address
        Removed value: -{
        -  "type": [
        -    "string",
        -    "null"
        -  ]
        -}
      • addedOutput schema / properties / open / items / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / open / items / properties / date
        Removed value: -{
        -  "type": "string"
        -}
      • removedOutput schema / properties / open / items / properties / email
        Removed value: -{
        -  "type": [
        -    "string",
        -    "null"
        -  ]
        -}
      • removedOutput schema / properties / open / items / properties / mapUrl
        Removed value: -{
        -  "type": [
        -    "string",
        -    "null"
        -  ]
        -}
      • removedOutput schema / properties / open / items / properties / meals
        Removed value: -{
        -  "items": {
        -    "enum": [
        -      "Breakfast",
        -      "Lunch",
        -      "Dinner"
        -    ],
        -    "type": "string"
        -  },
        -  "type": "array"
        -}
      • removedOutput schema / properties / open / items / properties / path
        Removed value: -{
        -  "type": "string"
        -}
      • removedOutput schema / properties / open / items / properties / phone
        Removed value: -{
        -  "type": [
        -    "string",
        -    "null"
        -  ]
        -}
      • changedOutput schema / properties / open / items / properties / today / items / required
        Previous value: -[
        -  "label",
        -  "times",
        -  "closed"
        -]New value: +[
        +  "label",
        +  "times"
        +]
      • removedOutput schema / properties / open / items / properties / url
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / properties / open / items / required
        Previous 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"
        +]
      • changedOutput schema / required
        Previous value: -[
        -  "date",
        -  "open",
        -  "closed",
        -  "failures"
        -]New value: +[
        +  "date",
        +  "at",
        +  "open",
        +  "closed",
        +  "failures"
        +]
    • Changedget_hours17 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / date / description
        Previous 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."
      • addedInput schema / properties / hall / anyOf
        Added 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"
        +  }
        +]
      • addedInput schema / properties / hall / description
        Added value: +"Halls: one, a list, or none for all."
      • removedInput schema / properties / hall / enum
        Removed value: -[
        -  "bursley",
        -  "east-quad",
        -  "markley",
        -  "mosher-jordan",
        -  "north-quad",
        -  "south-quad",
        -  "twigs-at-oxford",
        -  "wolverine-village",
        -  "lawyers-club",
        -  "martha-cook"
        -]
      • removedInput schema / properties / hall / type
        Removed value: -"string"
      • addedOutput schema / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / failures
        Added 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"
        +}
      • addedOutput schema / properties / halls / items / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / halls / items / properties / regular
        Removed 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"
        -    }
        -  ]
        -}
      • changedOutput schema / properties / halls / items / properties / today / items / required
        Previous value: -[
        -  "label",
        -  "times",
        -  "closed"
        -]New value: +[
        +  "label",
        +  "times"
        +]
      • addedOutput schema / properties / halls / items / properties / weekday
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / halls / items / properties / weekly
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • changedOutput schema / properties / halls / items / required
        Previous value: -[
        -  "hall",
        -  "name",
        -  "date",
        -  "today",
        -  "regular",
        -  "stale"
        -]New value: +[
        +  "hall",
        +  "name",
        +  "date",
        +  "today",
        +  "weekly",
        +  "stale"
        +]
      • addedOutput schema / properties / weekday
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / weekly
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "date",
        -  "halls"
        -]New value: +[
        +  "date",
        +  "halls",
        +  "failures"
        +]
    • Changedget_item_nutrition22 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / date / description
        Previous 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."
      • addedInput schema / properties / meal
        Added value: +{
        +  "description": "Narrow the search to one meal.",
        +  "enum": [
        +    "Breakfast",
        +    "Brunch",
        +    "Lunch",
        +    "Dinner"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / station
        Added value: +{
        +  "description": "Narrow the search to one station.",
        +  "type": "string"
        +}
      • changedOutput schema / properties / allergens / items / type
        Previous value: -"string"New value: +"number"
      • addedOutput schema / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • changedOutput schema / properties / carbonFootprint / anyOf
        Previous value: -[
        -  {
        -    "enum": [
        -      "low",
        -      "medium",
        -      "high"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maximum": 3,
        +    "minimum": 1,
        +    "type": "integer"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / hall / enum
        Removed value: -[
        -  "bursley",
        -  "east-quad",
        -  "markley",
        -  "mosher-jordan",
        -  "north-quad",
        -  "south-quad",
        -  "twigs-at-oxford",
        -  "wolverine-village",
        -  "lawyers-club",
        -  "martha-cook"
        -]
      • changedOutput schema / properties / hall / type
        Previous value: -"string"New value: +"number"
      • removedOutput schema / properties / hallName
        Removed value: -{
        -  "type": "string"
        -}
      • addedOutput schema / properties / key
        Added value: +{
        +  "additionalProperties": {
        +    "additionalProperties": {
        +      "type": "string"
        +    },
        +    "propertyNames": {
        +      "type": "string"
        +    },
        +    "type": "object"
        +  },
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • removedOutput schema / properties / meal / enum
        Removed value: -[
        -  "Breakfast",
        -  "Lunch",
        -  "Dinner"
        -]
      • changedOutput schema / properties / meal / type
        Previous value: -"string"New value: +"number"
      • changedOutput schema / properties / nutrition / additionalProperties
        Previous value: -falseNew value: +{}
      • removedOutput schema / properties / nutrition / properties
        Removed 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"
        -    ]
        -  }
        -}
      • addedOutput schema / properties / nutrition / propertyNames
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / nutrition / required
        Removed value: -[
        -  "servingSize",
        -  "calories",
        -  "totalFat",
        -  "saturatedFat",
        -  "transFat",
        -  "cholesterol",
        -  "sodium",
        -  "totalCarbohydrate",
        -  "dietaryFiber",
        -  "sugars",
        -  "protein",
        -  "micronutrients"
        -]
      • changedOutput schema / properties / station / type
        Previous value: -"string"New value: +"number"
      • removedOutput schema / properties / traitLabels
        Removed value: -{
        -  "additionalProperties": {
        -    "type": "string"
        -  },
        -  "propertyNames": {
        -    "type": "string"
        -  },
        -  "type": "object"
        -}
      • changedOutput schema / properties / traits / items / type
        Previous value: -"string"New value: +"number"
      • addedOutput schema / properties / units
        Added value: +{
        +  "additionalProperties": {
        +    "type": "string"
        +  },
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedget_menu23 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / date / description
        Previous 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."
      • addedInput schema / properties / include_nutrients
        Added value: +{
        +  "description": "Nutrients to return, by key/name, or [\"all\"].",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedInput schema / properties / meal / anyOf
        Added value: +[
        +  {
        +    "enum": [
        +      "Breakfast",
        +      "Brunch",
        +      "Lunch",
        +      "Dinner"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "items": {
        +      "enum": [
        +        "Breakfast",
        +        "Brunch",
        +        "Lunch",
        +        "Dinner"
        +      ],
        +      "type": "string"
        +    },
        +    "minItems": 1,
        +    "type": "array"
        +  }
        +]
      • addedInput schema / properties / meal / description
        Added 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."
      • removedInput schema / properties / meal / enum
        Removed value: -[
        -  "Breakfast",
        -  "Lunch",
        -  "Dinner"
        -]
      • removedInput schema / properties / meal / type
        Removed value: -"string"
      • addedInput schema / properties / station
        Added value: +{
        +  "description": "Keeps only that station.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / hallName
        Removed value: -{
        -  "type": "string"
        -}
      • addedOutput schema / properties / key
        Added value: +{
        +  "additionalProperties": {
        +    "additionalProperties": {
        +      "type": "string"
        +    },
        +    "propertyNames": {
        +      "type": "string"
        +    },
        +    "type": "object"
        +  },
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • removedOutput schema / properties / mealLabels
        Removed value: -{
        -  "additionalProperties": {
        -    "type": "string"
        -  },
        -  "propertyNames": {
        -    "type": "string"
        -  },
        -  "type": "object"
        -}
      • removedOutput schema / properties / meals / additionalProperties
        Removed value: -false
      • addedOutput schema / properties / meals / items
        Added 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"
        +}
      • removedOutput schema / properties / meals / properties
        Removed 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"
        -  }
        -}
      • removedOutput schema / properties / meals / required
        Removed value: -[
        -  "Breakfast",
        -  "Lunch",
        -  "Dinner"
        -]
      • changedOutput schema / properties / meals / type
        Previous value: -"object"New value: +"array"
      • addedOutput schema / properties / note
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / nutrients
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / renderedDate / type
        Previous value: -[
        -  "string",
        -  "null"
        -]New value: +"string"
      • addedOutput schema / properties / units
        Added value: +{
        +  "additionalProperties": {
        +    "type": "string"
        +  },
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • addedOutput schema / properties / unknownMealLabels
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "hall",
        -  "hallName",
        -  "date",
        -  "mealLabels",
        -  "meals",
        -  "renderedDate",
        -  "totalStations",
        -  "totalItems",
        -  "stale"
        -]New value: +[
        +  "hall",
        +  "date",
        +  "key",
        +  "meals",
        +  "unknownMealLabels",
        +  "totalStations",
        +  "totalItems",
        +  "stale"
        +]
    • Changedlist_locations13 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / date / description
        Previous 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."
      • addedInput schema / properties / hall
        Added 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"
        +}
      • addedOutput schema / properties / locations / items / properties / asOf
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / locations / items / properties / date
        Removed value: -{
        -  "type": "string"
        -}
      • removedOutput schema / properties / locations / items / properties / mapUrl
        Removed value: -{
        -  "type": [
        -    "string",
        -    "null"
        -  ]
        -}
      • removedOutput schema / properties / locations / items / properties / meals
        Removed value: -{
        -  "items": {
        -    "enum": [
        -      "Breakfast",
        -      "Lunch",
        -      "Dinner"
        -    ],
        -    "type": "string"
        -  },
        -  "type": "array"
        -}
      • removedOutput schema / properties / locations / items / properties / path
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / properties / locations / items / properties / today / items / required
        Previous value: -[
        -  "label",
        -  "times",
        -  "closed"
        -]New value: +[
        +  "label",
        +  "times"
        +]
      • changedOutput schema / properties / locations / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / mapUrlTemplate
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / properties / warning
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "date",
        -  "locations",
        -  "failures",
        -  "warning"
        -]New value: +[
        +  "date",
        +  "locations",
        +  "failures"
        +]
    • Addedlist_nutrition_fields
    • Removedsearch_dishes
  2. 7 tool updatesv0.1.0
    • First observedfilter_dishes
    • First observedfind_open_now
    • First observedget_hours
    • First observedget_item_nutrition
    • First observedget_menu
    • First observedlist_locations
    • First observedsearch_dishes

TDQS

A4/5.0

Scored across 7 tools

Disambiguation3/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides 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 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables querying Yale Hospitality dining menus, listing residential colleges, fetching meal offerings, and searching dishes across colleges via MCP tools.
    MIT