api-mcp
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
api-mcp
Clusterflick is a free, open-source site listing every film showing in London, from 450+ venues in one search.
This repository is the Cloudflare Worker that lets a reader's own AI assistant search those listings, as a remote MCP server. See the Clusterflick organisation for how the pieces fit together.
A reader asks their assistant "what horror is on near Brixton this weekend?", and the assistant asks this Worker. The assistant does the thinking, so Clusterflick pays for no model; each tool call is an ordinary Worker request.
Every tool needs a reader signed in, with assistantAccess: a capability
the site grants one account at a time, and invitation-only for now. See
Sign-in.
It's at https://mcp.clusterflick.com/mcp.
Tools
All read-only, over the listings the site publishes. Every value is plain words, as a person would say them, resolved with the site's own vocabulary: its labels and aliases ("subs" is Subtitles), the people credited on what's showing, the venues showing it and London's stations. A value that names nothing, or more than one thing, is an error saying which, never a filter left off: a search quietly wider than the one asked for reads as an answer.
Tool | What it answers |
| Films and events showing, soonest first, with their showings: by title, dates, time of day, near a station or venue, venues, event type, genre, director, cast, accessibility, occasion, format, festival, film club, minimum ratings and sold-out. |
| One film by title (and year, when titles clash): its details and every showing from today. |
| The venues with something showing: all of them, or those near a place, nearest first. |
The reader's lists, when they allowed them (below): every film on their
watchlist or Seen list is marked onWatchlist or seen, and every showing or
venue among their venues yourVenue. search_screenings can leave out what
they've seen (hideSeen), and keep to their watchlist (watchlistOnly) or
their venues (myVenuesOnly, with near those of them near it). An empty
list is refused rather than searched, since an empty selection is no filter
at all; a connection without the lists says how to allow them.
search_screenings starts from the site's own default search (films,
multiple films and short films over the next week, finished showings hidden),
so the same search finds the same films on both. Its answer says what was
searched, in the site's words, and links the same search on the site.
A place is a Tube, Overground, Elizabeth line or DLR station, or a venue, as on the site. Not a postcode or a borough: the site has neither as a place.
Answers are JSON. Every film and venue carries its page on clusterflick.com and every showing where to book it; times are London's. Only what helps someone choose goes in: an answer lands whole in the assistant's context, so a search returns 10 films with 3 showings each by default, about 10,000 characters, and says how many there are in all.
Related MCP server: TorrentClaw-MCP
The listings
The Worker reads the listings the site publishes, not the pipeline's combined
data: the site's build joins in the ratings and more. It finds them from
https://clusterflick.com/data/current.json, the one file there with a fixed
name, and reads them with @clusterflick/catalogue, the package the site
searches with, so a search here means what it means there.
Held in the isolate, about 21MB parsed, and shared by every call it serves.
Checked every five minutes (
CHECK_EVERY_MS), withcurrent.jsonfetched uncached. A new release is a new meta file named there; anything else costs that one small request.Read again each London day, since reading drops earlier days' performances.
The last good copy while the site is down, retrying after a minute; with none held, a tool says it couldn't search, never that nothing is on.
Refused in a format it wasn't built for. Both
current.jsonand the meta file carryDATA_FORMAT_VERSION; a release in another means moving to the catalogue version that reads it.
Each load is logged as {"event":"listings-loaded","meta","generatedAt", "films","ms"}, and each failure as {"event":"listings-unavailable", "message","held"}.
Sign-in
OAuth 2.1, as MCP's 2026-07-28 authorization spec has it, from Cloudflare's
@cloudflare/workers-oauth-provider: it serves the discovery documents
(/.well-known/oauth-protected-resource/mcp,
/.well-known/oauth-authorization-server), client registration
(/oauth/register), the token endpoint (/oauth/token), and answers /mcp
without a valid token with the 401 that starts an assistant signing in. An
assistant identifies itself with a Client ID Metadata Document, which the spec
prefers, or by registering.
What it leaves to us is src/authorize.ts:
GET /authorizeasks the reader whether to let the assistant search as them, and whether it may see their lists (a tick-box, ticked to start with), naming it (escaped: an assistant chooses its own name), saying where access goes, warning when that's an app on their computer, and giving a metadata document's domain. It can't be framed.POST /authorize: Deny goes back to the assistant withaccess_denied. Allow goes to the site's/connect/(CONNECT_URL), since Firebase's sign-in lives there, with thestatethe library made.POST /callback?state=…:/connect/posts the reader's Firebase ID token (idToken). It's checked as api-profiles checks one, then the account'sassistantAccessis read fresh from Firestore. With it, the reader goes back to the assistant with a code, signed in as their uid (the token'sprops). Without it, they're told here, with a link back that tells the assistant onlyaccess_denied.
The library ties each step to the browser that started it with a cookie, and each works once, for ten minutes: a reader fetching their sign-in email has that long, and must open it in the same browser.
Scopes (src/scopes.ts): every connection has listings; lists, the
watchlist, Seen list and venues, is the reader's to untick. A connection keeps
the scopes it was given, so one made before a scope existed, or with it
unticked, lacks it until the reader connects it again; /connections returns
each connection's scopes for /personalise to say so. The lists are read fresh
on each call that may use them (src/reader-lists.ts): one read of
users/{uid}, masked to watchlist, seen and favouriteVenues, never
remembered, so a film marked seen a minute ago isn't suggested. If that ever
costs too much, the site already tells api-profiles when a reader's lists
change, and could tell this Worker too. If they can't be read, the call is
refused rather than answered without them. src/lists.ts is a copy of
api-profiles' too.
After signing in, an access token lasts an hour and is refreshed; a
connection lasts while it's used and ends after 30 days without a call. Every
call to /mcp then:
Is limited per reader, 60 a minute (
CALL_LIMIT), keyed on their uid.Checks the capability again, remembered for five minutes (
CAPABILITY_TTL_MS), so revoking it stops their assistant within five minutes. Firestore unreachable is a 503, never a guess either way.Is logged as
{"event":"mcp-call","uid","tool","outcome","lists","ms"}, never with what was searched for or what's on the reader's lists, only whether they were read. Sign-ins are logged asmcp-sign-in.
Firestore is read as this Worker's own service account, with Cloud Datastore
Viewer and nothing else. src/auth.ts, src/google-auth.ts and
src/firestore.ts are copies of api-profiles' until the Workers share a
package, which is to be the first job of alerts.
A reader's connections, for /personalise to list and disconnect
(src/connections.ts): GET /connections lists the assistants they've
connected, by name (a metadata document's domain beside it), newest first;
DELETE /connections/<id> disconnects one, and its tokens go with it. Both
take the reader's Firebase ID token, answer the site's origin
(CONNECT_URL's) for CORS, and skip the capability, so a reader whose access
was revoked can still see and remove what's connected. When it was last used
isn't kept: it would be a write on every call.
A daily cron sweeps the OAuth store (OAUTH_KV) for what KV's own expiry
leaves.
Requests
/mcp is the MCP endpoint, served by the MCP SDK's stateless handler: a fresh
server for each request, built for the reader making it. It speaks the
2026-07-28 protocol and the stateless form of the 2025 one. A request from a
browser page is refused unless the page is local (the MCP Inspector).
Working on it
npm install
npm test # Vitest in the Workers runtime
npm run lint # tsc over the Worker and the tests
npm run dev # http://localhost:8789/mcpnpm test runs the whole sign-in in the Workers runtime against fakes of
the site, Google and Firestore, with a Firebase key made up for the tests;
its bindings are pinned in vitest.config.ts, so nothing in .dev.vars
reaches it.
To sign in for real, locally:
Copy
.dev.vars.exampleto.dev.vars, with this Worker's service account key, and the localhost addresses it gives.Run the site's
next devwithNEXT_PUBLIC_MCP_ORIGIN=http://localhost:8789, so its/connect/posts here.npm run dev, then point an assistant athttp://localhost:8789/mcp, for exampleclaude mcp add --transport http clusterflick-local http://localhost:8789/mcp. It reads the live site's listings, and the live project's capabilities: sign in as an account withassistantAccess.
To read listings from somewhere else, such as a local copy of the site's
public/data/, set the origin: npx wrangler dev --var SITE_ORIGIN:http://127.0.0.1:8791. The copy needs a current.json naming
its meta file, which the site's build writes.
Deployment
.github/workflows/deploy.yml lints and tests every push and pull request,
and deploys on pushes to main. The service account key is a secret stored
on the Worker: it persists across deploys and never goes in the repo or CI.
The site's /connect/ must be live before the Worker is any use: without it,
every sign-in ends at a 404.
mcp.clusterflick.com is a custom domain (wrangler.jsonc's routes):
Cloudflare made its DNS record and certificate on the first deploy.
The service account
In the Google Cloud console for clusterflick-com, under IAM → Service
accounts, there's one for this Worker alone, api-mcp, with the Cloud
Datastore Viewer role (roles/datastore.viewer) and nothing else. That
role reads every document in the project, since Firestore has no narrower IAM
role; what this Worker reads is limited by its code, to a reader's
capabilities.
First deploy
As api-profiles' was: CI can't create a Worker whose wrangler.jsonc
requires a secret, and wrangler secret put only works on one that exists.
So the first deploy was from a machine with the key, creating the Worker and
its secret together, from a file holding only the secret (.dev.vars also
points the Worker at localhost):
grep "^FIREBASE_SERVICE_ACCOUNT=" .dev.vars > /tmp/mcp.env
npx wrangler deploy --secrets-file /tmp/mcp.env
rm /tmp/mcp.envThe OAuth store was created first, and its id is in wrangler.jsonc:
npx wrangler kv namespace create OAUTH_KV.
CI deploys with the repository secrets CLOUDFLARE_API_TOKEN (the "Edit
Cloudflare Workers" template, scoped to the account and the
clusterflick.com zone) and CLOUDFLARE_ACCOUNT_ID.
Rotating the key
Create a new key for the service account, then:
node -e 'process.stdout.write(JSON.stringify(require("./new-key.json")))' | npx wrangler secret put FIREBASE_SERVICE_ACCOUNTand delete the old key in the console.
Licence
MIT, as the site.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Track the TV and films you watch, and find more like them, by talking to your own AI.
Rights-aware documentary search, film details, and where-to-watch for AI assistants. 100,000+ films.
Booking gateway for AI agents — discover events, movies & hotels, hand off to partner checkout.
Search movies/TV by mood, genre, score, cast, and awards, filtered to what's streaming near you.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProvides movie listings, showtimes, and personalized recommendations for Amsterdam cinemas by scraping filmladder.nl, with support for filtering by date, cinema, rating, and preferred showtimes.1-

TorrentClaw-MCPofficial
AlicenseAqualityCmaintenanceGiving AI assistants the ability to search movies and TV shows, find torrents with magnet links, check streaming availability, and explore cast/crew metadata.661095 npm17MIT- AlicenseAqualityBmaintenanceEnables AI assistants to retrieve BookMyShow movie listings, showtimes, ticket prices, and venue locations (including GPS coordinates) via MCP tools.71MIT
- FlicenseAqualityBmaintenanceEnables users to search real movie theater showtimes, discover films and cinemas, and retrieve ticket deep links through natural language chat in Open WebUI or any MCP client.12-