Nexer Insight
Lunch and Learn
Demo
MCP & Agentic Systems to make life easier
Author
Date
2026-04-16
The lunch menu has a smaller selection of burgers, and all go for 125 SEK.
The website menus are quite varied, for example,
Docksideβs menu is a PDF that updates monthly
Holy Greensβ menu is a javascript page that shows available options and prices per restaurant location
Rekaβs Burgers menu is a static HTML page that hasnβt changed since last year
We could write custom scrapers for each restaurant, but that would be a lot of work, and would break easily if the website changes.
Follow along on GitHub at github.com/j-jayes/literate-broccoli

The admin authenticates with a shared team password before accessing the ordering panel.
Pre-scraped menus load instantly. The admin picks which restaurants to include in todayβs poll.
The AI agent can browse any restaurant website on demand and extract the menu automatically.
The admin can deselect items before publishing β useful for removing sold-out or seasonal dishes.
One click creates a shareable link. The session is live immediately.
Anyone with the link can see the full menu and check off what they want.
Name, tick items, done. Orders arrive in real time on the adminβs screen.
Submitted orders appear instantly. The admin sees a running tally as the team orders.
flowchart TB
subgraph Users["π₯ Users"]
Admin("Admin\nbrowser")
Team("Team\nbrowsers")
end
subgraph App["π³ Azure Container App"]
React("React\nfrontend")
FastAPI("FastAPI\nbackend")
Scraper("Agentic\nscraper")
Cache("Cached\nmenus.py")
end
subgraph LLMs["π§ LLM Providers"]
AzOAI("Azure\nOpenAI")
OAI("OpenAI")
Gemini("Google\nGemini")
end
subgraph CI["βοΈ GitHub Actions"]
Scrape("Monthly\nscraper job")
Deploy("Build &\ndeploy job")
end
subgraph Azure["βοΈ Azure"]
ACR("Container\nRegistry")
Storage("Blob\nStorage")
end
Admin -->|HTTPS| React
Team -->|HTTPS| React
React -->|REST API| FastAPI
FastAPI --> Scraper
FastAPI --> Cache
Scraper -->|fallback chain| AzOAI
AzOAI -->|fails| OAI
OAI -->|fails| Gemini
FastAPI -->|sessions| Storage
Scrape -->|refreshes| Cache
Deploy -->|pushes image| ACR
ACR -->|pulls| App
flowchart TD
Start("Restaurant name\nor URL") --> Fetch("Fetch page")
Fetch --> LLM("LLM reads page\n+ all links")
LLM -->|extract| Done("Return\nMenuItems")
LLM -->|navigate| Nav("Follow link")
Nav --> Fetch
LLM -->|search| Search("Gemini web\ngrounding")
Search --> Fetch
LLM -->|fail| Error("Return error")
style Done fill:#1b9e77,color:#fff
style Error fill:#d95f02,color:#fff
style LLM fill:#190878,color:#fff
sequenceDiagram
participant Admin
participant Backend
participant Storage
participant Team
Admin->>Backend: POST /api/sessions {restaurants}
Backend->>Storage: write session JSON
Backend-->>Admin: {id, url}
Admin->>Team: shares /session/{id} link
Team->>Backend: GET /api/sessions/{id}
Backend->>Storage: read session
Backend-->>Team: menu items
Team->>Backend: POST /sessions/{id}/orders {name, items}
Backend->>Storage: append order
Backend-->>Team: updated session (SSE push)
Backend-->>Admin: SSE update
flowchart LR
subgraph GH["GitHub"]
Push("git push\nmain") --> CI("CI: compile\ncheck")
Sched("Cron 3rd\nof month") --> Scrape("Scrape &\ncompare")
Scrape -->|menus changed| Build
end
subgraph Azure["Azure"]
Build("Docker\nbuild") --> ACR("Push image\nto ACR")
ACR --> Update("Update\nContainer App")
Update --> Live("Live\nrevision")
end
style Live fill:#1b9e77,color:#fff
flowchart TD
A("Azure OpenAI") -->|"fails"| B("OpenAI")
B -->|"fails"| C("Google Gemini")
style A fill:#190878,color:#fff
If one provider is down or rate-limited, the next one picks up automatically.
Model Context Protocol is an open standard that lets any AI client call any tool through a shared interface β like USB-C for AI.
flowchart LR
H("AI App - Host") --> C("MCP Client")
C --> S("MCP Server")
S --> T("Tools")
S --> D("Data Sources")
Called by: your frontend code
Same function, same types, same docstring β but now any AI app can discover and call it.
flowchart TB
subgraph Consumers
CS("Copilot Studio")
TB("Teams Bot")
WA("Web App")
end
subgraph MCP
Tool("get_menu_poll")
end
subgraph Scraper
Browse("browse_and_extract")
Extract("extract_menu")
Models("Pydantic models")
end
CS --> Tool
TB --> Tool
WA --> Tool
Tool --> Browse
Browse --> Extract
Extract --> Models
GET /api/health returns {"status": "ok"}[skip ci] on menu commits β prevents CI loopsuv.lock β reproducible dependency snapshots across CI and Dockerflowchart LR
subgraph Now["β
Today"]
Logs("Log Analytics")
Issues("GitHub Issues")
Health("Health endpoint")
end
subgraph Soon["π Next steps"]
Alerts("Azure Monitor\nalerts")
Dash("App Insights\ndashboard")
SLA("Uptime\nchecks")
end
subgraph Later["π
Later"]
Audit("Audit log\n(who ordered what)")
Cost("Cost tracking\nper run")
Rate("Rate limiting\n& auth hardening")
end
Auth Replace shared password with Azure AD / Teams SSO
Scale Persistent session storage β Blob today, Cosmos DB at scale
UX Teams bot IT approval β same MCP tool, richer surface
Agency Let LLMs navigate, not just parse
MCP Build once, connect everywhere
Pydantic Structured outputs you can trust
Follow along on GitHub at github.com/j-jayes/literate-broccoli

Built with FastMCP, FastAPI, and React.
flowchart LR
U("User") --> CS("Copilot Studio")
CS --> MCP("MCP Scraper")
CS -.->|"Cannot"| GP("Group Poll")
style GP stroke:#ff5028,stroke-dasharray: 5 5
flowchart LR
CS("Copilot Studio") --> S("MCP Scraper")
TB("Teams Bot") --> S
WA("Web App") --> S
style S fill:#190878,color:#fff
The Teams bot is not yet IT-approved β so today we demo the web app. But the scraper powering all three is identical.
NEXER