Seçki İstanbul MCP server
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.
Seçki İstanbul MCP server
This repo is a read-only mirror of the MCP module from Seçki İstanbul's private repository (the site's data pipeline and personal settings stay private). The live server is
https://secki.istanbul/mcp. Paths here match the original layout:functions/are Cloudflare Pages Functions,server/mcp/is the server, andserver/events.ts,server/http.tsandsrc/lib/*are the few site helpers it uses.Run it locally (Node ≥ 22.13):
npm install npm test && npm run typecheck npm run index # builds public/data/mcp-index.json and public/llms.txt from the public events.json npm run dev # wrangler pages dev on http://localhost:8791 npm run smoke # in a second terminal: protocol smoke test against the local server
A public, read-only MCP server for Seçki İstanbul, a curated, daily-refreshed list of upcoming public events in Istanbul. No sign-in, no accounts, no write tools.
URL:
https://secki.istanbul/mcp(Streamable HTTP, stateless)Protocol:
2026-07-28(server/discover, per-request_meta) and the legacyinitializehandshake (2025-11-25,2025-06-18and2025-03-26were each checked locally and echoed back; the SDK also accepts2024-11-05). One JSON-RPC message per POST: batches are refused with 400.Tools:
search_eventsandget_eventBuilt with: the official TypeScript SDK
@modelcontextprotocol/server2.4.0 and zod 4.6.7, pinned exactly, as a Cloudflare Pages Function
Connect in 1 minute
Server URL: https://secki.istanbul/mcp (no sign-in, read-only)
Claude Code
claude mcp add --transport http --scope user secki https://secki.istanbul/mcp--scope user makes it available in every project; leave it out to add it only to the current one. Check with claude mcp list, or /mcp inside Claude Code. Then ask: "Bu hafta sonu Kadıköy'de ne var?" or "Any jazz in Istanbul next week?"
Claude (claude.ai / Claude Desktop): Settings → Connectors → Add custom connector → URL https://secki.istanbul/mcp → no authentication.
Cursor: ~/.cursor/mcp.json
{ "mcpServers": { "secki": { "url": "https://secki.istanbul/mcp" } } }VS Code: .vscode/mcp.json
{ "servers": { "secki": { "type": "http", "url": "https://secki.istanbul/mcp" } } }Check it without a client (MCP Inspector CLI, needs Node ^22.23.4 || ^24.22.1 || >=26.12.1):
npx @modelcontextprotocol/inspector --cli https://secki.istanbul/mcp --transport http --method tools/listStatus: the server is not deployed yet. Every command above was run against a local
wrangler pages devcopy (see Run it locally); only the Inspector CLI was run end to end. The Claude Code, Claude Desktop, Cursor and VS Code steps follow each client's documented syntax but must be checked by hand against the live URL before this section is published.
Related MCP server: SeatGeek MCP
Tools
Both tools carry readOnlyHint: true, destructiveHint: false, idempotentHint: true and openWorldHint: false, and both have an outputSchema. Results come as structuredContent plus one compact text block (not a JSON dump), so models without structured-output support still get a readable answer.
search_events
Finds upcoming events by any mix of:
Argument | Meaning |
|
|
|
|
| words that must all start a word in the title, venue, neighbourhood, district, its ilçe ( |
|
|
|
|
| price filters; events with no stated price are left out (and the |
| two-letter code ( |
|
|
| sold-out and full events are hidden by default |
|
|
| 1–20 per page (default 10); |
A show with several dates appears once, with dates = its number of dates in the range. A single event that has started is left out, unless its end time is listed and not reached yet; then it is flagged in progress (started 14:00). An overnight event (ending before 06:00 the next day) counts on its start day. In soonest order an event without a start time comes after the timed ones of its day.
Zero results is not an error: the result has total: 0 and a hint that says, per active filter, how many events dropping just that filter would give (Without "free_only": 2 events.), how many each query word finds alone, and how many sold-out events were hidden. All counts are per show, like total. Query words that ask for something out of scope (çocuk, kids, dj) or that are really filters (free, ücretsiz, english) add a hint that says so.
get_event
Takes an id from search_events ("e" + 11 hex; an id missing its e also works) or a secki.istanbul/etkinlik/… link or slug (also with non-ASCII or percent-encoded letters). Returns a status (scheduled, in_progress, ended for an event whose end time passed earlier today, sold_out, full; Seçki does not track cancellations and says so), exact start and end, venue and city side, price, Seçki's short Turkish editorial note (why it is listed, not a description), language, doors, workshop type, registration window, up to three nearby transit stops with line mode (Taksim (M2 metro, F1 funicular; 1 min walk)), up to 12 other dates of a repeating show, the ticket page when tickets are sold online, the organiser's page (info_url) and the secki.istanbul page. Ids can change when the daily list is rebuilt; with a link, an old id still finds the event by its title (resolved_by: "title"). When the title part of a link does not match the event its id found, warning says so.
What is not listed
The list is curated. Children's events, DJ sets and club nights, and ceremonies are filtered out by the pipeline, and the instructions, the tool descriptions, llms.txt and the search hint say so, so an agent can explain an empty answer. About 70% of events state no price and about 12% have no district (counted on the 2026-11-10 data), which is why the price and side filters leave many events out.
Errors an agent can recover from
Tool errors come back as results with isError: true. Each one names the problem, shows valid input and says what to do next. The texts are in messages.ts.
Case | Message (shortened) |
|
|
|
|
|
|
no searchable word |
|
bad, stale, foreign or past-the-end cursor, or a new day |
|
unknown id |
|
not an id or link |
|
data file down |
|
bad arguments | zod messages written for agents, with a suggestion where one is clear: |
HTTP-level answers, in this order:
403(JSON-RPC-32000) for anOriginthat is present and not allowed, on every method. One allowlist decides both the 403 and the CORS headers:https://secki.istanbul,https://www.secki.istanbul, andhttp(s)://localhost,127.0.0.1or[::1]on any port. A request withoutOrigin(CLIs, desktop apps, servers) is accepted without CORS headers.405withAllow: POST, OPTIONSand a short text for GET/DELETE/PUT/PATCH (also what a person sees in a browser).429withRetry-Afterfrom the per-isolate limiter;413above 64 KB.400/-32600for a JSON array body: JSON-RPC batching was removed from MCP in 2025-06-18, and the SDK's lenient legacy path would otherwise let one rate-limit token buy up to 100 tool calls.404/-32601for a well-formedsubscriptions/listen(this server has nothing to notify, so it never holds a stream open). A listen with an unknown protocol version, a mismatchedMcp-Methodor no id goes to the SDK, which answers-32022,-32020or202and opens no stream either (handler tests).
Protocol errors (-32020 header mismatch, -32022 unsupported version, 406 wrong Accept) come from the SDK unchanged. Legacy-era answers are always one SSE event (text/event-stream), whatever responseMode says: SDK 2.4.0 passes responseMode only to the modern path. Modern answers are application/json. responseMode stays 'auto' because 'json' prints a console warning per isolate and would not change legacy answers.
Behaviour under load
Idempotent and cheap. Both tools are reads from memory. The build writes a slim index (
/data/mcp-index.json, about 0.9 MB for 1,972 events: allowlisted fields plus pre-folded search text) so a cold isolate parses that instead of the 2 MBevents.jsonand folds no text. On an M-series Mac,JSON.parseof the index took about 2–2.6 ms against 6.7 ms forevents.json. The index is kept for 10 minutes per isolate; the SDK handler is built once per isolate and the JSON Schemas of the tools are converted once.Cache hints.
server/discoverandtools/listcarryttlMs: 86400000andcacheScope: "public": the tool list changes only on deploy.Rate limit inside the app. A token bucket per client IP (40 requests burst, 4 per second sustained), kept in memory per isolate. Honest scope: it stops one client that hammers the endpoint through one isolate (a loop, a misbehaving agent). It does not limit distributed traffic, does not share state between isolates or data centres, and does not protect the Workers Free-plan quota (100,000 requests a day, shared with the site's other Functions).
Recommended outer limit (not created; a Cloudflare dashboard step for the site owner): one WAF rate-limiting rule on
secki.istanbul, path equals/mcp, about 20–30 requests per 10 seconds per IP, block for 10 seconds (the Free plan allows one such rule, a 10 s period and IP counting only). Cloudflare's 429 is not produced by this code; MCP clients treat it as a transport error. Note that claude.ai users may share egress IPs, so watch for 429s after launch.Response size. A
search_eventspage stays under 16,000 characters (structured plus text; the largest of 319 pages over 12 searches on the 2026-11-10 data was 14,192) and aget_eventresult under 4 KB (largest 2,853). Tests assert both.
Privacy
The server reads only
/data/mcp-index.json, which the build writes field by field from an allowlist. It never reads the profile,hidden, alerts, watch list, flights, campaigns,health.json,state.jsonor D1. Source names, scores, coordinates, images, parking and series ids never leave the build; the index keeps only an editorial rank number and a small series group number.The religious category (
din) is published askultur/ "Kültür" (KVKK art. 6), as on the website.Nothing is logged: no request bodies, arguments or queries, and the SDK's
onerroris a no-op. The site's existing privacy notice (/kvkk/) already covers Cloudflare's own request logs.Event titles, venues and notes are third-party text. They are stripped of control characters and cut to 160 (titles) and 300 (notes) characters, and never put into the instructions or tool descriptions. That lowers the prompt-injection risk; it cannot remove it.
curl transcripts (captured locally, 2026-11-10)
Against npx wrangler pages dev dist-demo --port 8797 with that day's events (1,972 dated listings). Long lines are cut where marked ….
Legacy handshake (2025-11-25; the answer comes as one SSE event):
$ curl -sS -i http://127.0.0.1:8797/mcp -H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: message
data: {"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"secki-istanbul","title":"Seçki İstanbul","version":"1.0.0"},"instructions":"Seçki İstanbul (secki.istanbul) lists upcoming public events in Istanbul, Turkey: …"},"jsonrpc":"2.0","id":1}Modern discovery (2026-07-28):
$ META='"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1"},"io.modelcontextprotocol/clientCapabilities":{}}'
$ curl -sS -i http://127.0.0.1:8797/mcp -H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2026-07-28' -H 'mcp-method: server/discover' \
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"server/discover\",\"params\":{$META}}"
HTTP/1.1 200 OK
Content-Type: application/json
{"result":{"supportedVersions":["2026-07-28"],"capabilities":{"tools":{"listChanged":false}},"instructions":"…","resultType":"complete","ttlMs":86400000,"cacheScope":"public","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"secki-istanbul","title":"Seçki İstanbul","version":"1.0.0"}}},"jsonrpc":"2.0","id":1}search_events "caz", modern (Mcp-Name mirrors the tool name; "caz" also finds English "Jazz" titles and notes that start "Caz konseri"). The text block:
$ curl -sS http://127.0.0.1:8797/mcp -H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' -H 'mcp-protocol-version: 2026-07-28' \
-H 'mcp-method: tools/call' -H 'mcp-name: search_events' \
-d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"search_events\",\"arguments\":{\"query\":\"caz\",\"limit\":3},$META}}"
58 events in Istanbul for upcoming days (Salı, 10 Kasım 2026 and later). Showing 1-3, best first.
1. [eae071227311] Tomot Shkoza Trio | Çarşamba, 18 Kasım 2026, 21:30 | Nardis Jazz Club, Galata (avrupa) | Konser | 1.000 TL | international artist | https://secki.istanbul/etkinlik/tomot-shkoza-trio-eae071227311
2. [e8b74cbce8fa] Russions Trio | Salı, 1 Aralık 2026, 21:30 | Nardis Jazz Club, Galata (avrupa) | Konser | 1.000 TL | international artist | https://secki.istanbul/etkinlik/russions-trio-e8b74cbce8fa
3. [e52d5bb4664a] Jazzy By Su Soley | Cumartesi, 14 Kasım 2026, 21:00 | Zorlu PSM, Touché By N Kolay, Zincirlikuyu (avrupa) | Konser | price not stated | international artist | https://secki.istanbul/etkinlik/jazzy-by-su-soley-e52d5bb4664a
More: call search_events again with the same filters and cursor="eyJvIjozLCJ2IjoiMjAyNi0xMS0xMFQxNzoyNzo0NyswMzowMCIsInIiOiIyMDI2LTExLTEwfCIsImYiOiIxaHcyODM2In0".The first event in structuredContent:
{
"id": "eae071227311",
"title": "Tomot Shkoza Trio",
"when": "Çarşamba, 18 Kasım 2026, 21:30",
"start": "2026-11-18T21:30",
"venue": "Nardis Jazz Club",
"district": "Galata",
"side": "avrupa",
"category": "konser",
"category_label": "Konser",
"price": "1.000 TL",
"is_free": false,
"flags": ["international artist"],
"url": "https://secki.istanbul/etkinlik/tomot-shkoza-trio-eae071227311"
}Zero results with a hint, legacy era ({"date_from":"2026-11-14","date_to":"2026-11-14","query":"Kadıköy","categories":["konser"],"free_only":true}):
0 events in Istanbul for these dates (Cumartesi, 14 Kasım 2026).
Hint: No events match all filters. Without "categories": 3 events. Without "free_only": 2 events. "Kadıköy" alone has 285 upcoming events (next: Salı, 10 Kasım 2026). 2 events in this range do not state a price, so the price filter left them out.get_event with an old id in the link, legacy era (no handshake needed: the server is stateless):
$ curl -sS http://127.0.0.1:8797/mcp -H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' -H 'mcp-protocol-version: 2025-11-25' \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_event","arguments":{"id":"https://secki.istanbul/etkinlik/tomot-shkoza-trio-e00000000000"}}}'
[eae071227311] Tomot Shkoza Trio | Çarşamba, 18 Kasım 2026, 21:30 | Nardis Jazz Club, Galata (avrupa) | Konser | 1.000 TL
Resolved by title: the id in the link is old; the current id is eae071227311.
Status: scheduled, as of the list of 2026-11-10 17:27. Seçki does not track cancellations; confirm on the organiser's page.
Note: Yabancı caz
Flags: international artist
Info: https://www.nardisjazz.com/etkinlik/tomot-shkoza-trio/
Transit: Şişhane (M2 metro; 5 min walk); Tünel (F2 funicular; 6 min walk); Karaköy İskelesi (ferry pier; 9 min walk)
Page: https://secki.istanbul/etkinlik/tomot-shkoza-trio-eae071227311Recoverable errors (text of the isError result):
arguments {"date_from":"2026-02-30"}
→ Input validation error: Invalid arguments for tool search_events: date_from: 2026-02-30 is not a real calendar date.
arguments {"categories":["concert"]}
→ Input validation error: Invalid arguments for tool search_events: categories.0: "concert" is not a category; use "konser". categories must be from: konser, gece, …
arguments {"date_to":"2025-11-14"}
→ date_to (2025-11-14) is in the past. Seçki lists upcoming events only; today in Istanbul is 2026-11-10. Did you mean 2026-11-14? Use a date on or after today, or use "when".A browser or a GET:
$ curl -sS -i http://127.0.0.1:8797/mcp
HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain; charset=utf-8
Allow: POST, OPTIONS
Cache-Control: no-store
This is the Seçki İstanbul MCP endpoint. Connect an MCP client to https://secki.istanbul/mcp (POST, Streamable HTTP, no sign-in). Docs: https://secki.istanbul/llms.txtRun it locally
Node 24.22.1 (wrangler 4.156 needs Node 22+, the Inspector ^24.22.1):
cd web
mkdir -p public-demo/data && cp public/favicon.svg public-demo/ && cp -R public/kvkk public-demo/
cp <a copy of https://secki.istanbul/data/events.json> public-demo/data/events.json # public-demo/ is gitignored
npm run build:demo # also writes dist-demo/data/mcp-index.json and dist-demo/llms.txt
npx wrangler pages dev dist-demo --port 8791 # all bindings local; /mcp needs none of them
bash scripts/mcp-smoke.sh http://127.0.0.1:8791 # 19 protocol checks, both eras
npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8791/mcp --transport http --protocol-era modern --method tools/list --strict
npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8791/mcp --transport http --protocol-era legacy \
--method tools/call --tool-name search_events --tool-arg when=this_weekend --tool-arg limit=3The same gate as the daily workflow: npm ci && npm test && npx tsc -p functions/tsconfig.json && npm run build && npm run build:demo. With a real public-demo/data/events.json present, npm test also runs every event of that file through both output schemas.
--strict reports 0 errors and 9 warnings: the nullable output fields (venue, district, price, is_free, range.to) are emitted by zod as type: ["string","null"], which is legal JSON Schema but less portable to single-type dialects. The input schemas have no findings.
Files
File | Role |
| the Pages route for |
| a JSON 404 for |
|
|
|
|
| zod 4 schemas, the final descriptions, thin handlers |
| filter, rank, series collapse, pagination, public shapes, id and link resolution (no SDK import) |
| the build-time allowlisted index (called from |
| loads and caches |
| Istanbul clock, word matching, public categories, agent-facing texts, token buckets |
| neighbourhood → ilçe (Moda → Kadıköy), for search and for the side of a neighbourhood |
|
|
| vitest: about 20 hand-written events plus a live-shaped check |
| curl smoke test against any base URL |
The module imports a few small helpers from the rest of the site (web/src/lib/{text,price,geo,eventUrl,prerender}.ts and the Env/Handler types from web/server/http.ts), so a copy of this folder alone does not build. A public mirror would have to vendor those files or be labelled a read-only mirror of the module.
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
Upcoming theatre, concerts, stand-up, exhibitions and festivals in Türkiye. Read-only, no sign-in.
Read-only search of upcoming local events worldwide: music, comedy, AI/tech, arts, food, sports.
Search upcoming public events in Israel by date, city, region or category. Read-only; no API key.
Find public EventMatey events that organisers have approved for AI discovery.
Related MCP Servers
- AlicenseAqualityCmaintenanceProvides read-only tools for discovering, searching, and browsing events from Luma, Posh, and Partiful platforms, including event details and guest lists, with optional authenticated access to a user's Luma events.10MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to search and retrieve live event, performer, venue, taxonomy, and recommendation data through the SeatGeek platform API.303 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to search event calendars by keyword and list upcoming or date-filtered events from a chosen calendar, returning titles, dates, venues, coordinates, and keywords.349 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to discover upcoming events in a given city, optionally filtered by category slug or free-text keyword. It also retrieves full detail for a single event, including description, timing, venue, organizer, and ticket price range and availability.100 npmMIT