Skip to main content
Glama
francisfan0

Yale Dining MCP

by francisfan0

Yale Dining MCP

Lightweight TypeScript service for Yale Hospitality menus and Payne Whitney Gym hours:

  1. An MCP server (@modelcontextprotocol/sdk) with tools for LLMs (stdio or Streamable HTTP).

  2. A REST gateway (POST /api/chat) for an iOS Shortcut — built for Siri speech and notification text.

Menus come from the public Nutrislice API. Gym hours and closures are parsed from PWG Hours of Operation.

Host on Vercel (iOS Shortcut)

The Mac does not need to stay on. Vercel Hobby is free HTTPS and fits this app better than Cloudflare Workers (Nutrislice week JSON is large; Workers CPU limits are tight).

npm i -g vercel
vercel login
vercel --prod

When prompted, set environment variables (or add them in the Vercel dashboard → Settings → Environment Variables):

  • GROQ_API_KEY — from console.groq.com/keys

  • LLM_BASE_URL = https://api.groq.com/openai/v1

  • LLM_MODEL = openai/gpt-oss-120b

  • MCP_AUTH_TOKEN — optional, only if you expose /mcp publicly

Your Shortcut URL:

https://yale-dining.vercel.app/api/chat

Health check: https://yale-dining.vercel.app/health

Cloudflare Tunnel still runs on your laptop. A Cloudflare Worker rewrite is possible later, but Vercel Node is the path that matches this codebase without a computer left on.

Related MCP server: mit-dining-mcp

Setup

Requires Node.js 20+.

npm install
cp .env.example .env

Language model

The chat gateway talks to any OpenAI-compatible API. Groq is the default.

Create a free key at console.groq.com/keys, then in .env:

GROQ_API_KEY=gsk_...
LLM_BASE_URL=https://api.groq.com/openai/v1
LLM_MODEL=openai/gpt-oss-120b

openai/gpt-oss-120b is Groq’s current replacement for the retired Llama 3.3 IDs and supports tool calling. If Groq is down or the key is missing, the gateway still answers common questions (college + meal, “where is pizza”) by routing directly to Nutrislice.

Ollama (local)

LLM_BASE_URL=http://localhost:11434/v1
LLM_API_KEY=ollama
LLM_MODEL=llama3.1

OpenAI

LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=sk-...
LLM_MODEL=gpt-4o-mini

Run

npm run dev

Listens on http://0.0.0.0:3000.

  • Health: GET /health

  • iOS chat: POST /api/chat

  • MCP Streamable HTTP: POST /mcp

MCP over stdio (local Claude Desktop / Cursor):

npm run mcp

Docker

docker build -t yale-dining .
docker run --rm -p 3000:3000 \
  -e GROQ_API_KEY=gsk_... \
  -e MCP_AUTH_TOKEN=optional-secret \
  yale-dining

Set MCP_AUTH_TOKEN if the container is on a public URL so /mcp requires Authorization: Bearer <token>.

Example chat request:

curl -X POST http://localhost:3000/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"query":"What'\''s for lunch at Berkeley College today?"}'

Response:

{
  "reply": "Lunch at Berkeley College today includes grilled chicken, quinoa salad, and roasted vegetables."
}

MCP tools

Tool

Arguments

Purpose

list_colleges

none

All 14 residential colleges and Nutrislice slugs

get_college_menu

college_slug, meal, date?

One hall’s dishes for breakfast, lunch, or dinner (date defaults to today, Eastern Time)

search_dishes

query, meal?, date?

Search every residential college in parallel (Promise.all)

get_pwg_hours

date?, space?

Payne Whitney gym, pool, and squash hours

get_pwg_closures

date?

Closures and adjusted hours (recess, holidays, varsity meets)

get_activity_space_schedule

none

Lanman / rec-court open-rec policy and 25Live schedule URL

Cursor / Claude Desktop (stdio, local):

{
  "mcpServers": {
    "yale-dining": {
      "command": "npx",
      "args": ["tsx", "src/mcp-server.ts"],
      "cwd": "/absolute/path/to/Dining"
    }
  }
}

Remote Streamable HTTP (Docker, Fly, or the same process as /api/chat):

{
  "mcpServers": {
    "yale-dining": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer optional-secret"
      }
    }
  }
}

Omit headers if MCP_AUTH_TOKEN is unset.

iOS Shortcut setup

Use the Vercel HTTPS URL (the Mac does not need to be on):

https://yale-dining.vercel.app/api/chat

  1. Open Shortcuts on iOS.

  2. Create a new shortcut named Yale Dining.

  3. Add action: Ask for Input → Text with prompt What would you like to ask Yale Dining? (Skip this step if Siri should pass the spoken question directly.)

  4. Add action: Get Contents of URL

    • URL: https://yale-dining.vercel.app/api/chat

    • Method: POST

    • Headers: Content-Type = application/json

    • Request Body: JSON

    • Key: query → Value: Provided Input

  5. Add action: Get Dictionary Value (or Get Value for Key) → Key: reply

  6. Add action: Show Result (widget / screen) or Speak Text (Siri).

Optional Siri phrase: in shortcut details, set Use with Siri and a phrase such as “Yale Dining”.

Project layout

src/
  nutrislice.ts   # Nutrislice fetchers and menu parser
  pwg.ts          # Payne Whitney hours, closures, activity-space links
  tools.ts        # Shared MCP/LLM tool router
  mcp-server.ts   # MCP tool schemas and stdio server
  llm.ts          # Groq/OpenAI function calling + spoken-reply formatting
  http.ts         # Web Standard handlers shared by Fastify and Vercel
  server.ts       # Local Fastify POST /api/chat and POST /mcp
api/              # Vercel functions
Dockerfile        # Optional container image
vercel.json       # Vercel routes and timeouts

Related MCP Connectors

Related MCP Servers