pvr-inox-mcp
Sends notifications to a Discord channel when cinema booking events occur, such as new dates on sale or seats freeing up.
Sends real-time notifications to ntfy when a watched show becomes bookable, new shows appear, or seats free up in the preferred zone.
Sends alerts to a Slack channel for booking window openings, new shows, restocks, and freed seats.
Sends notifications to a Telegram chat when booking windows open or seat availability changes in the watched zone.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pvr-inox-mcpIs the IMAX show of Interstellar at PVR Palazzo bookable?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pvr-inox-mcp
Watches cinema booking APIs and pings you the moment a show you want becomes bookable - ntfy, Telegram, Pushover, Slack, Discord, email, or a webhook. Built because IMAX tickets at PVR Palazzo sell out before you notice the window opened.
Stdlib-only Python. No pip install, no browser, no scraping - it reads the same JSON API the PVR web app calls.
Two ways in:
watch.py- the cron watcher. Polls on a schedule, alerts you.mcp_server.py- an MCP server, so you can just ask: what's showing, where are the good seats, is that date on sale yet. Any city in India.
Both sit on core.py, which is the actual API client.
Coverage: the PVR/INOX chain only, across ~116 Indian cities. Independent cinemas and other chains are not here, and neither is BookMyShow - it blocks automated requests outright.
What it alerts on
Event | Meaning |
| A date that answered "closed" now has shows. The booking window just opened. |
| An extra session appeared on an already-open date. |
| A session went from Sold Out back to Available - a cancellation or a released block. |
| Seats opened up inside the zone - the rows and centre block you actually want. The one that matters. |
Related MCP server: AMC MCP Server
How it works
POST https://api3.pvrcinemas.com/api/v1/booking/content/csessions with a
cinema id and a date returns that day's sessions. Two findings make this cheap
and reliable:
A date not yet open for booking answers
status: 500. So "has the window opened" is a boolean, not a diff of show lists.The endpoint needs an
Authorization: Bearerheader with an empty token. Without the header it 403s; with it blank it works. No login, no key.
Every run polls horizon_days forward, keeps the shows matching your filters,
and diffs against state.json from the previous run.
Seat-level detail
With seat_detail: true, each showtime is followed up with
POST /api/v1/booking/ticketing/seatlayout using the encrypted token that
csessions returns per session. That gives the full seat map:
s == 1is a free seat,s == 2is taken (verified against the rendered map)entries with no seat name (
sn) are aisles and gaps - these break adjacency, since seats either side of an aisle are not "together"
The zone is the point
A show-level Available is close to meaningless: the good seats go first. On
AUDI 5 at Palazzo, shows sitting at 37-114 free seats had zero free in the
back-centre block. So zone_rows x zone_seats defines the seats you'd
actually sit in, and only those trigger seats_freed.
AUDI 5 is 15 rows, O nearest the screen through A at the back, each split into three blocks by two aisles - the centre block is seats 11-21:
SCREEN
O 1-9 11-21 22-29 front
N 1-10 11-21 22-31
...
G 1-6 11-21 22-23 (narrow rows)
D 1-10 11-21 22-31
A ----------- 1-34 ---------- back wallA seat outside the zone also breaks adjacency, so a run can never straddle the zone edge and report seats you don't want as part of a block.
The zone derives itself
Row letters mean different things in different houses - Palazzo's AUDI 5 runs O at the front to A at the back over 15 rows; Phoenix's IMAX runs P to A over 16. So hardcoded rows never transfer.
With zone_rows / zone_seats omitted, the zone is computed from the
auditorium's own geometry:
rows 60-85% of the way back from the screen
seats the aisle-delimited block containing the row's midpoint - the actual centre section, not a naive "middle half" that would straddle aisles
That reproduces a hand-picked F,E,D,C + 11-21 exactly on Palazzo (44
seats), and independently derives G,F,E,D,C (80 seats) on Phoenix. Set the
keys explicitly only to override it.
Alerts then read
GOOD SEATS: 11 together at D11-D21 (40 free in zone, 15% booked overall).
This costs one extra request per showtime, so the calls are issued concurrently. Already-started ("Lapsed") shows are skipped - they have no seat map. Sold-out ones are still fetched, since that is where a restock shows up.
Note bookmyshow.com is fully Cloudflare-gated - every plain request, including
the mobile-app endpoints with correct headers, returns 403. Going through PVR
direct avoids that entirely.
Config
watches.json:
{
"watches": [
{
"name": "The Odyssey - IMAX - PVR Palazzo",
"provider": "pvr",
"city": "Chennai",
"cinema_id": "388",
"cinema_slug": "PVR-Palazzo-The-Nexus-Vijaya-Mall",
"lat": "13.05053777",
"lng": "80.2093132",
"film_contains": "ODYSSEY",
"experience": "imax",
"language": "English",
"horizon_days": 16,
"weekdays": ["Sat", "Sun"],
"alert_on_restock": true,
"seat_detail": true,
"zone_rows": ["F", "E", "D", "C"],
"zone_seats": [11, 21],
"min_adjacent": 1
}
]
}weekdays limits the watch to days you'd actually go (%a names - Mon,
Sat...). Omit it to watch every day. It also cuts the request count sharply,
since every open date costs one seat-map call per showtime - so horizon_days
can reach further out for the same work.
zone_rows and zone_seats (inclusive seat numbers) override the good-seats
zone. Omit both and it derives itself from the auditorium's geometry, which is
usually what you want - see above.
min_adjacent is how many seats side by side you need; seats_freed fires only
when a show crosses that threshold from below, so a show already above it does
not re-fire every run. Set it to 0 to switch that off.
film_contains is a case-insensitive substring of the film name.
experience matches PVR's key (imax, pxl, bigpix, 4dx, ...); leave it
empty for any format. lat/lng should be the cinema's own coordinates - the
API applies a distance filter and will drop the cinema if you are too far.
Finding a cinema id
curl -s -X POST https://api3.pvrcinemas.com/api/v1/booking/content/cinemas \
-H 'Content-Type: application/json' -H 'Authorization: Bearer ' \
-H 'chain: PVR' -H 'city: Chennai' -H 'country: INDIA' \
-H 'appVersion: 1.0' -H 'platform: WEBSITE' -H 'flow: PVRINOX' \
-d '{"city":"Chennai","lat":"13.08","lng":"80.27","text":""}' \
| python3 -c 'import json,sys;[print(c["theatreId"],c["name"]) for c in json.load(sys.stdin)["output"]["cinemas"]]'Running
python watch.py --dry-run --show-all # poll and print, touches nothing
python watch.py # poll, diff, alert, save state
python watch.py --watch "<name>" # just one watchThe first run records a baseline silently - otherwise every currently-open date would fire as a discovery.
MCP server
Exposes the same core as tools, so any MCP client can ask instead of you writing throwaway scripts.
pip install -r requirements.txt
claude mcp add pvr-inox -- python3 /path/to/pvr-inox-mcp/mcp_server.pyTool | Answers |
| Which cities the chain covers |
| Cinemas in a city + the |
| What's playing, with certificate, length, formats |
| Showtimes at a cinema on a date |
| Live seat availability, zone counted separately - the one that matters |
| Is that date on sale yet |
| What the cron watches, and whether it's live |
| Create a watch conversationally |
| Delete one |
| Commit + push so the cron picks it up |
pvr_seats takes seat_map=true for an ASCII auditorium, which makes
the problem obvious at a glance (O free in zone, x taken in zone, o free
outside it, . taken outside it):
SCREEN
P oo ooooooooooooooo oooo front: wide open
...
G ...... xxxxxxxxxxxxxxxx .......o zone: solid
F ...... xxxxxxxxxxxxxxxx ........
E ...... xxxxxxxxxxxxxxxx ........That show reads "Filling Up Fast" with 75% booked - and not one free seat worth having. The server's instructions tell the client to never call a show bookable on show-level status alone.
Setting up a watch by asking
pvr_add_watch resolves a cinema name fragment to its id and
coordinates, then sanity-checks the film against what that cinema is listing
today - so a typo surfaces immediately rather than as months of silence:
WARNING: nothing matches 'ODDYSSEY' in imax at this cinema today.An ambiguous cinema is refused rather than guessed:
'PVR' matches 12 cinemas in Chennai - be more specific:
388 PVR Palazzo-The Nexus Vijaya Mall
331 PVR Sathyam Royapettah Chennai
...A new watch is not live when it is added. The cron runs the committed
config, so adding one only edits the local file; pvr_publish_watches
commits and pushes it. That split is deliberate - publishing pushes to a
public repository, which should be a decision rather than a side effect.
pvr_list_watches flags the gap whenever the file is dirty.
Deploy
Runs on GitHub Actions cron (.github/workflows/watch.yml), every 5 minutes,
committing state.json back to the repo so the diff survives between runs.
Push this to its own repo. Keep it public - 5-minute cron on a private repo burns ~8,600 Actions minutes a month against a 2,000 free allowance. Nothing sensitive is in the code.
Pick a notification channel below and add its secrets to the repo.
Actions tab -> "pvr-inox watch" -> Run workflow, to record the baseline.
Two ways to be told
If you already have an MCP client, you may not need a notification service at all - but the two modes are not interchangeable, and the difference is what happens when you close your laptop.
Durable watch | In-session watch | |
Runs on | GitHub Actions cron | Your machine, inside an agent session |
Setup | Repo + one secret | None - the MCP is already there |
Survives closing the laptop | Yes | No |
Survives closing the agent | Yes | No |
Good for | Days of waiting for an unknown moment | An afternoon of watching for a restock |
In-session is watch.py --stream, which polls forever and prints one line
per event on stdout - the shape an agent watch tool wants:
python watch.py --stream --interval 60🚨 Booking just opened | The Odyssey IMAX | Sat 15 Aug 09:00 AM | 11 together - D11-D21
🪑 Good seats opened up | The Odyssey IMAX | Sat 8 Aug 04:05 PM | 4 together - C16-C19Point an agent's monitor at that and each line becomes a notification. Note what it does not do: wake a model every minute to poll an API. The polling stays in Python, where it is free, and only real events reach the model.
The catch, and it decides the choice: agent-side schedulers are tied to the session. Claude Code's cron jobs live only in the current session, fire only while it is idle, and expire after 7 days; monitors end when the session ends. A booking window opening on a Monday morning while your laptop is shut is exactly the case that needs the durable path.
Use in-session for a watch measured in hours. Use the cron for anything longer.
Notification channels
Set the environment variables for the channel you want and it switches itself on. Configure several and all of them get the alert. Nothing to edit in code.
Channel | Variables | Cost / friction |
ntfy |
| No account at all. Install the app, pick a topic name. Sent at priority 5 so it breaks through a silenced phone. |
Telegram |
| Free. ~5 min with @BotFather. Reliable lock-screen push. |
Pushover |
| $5 one-off. The best custom alert sounds. |
Slack |
| Free. Only useful if you live in Slack. |
Discord |
| Free, same shape as Slack. |
| Universal, but does not reliably wake you. | |
Generic webhook |
| POSTs |
GitHub issue |
| Zero extra accounts - the Actions run already has both. Relies on GitHub app notifications. |
If you want to be woken up, use ntfy, Telegram or Pushover. Email and GitHub issues are for a record, not an interrupt - and an alert you don't see is not an alert.
Two that are deliberately absent, both because of Indian regulatory friction rather than technical difficulty: WhatsApp needs a Meta Business account and template pre-approval, and SMS/voice to Indian numbers needs DLT registration. Use the generic webhook to bridge to either if you have that set up already.
Messages are plain text with real emoji, so they render the same everywhere -
no Slack :codes: leaking into a Telegram message.
Timing caveat
GitHub's cron floor is 5 minutes and scheduled runs are routinely delayed 5-15 minutes under load, occasionally skipped. That is fine for catching a booking window opening - the listing goes up in a batch and stays up. It will not win a seat race measured in seconds. If that matters, move the same script to an always-on box on a 30-second loop; nothing in it is Actions-specific.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseBqualityDmaintenanceProvides Indian movie recommendations within Claude Desktop, allowing users to filter by genre, language, rating, and year, search for specific films, or discover random recommendations from Bollywood and regional cinema.3
- AlicenseAqualityDmaintenanceProvides a comprehensive movie booking experience for AMC Theatres, enabling users to discover movies, find showtimes, select seats, and process payments through conversational AI. Supports multi-location theater search with real-time seat availability and booking management.61MIT
- Flicense-qualityCmaintenanceEnables users to search for movies and retrieve showtimes from Allociné, including cinema locations, screening times, and formats (VF, VOST, 3D, IMAX) for specific cities or postal codes in France.
- Flicense-qualityDmaintenanceProvides a suite of tools for searching movies, checking showtimes, and managing ticket bookings for Bangalore cinemas. It enables AI clients to handle end-to-end movie theater interactions including seat availability checks and reservation management.
Related MCP Connectors
Track price drops, stock-outs, restocks, and new/removed products across Shopify stores.
Streaming availability across 200+ services: titles, sources, releases.
Trakt MCP — TV/movie metadata + watch tracking signals
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/notprashanth/pvr-inox-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server