Skip to main content
Glama
Austrian Parliament Open Data MCP Server

Austrian Parliament Open Data MCP Server

by austrolink
README.md
# Austrian Parliament Open Data MCP Server

[![CI](https://github.com/austrolink/austrian-parliament-open-data-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/austrolink/austrian-parliament-open-data-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

[English](README.md) | [Deutsch](README.de.md)

This [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server gives AI assistants read-only access to
data of the Austrian Parliament: plenary speeches, calls to order (Ordnungsrufe), stenographic protocols, agendas,
votes, legislative processes, people, and other
[Open Data](https://www.parlament.gv.at/recherchieren/open-data/index.html). No API key is necessary.

> [!NOTE]
> This is an unofficial project. It is not affiliated with or endorsed by the Austrian Parliament
> (Parlamentsdirektion). Make sure that you check important facts in the official sources.

## Contents

- [Installation](#installation)
  - [Requirements](#requirements)
  - [Client configuration](#client-configuration)
- [Example questions](#example-questions)
- [Tools](#tools)
- [Prompts and resources](#prompts-and-resources)
- [How the searches work](#how-the-searches-work)
- [Caching and request limits](#caching-and-request-limits)
- [Data sources, license, and terms of use](#data-sources-license-and-terms-of-use)
- [Development](#development)
- [Security](#security)
- [License](#license)

## Installation

### Requirements

- Node.js 22 or later
- npm
- Git, if you install from GitHub

### Install from source

```sh
git clone https://github.com/austrolink/austrian-parliament-open-data-mcp.git
cd austrian-parliament-open-data-mcp
npm ci
```

`npm ci` also builds the server into `dist/`. The entry point is `dist/index.js`.

### Start directly from GitHub

You can start the server directly from GitHub with `npx`. The first start downloads and builds the server, so it
takes longer:

```sh
npx -y github:austrolink/austrian-parliament-open-data-mcp
```

### Client configuration

The server uses the MCP stdio transport. In the examples, replace
`/absolute/path/to/austrian-parliament-open-data-mcp` with the path of your clone. To use `npx` instead, set `command`
to `npx` and `args` to
`["-y", "github:austrolink/austrian-parliament-open-data-mcp"]`.

#### Visual Studio Code

Add the server to `.vscode/mcp.json` in a workspace or to your user `mcp.json`:

```json
{
  "servers": {
    "austrian-parliament-open-data-mcp": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/austrian-parliament-open-data-mcp/dist/index.js"]
    }
  }
}
```

#### GitHub Copilot CLI

Add the server to `~/.copilot/mcp-config.json`:

```json
{
  "mcpServers": {
    "austrian-parliament-open-data-mcp": {
      "type": "local",
      "command": "node",
      "args": ["/absolute/path/to/austrian-parliament-open-data-mcp/dist/index.js"],
      "tools": ["*"]
    }
  }
}
```

#### Claude Desktop

Add the server to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "austrian-parliament-open-data-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/austrian-parliament-open-data-mcp/dist/index.js"]
    }
  }
}
```

Restart the client after you change the configuration.

## Example questions

Ask a normal question in your MCP client. For example:

> Find parliamentary initiatives about climate protection in the XXVIII legislative period.
>
> Find events about European policy between 2025-01-01 and 2025-06-30.
>
> Find Katayun Pracher-Hilander's speeches in the last six months.
>
> Show the top five speakers in each National Council club in the last year.
>
> Find Christoph Matznetter's plenary and committee activity between 2025-10-01 and 2026-03-31.
>
> Find references to Christoph Steiner, including possible nicknames, between 2025-07-01 and 2026-06-30.
>
> Count uses of "Remigration" by National Council members in the last year.
>
> How many calls to order did Lukas Hammer receive as a member of the National Council?
>
> Which calls to order in the last year were connected with "Remigration"? Group them by club and member, and name the
> presiding officer.
>
> Find Yannick Shetty's transcript mentions of Rosenkranz between 2026-01-01 and 2026-06-30 from 21:40 to 21:55,
> including five minutes of context.
>
> Get the National Council agenda for 2026-10-14.
>
> Show all statements on the climate law whose review period ended yesterday.
>
> How many seats does each club have in the Federal Council, by state? Which state holds the presidency?
>
> Who spoke most often in the Federal Council in the last three months?
>
> Which written inquiries to the Ministry of Justice are overdue? How fast does each ministry answer?
>
> How often did the FPÖ and the Greens vote alike in the last year? Which motions were postponed in committees?
>
> What happens in Parliament next week? What did the agriculture committee decide on 2026-10-01?
>
> Who is Walter Rosenkranz, and which committees does he chair?
>
> How long did the Strafvollzugsnovelle 2026 take from the draft to the Federal Law Gazette?
>
> Which citizens' initiatives have the most supports? Which popular initiative had the most entries?
>
> Who applauded whom in the current hour on 2026-10-14, and who made the most interjections?
>
> When was the word "Klimakrise" first used in a plenary debate? Which current hours did the FPÖ request this year?
>
> Who was questioned in the COFAG investigative committee, and where does Benko appear in the protocols?
>
> Which analyses of laws has the Budget Office published this year? Which Court of Audit reports wait for the committee?
>
> Who was President of the National Council in 1995? Who were the finance ministers since 2000? Which sitting of the
> current period was the longest? Who are the education spokespersons of the clubs?
>
> Which members of Parliament have a paid seat on a supervisory board? Who reports the highest income category?
>
> Get the vote results for /gegenstand/XXVIII/I/638.
>
> Show the legislative process for /gegenstand/XXVIII/I/625.

The assistant selects a category such as applications, events, or protocols and passes the corresponding documented
filters to the server. The numeric API identifiers remain internal.

## Tools

- `search_parliament_website`: Search the parlament.gv.at website like its search box: records, consultations and
  statements, plenary transcripts, press releases, news, and expert information. Filter by category, document type,
  topic, date range, chamber, legislative period, exact phrase, or titles only. Each result gives the title, date,
  category, link, and a short text excerpt. Use this tool to find a record or page by words, for example a draft law
  and its number.
- `get_parliament_overview`: Get an overview of a period of up to 31 days, by default today and the next six days:
  plenary sessions, committee meetings with agenda links, enquêtes and events, review periods that end, new government
  bills and ministerial drafts, and press releases (Parlamentskorrespondenz). The response contains a Markdown overview
  by day in `displayMarkdown`.
- `get_parliament_committee`: Get a committee by name or code: officers, planned meetings, the decisions of recent
  meetings or of the meeting on a date with the parties in favor and against, press releases about the committee, and
  pending items.
- `search_parliament_expert_information`: List the publications of the Budget Office (Budgetdienst), the Research
  Service, the legal monitoring (Rechtsmonitoring) of drafts and bills, and the library and archive; or the reports of
  the Court of Audit (Rechnungshof) with their status in Parliament. Filter by words, category, topic, and date.
- `search_parliament_consultations`: List review procedures (Begutachtungsverfahren) on ministerial drafts with number,
  title, ministry, topics, receipt date, end of the review period, number of statements, and link. Filter by title
  words, open or closed status, topic, ministry code, and the end date of the review period.
- `get_parliament_consultation_statements`: List all statements (Stellungnahmen) on a ministerial draft or another
  record with statements. The response gives the counts of public, non-public, and late statements, and a Markdown
  table with number, date, sender, and link in `displayMarkdown`. When statements have online supports, a second table
  shows the five statements with the most supports.
- `search_parliament_participation`: List citizens' initiatives (Bürgerinitiativen) and petitions with their online
  supports, statements, participations in the last 30 days, sponsor, and treatment status. Or list popular
  initiatives (Volksbegehren) with the valid entries and the turnout. Sort by supports, date, or trend.
- `search_parliament_open_data`: Search a named Parliament Open Data category. The server maps categories to official
  API filters internally. Users do not need to find or provide filter IDs.
- `search_parliament_protocols`: Search stenographic protocols of the National Council, the Federal Council, or the
  Federal Assembly (Bundesversammlung) by date range and optional legislative period.
- `search_parliament_speeches`: Find a person's plenary speeches and other named contributions over a date range of
  up to ten years. Results separate speeches from other contributions and include transcript or video links when
  available. The response compares the results with the person's official speech list. Speeches that the list
  contains but no session record contains are returned in `speechesOnlyInOfficialList`. Each speech shows whether its
  official reference title contains the AI-language-model assistance marker. The response compares marked speeches
  with the total speech count and reports any unmarked or omitted speeches. Set `include_related_activity` to `true`
  to also get committee memberships, committee meeting dates and agenda links, and associated formal records.
- `rank_parliament_speakers`: Rank National Council or Federal Council speakers by the number of recorded speech
  entries, grouped by parliamentary club. The tool reads session detail records only, includes members explicitly
  marked as having no club, reports unclassified entries, and returns the requested number of top speakers for each
  category, including ties at the cutoff. It uses the affiliation recorded for each speech, so changes over time are
  reflected. With `metric: speaking_time`, the tool ranks by the approximate speaking time from the official video
  timeline.
- `search_parliament_person_mentions`: Find a person's surname in plenary speech transcripts from both chambers.
  Results include cited text, the speaker, date, session, agenda item, and transcript or video links. The tool returns
  25 passages by default and gives the total number of matching passages. It also returns possible compound labels for
  review; it does not determine whether a phrase is an established nickname.
- `search_parliament_contributions`: Search plenary sessions for speeches and other recorded contributions by text,
  speaker, and optional time of day. The tool can also search matching transcript passages and return nearby events
  with video links when available.
- `search_parliament_calls_to_order`: List calls to order (Ordnungsrufe) for a date range of up to ten years. A
  Markdown table in `displayTable` gives one row for each member and session with the number of calls to order, the
  date, the presiding officer, and links to the session's stenographic protocol index and to the exact transcript
  passages. The response counts calls to order by club, recipient, and presiding officer. Set `wording_query` to find
  calls to order that are connected with an expression.
- `analyze_parliament_debate`: Analyze the debates of a sitting: speakers, approximate speaking time, applause by club
  (who applauds whom), interjections (Zwischenrufe) by person and club, and laughter. Filter by words in the debate
  title.
- `search_parliament_special_debates`: List the special debates of a period: current hours (Aktuelle Stunde) and EU
  hours with topic and requesting club, question times, urgent inquiries and motions, short debates, and statements.
- `get_parliament_term_history`: Find the earliest Parliament documents that contain a word or phrase, and count the
  documents per decade.
- `count_parliament_speech_word_occurrences`: Count a whole word in the speech transcripts of National Council or
  Federal Council members and report totals by member, with session, date, time, transcript link, and video link when
  available for each matching speech.
- `get_parliament_session_agenda`: Get the agenda of a National Council, Federal Council, or Federal Assembly session,
  including special activities, agenda amendments, related records, and official agenda links.
- `get_parliament_vote_results`: Get published vote outcomes and party positions for an official record.
- `get_parliament_legislative_process`: Get the phases and dated steps for a bill or other parliamentary record.
- `get_parliament_law_status`: Follow a law from the ministerial draft and its review period to the government bill or
  motion, the committee reports, the decisions of the National Council and the Federal Council, and the publication in
  the Federal Law Gazette (BGBl.). The response gives the time between the steps, the RIS link, and the EU law that the
  law implements. Accepts a record path, a citation such as `625 d.B.`, `121/ME`, or `1007/A`, or a gazette number
  such as `BGBl. I Nr. 91/2026`.
- `search_parliament_people`: Search public parliamentary people by name or function.
- `get_parliament_person_profile`: Get the profile of a current or former member of Parliament or of the government
  since 1918: biography, mandates and functions, current committees, plenary speeches, parliamentary activities, and
  the declared positions, activities, and income categories. If a name matches more than one person, the result lists
  the candidates.
- `search_parliament_inquiries`: Search written inquiries (schriftliche Anfragen) of the National Council or Federal
  Council and their answers. Filter by asking member, addressed minister or ministry, party, status (open, overdue,
  answered, urgent), and title words. The response gives counts, the answer rate and average answer days for each
  addressee, and links to the inquiries and answers.
- `search_parliament_votes`: Get the decisions of the National Council or Federal Council with the outcome and the
  parties in favor and against, in plenary and in committees. The response counts the positions of each party and
  shows how often each pair of parties voted alike. Federal Council decisions include objections (Einspruch). Other
  kinds list motions that a committee postponed (vertagt) and roll-call votes.
- `get_parliament_composition`: Get the current composition of the National Council, the Federal Council, or the
  Federal Assembly: seats by club and state, women and men, and the presidium. For the Federal Council, the tool also
  gives the seats of each state by club and the state that holds the presidency. It can list the members of a club or
  state, and the area spokespersons (Bereichssprecher) of the clubs, for example for Bildung.
- `get_parliament_history`: Get history and records: the presidents of the National Council and the Federal Council
  since 1920, the federal governments since 1918 with chancellors and parties, the holders of a ministry, the
  legislative periods with their sittings, and sitting records such as the longest sittings and the latest ends.
- `search_parliament_transparency`: Search the declarations of all current members of both chambers under the
  Unvereinbarkeits- und Transparenz-Gesetz: leading positions in companies, other paid activities, leading honorary
  positions, and income categories. Filter by words, category, club, person, or minimum income category. The response
  counts the members of each club with paid positions and income.
- `get_parliament_investigative_committee`: Get an investigative committee (Untersuchungsausschuss) by name words:
  subject, chair, sittings, the persons questioned (Auskunftspersonen) with the published protocols, reports, and press
  releases. Set `quote` to find a phrase in the hearing protocols of the committee. Without a name, the tool lists all
  investigative committees and shows the active one.
- `get_parliament_record`: Fetch a detail page as JSON by its official relative URL path.

The server also sends short instructions to the client: start a search by words with `search_parliament_website`,
start a question about one person with `get_parliament_person_profile`, and show the Markdown tables in
`displayMarkdown` and `displayTable` directly in the chat instead of in a file.

## Prompts and resources

The server provides prompts (slash commands) for frequent tasks. In Visual Studio Code, type
`/austrian-parliament-open-data-mcp.` in the chat and select a prompt. In Claude Code, type
`/mcp__austrian-parliament-open-data-mcp__` and the prompt name. The prompts write the task in German, so the answer is
in German. The client can complete person names.

| Prompt               | Arguments                 | Task                                                                  |
| -------------------- | ------------------------- | --------------------------------------------------------------------- |
| `wochenbriefing`     | `start`                   | Sittings, committee meetings, review periods, and new bills of a week |
| `gesetz-verfolgen`   | `gesetz`                  | A law from the ministerial draft to the Federal Law Gazette           |
| `personendossier`    | `person`                  | Functions, speeches, inquiries, calls to order, and declarations      |
| `abstimmungsanalyse` | `start`, `ende`, `kammer` | Party agreement, disputed decisions, and postponed motions            |
| `ordnungsrufe`       | `start`, `ende`, `person` | Calls to order by club, person, and presiding officer                 |
| `stellungnahmen`     | `entwurf`                 | All statements on a ministerial draft as a table                      |

Resources are reference data that you can add to a chat. In Visual Studio Code, select **Add Context** > **MCP
Resources**, or run **MCP: Browse Resources**. The client can complete the names of glossaries, glossary terms, and
chambers.

| Resource                              | Content                                                                   |
| ------------------------------------- | ------------------------------------------------------------------------- |
| `parliament://document-types`         | Codes for the `document_types` filter of `search_parliament_website`      |
| `parliament://glossary/{name}`        | The terms of one of the seven glossaries of the Parliament, with links    |
| `parliament://glossary/{name}/{term}` | The explanation of one term from a glossary                               |
| `parliament://committees/{chamber}`   | The committees of `national-council` or `federal-council`, with codes     |
| `parliament://legislative-periods`    | The legislative periods of the National Council with their first sittings |

When a name matches more than one person, the server asks which person you mean, if the client supports forms (MCP
elicitation). Otherwise the tool returns the candidates.

## How the searches work

### Dates and ranges

All tools treat `start_date` and `end_date` as inclusive. The Parliament filter API treats the second date of a range
as exclusive and a single date as "from this date onward". The server converts each inclusive range before it sends
the request. Raw `filters` in `search_parliament_open_data` use the API rules.

Protocol searches, person-mention searches, and word counts accept date ranges of up to 366 days. Protocol searches
for the Federal Assembly accept any period, because it met only 19 times since 1945. Speech,
contribution, and call-to-order searches accept date ranges of up to ten years. Speech and contribution searches load
up to 500 sessions. Other tools that use session records remain limited to 100 sessions. Historical searches use the
protocol index. If a search includes today or yesterday, the server also checks the current legislative period's
plenary-session index so it can find current sessions before the protocol index updates.

### Website search

`search_parliament_website` uses the search of the parlament.gv.at website. One request finds records, consultations,
statements, transcript passages, press releases, and other pages by words. The search also finds other forms of a
word. For example, "Remigration" also finds "Remigrations". Use `match: "phrase"` for an exact phrase and
`title_only: true` to search only titles. `document_type` takes one of 255 codes of the website, for example `AB`
(answer to a written inquiry), `AUB` (committee report), `BRH` (Rechnungshof report), `KOMMUSA` (communiqué of an
investigative committee, for example a hearing protocol), or `MEG` (ministerial draft). It also accepts words of the
German type name if they identify one type. `topic` takes one of the 20 topics of the website, for example
"Klima, Umwelt und Energie". This search interface is not part of the documented Open Data offer. If the website
changes the interface, the tool can fail until the server is updated.

### People

For person-name lookups, use `search_parliament_people` or the `people` dataset, not `parliamentarians`. In
`search_parliament_people`, full names use the Parliament's server-side name filter, and partial names and function
searches use the complete people directory. The search returns at most 50 records. The other tools resolve names with
the cached people directory. When a name matches more than one person, a match of whole words wins: "Bures" finds
Doris Bures, not Karl Buresch. Otherwise the server asks which person you mean, if the client supports forms, or the
tools name the candidates. The form closes when the tool call ends. `search_parliament_contributions` does not ask,
because it filters by the words of the name. The `parliamentarians` dataset has no name filter and needs a documented
filter to avoid an oversized response.

`get_parliament_person_profile` reads the person page and three lists of the page: speeches (filter list 251),
activities (filter list 25), and committee memberships (filter list 250). Activities are the records that the person
page lists, for example motions and inquiries that the person submitted and answers that the person gave as a
minister. The tool shows the current committees with one row for each committee. If one of the three lists fails,
the profile contains the other parts and lists the failure in `warnings`.

Members of the National Council and the Federal Council declare leading positions in companies, other activities with
income, and leading honorary positions (§ 6 Unvereinbarkeits- und Transparenz-Gesetz). By 30 June of the next year,
they also report the income category of a year: the average monthly gross income from the leading positions and paid
activities. Since 2021, the categories are 1 (1 to 1,150 euros), 2 (up to 4,000 euros), 3 (up to 8,000 euros), 4 (up
to 12,000 euros), and 5 (more than 12,000 euros). A category is a range, not an amount. `search_parliament_transparency`
reads the full lists of both chambers and uses the latest year that most members have reported. A position with an
asterisk in the official list brings no income; the tool marks it "ohne Einkommen".

### Speeches and contributions

Speech searches, and contribution searches with a speaker that matches one person, select the sessions to load:

- If the range has 80 sessions or fewer and the client-side request burst has room for the sessions that are not in
  the cache, the server loads all of them.
- Otherwise, the server loads only the sessions in which the person spoke or has an official record. Examples of
  official records are calls to order, procedural statements, and oral questions. The person's official speech list
  and formal-record list identify these sessions.

The response field `sessionSelection` shows the strategy and the number of sessions in the range and loaded. If the
person's lists are not available, the server loads all sessions in the range.

Person activity searches include both plenary chambers by default. With `include_related_activity`, they also include
committee membership, committee meeting dates, published agendas, and formal records associated with the person by
the official person filter. Committee membership and meeting records do not prove attendance or an individual spoken
contribution. Formal record associations do not prove authorship or speech.

Contribution searches can use `start_time` and `end_time` to narrow results to a time of day. Set
`search_transcripts` to search the linked transcript passages for the query; this requires a speaker or time range
and checks at most 400 passages. Set `context_minutes` to include nearby recorded contributions. Procedural
contributions include the speaker and official video link when the session data provides them.

Transcript search is supplementary: unavailable transcript pages set `transcriptSearchStatus: "partial"` and are
listed in `transcriptSearchFailures`. A search above the passage limit reports `transcriptSearchStatus:
"skipped_limit"`. Neither condition removes matching structured session records from the results.

### Overview and committees

`get_parliament_overview` reads the date list of the website (filter list 600) and leaves out guided tours and visits
to sittings unless you ask for them. It adds the review periods that end in the period, new government bills and
ministerial drafts, the press releases of the period, and the weekly preview "Die Parlamentswoche". If one of these
sources fails, the tool returns the other parts and lists the failure in `warnings`.

Committee meetings are not public, so the press releases (Parlamentskorrespondenz) often are the only public report of
a meeting. `get_parliament_committee` reads the committee page: the committee decisions that settled an item, the
meeting dates, and the reports to the plenary. The committee decision on a report is a step in the record of the
report, so the tool reads these records for the meetings that it shows. Pending items include motions that the
committee postponed.

### History and records

`get_parliament_history` reads the list of the presidents of the National Council since 1920 (`WFW_003`), the list of
the presidents of the Federal Council (filter list 10401), and the government members since 1918 (filter list 1717).
A government ends when the next government starts. When all its members left office before, as in March 1938, the
government ends on the last day of its members. The party data of the government list is mostly complete from the
mid-1990s and partial or missing before. The list has no head of the State Chancellery of 1918–1920 and 1945. A
government name such as "Kurz I" selects one government; a name such as "Kurz" lists the governments and the roles of
the person. A query such as "Finanzminister" lists the ministers of the ministry without the state secretaries. The
legislative periods come from the sittings in the protocol index (filter list 211).

For sitting records, the tool reads the sitting records of a legislative period (by default the current period) or of
a date range of up to 366 days (by default the year before the end date). The time line of a record has the opening,
the closing, the interruptions, and the continuations of a sitting from the XXVII legislative period (October 2019).
The duration is the time in session without interruptions; some sittings, for example budget sittings, continue on
the next days. A first query of a whole period reads up to about 300 records and can take half a minute.

The area spokespersons come from the list that the clubs report (filter list 403).

### Investigative committees

`get_parliament_investigative_committee` finds the investigative committees in the committee list (filter list 2008)
and reads the committee page. The persons questioned come from the list of communiqués on the page: each published
hearing protocol is a communiqué that names the person. The tool shows the publication date of the protocol; the
Parliament data has no separate field for the date of the hearing. Hearings without a published protocol do not
appear. The quote search uses the website search for the document type `KOMMUSA` from the start of the committee
until 90 days after its end. It keeps only the documents of the committee: the record number in the document path
identifies the committee. When the hearing protocol of a record has a hit, the tool does not show the communiqué of
the same record again. For older committees, the hearing protocol is part of the communiqué.

### Expert information

`search_parliament_expert_information` reads the list of expert publications (filter list 616) with the server-side
filters for the type and the date, and the list of legal monitorings (filter list 614). The reports of the Court of
Audit come from the record list (filter list 101, document type `BRH`); for the returned reports, the tool reads the
records for the status of the committee treatment. To search the text of these publications, use
`search_parliament_website` with the category `expert_information`.

### Votes and decisions

`search_parliament_votes` reads the decisions (resolutions) in the record of each sitting. They contain the plenary
decisions of the sitting and the committee decisions on its agenda items, each with the parties in favor and against.
The tool keeps only the third reading of a law when a second reading has the same result. A decision without votes
against counts as unanimous. An accepted report that rejects a motion (ablehnender Ausschussbericht) means that the
motion failed. The party agreement shows, for each pair of parties, the share of decisions in which both voted the same
way. Postponed motions come from the website search for record pages with the word "vertagt" and from the motion
list (filter list 101), which gives the proposing party. Roll-call votes come from the protocol passages that mention
them.

### Written inquiries

`search_parliament_inquiries` reads the inquiry list of the Parliament (filter list 101) in one request. Each entry
shows whether the inquiry was answered and by which answer, or until when the answer is due. Ministers must answer
within two months, so an open inquiry after this date counts as overdue. Urgent inquiries (Dringliche Anfragen) are
answered orally in the sitting and never count as overdue. The server finds the addressee with the list of all members
of federal governments since 1918 (filter list 1717) and the date of the inquiry. The addressee filter selects the
roles that contain the words, for example "Inneres" for the Minister of the Interior. It finds the inquiries to the
holder of the role on the date of each inquiry, not the other inquiries to the same person. Inquiries of type JPR go to
the President of the National Council or to a committee chair. The answer days come from the dates of the answers.

### Consultations and statements

`search_parliament_consultations` reads the Parliament's list of ministerial drafts in review (filter list 143). The
end of the review period is a step in the record of each draft, so the tool reads the records of the matching drafts.
`get_parliament_consultation_statements` reads all statements on a record in one request (filter list 142). Each
public statement has a link; non-public statements have none. Statements with a date after the end of the review
period count as late. The tool returns the complete list as a Markdown table in `displayMarkdown`, so that the
assistant can show it directly in the chat.

### Citizens' initiatives, petitions, and popular initiatives

The same filter list 143 contains the citizens' initiatives and petitions with their online supports, the number of
statements, and the participations in the last 30 days ("Trend"). Supports are available from the XXIV legislative
period. `search_parliament_participation` reads the records of the returned items for the sponsor and the status. If
a record fails, the item stays in the result without these details, and `failedRecords` gives the number of failures.

Popular initiatives come from the record list (filter list 101). The Parliament data has no field for the number of
entries. The official text of each popular initiative contains the result of the Federal Electoral Board, so the tool
reads the row for all of Austria: eligible voters, valid entries, and turnout. The tool accepts a row only if the
turnout agrees with the two numbers. Popular initiatives before about 2003 have only a PDF text, so their entries are
missing. For a ranking by entries, the tool reads the texts of all matching popular initiatives.

### Calls to order

Use `search_parliament_calls_to_order` for questions about calls to order. It reads the Parliament's call-to-order
records, so a search over several years needs few requests. Set `recipient` or `presiding_officer` to filter the
results. The `displayTable` field contains every returned call to order as a Markdown table, with one row for each
member and session. Rows are sorted by date and session number in ascending order. Each row shows the number of calls
to order in that session, the date, the presiding officer, the linked protocol, and the exact transcript passages.
Duplicate links are listed once, and an unreachable link is marked "(nicht erreichbar)". The assistant can show the
table directly in the chat. The fields `byClub`, `byRecipient`, and `byPresidingOfficer` count all matching calls to
order. If `resultsTruncated` is true, the table only covers the returned calls. The result does not include links to
individual call-to-order records.

Set `wording_query` to read the presiding officer's call-to-order passage and the linked preceding passage. The
response then contains `items` with the excerpts, and the field `wording.matchedIn` shows where the expression occurs.
Read each excerpt before you attribute a call to order to an expression. A presiding officer can mention the
expression without it being the reason, and one statement can cover several calls to order. Calls to order with
unreadable passages are listed in `callsToOrderWithUnreadableWording`. They are not evidence that the expression was
absent.

### Debates, speaking time, and reactions

The media library page of a sitting (`/aktuelles/mediathek/...`) has a timeline with the start time of every turn,
including the turns of the presiding officer and the votes. The tools use the time from the start of a turn to the
next turn as the speaking time. This is an approximation that includes interruptions; the Parliament publishes no
official duration for each speech. The timeline is complete for the sittings of the current legislative period and
partial or missing for older sittings. For a ranking by speaking time, a sitting counts only if its timeline has at
least 80 % of the speech entries of the sitting record. The response lists the sittings that the ranking leaves out.

`analyze_parliament_debate` reads the transcript of each speech and counts the notes in brackets: applause ("Beifall
bei der FPÖ"), interjections ("Zwischenruf des Abg. …", "Abg. … [NEOS]: …"), shouts of a club ("Rufe bei der SPÖ"),
and laughter ("Heiterkeit"). Current transcripts give the club of a member in square brackets. A note can name several
clubs; each named club counts once. Turns of the presiding officer and procedural turns (for example points of order,
calls to order, announcements, and the reports of a rapporteur) do not count as speeches. The title filter first
looks for a debate with the exact title, then for the words as whole words, and then for parts of words. Without a
title filter, the tool reads at most 100 transcripts and gives the number of transcripts that it did not read.

`search_parliament_special_debates` reads the special activities of each sitting record. The topic and the requesting
club come from the description of the Parliament; when the description does not name a club, the field is empty.

`get_parliament_term_history` uses the website search. The website search has no sort by date, so the tool halves the
period from 1918 until the earliest documents are in a period of at most 45 days. The search finds word forms and the
text of scanned documents, which can contain recognition errors. An early hit does not prove who first used a term.

### Transcripts and word counts

Person mention searches look for the member's surname, including compound forms. Treat returned text as evidence to
assess, not proof that a phrase is a nickname or that a speaker intended it as one. The available committee data does
not provide reliable individual speech transcripts, so nickname and name-mention searches cover plenary speech
transcripts only.

Word counts use case-insensitive whole-word matches in the official HTML transcript passages. For full-protocol
documents, the server isolates the speech section linked by the transcript reference before it counts words. It does
not attribute transcript-marked interjections by other members to the main speaker.

Word counts and person-mention searches first use the website search to find the passages that contain the word or
the surname. The website search also finds other forms of the word and parts of hyphenated compounds, so it finds a
superset of the passages with exact matches. The server then reads only these passages and matches the text exactly.
It reads all passages of the sittings of the last three days, because the search index can lag behind new
transcripts. If the website search fails, finds more than 3,000 passages, or cannot search the term reliably, the
server reads every passage in the range. The field `transcriptSelection` shows the method. A comparison with the full
scan gave identical results for a one-year word count (54 instead of 2,907 requests) and for two person-mention
searches.

### Links and evidence

Speech, contribution, and call-to-order responses check the returned transcript links for HTTP reachability.
Call-to-order responses also check session protocol indexes. The field `protocolLinkSummary` gives the number of
checked, reachable, and unreachable links. The field `unreachableProtocolLinks` lists each failed link with its HTTP
status or request error. An item with an unreachable transcript link also has a `protocolLinkCheck` field. In the
call-to-order table, an unreachable link is marked "(nicht erreichbar)". Missing protocol links are listed in
`linkIssues`.

The server does not play or analyze audio or video. A video URL identifies an official recording but does not show
that the recording was reviewed. Transcript text can support claims about recorded wording; it cannot by itself verify
pronunciation, vocal delivery, gestures, or audience reactions. Speech-search responses include `evidenceBasis`,
`transcriptTextReviewed`, and `audioVideoReviewed` fields to show what source content the tool inspected. The speech
search also reports `speechEvidenceAudit` and marks each speech whose official reference title says
`Rede von KI-basiertem Sprachmodell unterstützt`. This label records AI-model assistance; it does not prove that the
voice in the recording was synthetic. A missing label does not prove that no AI assistance was used.

### Laws and the Federal Law Gazette

`get_parliament_law_status` reads the record of the bill or motion. For a ministerial draft, it follows the link to
the government bill; for a committee report, it follows the link to the main record. The National Council decision is
the vote in third reading. The tool counts the statements on the draft with the statement list.

The Parliament records do not contain the Federal Law Gazette number. For this number, the tool uses the Open
Government Data interface of the RIS (Rechtsinformationssystem des Bundes). Each gazette entry in the RIS names the
legislative period, the government bill or motion, and the committee report. After the National Council decision, the
tool searches the gazettes of the next 240 days and keeps the entry that names the record. With a gazette number, the
tool reads the entry and then the record that it names. The RIS has the authentic gazette from 2004. Some gazettes
have no parliamentary record, for example a decision of the Constitutional Court. Budget laws have no Federal Council
decision.

### Votes, processes, and other datasets

For vote results or legislative history, first find a record with `search_parliament_open_data`, then pass its
official relative path to the matching detail tool. The vote tool reports only results and party positions that the
Parliament publishes. It does not infer individual vote counts.

The server limits response sizes and search result counts. A text `query` in `search_parliament_open_data` only
filters rows returned by the API; it does not reduce the API response. Use documented `filters` for large datasets. If
an API response exceeds its size limit, narrow the search and try again.

## Caching and request limits

The Parliament website limits filter-list and record requests from one client. After a burst of approximately 120
requests, it answers approximately 15 of these requests per second. More concurrent requests do not make a search
faster. The website does not delay transcript documents. Thus the server keeps the number of limited requests low and
stays below the limit:

- It caches successful responses in memory and on disk. Searches that repeat or overlap earlier searches use the
  cache, also after a restart.
- Transcript searches group speech passages by source document and read each document once.
- The server reads expired transcript documents again with their `ETag`. If the document did not change, the server
  receives no new content.
- Transcript link checks use the cache. A transcript document that the server read recently counts as reachable.
- A client-side rate limit allows a burst of 100 filter-list and record requests and then 14 of these requests per
  second. Transcript documents and link checks are not part of this limit.
- Each request identifies the server with the user agent `austrian-parliament-open-data-mcp/<version>` and the project URL.

The cache lifetime depends on the date of the data:

| Data                                   | Cache lifetime                     |
| -------------------------------------- | ---------------------------------- |
| Today or yesterday, on a plenary day   | 1 minute                           |
| Today or yesterday, other days         | 5 minutes                          |
| Up to 14 days old                      | 30 minutes                         |
| Up to 90 days old                      | 6 hours                            |
| Up to one year old                     | 1 day                              |
| Older than one year                    | 7 days                             |
| Lists without a date, on a plenary day | 1 minute                           |
| Lists without a date, other days       | 10 minutes                         |
| People, person pages, and committees   | 12 hours                           |
| Federal Law Gazette searches (RIS)     | 1 day; 30 minutes without a result |
| Failed transcript link checks          | 1 minute, memory only              |

A plenary day is a day with a National Council or Federal Council sitting that the server found in the official
session lists. The day after a sitting also counts as a plenary day. The server does not cache failed requests.

The disk cache is in `~/.cache/austrian-parliament-open-data-mcp`, or in
`$XDG_CACHE_HOME/austrian-parliament-open-data-mcp` when `XDG_CACHE_HOME` is set. The server removes files older than
eight days. When the cache is larger than its limit, it removes the oldest files. Use these environment variables to
change the behavior:

| Variable                             | Effect                                                                                             |
| ------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `PARLIAMENT_MCP_CACHE_DIR`           | Sets the cache directory.                                                                          |
| `PARLIAMENT_MCP_DISK_CACHE`          | `off` disables the disk cache. The memory cache stays.                                             |
| `PARLIAMENT_MCP_CACHE_MAX_MB`        | Sets the size limit of the disk cache. The default is 512 MB.                                      |
| `PARLIAMENT_MCP_REQUESTS_PER_SECOND` | Sets the client-side rate for filter-list and record requests. The default is 14. `0` disables it. |

Long searches send MCP progress notifications when the client supplies a progress token. If the client cancels a tool
call, the server stops its open requests. Tool responses use compact JSON to keep them small.

## Data sources, license, and terms of use

The data comes from the Austrian Parliament (Parlamentsdirektion). The Parliament publishes its
[Open Data](https://www.parlament.gv.at/recherchieren/open-data/index.html) under the
[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/) license.
When you reuse data that you get through this server, name the source, for example "Source: Parlament Österreich,
CC BY 4.0", and follow the
[license and terms of use](https://www.parlament.gv.at/recherchieren/open-data/daten-und-lizenz/index.html).

The server reads these sources on `www.parlament.gv.at`:

| Source                                                | Use                                                             |
| ----------------------------------------------------- | --------------------------------------------------------------- |
| Open Data filter API (`/Filter/api/filter/data/{id}`) | Datasets such as protocols, proceedings, people, and committees |
| JSON detail pages (`?json=TRUE`)                      | Session, person, committee, and record details                  |
| Stenographic protocol documents (`/dokument/...`)     | Transcript text and link checks                                 |
| Plenary-session list of the website (`listeId=11070`) | Current sessions before the protocol index lists them           |
| Speech list of a person page (filter list 251)        | A person's speeches for session selection and reconciliation    |
| Participation (filter lists 143, 142)                 | Drafts in review, statements, initiatives, and petitions        |
| Popular initiatives (filter list 101, official texts) | Popular initiatives and the result of the Electoral Board       |
| Member lists of the website (`WFW_002`, `WFW_005`)    | Current members of the National Council and the Federal Council |
| Presidium list (filter list 402)                      | Presidents and vice presidents of both chambers                 |
| Government members (filter list 1717)                 | Ministers and state secretaries since 1918, with their terms    |
| Dates (filter list 600) and committees (list 2008)    | Sittings, committee meetings, events, and committee pages       |
| Media library pages (`/aktuelles/mediathek/...`)      | Speaker timeline of a sitting with the start of each turn       |
| Expert publications (filter lists 616, 614)           | Budget Office, Research Service, and legal monitoring           |
| Presidents (`WFW_003`, filter list 10401)             | Presidents of both chambers since 1920                          |
| Area spokespersons (filter list 403)                  | Spokespersons that the clubs report for each area               |
| Person lists (filter lists 25, 250)                   | Activities and committee memberships of a person                |
| Declarations (`/person/unvtrans/...`)                 | Positions, activities, and income categories of all members     |
| Website search (`/Filter/api/filterform/vts/data`)    | Full-text search of website pages and transcript passages       |

For the Federal Law Gazette, the server also reads the Open Government Data interface of the RIS
(`data.bka.gv.at/ris/api/v2.6`, application `BgblAuth`). The Federal Chancellery publishes the RIS data under
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Name the source, for example "Source: RIS, Bundeskanzleramt,
CC BY 4.0".

The Parliament marks the datasets of its Open Data offer as such. Data that is not marked as Open Data is subject to
the general terms of use of the Parliament website. The license of this project covers only the code, not the data.

## Development

```sh
npm ci              # install dependencies and build
npm run dev         # run the server from the TypeScript sources
npm run check       # lint, Markdown lint, format check, type check, and tests
npm run build       # compile to dist/
npm run format      # format all files
```

The code has this structure:

| Directory           | Content                                                                   |
| ------------------- | ------------------------------------------------------------------------- |
| `src/api/`          | HTTP access, cache, rate limit, and request context                       |
| `src/domain/`       | Parsing and search logic for sessions, speeches, transcripts, and records |
| `src/presentation/` | Markdown tables that the assistant shows in the chat                      |
| `src/shared/`       | Constants, date handling, link checks, and general helpers                |
| `src/tools/`        | One file for each MCP tool, and `index.ts` to register all tools          |
| `tests/`            | Unit tests and tool tests with a mocked Parliament API                    |

The tests do not send requests to the Parliament website. Use
[Conventional Commits](https://www.conventionalcommits.org/) for commit messages.

## Security

To report a vulnerability, read [SECURITY.md](SECURITY.md).

## License

The code is available under the [MIT License](LICENSE). The data that the server reads is subject to the license and
terms of the Austrian Parliament. Refer to [Data sources, license, and terms of use](#data-sources-license-and-terms-of-use).