# FragDenStaat (pypi · fds-mcp)

Freedom-of-information requests for Germany via FragDenStaat.de, the froide FOI portal.

- Trust score: 58/100 (low)
- Change this week: +4
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-22

## Components

- pypi · `fds-mcp`: 58/100 (this document), [markdown](https://verifymcp.io/servers/notdirk-fds-mcp/fds-mcp.md), [page](https://verifymcp.io/servers/notdirk-fds-mcp/fds-mcp)

## Channel facts

- Registry: `pypi`
- Package: `fds-mcp`
- Version: `0.1.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-22.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised native-build step with no shell scripting around it.
  - 1 of 33 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 32/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - License check failed: the license (MIT License) isn't a recognized OSI-approved license.
  - Actively maintained (last published 16 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 72/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 2445 tokens (~163/item across 15 items; 15 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 57/100
  - Stability observed for 17 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 67/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 0% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 15 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 16 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the FragDenStaat MCP server?

FragDenStaat runs locally as a PyPI package, launched with uvx fds-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add notdirk-fds-mcp -- uvx fds-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "notdirk-fds-mcp": {
      "command": "uvx",
      "args": [
        "fds-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "notdirk-fds-mcp": {
      "command": "uvx",
      "args": [
        "fds-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add notdirk-fds-mcp -- uvx fds-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "notdirk-fds-mcp": {
      "type": "local",
      "command": [
        "uvx",
        "fds-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add notdirk-fds-mcp --command uvx --arg fds-mcp
```

### Hermes

```yaml
mcp_servers:
  notdirk-fds-mcp:
    command: "uvx"
    args: ["fds-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "notdirk-fds-mcp": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "fds-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add notdirk-fds-mcp -t stdio -c uvx -a fds-mcp
```

### Other

```json
{
  "mcpServers": {
    "notdirk-fds-mcp": {
      "command": "uvx",
      "args": [
        "fds-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-22 (score 58, +1)

No change was recorded against any check on this day. Stability & Change Management went from 53 to 57. That category is still filling its 30-day observation window: 16 days of observed history at the previous scan, 17 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-20 (score 57, +1)

No change was recorded against any check on this day. Stability & Change Management went from 47 to 50. That category is still filling its 30-day observation window: 14 days of observed history at the previous scan, 15 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-18 (score 56, +1)

No change was recorded against any check on this day. Stability & Change Management went from 40 to 43. That category is still filling its 30-day observation window: 12 days of observed history at the previous scan, 13 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-16 (score 55, +1)

No change was recorded against any check on this day. Stability & Change Management went from 33 to 37. That category is still filling its 30-day observation window: 10 days of observed history at the previous scan, 11 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-13 (score 54, +4)

- [functional improvement] Stability: unverified → 0.27

### 2026-09-05 (score 50)

First indexed and scored.

## MCP tools (15)

### `search_authorities` (~115 tokens)

Search public bodies on fragdenstaat.de. GREEN: no auth, no side effects.

Args:
    query: free-text search, e.g. a town or an authority name.
    jurisdiction: optional filter — numeric id, slug ("rheinland-pfalz") or
        name ("Rheinland-Pfalz"). The API itself only accepts the id.
    limit: maximum number of results (server page size is 50).

Input parameters:

- `jurisdiction`
- `limit` (integer)
- `query` (string, required)

### `get_authority` (~116 tokens)

Full record for one public body, including its laws. GREEN.

The API does not expose ``default_law``, so the law the REST API would apply is
recomputed here the way froide's ``get_applicable_law()`` does it
(``order_by('-meta', '-priority')``). That default is almost always the *meta* law,
which is why the API cannot be used to file under a specific act.

Args:
    id: numeric public body id.

Input parameters:

- `id` (integer, required)

### `get_law` (~50 tokens)

One freedom-of-information act with its deadline rules. GREEN.

Args:
    id: numeric law id, e.g. from ``get_authority(...)["laws"]``.

Input parameters:

- `id` (integer, required)

### `check_jurisdiction` (~219 tokens)

Which authority covers a given place? GREEN — returns the evidence trail.

Resolves ``/georegion/?name=<place>``, walks its ``part_of`` chain upwards, and asks
\``/publicbody/?regions=<id>`` level by level, from the most specific outwards. It
stops at the **first level that yields any authority**, because that is the body
actually responsible; the remaining, wider levels are reported by id only.

This matters in states such as Rhineland-Palatinate, where an *Ortsgemeinde* is often
not listed on fragdenstaat.de at all while the *Verbandsgemeindeverwaltung* that runs
its administration is.

Args:
    place_name: name of the municipality, district or state.
    include_wider: also list the authorities of the wider levels. Off by default —
        at country level that is thousands of bodies and tells you nothing.

Input parameters:

- `include_wider` (boolean)
- `place_name` (string, required)

### `list_my_requests` (~72 tokens)

Your own FOI requests. YELLOW: needs a token (scope read:request), read only.

Args:
    status: optional filter, e.g. "awaiting_response" or "resolved".
    limit: maximum number of requests to return.

Input parameters:

- `limit` (integer)
- `status`

### `get_request` (~41 tokens)

One FOI request in detail. YELLOW (public requests work without a token).

Args:
    id: numeric request id.

Input parameters:

- `id` (integer, required)

### `get_messages` (~38 tokens)

All messages of a request, oldest first. YELLOW.

Args:
    request_id: numeric request id.

Input parameters:

- `request_id` (integer, required)

### `list_attachments` (~41 tokens)

Attachments belonging to one message. YELLOW.

Args:
    message_id: numeric message id (from get_messages).

Input parameters:

- `message_id` (integer, required)

### `download_attachment` (~137 tokens)

Download one attachment into a local directory. YELLOW: reading only.

The directory has to exist already: this tool will not create a path, because
\``target_dir`` is a model-chosen argument and the file name comes from the API, and
together they were enough to drop a file into e.g. ~/.config/autostart/. Set
\``FDS_MCP_DOWNLOAD_DIR`` to confine downloads to one directory.

Args:
    attachment_id: numeric attachment id (from list_attachments).
    target_dir: an existing local directory.

Input parameters:

- `attachment_id` (integer, required)
- `target_dir` (string, required)

### `check_deadlines` (~51 tokens)

Your open requests whose statutory deadline has passed. YELLOW.

froide exposes no "deadline expired" flag; it is computed from ``due_date`` against
the current time, exactly as the frontend does.

### `build_reply_draft` (~415 tokens)

Prepare a follow-up message to an authority. YELLOW: reads the API, sends nothing.

The counterpart of ``build_submit_url`` for a request that already exists. It looks
the request up, validates the text against the rules that apply to a follow-up, and
hands back the finished text plus the URL of the form. **Pressing send stays with
you**, and there is no tool argument that changes that: replying is not possible
through the API at all. Measured 2026-09-05 (tests/test_api_contract.py):
\``POST /api/v1/message/`` refuses ``kind: "email"``, and the web view at
\``/anfrage/<slug>/send/message/`` answers 302 to the login page whether or not a
bearer token is attached.

A follow-up is validated differently from a request. froide does **not** frame it:
the textarea arrives prefilled with a salutation, the placeholder U+2026 and a
closing formula, and exactly what stands in it is what the authority receives. So
R19 requires a salutation and a closing formula — the inverse of R10 — R04 rejects
the placeholder that is sitting in the form right now, R06 keeps e-mail addresses and
IBANs out of a public thread, and the subject is capped at 230 characters.

Args:
    request_id: numeric id of your existing request.
    text: the complete message, salutation and closing formula included.
    subject: reply subject. Defaults to "AW: <title> [#<id>]".
    path: optional path to a ``.yaml`` file to write the draft to. Needed only if you
        intend to use ``send_reply_via_browser`` later; the file is written with
        ``status: draft`` and a placeholder confirmation token.

Input parameters:

- `path`
- `request_id` (integer, required)
- `subject`
- `text` (string, required)

### `create_request_draft` (~413 tokens)

Write a local YAML draft. RED tier, but performs NO network traffic at all.

The file starts in status ``draft`` with a placeholder confirmation token. A human
has to read the text, set ``status: approved`` and replace ``confirmation_token``
before anything can be submitted.

Args:
    path: where to write the YAML file.
    subject: request subject, 8-230 characters.
    body: the request text, at most 5000 characters.
    publicbody_id: recipient authority id.
    law_wunsch_id: the law you want to file under.
    law_api_default_id: the law the REST API would apply — see get_authority().
    law_wunsch_law_type: law_type of the desired law, e.g. "IFG" or "UIG".
    publicbody_name: authority name, for the L01 cross-check.
    publicbody_email: authority e-mail, for the L01 cross-check.
    ermittelt_ueber: how responsibility was established (a URL or a sentence).
    public: whether the request will be public. Public means CC0 and visible to all.
    full_text: True sends your text verbatim, False lets froide frame it.
    submit_via: "web_form" (human presses send) or "api" (immediate dispatch).
    dry_run: True (default) returns the draft without writing the file.

Input parameters:

- `body` (string, required)
- `dry_run` (boolean)
- `ermittelt_ueber`
- `full_text` (boolean)
- `law_api_default_id`
- `law_wunsch_id` (integer, required)
- `law_wunsch_law_type`
- `path` (string, required)
- `public` (boolean)
- `publicbody_email`
- `publicbody_id` (integer, required)
- `publicbody_name`
- `subject` (string, required)
- `submit_via` (string)

### `validate_draft` (~113 tokens)

Run the rule set against a draft. RED tier.

Offline rules R01-R16 always run. The live rules L01-L04 (authority exists, law is
offered, API default law, duplicate check) need the network and therefore only run
with ``dry_run=False``.

Args:
    path: path to the draft YAML file.
    dry_run: True (default) runs offline rules only and does not touch the file.

Input parameters:

- `dry_run` (boolean)
- `path` (string, required)

### `build_submit_url` (~174 tokens)

Build the prefilled web form URL — the recommended way out. RED tier.

This is the only route on which the legal basis can actually be chosen
(``?law_type=...``); the REST API's MakeRequestSerializer has no such field. Sending
stays with the human.

fragdenstaat.de answers GET URLs above roughly 4096 bytes with HTTP 400 (measured
2026-09-05). Longer drafts therefore get a two-step answer: a short URL that
prefills subject and law_type, plus the body in a sidecar text file.

Args:
    path: path to the draft YAML file.
    dry_run: True (default) does not write the sidecar .body.txt file.

Input parameters:

- `dry_run` (boolean)
- `path` (string, required)

### `submit_request` (~260 tokens)

Actually POST the request to fragdenstaat.de. RED tier — IRREVERSIBLE.

\``POST /api/v1/request/`` sends the e-mail to the authority immediately. There is no
draft, no preview and no undo. Five gates therefore have to agree, in this order:

  1\. the draft's status is ``approved`` (a human set it),
  2\. no ERROR finding is open,
  3\. ``law.wunsch_id == law.api_default_id`` — the API cannot set law_type, so a
     mismatch would file the request under the wrong act,
  4\. ``confirmation_token`` matches the value a human wrote into the draft file,
  5\. the local throttle ledger says another submission stays inside
     5/5min, 6/6h, 10/24h, 20/7d.

Args:
    path: path to the approved draft YAML file.
    confirmation_token: must equal the ``confirmation_token`` inside the draft file.
    dry_run: True (default) runs every gate and reports, but sends nothing.

Input parameters:

- `confirmation_token` (string, required)
- `dry_run` (boolean)
- `path` (string, required)

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/notdirk-fds-mcp/fds-mcp#diagnostics

## Score history

- 2026-09-22: 58
- 2026-09-21: 57
- 2026-09-20: 57
- 2026-09-19: 56
- 2026-09-18: 56
- 2026-09-17: 55
- 2026-09-16: 55
- 2026-09-15: 54
- 2026-09-14: 54
- 2026-09-13: 54
- 2026-09-12: 50
- 2026-09-11: 50
- 2026-09-10: 50
- 2026-09-09: 50
- 2026-09-08: 50
- 2026-09-07: 50
- 2026-09-06: 50
- 2026-09-05: 50

## Common questions

### What is the FragDenStaat MCP server?

FragDenStaat is an MCP server listed in the public MCP registry as io.github.notDIRK/fds-mcp. Freedom-of-information requests for Germany via FragDenStaat.de, the froide FOI portal. This page covers its PyPI package (fds-mcp).

### Is the FragDenStaat MCP server safe to use?

FragDenStaat scores 58 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 22 September 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the FragDenStaat MCP server expose?

FragDenStaat exposes 15 tools: search_authorities, get_authority, get_law, check_jurisdiction, list_my_requests, and 10 more. Their descriptions and schemas cost roughly 2,255 tokens of context every time the server is loaded.

### Is the FragDenStaat MCP server still maintained?

FragDenStaat is still listed as active in the MCP registry. We last reached this channel on 22 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- PyPI project: https://pypi.org/project/fds-mcp/
- Socket report: https://socket.dev/pypi/package/fds-mcp
- Repository: https://github.com/notDIRK/fds-mcp
- Changelog RSS feed: https://verifymcp.io/servers/notdirk-fds-mcp/fds-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/notdirk-fds-mcp/fds-mcp.json
- HTML version of this page: https://verifymcp.io/servers/notdirk-fds-mcp/fds-mcp
