Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions content/guides/de/destatis-genesis-to-mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ git clone https://github.com/HelpCode-ai/anythingmcp.git
cd anythingmcp && docker compose up -d
```

### Schritt 2: Destatis-Adapter importieren
### Schritt 2: GENESIS-Zugangsdaten erhalten

Öffnen Sie `http://localhost:3000/connectors/store` und klicken Sie auf **Import**. Geben Sie Ihren GENESIS-Benutzernamen und Passwort ein.
Registrieren Sie sich kostenlos auf [genesis.destatis.de](https://genesis.destatis.de/).

### Schritt 3: GENESIS-Zugangsdaten erhalten
Destatis empfiehlt die Identifizierung über einen **persönlichen API-Token** anstelle des Passworts: Nach dem Login finden Sie die 32-stellige Zeichenkette im Modal **„Webservice-Schnittstelle (API)"**. Der Token lässt sich unabhängig von Ihren Zugangsdaten zurücksetzen — praktisch bei gemeinsam genutzten Projekten. Beachten Sie: Mit Token sind nur lesende Zugriffe möglich; sehr große Tabellenabfragen, die Destatis in die Warteschlange schreibt (`job=true`), erfordern weiterhin Benutzername und Passwort.

Registrieren Sie sich kostenlos auf [www-genesis.destatis.de](https://www-genesis.destatis.de/).
### Schritt 3: Destatis-Adapter importieren

Öffnen Sie `http://localhost:3000/connectors/store` und klicken Sie auf **Import**. Tragen Sie **entweder** Ihren API-Token **oder** Ihre Nutzerkennung in das Feld *Username or Token* ein. Das Feld *Password* ist optional — bei Verwendung eines Tokens lassen Sie es leer.

> **Hinweis zur Authentifizierung:** Destatis hat GET-Anfragen mit Zugangsdaten in der URL zum **30.06.2025** endgültig abgeschaltet. Die API ist seitdem ausschließlich per POST mit Zugangsdaten im HTTP-Header erreichbar; der Adapter ist entsprechend konfiguriert. Kommt eine Tabellensuche leer zurück, rufen Sie zuerst `destatis_login_check` auf: Lassen sich die Zugangsdaten nicht auflösen, antwortet `find/find` weiterhin mit `Code: 0` als Gast-Konto `GAST` — alle Trefferlisten sind dann `null`, was von einer echten Nulltreffer-Suche nicht zu unterscheiden ist. Die Daten- und Metadaten-Dienste scheitern dagegen deutlich mit `Code: 15`.

### Schritt 4: Mit KI-Agent verbinden

Expand Down
15 changes: 10 additions & 5 deletions content/guides/en/destatis-genesis-to-mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ git clone https://github.com/HelpCode-ai/anythingmcp.git
cd anythingmcp && docker compose up -d
```

### Step 2: Import the Destatis Adapter
### Step 2: Get GENESIS Credentials

Open `http://localhost:3000/connectors/store` and click **Import** on the Destatis GENESIS Statistics adapter. Enter your GENESIS username and password when prompted.
Register for a free account at [genesis.destatis.de](https://genesis.destatis.de/).

### Step 3: Get GENESIS Credentials
Destatis recommends identifying with a **personal API token** rather than your password: log in, open the **"Webservice-Schnittstelle (API)"** modal and copy the 32-character token. It can be regenerated independently of your password, which is useful for shared projects. Note that a token allows read access only — very large table queries that Destatis pushes into its queue (`job=true`) still require username and password.

Register for a free account at [www-genesis.destatis.de](https://www-genesis.destatis.de/) to get API access credentials.
### Step 3: Import the Destatis Adapter

Open `http://localhost:3000/connectors/store` and click **Import** on the Destatis GENESIS Statistics adapter. Put **either** your API token **or** your Nutzerkennung into the *Username or Token* field. The *Password* field is optional — leave it blank when using a token.

> **Authentication note:** Destatis permanently shut down GET requests with credentials in the URL on **30 June 2025**. The API is now reachable only via POST with credentials in the HTTP header, and this adapter is configured accordingly. If a table search comes back empty, run `destatis_login_check` first: when credentials fail to resolve, `find/find` still answers `Code: 0` as the `GAST` guest account with all result lists `null`, which is indistinguishable from a genuine no-hits search. The data and metadata services are stricter and fail loudly with `Code: 15`.

### Step 4: Connect to Your AI Agent

Expand All @@ -44,9 +48,10 @@ Register for a free account at [www-genesis.destatis.de](https://www-genesis.des

| Tool | Description |
|------|-------------|
| `destatis_login_check` | Verify credentials and confirm which account the API sees |
| `destatis_search_tables` | Search for statistical tables by keyword |
| `destatis_get_table` | Retrieve data from a specific statistical table |
| `destatis_get_metadata` | Get metadata and definitions for a table |
| `destatis_get_table_metadata` | Get metadata and definitions for a table |
| `destatis_list_statistics` | List all available statistics catalogs |

## AI Agent Use Cases
Expand Down
14 changes: 11 additions & 3 deletions content/guides/it/destatis-genesis-to-mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,19 @@ git clone https://github.com/HelpCode-ai/anythingmcp.git
cd anythingmcp && docker compose up -d
```

### Passo 2: Importa l'Adattatore Destatis
### Passo 2: Ottieni le Credenziali GENESIS

Apri `http://localhost:3000/connectors/store` e clicca **Import**. Inserisci username e password GENESIS.
Registrati gratuitamente su [genesis.destatis.de](https://genesis.destatis.de/).

### Passo 3: Collega al Tuo Agente AI
Destatis raccomanda di identificarsi con un **token API personale** anziché con la password: dopo il login, copia la stringa di 32 caratteri dal modale **"Webservice-Schnittstelle (API)"**. Il token può essere rigenerato indipendentemente dalle credenziali. Attenzione: con il token è possibile solo l'accesso in lettura; le query su tabelle molto grandi che Destatis accoda (`job=true`) richiedono ancora username e password.

### Passo 3: Importa l'Adattatore Destatis

Apri `http://localhost:3000/connectors/store` e clicca **Import**. Inserisci **il token API oppure** la tua Nutzerkennung nel campo *Username or Token*. Il campo *Password* è opzionale — lascialo vuoto se usi un token.

> **Nota sull'autenticazione:** Destatis ha disattivato definitivamente le richieste GET con credenziali nell'URL il **30 giugno 2025**. L'API è ora raggiungibile solo via POST con le credenziali nell'header HTTP, e l'adattatore è configurato di conseguenza. Se una ricerca di tabelle torna vuota, esegui prima `destatis_login_check`: quando le credenziali non vengono risolte, `find/find` risponde comunque con `Code: 0` come account ospite `GAST` e con tutte le liste di risultati a `null`, indistinguibile da una ricerca senza risultati. I servizi dati e metadati invece falliscono in modo esplicito con `Code: 15`.

### Passo 4: Collega al Tuo Agente AI

```json
{
Expand Down
10 changes: 9 additions & 1 deletion packages/backend/src/adapters/adapters.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,15 @@ export class AdaptersService {
credentials?: Record<string, string>,
): string {
if (!credentials) return str;
return str.replace(/\{\{(\w+)\}\}/g, (_, key) => credentials[key] || `{{${key}}}`);
// An explicitly-supplied empty value must resolve to empty, not fall back
// to the literal placeholder — some APIs require a credential header to be
// present but blank (e.g. Destatis GENESIS wants `password: ""` when
// identifying via API token). A `||` fallback here would send the string
// "{{DESTATIS_PASSWORD}}" as the password. Only an *absent* key keeps its
// placeholder, so the operator can still fill it in later.
return str.replace(/\{\{(\w+)\}\}/g, (_, key) =>
key in credentials ? credentials[key] : `{{${key}}}`,
);
}

/** Deep-replace {{VAR}} placeholders in an object/value */
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/adapters/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ export interface AdapterMeta {
icon: string;
docsUrl: string;
requiredEnvVars: string[];
/** Env vars the connector references but that may legitimately be left blank
* (e.g. Destatis GENESIS needs no password when an API token is used). The
* install modal prompts for these without blocking submission, and submits
* them as an empty string so the placeholder resolves instead of leaking
* into the request verbatim. */
optionalEnvVars?: string[];
toolCount: number;
/** Content-addressed version of the adapter's installable content (tools +
* connector meta + instructions). Stamped onto a connector at install
Expand Down Expand Up @@ -490,6 +496,7 @@ export function listAdapters(): AdapterMeta[] {
icon: adapter.icon,
docsUrl: adapter.docsUrl,
requiredEnvVars: adapter.requiredEnvVars,
optionalEnvVars: adapter.optionalEnvVars,
toolCount: adapter.tools.length,
version: adapter.version,
authType: adapter.connector.authType,
Expand Down
59 changes: 45 additions & 14 deletions packages/backend/src/adapters/de/destatis-genesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,49 @@
"region": "de",
"category": "government",
"icon": "destatis",
"docsUrl": "https://www-genesis.destatis.de/genesis/misc/GENESIS-Webservices_Einfuehrung.pdf",
"docsUrl": "https://genesis.destatis.de/datenbank/online/docs/GENESIS-Webservices_Einfuehrung.pdf",
"instructions": "This connector uses the GENESIS-Online RESTful/JSON web service (Anwenderdokumentation Version 5.1, 01.06.2026).\n\n**IMPORTANT — POST only.** Destatis permanently shut down GET requests with credentials in the URL on **30 June 2025** (\"Zur Verbesserung des Schutzes Ihrer Nutzerdaten wird die bisher angebotene Möglichkeit der API Nutzung mittels GET Requests am 30. Juni 2025 endgültig abgeschaltet\"). Since then the API is reachable **only via POST**, with credentials sent as HTTP **header** fields and all other parameters in a `application/x-www-form-urlencoded` request body. Every tool here is mapped that way. Do not convert any tool back to GET — a GET now returns the GENESIS HTML web interface instead of JSON.\n\n**Setup**:\n1. Register free at https://genesis.destatis.de (Registrierung).\n2. Recommended: log in and open the modal **\"Webservice-Schnittstelle (API)\"** to copy your personal **API token** (32 characters). Destatis explicitly recommends the token: it can be reset independently of your password and contains no characters that need escaping.\n3. Set `DESTATIS_USERNAME_OR_TOKEN` to **either** your Nutzerkennung/e-mail **or** the API token. When using a token, leave `DESTATIS_PASSWORD` empty — a password is not required. When using a Nutzerkennung, set `DESTATIS_PASSWORD` as well.\n\n**Token limitation**: a token identifies but cannot authenticate write access. Requests with `job=true` (used automatically for very large tables), `profile/password` and `profile/removeresult` require Nutzerkennung + password. All tools in this connector are read-only, so a token covers them — except when a table is large enough to be pushed into the queue.\n\n**Silent guest fallback — watch out**: if credentials are missing or unresolved, `find/find` does **not** return an auth error. It runs as the **`GAST`** guest account and answers `Status.Code = 0` / \"erfolgreich\" with every result list `null` — indistinguishable from a genuine no-hits search. The data, metadata and catalogue services are stricter and fail loudly with `Code = 15` (\"Sie sind nicht berechtigt diesen Service aufzurufen...\"). So: an empty `destatis_search_tables` result, or any `Code = 15`, most likely means the credentials never arrived — call `destatis_login_check` and confirm the returned `Username` is yours and not `GAST`.\n\n**Resource limits** (chapter 1.7 of the docs): a maximum of **3 parallel requests** per account; requests running longer than 15 minutes are terminated. Tables with more than ~40,000 values cannot be downloaded in the dialog and return `Status.Code = 98` — split the query by time range or narrow the classifying variables.\n\n**Reading responses**: every response carries a `Status` block. `Code: 0` means success; a non-zero `Code` carries the reason in `Content`. Table codes look like `12411-0001` (population) or `61111-0001` (consumer price index) — use `destatis_search_tables` to find them before calling `destatis_get_table`.",
"requiredEnvVars": [
"DESTATIS_USERNAME",
"DESTATIS_USERNAME_OR_TOKEN"
],
"optionalEnvVars": [
"DESTATIS_PASSWORD"
],
"connector": {
"name": "Destatis GENESIS",
"type": "REST",
"baseUrl": "https://www-genesis.destatis.de/genesisWS/rest/2020",
"authType": "QUERY_AUTH",
"baseUrl": "https://genesis.destatis.de/genesisWS/rest/2020",
"authType": "API_KEY",
"authConfig": {
"username": "{{DESTATIS_USERNAME}}",
"password": "{{DESTATIS_PASSWORD}}"
"headerName": "username",
"apiKey": "{{DESTATIS_USERNAME_OR_TOKEN}}",
"extraHeaders": {
"password": "{{DESTATIS_PASSWORD}}"
}
}
},
"tools": [
{
"name": "destatis_login_check",
"description": "Verify that the configured GENESIS credentials work and report which account the API sees. Use this first when results look wrong — if the returned Username is 'GAST' the credentials did not resolve and you are getting guest-level data.",
"parameters": {
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "Language for status messages: 'de' for German or 'en' for English. Default: 'de'"
}
}
},
"endpointMapping": {
"method": "POST",
"path": "/helloworld/logincheck",
"bodyEncoding": "form-urlencoded",
"bodyMapping": {
"language": "$language"
}
}
},
{
"name": "destatis_search_tables",
"description": "Search for statistical tables in the Destatis GENESIS database by keyword. Returns table codes, titles, and metadata. Use this to find the right table before retrieving data.",
Expand All @@ -41,9 +68,10 @@
]
},
"endpointMapping": {
"method": "GET",
"method": "POST",
"path": "/find/find",
"queryParams": {
"bodyEncoding": "form-urlencoded",
"bodyMapping": {
"term": "$searchterm",
"category": "tables",
"language": "$language"
Expand Down Expand Up @@ -78,9 +106,10 @@
]
},
"endpointMapping": {
"method": "GET",
"method": "POST",
"path": "/data/table",
"queryParams": {
"bodyEncoding": "form-urlencoded",
"bodyMapping": {
"name": "$name",
"startyear": "$startyear",
"endyear": "$endyear",
Expand Down Expand Up @@ -108,9 +137,10 @@
]
},
"endpointMapping": {
"method": "GET",
"method": "POST",
"path": "/metadata/table",
"queryParams": {
"bodyEncoding": "form-urlencoded",
"bodyMapping": {
"name": "$name",
"language": "$language"
}
Expand All @@ -133,9 +163,10 @@
}
},
"endpointMapping": {
"method": "GET",
"method": "POST",
"path": "/catalogue/statistics",
"queryParams": {
"bodyEncoding": "form-urlencoded",
"bodyMapping": {
"selection": "$searchterm",
"language": "$language"
}
Expand Down
125 changes: 125 additions & 0 deletions packages/backend/src/adapters/de/destatis-genesis.live.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { RestEngine } from '../../connectors/engines/rest.engine';
import { OAuth2TokenService } from '../../connectors/engines/oauth2-token.service';
import { LoginTokenService } from '../../connectors/engines/login-token.service';

/**
* Live reachability check for the Destatis GENESIS adapter. Skipped unless
* RUN_DESTATIS_LIVE is set AND DESTATIS_USERNAME_OR_TOKEN is provided.
*
* This is the layer that proves what static tests cannot: that the real API
* accepts credentials as HTTP *header* fields and that each endpoint accepts
* its parameters as *body* fields (they used to be query params).
*
* Run with a personal API token (password stays empty):
* RUN_DESTATIS_LIVE=1 DESTATIS_USERNAME_OR_TOKEN=<32-char-token> \
* npx jest src/adapters/de/destatis-genesis.live.spec.ts
*
* ...or with Nutzerkennung + password:
* RUN_DESTATIS_LIVE=1 DESTATIS_USERNAME_OR_TOKEN=<kennung> \
* DESTATIS_PASSWORD=<passwort> \
* npx jest src/adapters/de/destatis-genesis.live.spec.ts
*
* NOTE: importing RestEngine currently drags in
* connectors/engines/unblocker-proxy-agent.ts, which does not type-check
* against the installed https-proxy-agent typings. That breakage is
* pre-existing (oxomi.live.spec.ts fails the same way) and blocks this suite
* from running until it is fixed. The static guards in
* destatis-genesis.spec.ts are unaffected.
*/

const live =
process.env.RUN_DESTATIS_LIVE && process.env.DESTATIS_USERNAME_OR_TOKEN
? describe
: describe.skip;

live('destatis-genesis adapter — live GENESIS API reachability', () => {
const oauth = {} as unknown as OAuth2TokenService;
const login = {} as unknown as LoginTokenService;
const engine = new RestEngine(oauth, login);

const config = {
baseUrl: 'https://genesis.destatis.de/genesisWS/rest/2020',
authType: 'API_KEY',
authConfig: {
headerName: 'username',
apiKey: process.env.DESTATIS_USERNAME_OR_TOKEN as string,
// Empty string when identifying via API token — matching Destatis'
// own Python example (`'password': ""`).
extraHeaders: { password: process.env.DESTATIS_PASSWORD || '' },
},
};

const call = (
path: string,
bodyMapping: Record<string, unknown>,
params: Record<string, unknown>,
) =>
engine.execute(
config,
{ method: 'POST', path, bodyEncoding: 'form-urlencoded', bodyMapping },
params,
);

it('logincheck authenticates and does NOT fall back to the GAST guest account', async () => {
const res = (await call(
'/helloworld/logincheck',
{ language: '$language' },
{ language: 'de' },
)) as { Status?: string; Username?: string };
expect(res).toBeDefined();
// A missing/unresolved credential does not error — GENESIS silently logs
// the caller in as GAST and returns guest-level data. Assert we are not it.
expect(res.Username).not.toBe('GAST');
expect(res.Status).toContain('erfolgreich');
}, 30000);

it('find/find accepts term/category as body fields', async () => {
const res = (await call(
'/find/find',
{ term: '$searchterm', category: 'tables', language: '$language' },
{ searchterm: 'Bevölkerung', language: 'de' },
)) as { Status?: { Code?: number }; Tables?: unknown[] };
expect(res.Status?.Code).toBe(0);
expect(Array.isArray(res.Tables)).toBe(true);
}, 30000);

it('data/table returns a table for a known code', async () => {
const res = (await call(
'/data/table',
{
name: '$name',
startyear: '$startyear',
endyear: '$endyear',
language: '$language',
},
{
name: '12411-0001',
startyear: '2020',
endyear: '2024',
language: 'de',
},
)) as { Status?: { Code?: number }; Object?: unknown };
expect(res.Status?.Code).toBe(0);
expect(res.Object).toBeDefined();
}, 30000);

it('metadata/table returns table metadata', async () => {
const res = (await call(
'/metadata/table',
{ name: '$name', language: '$language' },
{ name: '12411-0001', language: 'de' },
)) as { Status?: { Code?: number } };
expect(res.Status?.Code).toBe(0);
}, 30000);

it('catalogue/statistics lists statistics with the selection filter omitted', async () => {
// `selection` maps from an optional tool param — when the caller omits it
// the key must drop out of the body rather than be sent as "$searchterm".
const res = (await call(
'/catalogue/statistics',
{ selection: '$searchterm', language: '$language' },
{ language: 'de' },
)) as { Status?: { Code?: number } };
expect(res.Status?.Code).toBe(0);
}, 30000);
});
Loading
Loading