Skip to content

Commit b82d476

Browse files
authored
Merge pull request #436 from HelpCode-ai/keysersoft/datev-sandbox-adapter-reload
feat(connectors): DATEV sandbox adapter + reload REST OAuth tools (v0.3.6)
2 parents 195bb77 + 9d93dcf commit b82d476

13 files changed

Lines changed: 485 additions & 31 deletions

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "anythingmcp",
3-
"version": "0.3.5",
3+
"version": "0.3.6",
44
"description": "Self-hosted MCP gateway for REST, SOAP/WSDL, GraphQL and SQL — turn any API into MCP tools for Claude, ChatGPT, Gemini, Copilot and Cursor. 30+ pre-built adapters, on-prem audit log, OAuth2/RBAC. Open source (AGPL-3.0).",
55
"private": true,
66
"license": "AGPL-3.0-only",

packages/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@anythingmcp/backend",
3-
"version": "0.3.5",
3+
"version": "0.3.6",
44
"description": "AnythingMCP — NestJS Backend + Dynamic MCP Server",
55
"private": true,
66
"license": "AGPL-3.0-only",

packages/backend/src/adapters/catalog.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as billbee from './de/billbee.json';
33
import * as billomat from './de/billomat.json';
44
import * as bundesbank from './de/bundesbank.json';
55
import * as datev from './de/datev.json';
6+
import * as datevSandbox from './de/datev-sandbox.json';
67
import * as destatisGenesis from './de/destatis-genesis.json';
78
import * as deutscheBahn from './de/deutsche-bahn.json';
89
import * as dhlTracking from './de/dhl-tracking.json';
@@ -283,6 +284,7 @@ const RAW_ADAPTERS: AdapterDefinition[] = [
283284
billomat as unknown as AdapterDefinition,
284285
bundesbank as unknown as AdapterDefinition,
285286
datev as unknown as AdapterDefinition,
287+
datevSandbox as unknown as AdapterDefinition,
286288
destatisGenesis as unknown as AdapterDefinition,
287289
deutscheBahn as unknown as AdapterDefinition,
288290
dhlTracking as unknown as AdapterDefinition,
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
{
2+
"slug": "datev-sandbox",
3+
"name": "DATEV Online APIs (Sandbox)",
4+
"description": "Sandbox environment for the DATEV Online APIs — read accounting Mandanten (clients), document types and DUO version metadata against DATEV's test data. Use this while you build and test; every new DATEV app must run in sandbox until DATEV grants production approval. Same OAuth2 Authorization Code + PKCE flow as production, pointed at DATEV's sandbox endpoints.",
5+
"instructions": "**Use this connector for testing.** Every DATEV app runs in the sandbox until DATEV completes its production-approval review — so this is where you start.\n\n**Setup**:\n1. Create a **Confidential** app on the DATEV-Entwicklerportal (https://developer.datev.de) with **OpenID Connect Authorization Code Flow**. Set the redirect URI to `<your-server>/api/mcp-oauth/callback`.\n2. Subscribe the app to the sandbox API products: **accounting:clients 2.0** and **accounting:documents 2.0**.\n3. Set `DATEV_CLIENT_ID` and `DATEV_CLIENT_SECRET` (from the portal — copy the secret in full, it is shown only once).\n4. Import this adapter — it is pre-configured for the sandbox (authorize `login.datev.de/openidsandbox/authorize`, token `sandbox-api.datev.de/token`, API base `platform-sandbox/v2`) and authenticates at the token endpoint via **HTTP Basic** (`client_secret_basic`), which DATEV requires.\n5. Run the one-time OAuth flow: `POST /api/connectors/{id}/oauth/authorize` -> open the returned URL -> log in with your DATEV-Konto -> the platform stores an encrypted refresh token automatically.\n6. Every call then sends `Authorization: Bearer <token>` AND the mandatory `X-DATEV-Client-Id: <DATEV_CLIENT_ID>` header automatically.\n\n**Going to production**: once DATEV approves your app for production, use the separate **DATEV Online APIs** connector (same credentials, production endpoints).\n\n**Scopes requested**: `datev:accounting:clients accounting:clients:read accounting:documents`. Do NOT add `openid` — it is the issuer name, not a requested scope.\n\n**Mandant scoping**: every per-client tool takes a `clientId` parameter — the **Mandant UUID** (RFC4122), returned by `datev_list_clients`. Distinct from `DATEV_CLIENT_ID` (the OAuth app identifier, sent in the header).",
6+
"region": "de",
7+
"category": "accounting",
8+
"icon": "datev",
9+
"docsUrl": "https://developer.datev.de/en/products/988700e4-e0c5-40bf-814b-cd7327dad67a",
10+
"requiredEnvVars": [
11+
"DATEV_CLIENT_ID",
12+
"DATEV_CLIENT_SECRET"
13+
],
14+
"connector": {
15+
"name": "DATEV Online APIs (Sandbox)",
16+
"type": "REST",
17+
"baseUrl": "https://accounting-clients.api.datev.de/platform-sandbox/v2",
18+
"authType": "OAUTH2",
19+
"authConfig": {
20+
"clientId": "{{DATEV_CLIENT_ID}}",
21+
"clientSecret": "{{DATEV_CLIENT_SECRET}}",
22+
"authorizationUrl": "https://login.datev.de/openidsandbox/authorize",
23+
"tokenUrl": "https://sandbox-api.datev.de/token",
24+
"tokenAuthMethod": "basic",
25+
"scopes": "datev:accounting:clients accounting:clients:read accounting:documents"
26+
},
27+
"headers": {
28+
"X-DATEV-Client-Id": "{{DATEV_CLIENT_ID}}"
29+
}
30+
},
31+
"tools": [
32+
{
33+
"name": "datev_list_clients",
34+
"description": "List DATEV accounting Mandanten (clients) your DATEV-Konto user is provisioned for, from the accounting-clients v2 master-data product. Returns each client's UUID, consultant number, client number and display name. Use the returned UUID as the `clientId` parameter on all other tools.",
35+
"parameters": {
36+
"type": "object",
37+
"properties": {
38+
"skip": {
39+
"type": "integer",
40+
"description": "Pagination offset (>= 0)."
41+
},
42+
"top": {
43+
"type": "integer",
44+
"description": "Max items per page (1-100)."
45+
}
46+
}
47+
},
48+
"endpointMapping": {
49+
"method": "GET",
50+
"path": "https://accounting-clients.api.datev.de/platform-sandbox/v2/clients",
51+
"queryParams": {
52+
"skip": "$skip",
53+
"top": "$top"
54+
},
55+
"headers": {
56+
"Accept": "application/json"
57+
}
58+
}
59+
},
60+
{
61+
"name": "datev_get_client",
62+
"description": "Get one Mandant's master data (consultant/client number, name, address) from accounting-clients v2.",
63+
"parameters": {
64+
"type": "object",
65+
"properties": {
66+
"clientId": {
67+
"type": "string",
68+
"description": "Mandant UUID (RFC4122 8-4-4-4-12). Get this from datev_list_clients."
69+
}
70+
},
71+
"required": [
72+
"clientId"
73+
]
74+
},
75+
"endpointMapping": {
76+
"method": "GET",
77+
"path": "https://accounting-clients.api.datev.de/platform-sandbox/v2/clients/{clientId}",
78+
"headers": {
79+
"Accept": "application/json"
80+
}
81+
}
82+
},
83+
{
84+
"name": "datev_documents_list_clients",
85+
"description": "List the subset of Mandanten that have DATEV Unternehmen online (DUO) document access via the accounting-documents v2 product. This may be smaller than datev_list_clients — only clients with active DUO subscriptions appear here.",
86+
"parameters": {
87+
"type": "object",
88+
"properties": {}
89+
},
90+
"endpointMapping": {
91+
"method": "GET",
92+
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients",
93+
"headers": {
94+
"Accept": "application/json"
95+
}
96+
}
97+
},
98+
{
99+
"name": "datev_get_documents_client",
100+
"description": "Get one Mandant's basic data as exposed by the accounting-documents v2 product (the DUO view, distinct from the accounting-clients master record).",
101+
"parameters": {
102+
"type": "object",
103+
"properties": {
104+
"clientId": {
105+
"type": "string",
106+
"description": "Mandant UUID (from datev_list_clients or datev_documents_list_clients)."
107+
}
108+
},
109+
"required": [
110+
"clientId"
111+
]
112+
},
113+
"endpointMapping": {
114+
"method": "GET",
115+
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients/{clientId}",
116+
"headers": {
117+
"Accept": "application/json"
118+
}
119+
}
120+
},
121+
{
122+
"name": "datev_list_document_types",
123+
"description": "List the document types (Belegtypen) configured for a Mandant on DATEV Unternehmen online — needed before uploading documents so you know which `documentType` values are accepted.",
124+
"parameters": {
125+
"type": "object",
126+
"properties": {
127+
"clientId": {
128+
"type": "string",
129+
"description": "Mandant UUID (from datev_list_clients)."
130+
}
131+
},
132+
"required": [
133+
"clientId"
134+
]
135+
},
136+
"endpointMapping": {
137+
"method": "GET",
138+
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients/{clientId}/document-types",
139+
"headers": {
140+
"Accept": "application/json"
141+
}
142+
}
143+
},
144+
{
145+
"name": "datev_get_duo_version",
146+
"description": "Get the DATEV Unternehmen online (DUO) version and the permitted file extensions for document upload on a given Mandant. Useful before generating uploads to know what file formats DUO will accept (PDF, JPG, TIFF, etc.) and any DUO-version-specific limits.",
147+
"parameters": {
148+
"type": "object",
149+
"properties": {
150+
"clientId": {
151+
"type": "string",
152+
"description": "Mandant UUID (from datev_list_clients)."
153+
}
154+
},
155+
"required": [
156+
"clientId"
157+
]
158+
},
159+
"endpointMapping": {
160+
"method": "GET",
161+
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients/{clientId}/duo-version",
162+
"headers": {
163+
"Accept": "application/json"
164+
}
165+
}
166+
}
167+
]
168+
}

packages/backend/src/adapters/de/datev.json

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
"slug": "datev",
33
"name": "DATEV Online APIs",
44
"description": "Read accounting Mandanten (clients), document types and DATEV Unternehmen online (DUO) version metadata from DATEV's official Online APIs. OAuth2 Authorization Code + PKCE bound to a real DATEV-Konto. Required by virtually every German tax consultant (Steuerberater).",
5-
"instructions": "**Important — this connector is NOT plug-and-play.** DATEV's Online APIs require:\n\n1. A registered app on the DATEV-Entwicklerportal (https://developer.datev.de) → **Confidential client** + **OpenID Connect Authorization Code Flow**. Set the redirect URI to `<your-server>/api/mcp-oauth/callback`.\n2. A subscription to each API product you want to use — currently `accounting-clients v2.0` and `accounting-documents v2.0`. Both are *Already subscribed* checks on the product page.\n3. A real DATEV-Konto user with SmartLogin or SmartCard/mIDentity. DATEV does NOT expose `client_credentials` — every call is on-behalf-of a human accountant. Confidential clients get 2-year rolling refresh tokens (the engine refreshes proactively).\n4. For *.api.datev.de (production) you must complete DATEV's production-approval review. Until then, point the connector at the **sandbox** (see below).\n\n**Authentication endpoints** (canonical, from `https://login.datev.de/openid/.well-known/openid-configuration`):\n- Production: `authorize=https://login.datev.de/openid/authorize`, `token=https://api.datev.de/token`\n- Sandbox: `authorize=https://login.datev.de/openidsandbox/authorize`, `token=https://sandbox-api.datev.de/token`\n\n**Setup**:\n1. Set `DATEV_CLIENT_ID` and `DATEV_CLIENT_SECRET` (from the DATEV portal).\n2. Import this adapter — defaults to **production** auth URLs.\n3. Run the one-time OAuth flow: `POST /api/connectors/{id}/oauth/authorize` → open the returned URL → log in with your DATEV-Konto → the platform stores an encrypted refresh token automatically.\n4. From now on every call sends `Authorization: Bearer <token>` AND the mandatory `X-DATEV-Client-Id: <DATEV_CLIENT_ID>` header automatically.\n\n**Switching to sandbox** (DATEV apps default to sandbox after creation, until production approval): after import, edit the connector's `authConfig`:\n- `authorizationUrl` → `https://login.datev.de/openidsandbox/authorize`\n- `tokenUrl` → `https://sandbox-api.datev.de/token`\n\nAND replace `platform` with `platform-sandbox` in each tool's `endpointMapping.path` (5 paths). A future iteration may expose this via env-var switching.\n\n**Scopes requested**: `datev:accounting:clients accounting:clients:read accounting:documents`. These cover both API products in one consent screen. Note: the current DATEV `datev_openid` flow does NOT include `openid` in its scope list (it's the issuer name, not a requested scope) — confirmed in the OpenAPI security schemes.\n\n**Mandant scoping**: every per-client tool takes a `clientId` parameter — that's the **Mandant UUID** (RFC4122 8-4-4-4-12), returned by `datev_list_clients`. **Distinct** from `DATEV_CLIENT_ID` (the OAuth app identifier, sent in the header).\n\n**Not exposed here** (engine limitation):\n- `POST /clients/{id}/documents`, `PUT /clients/{id}/documents/{guid}`, `PUT /clients/{id}/documents/stapled`: all require `multipart/form-data` with a binary file part. The REST engine currently serializes form-data field values via `String(v)`, which corrupts binary PDFs/images. These upload tools land once the engine accepts `Buffer`/base64 in form-data values.\n- `accounting:dataexchange`, `accounting:dxso-jobs`, `accounting:extf-files`, HR / Lohn, DMS, addressee: separate adapters per product line.\n- The `datev_idp_next` security option (next-gen `signin.datev.de/datevam/...` IdP) is defined in the spec but not yet exposed here.",
5+
"instructions": "**Important — this connector is NOT plug-and-play.** DATEV's Online APIs require:\n\n1. A registered app on the DATEV-Entwicklerportal (https://developer.datev.de) → **Confidential client** + **OpenID Connect Authorization Code Flow**. Set the redirect URI to `<your-server>/api/mcp-oauth/callback`.\n2. A subscription to each API product you want to use — currently `accounting-clients v2.0` and `accounting-documents v2.0`. Both are *Already subscribed* checks on the product page.\n3. A real DATEV-Konto user with SmartLogin or SmartCard/mIDentity. DATEV does NOT expose `client_credentials` — every call is on-behalf-of a human accountant. Confidential clients get 2-year rolling refresh tokens (the engine refreshes proactively).\n4. For *.api.datev.de (production) you must complete DATEV's production-approval review. Until then, point the connector at the **sandbox** (see below).\n\n**Authentication endpoints** (canonical, from `https://login.datev.de/openid/.well-known/openid-configuration`):\n- Production: `authorize=https://login.datev.de/openid/authorize`, `token=https://api.datev.de/token`\n- Sandbox: `authorize=https://login.datev.de/openidsandbox/authorize`, `token=https://sandbox-api.datev.de/token`\n\n**Setup**:\n1. Set `DATEV_CLIENT_ID` and `DATEV_CLIENT_SECRET` (from the DATEV portal).\n2. Import this adapter — defaults to **production** auth URLs.\n3. Run the one-time OAuth flow: `POST /api/connectors/{id}/oauth/authorize` → open the returned URL → log in with your DATEV-Konto → the platform stores an encrypted refresh token automatically.\n4. From now on every call sends `Authorization: Bearer <token>` AND the mandatory `X-DATEV-Client-Id: <DATEV_CLIENT_ID>` header automatically.\n\n**Testing first?** Every DATEV app runs in the sandbox until DATEV grants production approval. For sandbox testing use the dedicated **DATEV Online APIs (Sandbox)** connector (pre-configured for the sandbox endpoints) rather than editing this one — then switch to this production connector once approved, using the same credentials.\n\n**Scopes requested**: `datev:accounting:clients accounting:clients:read accounting:documents`. These cover both API products in one consent screen. Note: the current DATEV `datev_openid` flow does NOT include `openid` in its scope list (it's the issuer name, not a requested scope) — confirmed in the OpenAPI security schemes.\n\n**Mandant scoping**: every per-client tool takes a `clientId` parameter — that's the **Mandant UUID** (RFC4122 8-4-4-4-12), returned by `datev_list_clients`. **Distinct** from `DATEV_CLIENT_ID` (the OAuth app identifier, sent in the header).\n\n**Not exposed here** (engine limitation):\n- `POST /clients/{id}/documents`, `PUT /clients/{id}/documents/{guid}`, `PUT /clients/{id}/documents/stapled`: all require `multipart/form-data` with a binary file part. The REST engine currently serializes form-data field values via `String(v)`, which corrupts binary PDFs/images. These upload tools land once the engine accepts `Buffer`/base64 in form-data values.\n- `accounting:dataexchange`, `accounting:dxso-jobs`, `accounting:extf-files`, HR / Lohn, DMS, addressee: separate adapters per product line.\n- The `datev_idp_next` security option (next-gen `signin.datev.de/datevam/...` IdP) is defined in the spec but not yet exposed here.",
66
"region": "de",
77
"category": "accounting",
88
"icon": "datev",
99
"docsUrl": "https://developer.datev.de/en/products/988700e4-e0c5-40bf-814b-cd7327dad67a",
10-
"requiredEnvVars": ["DATEV_CLIENT_ID", "DATEV_CLIENT_SECRET"],
10+
"requiredEnvVars": [
11+
"DATEV_CLIENT_ID",
12+
"DATEV_CLIENT_SECRET"
13+
],
1114
"connector": {
1215
"name": "DATEV Online APIs",
1316
"type": "REST",
@@ -65,7 +68,9 @@
6568
"description": "Mandant UUID (RFC4122 8-4-4-4-12). Get this from datev_list_clients."
6669
}
6770
},
68-
"required": ["clientId"]
71+
"required": [
72+
"clientId"
73+
]
6974
},
7075
"endpointMapping": {
7176
"method": "GET",
@@ -101,7 +106,9 @@
101106
"description": "Mandant UUID (from datev_list_clients or datev_documents_list_clients)."
102107
}
103108
},
104-
"required": ["clientId"]
109+
"required": [
110+
"clientId"
111+
]
105112
},
106113
"endpointMapping": {
107114
"method": "GET",
@@ -122,7 +129,9 @@
122129
"description": "Mandant UUID (from datev_list_clients)."
123130
}
124131
},
125-
"required": ["clientId"]
132+
"required": [
133+
"clientId"
134+
]
126135
},
127136
"endpointMapping": {
128137
"method": "GET",
@@ -143,7 +152,9 @@
143152
"description": "Mandant UUID (from datev_list_clients)."
144153
}
145154
},
146-
"required": ["clientId"]
155+
"required": [
156+
"clientId"
157+
]
147158
},
148159
"endpointMapping": {
149160
"method": "GET",

packages/backend/src/connectors/connectors.service.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,31 @@ export class ConnectorsService {
157157
});
158158
}
159159

160+
/**
161+
* Merges `patch` into the connector's existing (decrypted) authConfig and
162+
* re-encrypts it, instead of replacing the whole object. Used by the OAuth
163+
* callback so storing the freshly-issued tokens does not drop static config
164+
* such as `authorizationUrl` and `scopes` (needed for later re-authorization).
165+
*/
166+
async updateAuthConfigMerge(
167+
id: string,
168+
patch: Record<string, unknown>,
169+
): Promise<Connector> {
170+
const connector = await this.findByIdInternal(id);
171+
const existing = connector.authConfig
172+
? (JSON.parse(
173+
decrypt(connector.authConfig, this.encryptionKey),
174+
) as Record<string, unknown>)
175+
: {};
176+
const merged = { ...existing, ...patch };
177+
return this.prisma.connector.update({
178+
where: { id },
179+
data: {
180+
authConfig: encrypt(JSON.stringify(merged), this.encryptionKey),
181+
},
182+
});
183+
}
184+
160185
async remove(id: string): Promise<void> {
161186
await this.findById(id);
162187
await this.prisma.connector.delete({ where: { id } });

0 commit comments

Comments
 (0)