AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻译准备中
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Live city-event tools for AI agents through the Model Context Protocol
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Recommended: add the hosted connector. It runs on our servers, so it never
gets access to your computer (no scary local-install warning), needs no install
and no auth:
Paste that into your client's Settings → Connectors → Add custom connector
(claude.ai, Claude Desktop, ChatGPT developer mode, Cursor). Step-by-step per
client: https://mcp.dizko.app/install查看完整 README ↗The same /mcp connection now offers two optional paths:
get_dizko_onboarding for an exact database match, review published examples, then continue on the website to claim an existing identity or create a page. Venues, collectives and promoters can start with their name and city.Ask your agent “Help me get started with Dizko”, or select the dizko_onboarding prompt. Setup is optional and does not interrupt specific event searches. It can explain discovery connections, artist pages, EPKs and release tools, and return marketing, signup, builder and billing links. Ownership verification and all payments remain on the website. The advertised offer is dated and labelled; checkout determines the current price and terms. Press distribution is a separate charge.
Requires the API's GET /scene/artist-onboarding endpoint. Deploy that backend change before this MCP release. If lookup is unavailable, the tool says so and offers the marketing page without claiming the artist is absent. Only public profile information and published page addresses are returned. No drafts or private EPK details are read. The /openai/mcp directory surface also includes this read-only guide; it still exposes no account mutation or payment tools.
Recommended: add the hosted connector. It runs on our servers, so it never gets access to your computer (no scary local-install warning), needs no install and no auth:
https://mcp.dizko.app/mcp
Paste that into your client's Settings → Connectors → Add custom connector
(claude.ai, Claude Desktop, ChatGPT developer mode, Cursor). Step-by-step per
client: https://www.dizko.app/mcp/install (https://mcp.dizko.app/install
redirects there).
For Claude Code:
claude mcp add --transport http dizko-events https://mcp.dizko.app/mcp
Advanced: run the server locally (only if you can't use the hosted connector, e.g. a free Claude plan or a client that only supports local servers). A local server runs as you, so Claude Desktop warns it "can access everything on your computer". That is inherent to any local MCP extension, not something this package over-requests. The hosted connector above avoids it.
npx -y dizko-events install claude-desktop # writes the local config
# or the raw stdio snippet for any client:
{ "command": "npx", "args": ["-y", "dizko-events", "mcp"] }
Claude Desktop one-click bundle: npm run build:mcpb produces
dist/dizko-events-<version>.mcpb; the hosted server serves it at
/download/dizko-events.mcpb.
Framework agents don't have a "Connectors" UI, so they integrate programmatically. Four paths, simplest first:
Remote MCP over HTTP: point the framework's MCP client at the hosted
endpoint https://mcp.dizko.app/mcp
(streamable-http, no auth). Works with the official MCP SDKs, the OpenAI
Agents SDK, LangGraph/LangChain MCP adapters, Pydantic AI, etc.
stdio MCP: spawn npx -y dizko-events mcp as a subprocess and
speak MCP over stdio.
Raw HTTP JSON-RPC: no MCP library needed; POST to /mcp:
curl -s https://mcp.dizko.app/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-method: tools/call' \
-H 'mcp-name: search_events' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_events","arguments":{"city":"los angeles","when":"week"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
The _meta block is what makes this a 2026-07-28 request: the revision
is stateless, so there is no initialize handshake and no session - every
request carries its own protocol version and client capabilities. Call
server/discover to see which revisions the endpoint speaks. Clients on
2025-11-25 and earlier keep working unchanged; omit _meta and the
server answers them over the 2025-era path instead.
In-process library: embed the tools directly (no MCP layer). The package exposes a stable API:
import { tools, prompts, callTool, getPrompt, searchEvents } from "dizko-events";
// Hand `tools` (JSON Schemas) to your model as function definitions, then:
const result = await callTool("search_events", { city: "berlin", when: "weekend" });
By default the ticket tools return a checkout handoff (a link). To enable
bounded autonomous purchase, supply a ticketPurchaseProvider adapter. This is
only injectable when you embed the package or self-host the server - the
hosted endpoint runs in our process and cannot accept your payment adapter.
import { createHttpMcpServer, callTool } from "dizko-events";
const hermesAdapter = {
canPurchase: (event, summary) => true,
purchase: async ({ quote, confirmation_text, delivery_email, add_to_calendar }) => {
// ... perform the bounded purchase, return { status, order_id, receipt_url }
}
};
// Self-host with the adapter wired in:
createHttpMcpServer({ ticketPurchaseProvider: hermesAdapter }).listen(8787);
// ...or call tools in-process with it:
await callTool("purchase_ticket_order", input, { ticketPurchaseProvider: hermesAdapter });
purchase_ticket_order still requires a signed quote from quote_ticket_order
and explicit written confirmation before the adapter is invoked. The
dizko_ticket_policy prompt explains the rules. Set DIZKO_QUOTE_SIGNING_SECRET
so quote tokens survive restarts.
This package exposes Dizko's live event inventory to agents and humans:
dizko-events: a CLI for quick searches, ranked recommendations, night plans, client install, and diagnostics.dizko-events mcp (binary dizko-events-mcp): a stdio MCP server for local developer clients.dizko-events serve (binary dizko-events-http): an HTTP MCP server for hosted connectors/apps.General chat can describe likely events, but it does not reliably know current inventory. This tool gives an agent:
/events API across 49 cities, with every time already in the city's local timezone.The agent can still write conversational prose, but its event facts come from the tool.
Run without installing:
npm exec --yes --package dizko-events -- dizko-events search --city "Los Angeles" --when week --limit 5
npm exec --yes --package dizko-events -- dizko-events recommend --city "New York" --when tonight --vibe underground,intimate --max-price 30
Or install the early-access developer package:
npm install -g dizko-events
dizko-events search --city berlin --when weekend --genres techno --limit 5
From this repository:
node ./bin/dizko.js search --city berlin --when weekend --genres techno --limit 5
node ./bin/dizko.js recommend --city new-york --when tonight --vibe underground,intimate --max-price 30
node ./bin/dizko.js plan --city london --when weekend --event-types party --avoid mainstream
node ./bin/dizko.js cities
node ./bin/dizko.js doctor
doctor checks DNS resolution, the API health endpoint, the hosted MCP endpoint (health, metadata, tools/list), and one small live search, reporting the underlying cause of any failure. Run it first whenever a search fails.
Transient network failures (EAI_AGAIN, ETIMEDOUT, ECONNRESET, ENOTFOUND, temporary 5xx) are retried automatically and reported as retryable: true with the underlying cause, code, hostname, and target URL when they persist. All surfaces (CLI, stdio MCP, hosted MCP, smoke test, monitor) resolve endpoints through src/config.js; see Configuration.
Run the stdio MCP server from npm:
npm exec --yes --package dizko-events -- dizko-events-mcp
Or from this repository:
node ./bin/dizko-mcp.js
Example MCP client config:
{
"mcpServers": {
"dizko": {
"command": "node",
"args": ["/absolute/path/to/dizko-mcp/bin/dizko-mcp.js"],
"env": {
"DIZKO_API_BASE_URL": "https://api.dizko.app"
}
}
}
}
Example MCP client config using npm:
{
"mcpServers": {
"dizko-events": {
"command": "npm",
"args": ["exec", "--yes", "--package", "dizko-events", "--", "dizko-events-mcp"],
"env": {
"DIZKO_API_BASE_URL": "https://api.dizko.app"
}
}
}
}
The server lists 32 tools (the count comes from tools.length in src/tools.js; scripts and monitors read it from there rather than hard-coding it). Every parameter carries a description and defaults are served in the schema. The names are the published connector surface, so they do not change between releases.
Discovery:
search_events: search live events in one city and timeframe. Use for any "what's on" request that names a city, a venue, an artist, or a timeframe. Filters you pass (genres, vibe, event_types, neighborhoods, venue, featuring, promoter, free, pride, price_min, price_max) are hard filters; avoid and max_price are ranking hints. count is the total matching; page by passing page.next_cursor back as cursor (offset/next_offset still work for older clients). With rank: "taste" it returns one ranked window of at most 50 and no cursor.recommend_events: the same search ranked by taste (rank: "taste"). Every result carries recommendation_reasons. result_limit (or limit) sets how many ranked events come back, at most 50; taste ranking scores one candidate window, so there is no cursor.recommend_events_for_user: taste-ranked search driven by a saved profile (profile_id + profile_secret required), with the same result_limit/limit of at most 50. Saved and learned taste rank the results; they never hide them.plan_night: a night plan for one city and date: a primary event plus a nearby fallback (best taste fit within 6 km), a later-starting fallback, and alternates. Same filters as search; city may be omitted when a profile with a saved home city is given.get_daily_roundup: one-day digest for a city: top picks plus sections for parties, live music, art, comedy and theatre, talks, food, and more. compact=true gives a short push-style digest. Built for "what's happening today" and scheduled briefings.get_city_pulse: aggregate read of a city's scene over 1-14 days: busiest nights, top venues, genre mix, headline events and free-event count, every stat with evidence counts. Public inventory only.get_event: full detail for one event id (local times, venue and address, price, lineup, set times, artist socials, image, coordinates, links). Search results already contain what the render template needs, so only call it for an id the user gave you.list_cities: live coverage: every city with status (live, unlocking, early), event count, timezone and freshness.Entities:
All three name searches rank candidates by how well the name answers the query, not by upstream relevance: an exact name first, then a name that starts with the query, then a whole-word hit, then a hit buried inside a longer word. The catalog name has to contain what was typed, never the reverse, so a profile called "Honey" is not an answer for "Honey Dijon". Two rows for the same name are one answer rather than an ambiguity, since catalogs carry the same venue twice under different casing.
When several candidates answer the name equally well, the name has said all it can, so how much of an answer each one actually is decides. prominence scores that: for an artist, listed Dizko dates, upcoming dates, co-billed artists, press clips, mixes, career appearances and editorial standing; for a venue, whether the record carries a capacity, genres and a real bio rather than a one-line stub; for a promoter, its upcoming count. best_match.confident is true only when the leader wins outright on name, or is decisively ahead on prominence. So "Klock" resolves confidently to Ben Klock (nine listed dates, twelve press clips) over BJ Klock (one appearance), while two comparable artists sharing a name stay ambiguous and best_match.alternatives carries the candidates to ask about.
An exact name match holds unless the fuller record is decisively ahead. Catalogs carry the same real place twice, once as a one-line encyclopedia stub that happens to win the name and once as the record with the capacity, the genres and the bio, so "Berghain" serves "Berghain / Panorama Bar" rather than the stub named exactly Berghain. Displacement is one name tier and never reaches below a whole-word match, so a big venue called "Medlock Hall" cannot win a search for "Lock". Artist prominence costs two extra upstream calls per competing candidate and is fetched only when something is actually competing, so a query one profile answers outright still costs a single request; venue and promoter scores come from rows the search already returned. Upstream relevance cannot do this job: its score ranked Nina Kraviz last of nineteen "Nina" profiles and its authority field is 0.59 for every artist in the catalog.
get_artist: pass an id for the full profile (bio, cities, genres, links, upcoming events, insights, mixes, press, and the published Dizko page when one exists), or a name query. A query whose best_match is confident is resolved to that profile in the same call and carries resolved_from; an ambiguous one returns the candidates and the assistant is told to ask rather than guess.get_venue: the same contract for venues: neighborhood, capacity, genres, bio, links, and upcoming events. Listings are matched room by room, so Berghain Kantine does not inherit Berghain's Klubnacht and Berghain does not claim Kantine's programme. A venue with no listing under its full name falls back to its colloquial short form, which is how "Salon zur Wilden Renate" finds events listed at "Renate".find_scene_entities: the general catalog search across kind = dj, venue, collective or promoter, or a full profile by id. Promoters and collectives are one crew to a user, so either kind searches both: pass city to include promoters with upcoming listings (promoter ids are per city; collectives are searched worldwide).get_artist_events: upcoming shows grouped by artist for up to 8 named DJs, performers or comedians, deduplicated and date-ordered, optionally scoped to a city. With a profile and no artists named, it tracks the profile's saved featuring list.get_artist_page: an artist's published Dizko page by handle, with stable block ids for deep links to a specific mix or set.Preferences (opt-in, protected by profile_id + profile_secret):
get_preference_onboarding: the consent-first questions to ask before saving anything.create_event_preference_profile: create a private preference profile after explicit consent (consent: true). Returns profile_id (dzk_...) and a one-time profile_secret (dzs_...); the service stores only a hash of the secret.save_event_preferences: add to (mode: merge, default) or replace saved preferences: cities, event types, genres, vibe, neighborhoods, venues, promoters, artists to track (featuring), avoid, budget, and per-weekday day_filters.get_event_preferences: saved preferences, learned taste (with scores) and feedback count.delete_event_preferences: delete saved preferences and feedback history; requires confirm_delete: true after the user confirms. Destructive.get_event_feedback_prompt: short post-event questions for one event, asked before feedback is saved.record_event_feedback: store post-event feedback (liked, 1-5 rating, notes) and update learned taste. At least one signal is required.get_event_search_followups: the missing-context questions worth asking before a broad "what's on" search, and only when the request is genuinely ambiguous.Signed-in Dizko account (OAuth, Muse connector contract):
get_taste_profile (saved:read): the connected user's learned taste plus saved and binned events.save_event, add_to_dizko_plan, bin_event (saved:write): save an event, add it to the user's plan, or hide it and teach their taste model. Each needs confirmed: true after the user agrees and an idempotency_key of at least 16 characters, so a retried call does not act twice.These four declare an oauth2 security scheme. Without a signed-in caller holding the scope they return authentication_required or insufficient_scope before any argument is read.
Tickets and calendar:
get_ticket_purchase_policy: current purchase modes, the hard safety rules, and what a provider needs to support autonomous purchase.get_ticket_offers: ticket options for one event: provider, checkout link, estimated price, free entry, whether autonomous purchase is supported, and the purchase policy. Call before quoting.quote_ticket_order: a signed, time-limited (10 minute) quote: quantity, ticket type, max total, currency, refund terms, delivery email and stop conditions. Returns quote_token and the exact confirmation text to ask the user for.purchase_ticket_order: execute a quoted order after the user's explicit written confirmation. The server rejects a confirmation that does not name the quantity and max total, or that refuses, hedges or asks ("don't buy 2 for 120"). With a third-party link it returns status: requires_external_checkout and the checkout_url; never claim a purchase unless status is purchased. The idempotency key is derived from the signed quote, so a retried purchase cannot buy twice. Destructive and open-world.create_event_calendar_file: an importable .ics entry for one event; the per-event calendar_url is the one-click alternative.Mixes:
open_mix_in_dizko_id: hands a SoundCloud mix to Dizko ID, Dizko's Chrome extension that identifies a mix's tracks with Shazam and fills the user's Beatport cart (falling back to Bandcamp) from their own logged-in browser. The server identifies nothing: Shazam's unofficial endpoint rate-limits a network after a few hundred lookups and the carts need the user's logins, so both stay in the browser. The tool checks the link (track pages only; profiles, sets and private s- links are refused, on.soundcloud.com share links are resolved one hop without following them), reads the title and uploader from SoundCloud's public oEmbed, and returns open_url, the Chrome Web Store install_url (override with DIZKO_ID_INSTALL_URL) and the steps: press the Dizko ID button under the waveform, tick tracks, Add to cart, then Buy yourself. YouTube and Mixcloud links return platform_not_yet_supported until the extension ships them. Served on /mcp only, not on the reviewed /openai/mcp surface.Annotations: discovery, entity, onboarding, follow-up, get_event_preferences, get_taste_profile, the ticket policy, offer and quote tools, create_event_calendar_file and open_mix_in_dizko_id are readOnlyHint: true (open_mix_in_dizko_id also openWorldHint: true, since it reads SoundCloud). create_event_preference_profile, record_event_feedback, save_event and add_to_dizko_plan write private user state (readOnlyHint: false, destructiveHint: false). save_event_preferences (its replace mode overwrites the profile), delete_event_preferences and bin_event are destructiveHint: true; purchase_ticket_order is destructiveHint: true and openWorldHint: true because it is the bounded action point for ticket purchase or checkout handoff. No tool serves an outputSchema; results come back as structuredContent plus a JSON text block.
Auth note: the public hosted connector submits as noauth for event discovery. Preference tools still require the user's opaque profile_id plus private profile_secret. Tool descriptors include securitySchemes (noauth, or oauth2 with the scope for the four account tools) and mirror it in _meta.securitySchemes for ChatGPT compatibility, plus _meta["openai/toolInvocation/invoking"] / invoked status text.
Retention note: saved preference profiles are pruned after the configured inactivity window (DIZKO_PREFERENCE_RETENTION_DAYS, default 730 days), matching the published 24-month retention policy.
The server also advertises the prompts capability with four prompts (prompts/list, prompts/get). They carry the same guidance as the onboarding, follow-up, feedback and policy tools, for clients that surface prompts rather than call tools:
dizko_onboarding: consent-first questions to ask before create_event_preference_profile.dizko_search_followups (city, when optional): the clarifying questions worth asking before a broad "what's on" search.dizko_post_event_feedback (event_id required): short questions to ask before record_event_feedback.dizko_ticket_policy: how quoting, confirmation, checkout handoff and autonomous purchase work, plus the hard safety rules.when accepts today, tonight, tomorrow, weekend, next weekend, week, next week, month, a weekday name, any, or an exact YYYY-MM-DD; all are resolved in the city's timezone. date_from/date_to (inclusive, city-local) override when for custom ranges. get_daily_roundup accepts single-day presets only.when (already local, e.g. Fri 11 Sep, 22:00), starts_at/ends_at (UTC), starts_at_local, timezone, venue, address, city, price, genres, vibe, event_types, lineup (capped, with lineup_count when truncated), set_times, pick, promoters (names), image_url, ticket_url, event_url/dizko_url, calendar_url (a downloadable .ics), directions_url, and the connector contract fields: source, source_url, source_provenance, last_updated_at, retrieved_at, availability, price_min/price_max and price_freshness. Their unverified and unknown values are intentional, not missing data. Ask for description, coordinates, socials, or full promoters objects via fields. See Data honesty for quality_flags, currency_status, ends_on_local, also_listed_by and alternate_starts_local.sort_by: soonest (default for single-day requests), (default for ranges), , , or with / (only pass coordinates the user gave you). : (default) keeps the API order; re-ranks the page by genres, vibe, avoid and budget from the request and the profile. defaults to when a profile is given.The backend fixes listing data at the root; the connector still refuses to pass on what it cannot stand behind (src/quality.js):
currency_inferred (a boolean), it decides: a code the source stated is kept whatever it is (a Goa festival that really sells in EUR), and an inferred one is shown with currency_status: "inferred" (relabelled to the city's currency if it still disagrees). Without that field (older backends) the connector checks itself: a price whose currency disagrees with the city's country (EUR on a Goa listing) is shown as 500 (currency unconfirmed), with currency omitted and currency_status: "unverified". USD is accepted alongside the local currency in Mexico, Argentina, Colombia and Turkiye; EUR never is, because it was the old universal fallback. Amounts are never judged by size, so a high price in the right currency is not flagged. Event payloads carry currency_inferred (true or false) whenever the provenance of the shown code is known, and omit it otherwise. Ticket offers and quotes never carry an unverified code forward.ends_at/ends_at_local are omitted, calendar entries use the default three-hour duration, and a multi-day listing keeps its closing date (ends_on_local, when like Wed 14 Oct, 12:00 → Sun 1 Nov).also_listed_by; differing starts set start_time_conflict and alternate_starts_local. One source listing two nights, and sessions more than 90 minutes apart, are never merged.quality_flags (an array, even empty), it is the authority on listing-level flags such as zero_duration_aggregator and start_time_assumed (a date-only source such as AgendaLX, whose stored 12:00 is made up: when then shows the date only, starts_on_local carries it and starts_at_local is omitted); a flag the connector does not know costs a small ranking penalty, never more than a known one; the connector adds only what it alone can know: currency_unverified or currency_inferred for the price it is about to show, and start_time_conflict from merging two sources in one response. Without backend flags the connector derives its own and (only an aggregator such as allevents lists it). The backend's passes through as . Flagged listings rank lower in recommend, plan and roundup flows (with the reason ), never hidden, and the result's gains one sentence asking the assistant to mention the uncertainty.Profile ids changed prefix from upg_ to dzk_ and secrets from ups_ to dzs_; profiles created before that change keep working with their existing values.
For normal users, publish a hosted MCP endpoint rather than asking them to run a local command.
PORT=8787 node ./bin/dizko-http.js
Endpoints:
POST /mcp: JSON-RPC MCP endpoint (rate-limited, optional bearer token).POST /openai/mcp: the ChatGPT and Codex plugin surface (src/surfaces.js): 14 read-only discovery and onboarding tools, no stored preference inputs, no prompts, and no ticket purchase or payment execution. Artist setup returns website signup and billing links.GET /.well-known/openai-apps-challenge: the OpenAI domain-verification token from DIZKO_OPENAI_APPS_CHALLENGE (404 when unset).GET /health: deployment health check.GET /: basic service metadata.GET /install: redirects to the per-client install guide at https://www.dizko.app/mcp/install.GET /e/<event-id>/cal, /map, /ics: short links used in tool payloads: Google Calendar template, Google Maps directions, and a downloadable .ics file. They share the /mcp rate limiter.GET /download/dizko-events.mcpb: the Claude Desktop bundle when dist/ contains one.GET /privacy-policy.html, /support.html, /terms.html, /user-guide.html, /install.html: public pages.GET /.well-known/security.txt (also /security.txt): vulnerability-reporting contact metadata.GET /logo-512.png: 512px connector logo for submission metadata.Current deployed endpoint:
https://mcp.dizko.app/mcp
To become a ChatGPT app/plugin:
Claude users can add the same public MCP URL as a custom connector where their plan/workspace supports remote MCP.
See plugin-submission.md and submission-fields.json for the working submission copy.
See OPENAI_PLUGIN_SUBMISSION.md for the ChatGPT and Codex plugin package, which uses the read-only /openai/mcp endpoint.
See SCREENSHOT_CHECKLIST.md for exact ChatGPT Developer Mode screenshot prompts and filenames.
See SUBMISSION_AUDIT.md for requirement-to-evidence mapping and remaining external review gates.
See USER_GUIDE.md for normal-user prompts, preference-memory behavior, and support/privacy links.
See DEPLOYMENT.md for Railway, Docker, custom-domain, and review steps.
See OPERATIONS.md for health checks, logs, rollback, preference-data handling, and resubmission triggers.
See golden-prompts.md for direct, indirect, and negative prompt tests for connector discovery and tool routing.
See SECURITY.md for vulnerability reporting, the security posture, and operational safeguards.
See REGISTRY_PUBLISHING.md and DIRECTORY_SUBMISSIONS.md for registry and directory listings.
Live smoke test:
npm run monitor:live
npm run smoke:live
monitor:live is read-only and suitable for uptime checks; it expects the tool count from the package's tool registry. smoke:live also exercises the prompts and temporary preference profile creation and deletion.
Submission evidence:
npm run verify:submission
npm run verify:submission:write
npm run verify:submission:bundle
npm run verify:submission:fields
npm run submission:status
npm run review:demo
npm run preflight:submission
The bundle command saves submission-evidence/latest.json and submission-evidence/latest-summary.md for dashboard review prep while keeping generated evidence out of git. The evidence includes the verified endpoint, current Railway deployment id, image digest, public-page checks, tool metadata, live search, prompt checks, and the preference-memory flow.
The fields command validates submission-fields.json, the stable dashboard-copy artifact, against the latest live evidence, including deployment metadata and the served tool count.
The preflight command runs local tests, live smoke, live submission evidence generation, and dashboard-field validation in one pass.
The status command gives a final go/no-go for OpenAI dashboard submission and names the single endpoint to submit.
The review-demo command writes a sanitized submission-evidence/review-demo.md transcript from live MCP calls for dashboard prompt/response prep.
Support deletion utility:
npm run preferences:delete -- --profile-id dzk_... --profile-secret dzs_... --preferences-path /data/preferences.json
This is for support deletion requests when the user cannot call delete_event_preferences through their MCP client.
DIZKO_* names are preferred. EVENTCHAT_* (and, for the four URLs, UPLAYGROUND_*) are legacy aliases the code still accepts; the HTTP server's own settings are read under EVENTCHAT_* only.
Shared by every surface (src/config.js):
| Variable | Default | Legacy aliases | Purpose |
|---|---|---|---|
DIZKO_API_BASE_URL | https://api.dizko.app | EVENTCHAT_API_BASE_URL, UPLAYGROUND_API_BASE_URL | Events API. |
DIZKO_WEB_BASE_URL | https://www.dizko.app | EVENTCHAT_WEB_BASE_URL, UPLAYGROUND_WEB_BASE_URL | Public event links. |
DIZKO_MCP_URL | https://mcp.dizko.app/mcp | EVENTCHAT_MCP_URL, UPLAYGROUND_MCP_URL | Hosted endpoint; also the base for /e/<id>/... short links. |
DIZKO_APP_DOWNLOAD_URL | https://www.dizko.app/ios | EVENTCHAT_APP_DOWNLOAD_URL, UPLAYGROUND_APP_DOWNLOAD_URL | app_download_url in results. |
DIZKO_API_TIMEOUT_MS | 8000 | EVENTCHAT_API_TIMEOUT_MS | Per-request upstream timeout. |
DIZKO_API_RETRIES | 2 | EVENTCHAT_API_RETRIES | Retries for transient network/5xx failures (backoff + jitter). |
DIZKO_API_RETRY_BASE_DELAY_MS | 250 | EVENTCHAT_API_RETRY_BASE_DELAY_MS | Backoff base delay. |
DIZKO_API_CACHE_TTL_MS | 300000 | EVENTCHAT_API_CACHE_TTL_MS | Upstream response cache; 0 disables. |
DIZKO_API_CACHE_STALE_MS | 3600000 | EVENTCHAT_API_CACHE_STALE_MS | How long an expired cache entry may be served while the upstream fails. |
DIZKO_USER_AGENT | DizkoEventsTool/<version> | EVENTCHAT_USER_AGENT | User agent sent upstream. |
DIZKO_MCP_UPSTREAM_SECRET | unset | EVENTCHAT_MCP_UPSTREAM_SECRET |
Tickets, preferences (src/tickets.js, src/preferences.js):
| Variable | Default | Legacy aliases | Purpose |
|---|---|---|---|
DIZKO_QUOTE_SIGNING_SECRET | random per process | EVENTCHAT_QUOTE_SIGNING_SECRET | HMAC key for quote tokens. Set it on hosted deployments so quotes survive restarts; the per-process fallback is only safe for a single instance. |
DIZKO_MAX_TRACKED_QUOTES | 20000 | none | How many spent-quote claims are held at once. Sized for concurrent live quotes: expired claims are pruned first, so the ceiling is only reached by that many unexpired claims. At the ceiling a purchase is refused (quote_registry_full) rather than an existing claim being dropped. |
DIZKO_PREFERENCES_PATH | ./data/preferences.json | EVENTCHAT_PREFERENCES_PATH | Preference store file. |
DIZKO_PREFERENCE_RETENTION_DAYS | 730 | EVENTCHAT_PREFERENCE_RETENTION_DAYS | Inactive profiles are pruned after this many days. |
DIZKO_MAX_PROFILES | 10000 | EVENTCHAT_MAX_PROFILES | Ceiling on stored profiles. At the ceiling, profiles that were created but never used or consented to are evicted oldest-first; only if that frees nothing does creation fail with profile_limit_reached. |
DIZKO_MAX_PROFILE_BYTES | 98304 | EVENTCHAT_MAX_PROFILE_BYTES | Ceiling on one serialized profile. Over it, the oldest feedback entries are dropped to fit (their signal already lives in learned); a profile still too large with no feedback left is refused with profile_too_large. |
EVENTCHAT_ALLOW_LEGACY_PROFILE_IDS | unset | none | true lets pre-secret legacy profiles be read without a secret. Keep it unset in production. |
HTTP server (src/httpServer.js, EVENTCHAT_* only):
| Variable | Default | Purpose |
|---|---|---|
PORT / EVENTCHAT_MCP_PORT | 8787 | Listen port (PORT wins). |
HOST / EVENTCHAT_MCP_HOST | 0.0.0.0 | Listen host. |
EVENTCHAT_MCP_BEARER_TOKEN | unset (public) | When set, /mcp requires Authorization: Bearer <token>. Short links and public pages stay open. |
EVENTCHAT_MCP_ALLOWED_ORIGINS | * | Comma list for Access-Control-Allow-Origin. When set to anything but *, a request carrying an Origin outside the list is refused with 403 rather than only being denied by the browser. Setting it to an empty or blank value denies every browser origin, since clearing an allowlist means locking down, not opening up. Requests with no Origin (every non-browser client) are unaffected, and browser-extension origins (chrome-extension://, moz-extension://, safari-web-extension://) are accepted under any non-empty list, since DNS rebinding cannot produce them. |
EVENTCHAT_MCP_RATE_LIMIT_MAX | 600 | Requests per window per client IP on /mcp and short links. |
EVENTCHAT_MCP_RATE_LIMIT_WINDOW_MS | 60000 | Rate-limit window. |
EVENTCHAT_MCP_RATE_LIMIT_EXEMPT | empty | Comma list of IP prefixes exempt from the limiter (hosted assistants call from shared egress addresses). Matched against the client IP resolved through EVENTCHAT_MCP_TRUSTED_PROXIES, so that setting has to be right before an exemption is safe. |
EVENTCHAT_MCP_RATE_LIMIT_DISABLED | false | Disable the limiter entirely. |
EVENTCHAT_MCP_MAX_BODY_BYTES | 1048576 | Maximum JSON-RPC body. |
EVENTCHAT_MCP_MAX_BATCH_SIZE | 20 | Maximum requests in one JSON-RPC batch. A larger batch is rejected with -32600 before any of it runs; each element in an accepted batch is charged to the rate limiter. |
Personalization:
dizko_onboarding): event types, genres, vibe, budget, locations, avoidances.create_event_preference_profile with consent: true and privately remembers the returned profile_id and profile_secret. The response also includes access_instructions, a user-facing access card for clients that cannot persist connector state across sessions.save_event_preferences with both profile_id and profile_secret.search_events call with the profile attached. Clarifying questions (type, vibe, budget, area) are asked conversationally and only when the request is genuinely ambiguous.day_filters, per-weekday rules such as techno Fridays but chill Sundays. Array fields add to the general taste and max_price/free/nightlife override it, but only on that weekday: single-day searches (tonight, tomorrow, an explicit date) and daily roundups apply the matching day automatically.get_daily_roundup with the city plus the profile credentials and renders top picks followed by category sections.dizko_post_event_feedback), then calls record_event_feedback only after the user provides liked/disliked, rating, or notes.Ticket purchase:
get_ticket_offers with the event id.quote_ticket_order with quantity, ticket type, max total, currency, and refund constraints, and receives a signed quote_token plus the confirmation prompt.Yes, buy 2 ticket(s) for Ostbahnhof XL, max total EUR240. Stop if price, date, venue, ticket type, quantity, or refund terms change. The confirmation must contain buy or purchase as a whole word plus the quantity and the max total as whole numbers; confirmation_mismatch lists what is missing.purchase_ticket_order with the unchanged quote_token only after that confirmation. Quotes expire after 10 minutes (quote_expired); edited tokens fail signature verification (invalid_quote_token).external_checkout (handoff_only: true, next_step: hand_off_to_checkout), the tool returns requires_external_checkout and a checkout URL: a hand off to checkout, where the user pays on the provider's site. The assistant must not claim it purchased or reserved the ticket. The confirmation check runs on the server before either path, so a call without matching written confirmation returns confirmation_mismatch and never reaches a provider.supported_modes: external_checkout, partner_api_purchase, dizko_checkout, delegated_payment_future.Provider adapter contract:
{
canPurchase(event, summary) {
return true;
},
async purchase({ quote, confirmation_text, user_payment_profile_id, idempotency_key }) {
return {
purchased: true,
status: "purchased",
order_id: "...",
receipt_url: "...",
ticket_delivery_status: "pending_delivery",
provider_response: {}
};
}
}
The public ChatGPT user surface is the hosted MCP endpoint plus OpenAI app submission. The npm package is the developer distribution path for local agents, Claude Desktop-style MCP clients, Cursor and Windsurf setups, and technical testers who can run a command. The source is available under the MIT License for inspection, reuse, and issue reporting.
Before publishing a new npm version:
npm test
npm pack --dry-run --json
npm publish --access public
popularcostevent_typedistanceorigin_latorigin_lngrankrelevancetasteranktasteprofile_id + profile_secret are accepted by search_events, recommend_events, recommend_events_for_user, plan_night, get_daily_roundup and get_artist_events. Saved taste is a ranking hint only; it is never turned into a filter.no_results with reason (filters_too_narrow, empty_timeframe or no_inventory), baseline_count (how many events exist in the same city and timeframe with every filter removed), active_filters, and suggested_relaxations: an ordered list of { relax, why, retry_with } where retry_with is a directly callable argument set. Price caps are suggested first because they also exclude every event with no published price. The assistant_instruction on an empty result tells the model to report what is on instead of rendering an empty list.consent, confirm_delete, confirmed) must be the literal boolean; "yes" or 1 is refused with invalid_argument, although other flags accept those spellings.isError: true with { error, code, field?, allowed?, hint? }. Codes: invalid_argument, invalid_cursor, unknown_tool, unsupported_city (with nearest_covered_city computed by distance from the live list), event_not_found, entity_not_found, profile_not_found, profile_secret_invalid, consent_required, confirmation_required, explicit_confirmation_required, feedback_signal_required, authentication_required, insufficient_scope, invalid_quote_token, confirmation_mismatch, quote_expired, upstream_unavailable, upstream_timeout.start_equals_endaggregator_onlyconfidencelisting_confidencelisting details unverifiedassistant_instructionget_artist returns it as entity.completeness and get_artist_page as completeness: { score, max, version, missing }, heaviest missing signal first. It is omitted when the backend has none.dizko_url, artist page_url and mix deep_link, entity dizko_url, collective_url) carry utm_source=dizko_mcp&utm_medium=ai_assistant, read by the web app's existing UTM capture. ref is not used because the web app treats it as a referral code. event_url, ticket, calendar and directions links stay clean.Shared secret the hosted deployment sends to the Dizko API as the X-Dizko-MCP-Secret header. |
EVENTCHAT_MCP_TRUSTED_PROXIES | 1 | How many proxy hops sit in front of the server. The rate-limit client IP is taken this many entries from the right of X-Forwarded-For, so a caller cannot pick their own bucket by prepending addresses. Set it to the real hop count behind a CDN plus load balancer, or 0 when nothing sits in front of the server (the socket address is then used and X-Forwarded-For is ignored). |