Skip to main content
Glama
Dizko-Labs

Dizko Events MCP

Official
by Dizko-Labs

Dizko Events CLI and MCP

Quick Start

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.

Related MCP server: touch-grass-mcp

Agent frameworks (Hermes, OpenClaw, LangGraph, OpenAI Agents SDK, custom loops)

Framework agents don't have a "Connectors" UI, so they integrate programmatically. Four paths, simplest first:

  1. 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.

  2. stdio MCP: spawn npx -y dizko-events mcp as a subprocess and speak MCP over stdio.

  3. 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.

  4. 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" });

Autonomous ticket purchase (the Hermes / OpenClaw integration point)

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.

  • Agentic ticket tools for ticket offers, signed quotes, written confirmation, checkout handoff, and future Hermes/OpenClaw/Dizko purchase adapters.

Why This Beats Normal Chat

General chat can describe likely events, but it does not reliably know current inventory. This tool gives an agent:

  • Live structured results from Dizko's /events API across 49 cities, with every time already in the city's local timezone.

  • Deterministic filters for city, date, genre, vibe, event type, venue, neighborhood, promoter, price, and artist.

  • Artist, venue and promoter lookups with upcoming dates, and city-level trend reads with evidence counts.

  • Explainable ranking so recommendations include reasons instead of opaque taste guesses.

  • Ticket links and Dizko event links for verification, plus one-click calendar and directions links.

  • A planning tool that returns a primary option plus fallbacks in a compact machine-readable shape.

  • Consent-based preference learning: onboarding questions, saved taste profiles, post-event feedback, note-derived signals, and learned ranking signals. Saved taste only ranks results; it never hides them.

  • Ticket purchase safety rails: agents can quote and prepare ticket orders, but autonomous purchase requires a signed quote, explicit written confirmation, and an integrated purchase provider.

The agent can still write conversational prose, but its event facts come from the tool.

CLI

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.

MCP For Local Developer Clients

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"
      }
    }
  }
}

Tools

The server lists 30 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.

Annotations: discovery, entity, onboarding, follow-up, get_event_preferences, get_taste_profile, the ticket policy, offer and quote tools, and create_event_calendar_file are readOnlyHint: true. 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.

Prompts

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.

Request conventions

  • Timeframes. 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.

  • Event payloads carry (a field with no value is omitted rather than sent as null) 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.

  • Ordering. sort_by: soonest (default for single-day requests), popular (default for ranges), cost, event_type, or distance with origin_lat/origin_lng (only pass coordinates the user gave you). rank: relevance (default) keeps the API order; taste re-ranks the page by genres, vibe, avoid and budget from the request and the profile. rank defaults to taste when a profile is given.

  • Profiles. profile_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.

  • Empty results. A search or plan that matches nothing is not an error. It returns 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 and confirmation flags (consent, confirm_delete, confirmed) must be the literal boolean; "yes" or 1 is refused with invalid_argument, although other flags accept those spellings.

  • Errors are returned as 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.

Data honesty

The backend fixes listing data at the root; the connector still refuses to pass on what it cannot stand behind (src/quality.js):

  • Currency. When the backend sends 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.

  • End times. A city-local 23:59 end, an end equal to the start, and an end before the start are all treated as unknown: 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).

  • Duplicates. Search, recommend, plan, roundup and venue pages collapse one show listed by two sources: same city-local date, the same or an adjacent venue (300 m or shared venue words), titles that share most of their words once venue, city and lineup words are removed, and starts within 90 minutes. The better listing is kept, with 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. When the backend sends 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 start_equals_end and aggregator_only (only an aggregator such as allevents lists it). The backend's confidence passes through as listing_confidence. Flagged listings rank lower in recommend, plan and roundup flows (with the reason listing details unverified), never hidden, and the result's assistant_instruction gains one sentence asking the assistant to mention the uncertainty.

  • Near duplicates. Two sources listing one party collapse only when they share the city, the local date, a start within 90 minutes, most of the title, and the venue: coordinates within 300 m, or a distinctive shared venue word. Coordinates a kilometre or more apart never merge, and generic room words (club, bar, cafe, jazz and the like) do not count as a shared venue name.

  • Artist page completeness. When the scene API scores an artist's page, get_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.

  • Attribution. Dizko web links meant for the user (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.

Learning rules

  • A like promotes the event's genres, vibe, event types, venue and promoters.

  • A dislike marks the venue and promoter. Genres are only penalized when the notes blame the music or lineup.

  • Notes about crowds, price, timing, mainstream or alcohol-focused events become learned avoid signals.

  • A term the user saved as a preference is never learned negative.

  • Genres, vibe and event types need two negative signals before they become an avoid rule; venues and promoters need one.

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.

Hosted MCP For ChatGPT And Claude

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 reviewed ChatGPT and Codex plugin surface (src/surfaces.js): 13 read-only discovery tools, no profile inputs, no prompts, no ticket flow, and no app promotion in results.

  • 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:

  1. Deploy this HTTP server on a public HTTPS domain.

  2. Test it in ChatGPT Developer Mode by creating a connector with that URL.

  3. Validate tool calls on web and mobile.

  4. Submit the app through the OpenAI dashboard with the public MCP URL, privacy/support URLs, logo, screenshots, test prompts, and tool descriptions.

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.

Configuration

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

Shared secret the hosted deployment sends to the Dizko API as the X-Dizko-MCP-Secret header.

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.

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.

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).

Flows

Personalization:

  1. User asks for personalized event help.

  2. Assistant asks the onboarding questions (prompt dizko_onboarding): event types, genres, vibe, budget, locations, avoidances.

  3. Assistant asks whether Dizko may save those preferences.

  4. If yes and there is no existing profile, assistant calls 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.

  5. If a profile already exists, assistant calls save_event_preferences with both profile_id and profile_secret.

  6. A request that names a city and a timeframe is one search_events call with the profile attached. Clarifying questions (type, vibe, budget, area) are asked conversationally and only when the request is genuinely ambiguous.

  7. Preferences can include 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.

  8. For a daily digest ("what's happening today?", a scheduled morning briefing), assistant calls get_daily_roundup with the city plus the profile credentials and renders top picks followed by category sections.

  9. After the event, assistant asks whether the user went and liked it (prompt dizko_post_event_feedback), then calls record_event_feedback only after the user provides liked/disliked, rating, or notes.

Ticket purchase:

  1. User asks to buy or reserve tickets for an event.

  2. Assistant calls get_ticket_offers with the event id.

  3. Assistant explains whether the offer supports autonomous purchase or only external checkout.

  4. Assistant calls quote_ticket_order with quantity, ticket type, max total, currency, and refund constraints, and receives a signed quote_token plus the confirmation prompt.

  5. Assistant asks for explicit written confirmation, for example: 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.

  6. Assistant calls 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).

  7. If the quote uses 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.

  8. If Hermes, OpenClaw, Dizko Checkout, or another integrated provider is configured, the provider can execute the bounded purchase and return order/receipt/ticket delivery status. 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: {}
    };
  }
}

Distribution Notes

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

Related MCP Connectors

Related MCP Servers