protogrid

Search / eu.projektionisten/tourism

TourismMCP

R2 · OAuth, once

Travel in Germany (Tourismus): sights, events, opening hours and prices, weather and tides.

v0.1.2 · active · website · descriptor JSON · versions

An agent can connect after a one-time OAuth consent by a human.Remote endpoint behind OAuth 2. Tokens stay with the agent; the registry only exposes the authorization metadata.
Quality75good
Trust83of 100
Uptime, 30 days100%
Latency p50728 ms

Connect

8 clients · secrets stay placeholders
{
  "mcpServers": {
    "tourism": {
      "type": "http",
      "url": "https://ai.projektionisten.eu/tmcp"
    }
  }
}
claude mcp add --transport http tourism https://ai.projektionisten.eu/tmcp
[mcp_servers.tourism]
url = "https://ai.projektionisten.eu/tmcp"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "tourism": {
      "type": "remote",
      "url": "https://ai.projektionisten.eu/tmcp",
      "enabled": true
    }
  }
}
{
  "mcpServers": {
    "tourism": {
      "type": "http",
      "url": "https://ai.projektionisten.eu/tmcp"
    }
  },
  "deeplink": "cursor://anysphere.cursor-deeplink/mcp/install?name=tourism&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vYWkucHJvamVrdGlvbmlzdGVuLmV1L3RtY3AifQ%3D%3D"
}
{
  "servers": {
    "tourism": {
      "type": "http",
      "url": "https://ai.projektionisten.eu/tmcp"
    }
  }
}
{
  "mcpServers": {
    "tourism": {
      "httpUrl": "https://ai.projektionisten.eu/tmcp"
    }
  }
}
extensions:
  "tourism":
    enabled: true
    name: "tourism"
    type: streamable_http
    uri: "https://ai.projektionisten.eu/tmcp"
    timeout: 300

Quality 75 / 100good

protocoln/a · weight 25
  • n/a
    protocol.modern, protocol.stateless, protocol.transport, protocol.list_ttlno remote answered a protocol handshake (not probed yet, unreachable, or local-only)
authorization83 · weight 15
  • pass
    auth.prmProtected Resource Metadata (RFC 9728) published
  • pass
    auth.as_metadataauthorization server metadata reachable
  • warn
    auth.cimdonly Dynamic Client Registration, deprecated in 2026-07-28 in favour of CIMD
  • n/a
    auth.secret_in_urlno templated URL
tool hygiene71 · weight 30
  • pass
    tools.descriptionsevery tool has a description
  • warn
    tools.description_length12 tools have descriptions over 1,024 characters, which crowds the context
  • pass
    tools.schemasevery tool has an object input schema
  • pass
    tools.annotations14 of 14 tools declare readOnlyHint or destructiveHint
  • fail
    tools.directory_hintsno tool declares all four of readOnlyHint, destructiveHint, idempotentHint and openWorldHint; missing: search_tourism (idempotentHint); get_current_weather (idempotentHint); get_weather_forecast (idempotentHint); …
  • warn
    tools.token_costabout 12,538 tokens to load every tool
  • pass
    tools.api_dumptools are not a one-to-one API dump
stabilityn/a · weight 15
  • n/a
    stability.changes, stability.rug_pulltool history not recorded yet
dependenciesn/a · weight 15
  • n/a
    deps.known_vulns, deps.mcp_sdk_version, deps.resolvableno npm or PyPI package

14 tools, about 12,538 tokens to load them all · checked 2026-10-09 23:53 UTC

Tool changes

No tool definition changes recorded. A server's first observation is its baseline; later probes record what changes.

Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed. How it is computed.

Trust 83 / 100

no-repository
provenance55
liveness100
freshness100
hygiene80

+dns-namespace +website +reachable +uptime-100% +updated-17d-ago -no-repository

Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed. How it is computed.

Endpoints

remoteauthreachableuptime 30dp50protocollast ok
https://ai.projektionisten.eu/tmcp streamable-httpoauth2 AS: https://ai.projektionisten.eu/auth/realms/mobilitymcpyes100%728 ms–2026-10-09

Tools (14)

expand_kg_poisread-onlyAttraktionen / Tourismus-POIs FÜR einen Place aus dem OSM-Knowledge-Graph (Ausdehnung einer Stadt-/Region-OSM-ID auf die POIs darin). Für „was kann ich in X unternehmen" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen — erst verorten, dann abfragen: die `osm_id` der Region stammt aus `search_place`. **Tool-Semantik-Abgrenzung**: dieses Tool = „WELCHE Attraktionen gibt es IN diesem Place" (KG-basiert). NICHT `nearby` (das ist Vicinity — „Dinge im Radius um EINEN Punkt"). NICHT `search_place` (das ist Place- Disambiguierung — „WELCHER Ort ist gemeint"). NICHT `resolve_location` (Transit-Halte). **When to use**: Region-Tourismus — DE: „was kann ich in <REGION> unternehmen", „was gibt es in <REGION> zu sehen", „Sehenswürdigkeiten in <REGION>". EN: „what can I do in <REGION>", „attractions in <REGION>". Nach `search_place(<REGION>)` mit der zurückgegebenen osmRel-ID aufrufen. **When NOT to use**: Vicinity-zu-Punkt (Radius um Koordinaten) → `nearby`; Detail-Info zu EINEM bereits gefundenen POI → `get_poi_details`; Place- Auflösung Name→ID → `search_place`; Wetter/Tide → `get_current_weather`/`get_tide`. **Required args**: `osm_id` (i64 — die osmRel-ID der Stadt/Region aus einem `search_place`/`places`-Treffer, z.B. 1187768 für Wangerland; osmNode als Fallback). Optional: `limit` (Default 8, clamped 1..=20), `types` (schema.org-Keywords als Substring-Filter, z.B. ["TouristAttraction"], ["Museum"], ["Event"] — case-insensitive; leer = alle Aktivitäts-/ Tourismus-Klassen), `include_address` (bool, Default true — löst die Postadresse je Entity auf), `family_only` (bool, Default false — nur family-taugliche POIs, je mit den tag-belegten Feldern `family`/`indoor`/`family_categories`) und, nur zusammen damit, `indoor_only` (bool — davon nur die Indoor-/Schlechtwetter-tauglichen). **Unterkünfte ausgeschlossen**: dieses Tool ist der Aktivitäts-/„unternehmen"- Pfad — Unterkünfte (`tourism`=hotel/hostel/guest_house/motel/apartment/… ) werden backend-seitig AUSGESCHLOSSEN (ein Hotel ist keine Unternehmung); auch ein Hotel-`types`-Filter liefert hier nichts. Für Hotels/Pensionen → `search_tourism(type=accommodation)` (kuratierte Unterkünfte). **Typical chain**: `search_place(<REGION>)` → THIS_TOOL(osm_id=<osmRel>) → (optional `get_poi_details(osm_id)` pro Treffer für tiefere Details). **Multi-call**: ein Call pro Region/Typ-Filter; Bursting nicht nötig (das Tool fan-out't intern über alle containedInPlace-Entities). **Anti-Fab note**: POI-Name, `type`, Beschreibung, Adresse kommen AUSSCHLIESSLICH aus `pois[]` dieses Aufrufs. Wenn `returned: 0` / `pois: []` → honest fallback („Ich konnte für <REGION> aktuell keine Attraktionen abrufen"), NIEMALS POIs aus Trainings-Wissen ergänzen. OSM-KG-Coverage: deutschlandweit; Tag-Dichte variiert je Region wie in OSM üblich. Returns {place_osm_id, total_contained, tourism_candidates, returned, pois:[{name, type, types, description?, address?, uri, coord?, opening_hours?, fee?, source:'osm'}], attribution?}. Die optionalen Felder je POI sind tag-belegt oder abwesend, nie geraten. **`attribution` (additiv, top-level)**: die ODbL-Namensnennung der gelieferten OSM-Daten (ODbL) — `{id:'ODbL-1.0', notice:'© OpenStreetMap contributors', url:'https://www.openstreetmap.org/copyright'}`. EINMAL pro Antwort (nicht pro POI) und nur wenn `pois` nicht leer ist. Nennst du diese POIs, nenne „OpenStreetMap" als Quelle. „Kinderfreundlich" ist ein Daten-Fakt (Kategorie/Tag-belegt), kein Modell-Raten.
get_current_weatherread-onlyAktuelles Wetter zu Koordinaten. Für „wie ist das Wetter in X" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen — erst verorten, dann abfragen: `lat`/`lon` kommen aus `search_place`, nie aus eigener Schätzung. **When to use**: aktuelle Wetterfrage zu einem Ort — DE: „wie ist das Wetter in <ORT>", „regnet es gerade in <ORT>". EN: „what's the weather like in <CITY> right now". Auch als Teil des Wetter-POI-Pfads („was kann ich bei dem Wetter unternehmen"). **When NOT to use**: Vorhersage > 1 Stunde voraus → `get_weather_forecast`; Gezeiten → `get_tide`. **Required args**: `lat`, `lon`. Optional: `units` ('metric' (°C, default), 'imperial' (°F), 'standard' (K)), `lang` ('en' default, 'de'). **Typical chain**: `search_place`(city) → THIS_TOOL(lat, lon) → (optional `nearby`/`resolve_location` für POI-Match). **Multi-call**: ein Call pro Ort. Für Multi-Tag-Anfragen `get_weather_forecast` bevorzugen. **Anti-Fab note**: Temperatur, Bedingungen, Wind, Feuchte kommen NUR aus dem Tool-Output dieses Aufrufs — KEINE Schätz-Werte. **`attribution` (additiv, top-level)**: die von der GeoNutzV verlangte Quellenangabe der Wetterdaten — `{id:'GeoNutzV', notice:'Quelle: Deutscher Wetterdienst', url:…}`. Nennst du Wetter-Werte, nenne den **Deutscher Wetterdienst** als Quelle; die Auflage reist mit dem Tool-Output, nicht mit dem Prompt.
get_poi_detailsread-onlyPOI-Detail-Lookup per OSM-ID (Daten aus OpenStreetMap). Für „Öffnungszeiten von X", „erzähl mir was über X" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen — erst verorten, dann abfragen: die `osm_id` stammt aus `nearby`/`resolve_location`/`search_place`. **When to use**: nach `nearby` / `resolve_location` / `search_place` lieferte einen POI mit `osm_id` und die Anfrage will Detail-Info — DE: „erzähl mir was über <POI>", „was kostet der Eintritt", „Öffnungszeiten von <POI>", „bei dem Wetter in <REGION> unternehmen". EN: „tell me about <POI>", „opening hours of <POI>", „what can I do in <REGION> given the weather". **When NOT to use**: für Stadt-/Region-IDs (nutze `search_place`); ohne vorherigen Tool-Call mit OSM-ID-Output (KEIN raten); wenn die Anfrage Routing/Abfahrten will (dafür der Server MobilityMCP). **Required args**: `osm_id` — bare numeric string (pattern `^\d+$`, z.B. "296222553"), kein `node:`/`way:`-Prefix (der wurde beim emittierenden Tool bereits gestrippt). **Typical chain**: TWO distinct chains feed this tool — (1) WETTER-CONTEXT: `get_current_weather` + `nearby`(node_types='poi') → THIS_TOOL ×3-5 (Trigger: „bei dem Wetter", „given the weather"). (2) REGION-TOURISM: `search_place` + `resolve_location`(node_types='poi') → THIS_TOOL ×3-5 (Trigger: „was kann ich in <REGION> unternehmen" ohne Wetter-Bezug — dichtere OSM-Coverage über `resolve_location`). **Multi-call**: JA. Nach `resolve_location`/`nearby` mit N POI-Treffern: THIS_TOOL pro Top-3 bis Top-5 OSM-IDs PARALLEL aufrufen — jeder Call ist unabhängig, ein Burst ist erlaubt. NIE nur 1× rufen wenn N>1 POIs zurückkommen. **Anti-Fab note**: POI-Name, Operator, Öffnungszeiten, Adresse, Tags kommen AUSSCHLIESSLICH aus `sources[].subjects[].properties` dieses Aufrufs. Wenn `sources: []` → honest fallback („Zu diesem Ort konnte ich aktuell keine Detail-Informationen abrufen"), NIEMALS aus Trainings-Wissen ergänzen. OSM-Coverage: deutschlandweit; Tag-Dichte variiert je Region wie in OSM üblich. **Shape**: `{osm_id, sources:[{source:'openstreetmap', subjects:[{subject, properties:{…raw OSM tags: name, tourism/historic/leisure, opening_hours, fee, website, wheelchair, addr:*…}, coord?:{lat,lon}}]}], facts?:{opening_hours?:{value,source}, price?:{free?,raw,source}}}`. Die rohen Tags tragen Typ/Adresse/Öffnungszeiten/Preis/Beschreibung bereits; `coord` (Geometrie-Center, additiv) liegt je Subject NEBEN `properties` und speist die Karte — present nur wenn das Element Geometrie hat. **`facts` (additiv, top-level)**: normalisierte Öffnungszeiten + Preis mit Pro-Feld-Provenance (`source:'openstreetmap'`) aus den `opening_hours`/`fee`-Tags — EINE stabile Stelle statt Roh-Tags durchwühlen. `price.free=true` bei `fee=no` (explizit „kostenlos"/Eintritt frei), `false` bei `fee=yes`/Betrag; `price.raw` trägt den Roh-Tag verbatim. Fehlt opening_hours UND fee → `facts` ABWESEND (honest, nie erfunden — Quellen-Priorität: kuratiert via `get_tourism_details` VOR diesem OSM-Bridge). **`attribution` (additiv, top-level)**: die ODbL-Namensnennung der gelieferten OSM-Daten (ODbL) — `{id:'ODbL-1.0', notice:'© OpenStreetMap contributors', url:'https://www.openstreetmap.org/copyright'}`. EINMAL pro Antwort (nicht pro Treffer) und nur wenn `sources` nicht leer ist. Nennst du OSM-Daten in der Antwort, nenne die Quelle „OpenStreetMap" — die Lizenz-Auflage reist mit dem Tool-Output, nicht mit dem Prompt.
get_tideread-onlyGezeiten (Niedrig-/Hochwasser) für einen Küstenort. Für „wann ist Ebbe in X", „Gezeiten bei Y" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen — erst verorten, dann abfragen: `city` aus der Frage oder `lat`/`lon` aus `search_place`. **When to use**: Gezeiten-/Ebbe-/Niedrigwasser-/Hochwasser-Frage — DE: „niedrigwasser in Cuxhaven", „wann ist Ebbe in Wilhelmshaven", „Tide bei Norderney". EN: „low tide in Cuxhaven", „when is high tide at Norderney". **When NOT to use**: normales Wetter → `get_current_weather`; Routing → der Server MobilityMCP; Haltestellen → `resolve_location`; Binnen-Orte ohne Küstenbezug (Hannover, Hildesheim, Braunschweig). **Required args**: entweder `city` (z.B. 'Hamburg', 'Cuxhaven', 'Norderney') ODER (`lat`+`lon`). Optional bei Koord-Mode: `station_limit`, `date_start`, `date_end` (YYYY-MM-DD). **Typical chain**: (optional `search_place`(city)) → THIS_TOOL. **Multi-call**: ein Call pro Ort/Tag-Range. **Anti-Fab note**: Tide-Zeiten kommen NUR aus dem Tool-Output dieses Aufrufs. KEINE „typischen" Tide-Schätzungen aus Bauch-Wissen.
get_tourism_detailsread-onlyKuratiertes Detail zu EINEM Niedersachsen-Hub-Treffer per `id` — Beschreibung, Öffnungszeiten, ECHTER Preis, Adresse, Medien. Für „was kostet der Eintritt für X", „wann hat X geöffnet" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen. **Tool-Semantik-Abgrenzung**: dieses Tool = Detail zu EINEM kuratierten NDS-Treffer (dessen `id`/`global_id`). NICHT `get_poi_details` (das ist die OSM-Detail-Bridge per OSM-ID). NICHT `search_tourism` (das ist die kuratierte Region-/Typ-SUCHE die die `id` erst liefert). **When to use**: nach `search_tourism` lieferte einen Treffer mit `id` und die Anfrage will Detail/Preis/Öffnungszeiten — DE: „was kostet der Eintritt für <Treffer>", „Öffnungszeiten von <Treffer>", „erzähl mir mehr zu <Event>". EN: „opening hours / price of <hit>". **When NOT to use**: ohne vorherigen `search_tourism`-Treffer mit `id` (KEIN raten/erfinden einer id); OSM-POI-Detail per OSM-ID → `get_poi_details`; Region-Suche → `search_tourism`. **Required args**: `id` (die `id` ODER `global_id` aus einem `search_tourism`-Treffer, z.B. "e_101102405"). Optional: `query` (Region/ Name-Hint — dieselbe Region wie bei der Suche; die kuratierte Quelle hat KEINEN Objekt-per-id-Endpunkt, daher re-sucht das Detail diesen Scope und matcht die id; ohne Hint ggf. honest-empty für ids außerhalb der Default-Seite). **Typical chain**: `search_tourism(region=<R>, type=event)` → `get_tourism_details(id=<treffer.id>, query=<R>)`. **Multi-call**: ein Call pro id; nach `search_tourism` mit N Treffern pro Top-Treffer parallel rufbar. **Anti-Fab note**: Name, Beschreibung, Preis, Öffnungszeiten, Adresse kommen AUSSCHLIESSLICH aus `sources[].subjects[].properties` dieses Aufrufs. Wenn `sources: []` → honest fallback („Zu diesem Treffer konnte ich keine Detail-Informationen abrufen"), NIEMALS aus Trainings-Wissen. Returns {id, sources:[{source, license?, subjects:[{subject, properties:{name, type, description?, opening_hours?, price?, address?, date?, media?, coord?, url?, hub_detail_url?}}]}], facts?}. Die Felder sind die schema.org-Felder des kuratierten Datensatzes: `price` aus `priceRange`/`offers`, `opening_hours` aus `openingHoursSpecification`, `coord` aus `geo`, `url` = schema.org-Link — alle additiv, present nur wo die Quelle sie trägt. **Bei einer Tour (`t_…`) zusätzlich**: `path_on_map` — ihr Verlauf als `[{lat,lon}]`, ausgedünnt auf die Punkte, die seine Form tragen; das ist die zeichenbare Strecke, und NUR das Detail trägt sie (die Suche nicht). Dazu die Achsen des Datensatzes: `length_m`, `duration_min`, `ascent_m`/`descent_m`, `round_trip`, `activities` (wörtlich, als was er die Tour führt). Alle aus `ET2014A.json`, alle nur present wo die Quelle sie führt — eine fehlende Achse ist unbekannt, keine Null. **`facts` (additiv, top-level)**: normalisierte Öffnungszeiten + Preis mit Pro-Feld-Provenance — kuratiert hat Quellen-Priorität VOR der OSM-Bridge `get_poi_details`. `price.free=true` bei explizit kostenlos/Eintritt frei, `false` bei einem Betrag; `price.raw` trägt den kuratierten Preis-String wörtlich. Fehlen opening_hours UND price → `facts` ABWESEND (ehrlich, nie erfunden). **`hub_detail_url` (additiv, in `properties`, nur bei `source: 'niedersachsen-hub'`)**: die kanonische Entitätsseite dieses Datensatzes beim Niedersachsen-Hub, dem Nutzer nennbar — NICHT die Betreiber-Website, die daneben in `url` steht. Jeder Datensatz dieser Quelle, dessen Art beim Hub eine Eintragsseite hat, trägt sie schon im ersten Aufruf: POI (`p_…`), Tour (`t_…`), Veranstaltung (`e_…`), Unterkunft (`h_…`), Gastronomie (`g_…`), Gebiet (`r_…`). Ein Medien-Datensatz (`m_…`) hat keine solche Seite und trägt sie nicht. Fehlt sie, keine konstruieren. **`license` (additiv, NEBEN `source` im `sources[]`-Eintrag — nicht in `properties`)**: `{id?, url?, notice?, holder?}` = Lizenz-Kennung, Lizenz-Text-URL, der WÖRTLICH wiederzugebende Lizenzverweis (DZT-`copyrightNotice`, § 3 der DZT-Nutzerbedingungen) und der zu nennende Urheber (das `author`- bzw. `copyrightHolder`-Feld des Datensatzes). Gibt er nichts an, ist das Feld **abwesend** — nie eine geratene Vorgabe-Lizenz. Nennst du einen Datensatz mit `license.holder`, nenne den Urheber mit.
get_usage_guideread-onlyDie ausführliche Anleitung zu den Werkzeugen dieses Katalogs: wofür ein Werkzeug da ist, wogegen es abzugrenzen ist, was seine Argumente bewirken, was zurückkommt und was daraus zitiert werden darf. Die `description` eines Werkzeugs ist die Kurzform, dieser Text die vollständige. **Optional** `tool` — der Name genau eines Werkzeugs, dessen Abschnitt du lesen willst; weggelassen kommt die ganze Anleitung. Lies den Abschnitt eines Werkzeugs, bevor du dessen Filter setzt oder ein leeres Ergebnis als Antwort weitergibst. **Der Abruf ohne `tool` ist teuer**: er bringt die Abschnitte ALLER Werkzeuge dieses Katalogs auf einmal, ein Vielfaches eines einzelnen. Setze `tool`, sobald feststeht, um welches Werkzeug es geht; ohne Argument nur für den Überblick über den ganzen Katalog.
get_weather_forecastread-onlyWetter-Vorhersage zu Koordinaten. Für „wie wird das Wetter morgen in X" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen — erst verorten, dann abfragen: `lat`/`lon` kommen aus `search_place`, nie aus eigener Schätzung. **When to use**: zukünftige Wetterfrage — DE: „wie wird das Wetter morgen / heute Abend / am Samstag in <ORT>", „regnet es morgen". EN: „forecast for <CITY> tomorrow / this weekend". Auch für Halbtages-Touren-Planung („wenn das Wetter mitspielt"). **When NOT to use**: jetziger Zustand → `get_current_weather`; Tide → `get_tide`. **Required args**: `lat`, `lon`. Optional: `units` ('metric' default, 'imperial', 'standard'), `lang` ('en' default, 'de'), `limit` (Anzahl Forecast-Slots). **Typical chain**: `search_place`(city) → THIS_TOOL(lat, lon, limit=N) → (optional `resolve_location(node_types='poi')` + `get_poi_details` für POI-Auswahl je nach Wetter-Branche). **Multi-call**: ein Call pro Ort. Multi-Day-Queries decken sich über `limit`. **Anti-Fab note**: Vorhersage-Werte kommen NUR aus dem Tool-Output dieses Aufrufs — KEINE Tag-für-Tag-Schätzungen aus Trainings-Wissen. **`attribution` (additiv, top-level)**: die von der GeoNutzV verlangte Quellenangabe — `{id:'GeoNutzV', notice:'Quelle: Deutscher Wetterdienst', url:…}`. Nennst du Vorhersage-Werte, nenne den **Deutscher Wetterdienst** als Quelle.
link_datetimeread-onlyLöst eine Zeit-Nennung in einen absoluten ISO-8601-Zeitpunkt auf — „morgen früh", „heute Abend um 18 Uhr", „in zwei Stunden", „um 8:30". Auch „jetzt"/„now"/„aktuell" wird aufgelöst: nutze das als kanonische Quelle für die aktuelle Zeit, statt dir selbst ein Datum auszudenken. Das Ergebnis (`candidates[].datetime_range.start`) ist der Zeitpunkt, mit dem du weiterarbeitest. Nennt der Nutzer bereits eine vollständige ISO-8601-Zeit mit Offset, ist kein Aufruf nötig. **Pflicht**: `text` — die Nennung, so wie der Nutzer sie schrieb. **Optional**: `lang` (`'de'` Default, `'en'`, `'fr'`) und `tz` (IANA-Name; ohne ihn wird die Lokalzeit als `Europe/Berlin` gelesen und als korrekter UTC-Instant zurückgegeben). **Was zurückkommt**: EIN Zeitpunkt, kein Fenster — `start` und `end` sind derselbe Instant. Eine nicht auflösbare Phrase liefert `candidates: []`; dann nachfragen, statt selbst zu rechnen. **Anleitung**: `get_usage_guide` mit `tool='link_datetime'` — insbesondere, auf welche Stunde eine vage Tageszeit fällt und was dann zu tun ist. **Anti-Fab**: Datum und Uhrzeit sind Fakten wie eine Liniennummer und stammen aus diesem Werkzeug — kein selbst-erfundenes Datum, keine eigene Datums-Arithmetik. Returns `candidates[].datetime_range:{start,end}`.
link_dynamicread-onlyMatch-Mention gegen Caller-supplied Kandidaten-Vokabular. **When to use**: Caller hat ein eigenes Vokabular (z.B. Custom-Enum, App-State, Domänen-spezifische Begriff-Liste) und will eine User-Mention darauf mappen. **When NOT to use**: Standard-Place/Stop/Time/MoT/Line-Linking → die spezialisierten `link_*`-Tools nutzen. Ohne Kandidaten ist der Linker leer. **Required args**: `text`, `candidates` (`[{name, synonyms?, coord?}, …]`) MUST be non-empty — die candidate-Liste IST das Vokabular. **Typical chain**: THIS_TOOL → (custom Caller-Logik). **Multi-call**: ein Call pro Mention/Vokabular-Set. **Anti-Fab note**: nur Kandidaten aus dem `candidates`-Arg matchen, KEIN impliziter Vokabular-Erweiterung. Returns `candidates[].candidate:<name>, score, span`.
nearbyread-onlyFindet Haltestellen, POIs und Adressen im Radius um eine KOORDINATE (Schwerpunkt Hannover/Niedersachsen) — „was ist in der Nähe von <lat,lon>", „welche Haltestellen liegen um diesen Punkt". Liegt nur ein Name vor, kommt erst eine Auflösung: `search_place` für eine Stadt oder Region, sonst die Ortsauflösung. **Pflicht**: `latitude`, `longitude`, `radius_m`. **Optional**: `limit` (Default 10); `node_types` — EIN String, mehrere Arten mit Komma: `'stop'`, `'address'`, `'poi'`, die Sharing-Angebote `'bike_rental'`, `'scooter_rental'`, `'car_sharing'`, `'taxi_stand'`, die Abstellanlagen `'park_and_ride'`, `'bike_and_ride'`, oder `'any'` (`'stop,bike_rental'`); `include_mots` (legt die bedienenden Linien über die Haltestellen-Treffer); `only_available` (nur Sharing-Treffer mit gemeldetem freiem Fahrzeug — UNBEKANNTE Verfügbarkeit gilt nicht als frei). **Format**: kompakter Einrück-Text (TOON), kein JSON. **Anleitung**: `get_usage_guide` mit `tool='nearby'` — die Werte im Einzelnen, was `'any'` nicht abdeckt, und was ein Treffer trägt (`category`, `modality`, `parking`, `contactInfo`). **Anti-Fab**: nur die zurückgegebenen Namen, Typen, Distanzen und Verfügbarkeiten nennen; fehlt ein Feld, war es in der Quelle nicht getaggt — Öffnungszeiten und Preise stehen hier NICHT.
resolve_locationread-onlyLöst Freitext in die Id auf, mit der gefahren wird — die Haltestelle, die Adresse oder den POI hinter „von <X> nach <Y>", „erzähl mir was über <POI>". Wo Routing, Abfahrtstafel oder POI-Steckbrief eine Id brauchen, steht es davor — auch wenn der Nutzer die Stadt dazu nennt („Hauptbahnhof Hannover"). Ein GEBIET (Stadt/Region) verortet `search_place`. **Pflicht**: `query` — nur der Name: `'Kelsterbach Bahnhof'`, NICHT `'für Kelsterbach Bahnhof'`. Wegzulassen sind `für`, `vom`, `von`, `nach`, `bis`; ein führendes `am`/`an`/`in`/`zur`/`auf` bleibt stehen — so heißen echte Halte („Am Wehrhahn"). **Optional**: `lat`/`lon` (Ranking-Bias), `limit`, `node_types` (`'stop'`/`'address'`/`'poi'`/`'any'`, mehrere mit Komma in EINEM String; eine andere Art ist ein Argument-Fehler), `city_station` (Query = ganze Stadt → deren (Haupt-)Bahnhof). **Art-Wort in `node_types`, nicht in den Namen**: „Haltestelle X" → `'stop'`, „Adresse X" → `'address'`, „POI X"/„Sehenswürdigkeit X" → `'poi'`, `query` je ohne das Wort. **A→B: zweimal rufen** — Start, Ziel. Jeder Treffer trägt `type` und `location`; NUR ein `stop` hat eine fahrbare DH-Id, nie eine erfinden. **Anleitung**: `get_usage_guide` — Abgrenzung, Argumente, Rangfolge. **Anti-Fab**: nur die Treffer aus dem Output dieses Aufrufs verwenden.
reverse_geocoderead-onlyBenennt, was an einer KOORDINATE liegt — der Ort („was liegt bei <lat,lon>"), mit `level` eine bestimmte Ebene davon, mit `level='street'` die Straße samt nächster Hausnummer (der Lookup für eine GPS-Startposition). **Abgrenzung**: den Weg zurück (Name → Koordinate und OSM-Ids) geht `search_place`, die fahrbare Halte-Id liefert die Ortsauflösung, und `nearby` listet auf, was UM eine Koordinate liegt, statt den Punkt selbst zu benennen. **Pflicht**: `lat` und `lon` — geschrieben auch `latitude`/`longitude`, so wie `nearby` die Koordinate nimmt; je Aufruf nur eine der beiden Schreibweisen. **Optional**: `radius_m` (Suchradius in METERN; ein Ort, IN dem die Koordinate liegt, hat Abstand 0 und ist in jedem noch so engen Radius dabei — ohne `radius_m` die nächstgelegenen Treffer), `limit` (Höchstzahl Treffer, Default 10, Maximum 50) und `level` — `'place'` (Default: die ganze Ortshierarchie, feinste Ebene zuerst), `'city'` (die Stadt/Gemeinde), `'suburb'` (der Stadtteil) oder `'street'`. Kennt der Datensatz die gewünschte Ebene hier nicht, antwortet die nächst-gröbere, erkennbar am `place_type`; ein unbekannter Wert wirkt wie `'place'`. **Anleitung**: `get_usage_guide` mit `tool='reverse_geocode'`. **Anti-Fab**: nur die zurückgegebenen Orts- und Straßen-Namen verwenden, einschließlich der Hausnummer aus dem Datensatz — nie eine erfinden.
search_placeread-onlyLöst den NAMEN einer Stadt, Region oder eines Bezirks in Koordinaten und OSM-Ids auf — der erste Schritt, wenn ein GEBIET verortet werden muss („was kann ich in <REGION> unternehmen", „wie ist das Wetter in <CITY>"). **Für eine HALTESTELLE ist dieses Werkzeug fast immer falsch**: es kennt Gebiete, keine Bahnsteige — auf einen Bahnhofs-Namen antwortet es mit dem Stadtteil, und die Id, die es liefert, ist eine OSM-Id und keine fahrbare Halte-Id. **Trägt die Anfrage `Bahnhof`, `Hauptbahnhof`, `Hbf` oder `Bf`, gehört sie an die Ortsauflösung** — „Köln Hauptbahnhof" und „Hannover Bahnhof" also dorthin, nicht hierher: auf das erste antwortet dieses Werkzeug mit einem gleichnamigen Ortsteil (einem in Potsdam), auf das zweite mit der Stadt Hannover. Dasselbe für Adresse und POI — alles, was Start, Ziel oder Abfahrtsort einer Fahrt sein kann; eine Stadt als Fahrt-Endpunkt („von Hannover nach Celle") ebenfalls. Von einer Koordinate zurück zum Namen geht `reverse_geocode`, die Umgebung einer Koordinate listet `nearby`. **Pflicht**: `name`. **Optional**: `lang` — wird für Symmetrie mit den übrigen Geo-Werkzeugen angenommen, derzeit aber nicht ans Backend durchgereicht und ändert das Ergebnis nicht. **Anleitung**: `get_usage_guide` mit `tool='search_place'` — die Abgrenzung im Detail, die typischen Ketten und der Umgang mit einem mehrdeutigen Namen. **Anti-Fab**: nur die zurückgegebenen Namen und Ids nutzen, keine Bauch-Geographie.
search_tourismread-onlyKuratierte Tourismus-Suche des Niedersachsen-Hubs — die redaktionelle TIEFE die OSM fehlt: EVENTS MIT DATUM, PREISE, Beschreibungen, Medien, Öffnungszeiten, Touren. Für „welche Veranstaltungen sind in X", „was kostet der Eintritt" ist dieses Werkzeug die Auskunft, nicht das Allgemeinwissen — erst verorten, dann abfragen: `region`/`query` aus der Frage, `lat`/`lon` nur aus `search_place`. **Tool-Semantik-Abgrenzung**: dieses Tool = die kuratierte Quelle. NICHT `expand_kg_pois` (das ist OSM-BREITE — alle Tags, all-NDS, aber ohne Events/ Preise/Redaktion). Für Region-Tourismus dürfen BEIDE laufen (OSM-Breite ∥ NDS-Kuration). NICHT `nearby` (Radius-um-Punkt), NICHT `search_place` (Place-Disambiguierung). **When to use**: Events — DE: „was ist diese Woche/am Wochenende in <Region> los", „Veranstaltungen in <Ort>". EN: „events in <Region> this weekend". Unterkünfte/Gastro — DE: „Hotels in <Ort>", „Restaurants in <Ort>". EN: „hotels/restaurants in <Ort>". Touren — DE: „touristische Radrouten/Radtouren in <Region>", „Radwanderwege/Wanderwege am <Ort>" → `type=tour`; jede Tour trägt dann `length_m`, `duration_min`, `ascent_m`/`descent_m`, `round_trip` und `activities` (wörtlich, als was der Datensatz sie führt: „Fahrrad", „E-Bike", „Wandern", „Kanu") — DARAN auswählen, nicht am Namen. Bei mehreren die best-passende per `get_tourism_details` zeichnen, die anderen namentlich nennen und fragen, welche noch. **When NOT to use**: reine OSM-Breite/„alle Sehenswürdigkeiten per Tags" → `expand_kg_pois`; Detail zu EINEM Treffer → `get_tourism_details`; Routing/ Abfahrten, auch „von A nach B mit dem Rad" → der Server MobilityMCP; Wetter/Tide → `get_current_weather`/`get_tide`. **Required args**: `region` ODER `query` (eines von beiden — Freitext-Region/ Name, z.B. region="Wangerland", query="Sielhafenmuseum"). Optional: `type` (genau eines von "event" | "poi" | "accommodation" (="hotel") | "gastro" (="restaurant") | "tour" — kuratierte Rad-/Wander-Routen, auch als "radtour"/"radroute"/"radwanderweg"/"wanderweg" akzeptiert; weggelassen = alle Typen; unbekannter Wert = alle. OHNE `type=tour` sind Touren praktisch nie unter den Treffern). Im Aktivitäts-Scope (type="poi"/"attraction"/"unternehmen"/…) sind Unterkünfte AUSGESCHLOSSEN, im Region- wie im Vicinity-Modus (ein Hotel ist keine Unternehmung); für Hotels explizit type="accommodation". `timeframe` (nur für type=event: "today" | "tomorrow" | "weekend" | "this_week" | "month" | ISO "YYYY-MM-DD" | Range "YYYY-MM-DD..YYYY-MM-DD"; ohne timeframe = kommende Events ab heute), `limit` (Default 8, clamped 1..=20), `family_only` (bool, Default false — nur die kuratierten Family-Kategorien, je mit der belegten `suitability`-Achse) und, nur zusammen damit, `indoor_only` (bool — davon nur die Schlechtwetter-tauglichen). `activity` (nur mit `type=tour`): wie eine Tour zurückgelegt wird — "wandern" | "fahrrad" | "kanu" + Synonyme ("wanderweg"/"e-bike"/ "paddeln"). Filtert serverseitig auf die GANZE Familie kuratierter Aktivitätswerte, also auch "Winterwandern"/"Nordic Walking"/ "Mountainbike" — 8 % der zu Fuß zurückgelegten Touren tragen keinen "Wandern"-Wert; weglassen oder unbekannt = alle. **Coord-Vicinity-Modus (Umkreis, POI-Anker)**: für „<Kategorie> am/bei <POI>" den POI ZUERST via `search_place` zu einer Coord auflösen, dann `lat`+`lon` setzen (beide nötig; + optional `radius_m`, Default 2000 m, clamped 100..=20000). `region`/`query` sind dann nur Pool-Hint, nicht mehr Pflicht; die Treffer sind auf den Umkreis gefiltert, nächster zuerst, je mit `distance_m` in Metern. NUR in diesem Modus kommt die OSM-Breite dazu, entdoppelt gegen die Kuration (kuratierter Treffer gewinnt): bei `type=gastro` die `amenity`-Gastronomie, im Aktivitäts-Scope (`type=poi`, „unternehmen") `tourism` ∈ attraction/museum/…, `historic` und `leisure` ∈ park/garden/… — AUSGESCHLOSSEN bleiben Unterkünfte (hotel/hostel) und Alltags-Versorgung (`shop`, Apotheke/Bank/Arzt), beides keine Ausflugsziele. **Typical chain**: `search_tourism(region=<R>, type=event)` → `get_tourism_details(id, query=<R>)` pro Treffer — der Regions-Hinweis ist nötig, sonst antwortet das Detail leer. **Multi-call**: ein Call pro Region/Typ. **Anti-Fab note**: Event-Namen, DATEN, PREISE, POI-Namen, Adressen kommen AUSSCHLIESSLICH aus `pois[]` / `events[]` dieses Aufrufs. Wenn `returned: 0` / `pois: []` / `events: []` → ehrlich sagen, dass für <Region> nichts abrufbar war, NIEMALS Events/Preise/POIs aus Trainings-Wissen ergänzen. Datum eines Events ist tool-belegt (`events[].date` / `pois[].date`, ISO-8601 Europe/Berlin) ODER abwesend — NIE geschätzt. **DZT-Bündelung (zweite kuratierte Quelle, DE-weit)**: kuratierte Treffer AUSSERHALB Niedersachsens kommen aus der DZT-KG (Deutsche Zentrale für Tourismus); Dubletten werden de-dupliziert, in der NDS-Region gewinnt der Niedersachsen-Hub-Eintrag (regionale Tiefe). Bei `type=tour` bleibt sie AUSSEN VOR — sie führt keinen Touren-Inhaltstyp; kuratierte Touren gibt es nur für Niedersachsen, außerhalb ehrlich "keine Tour abrufbar". Returns {place, source:'niedersachsen-hub', type, returned, mode?, center?, radius_m?, pois:[{name,type,description?,address?,uri?,date?,price?,media?, coord?,opening_hours?,id?,distance_m?,source?,license?,hub_detail_url?, length_m?,duration_min?,ascent_m?,descent_m?,round_trip?,activities?}], events:[{name,date,location,source?,license?}]}. Das Top-Level `source` bleibt shape-stabil; welche Quelle einen Treffer geliefert hat, sagt sein eigenes `source` ('osm' | 'niedersachsen-hub' | 'dzt'). `id` ist der Schlüssel für `get_tourism_details`; die Felder folgen schema.org. **`license` (additiv, PRO Treffer)**: die Lizenz, die der Datensatz selbst angibt — `{id?, url?, notice?, holder?}`: `id` = Lizenz-Kennung (z.B. 'CC-BY-SA-4.0', 'CC0-1.0', 'ODbL-1.0'), `url` = Lizenz-Text/Deed, `notice` = der WÖRTLICH wiederzugebende Lizenzverweis (DZT-`copyrightNotice`, § 3 der DZT-Nutzerbedingungen; bei OSM-Treffern die ODbL-Namensnennung), `holder` = der zu nennende Urheber (das `author`- bzw. `copyrightHolder`-Feld des Datensatzes). Gibt er nichts an, ist das Feld **abwesend** — nie eine geratene Vorgabe-Lizenz. Nennst du einen Treffer mit `license.holder`, nenne den Urheber mit. **`hub_detail_url` (additiv, PRO Treffer, nur bei `source: 'niedersachsen-hub'`)**: die Entitätsseite dieses Treffers beim Niedersachsen-Hub, dem Nutzer nennbar — NICHT die Betreiber-Website (`uri`). Jeder Treffer, dessen Art beim Hub eine Eintragsseite hat, trägt sie: POI (`p_…`), Tour (`t_…`), Veranstaltung (`e_…`), Unterkunft (`h_…`), Gastronomie (`g_…`), Gebiet (`r_…`). Ein Medien-Datensatz (`m_…`) hat keine und trägt keine; fehlt sie, keine konstruieren. Mit `family_only` trägt jeder Treffer zusätzlich `family`, `suitability` ({family, age_bands, pushchair, seal (Kinderferienland-Siegel), indoor_bad_weather}) und, wo die Quelle sie führt, `price_child`/`price_family` — alle feature-belegt: „kinderfreundlich" ist belegt, nicht geraten.

Schemas: list_tools.

Official server.json
{
  "name": "eu.projektionisten/tourism",
  "title": "TourismMCP",
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "remotes": [
    {
      "url": "https://ai.projektionisten.eu/tmcp",
      "type": "streamable-http"
    }
  ],
  "version": "0.1.2",
  "websiteUrl": "https://ai.projektionisten.eu/mcp-landingpage/",
  "description": "Travel in Germany (Tourismus): sights, events, opening hours and prices, weather and tides."
}