Grosser Rat MCP
Searches the parliament of Basel-Stadt: every business item (Geschäft), motion (Vorstoss), report of the Regierungsrat (Ratschlag) and transcript (Protokoll) of the Grosser Rat since 1973.
| Endpoint | https://mcp-grosserrat.statistik.bs.ch/mcp |
| Corpus status | /health |
| Code | grossrat-bs-mcp, not public yet |
| Image | ghcr.io/dcc-bs/grossrat-bs-mcp |
| Data source | Open data of the Grosser Rat, converted to text and indexed |
Source not public yet
The repository is still private. This page describes what the running server reports about itself: its MCP instructions, tool list and /health (version 0.1.0, October 2026). Check the live server for the current state.
The corpus
| Documents | about 54,000, about 795 million characters |
| Business items | about 21,200 |
| Period | 22 March 1973 to today |
| Language | German |
| Most common document types | GR Beschluss, Schreiben des RR, Beschluss, texts of Interpellationen, Schriftliche Anfragen, Anzüge and Motionen, Ratschläge |
get_corpus_stats returns the current numbers and list_facets the document types, parties, business types and statuses in their exact spelling.
The databases sit inside the image, about 3.3 GB compressed and 10 GB unpacked. The container needs no volume and no model server, because the embedding model runs on the CPU. The image is built and pushed from the machine that holds the data, not by CI, so a new image version also brings new data.
Two ways in: search and graph
Search answers "where does it say that?". It is hybrid. A BM25 full-text index and passage vectors each find candidates, and the two rankings are fused.
- BM25 matches words. Longer words are searched as prefixes, so "Velo" finds "Veloweg" and "Befragung" finds "Jugendbefragung".
- The vectors match meaning, so "Wechseljahre" finds "Menopause". About 770,000 passages are embedded with granite-embedding-311m-multilingual-r2 (ONNX, HNSW index), covering 99.8 % of the documents.
- Each hit says how it was found (
found_by). Ifsnippet_matchedis false, the snippet does not contain the search words, because the hit matched by meaning.
The graph answers "who?" and "which committees?". It links persons, committees (Gremien), parties, organisations, topics, sessions and business items. Every edge names its source: ogd for the open data, llm with the verbatim quote for what a model extracted, classifier for estimated topics. A member who submitted 1,386 business items is fully listed by explore_entity and barely found by text search.
Tools
| Tool | What it does |
|---|---|
search_documents | Passages from all documents, each with a citation link. Filters: partei, urheber, geschaeftsart, dokumentart, status, since, until. At most 50 hits per call. mode='lexical' lists every word match instead of the best-ranked candidates |
find_geschaefte | Complete list of business items for a research question. Takes 3 to 10 phrasings, reads every hit to the end and returns each item once. group_by (urheber, partei, geschaeftsart, status, jahr) counts instead of listing |
get_geschaeft | Full dossier of a business item by signature, for example 17.5209: master data, all documents, assignments, topic and the path through the council |
get_document | Text of a document by dok_laufnr, one page or windows of 20,000 characters |
cite_document | The PDF link that opens at a given page and phrase |
search_entities | Finds graph entries by name and returns their node_id. Several names per call |
explore_entity | Everything attached to one or more entries, grouped by relation (for example AUTHORED_BY, MEMBER_OF) and paged per relation |
connect_entities | Shortest chain between two entries, up to max_tiefe steps |
list_facets | Allowed filter values with counts |
get_corpus_stats | Size and period of the corpus |
search, fetch | The search and fetch pair that ChatGPT connectors expect. Same data as search_documents and get_document |
The server also offers the prompt recherche ("Vollständige Recherche"). Given a question, it walks the model through a complete survey: several phrasings, every list read to the end, results checked.
Using it well
The server's instructions tell the model these rules. They matter for anyone writing prompts or presets against it.
- Lists need
find_geschaefte. "Which open motions concern X" is not answered by the first page ofsearch_documents. Lower-ranked items and items worded differently get lost. - A list is complete only when
next_offsetisnull. - Counting goes through
group_by. "Who submitted most" or "how did it develop over the years" needs no listing. - Transcripts need a filter. A full transcript (Vollprotokoll) has about 600,000 characters, so BM25 ranks it below one-page motions. To find what was said in the council, search with
dokumentart='Vollprotokoll'. - Copy filter values from
list_facets. Guessed spellings match nothing. - Parties of former members. The open data often lacks a party for them.
explore_entitywithpredicate='MEMBER_OF'shows their parliamentary groups with dates. A group can unite several parties, for example "CVP/EVP", and the answer should say so. - Cite the signature and the URL. Every hit carries
dok_laufnr(internal),signatur_ges(the council's business number, for example "17.5209") and acitation_urlthat opens the official PDF at the right page. Cite the last two.
Endpoints
| Path | Answer |
|---|---|
/mcp | MCP streamable HTTP |
/healthz | {"status":"ok"} for the container healthcheck |
/health | Version, number of documents, search mode and vector coverage |
Opening the index and loading the embedding model takes time after a start. The compose healthcheck therefore allows 60 seconds before it counts failures.
Open issues
The deep research evaluation of October 2026 produced a list of requested changes, kept in docs/grossrat-mcp-changes.md of bebbi-deep-research. The main ones:
- No tool returns legislature periods, so "in der laufenden Legislatur" needs the dates from the user.
find_geschaeftereturns 100 items by default, over 8,000 characters. Clients should send alimit.search_documentshits had emptyurheberandpartei.
