Skip to content
Merged
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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "anythingmcp",
"version": "0.3.5",
"version": "0.3.6",
"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).",
"private": true,
"license": "AGPL-3.0-only",
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anythingmcp/backend",
"version": "0.3.5",
"version": "0.3.6",
"description": "AnythingMCP — NestJS Backend + Dynamic MCP Server",
"private": true,
"license": "AGPL-3.0-only",
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/adapters/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as billbee from './de/billbee.json';
import * as billomat from './de/billomat.json';
import * as bundesbank from './de/bundesbank.json';
import * as datev from './de/datev.json';
import * as datevSandbox from './de/datev-sandbox.json';
import * as destatisGenesis from './de/destatis-genesis.json';
import * as deutscheBahn from './de/deutsche-bahn.json';
import * as dhlTracking from './de/dhl-tracking.json';
Expand Down Expand Up @@ -283,6 +284,7 @@ const RAW_ADAPTERS: AdapterDefinition[] = [
billomat as unknown as AdapterDefinition,
bundesbank as unknown as AdapterDefinition,
datev as unknown as AdapterDefinition,
datevSandbox as unknown as AdapterDefinition,
destatisGenesis as unknown as AdapterDefinition,
deutscheBahn as unknown as AdapterDefinition,
dhlTracking as unknown as AdapterDefinition,
Expand Down
168 changes: 168 additions & 0 deletions packages/backend/src/adapters/de/datev-sandbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
{
"slug": "datev-sandbox",
"name": "DATEV Online APIs (Sandbox)",
"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.",
"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).",
"region": "de",
"category": "accounting",
"icon": "datev",
"docsUrl": "https://developer.datev.de/en/products/988700e4-e0c5-40bf-814b-cd7327dad67a",
"requiredEnvVars": [
"DATEV_CLIENT_ID",
"DATEV_CLIENT_SECRET"
],
"connector": {
"name": "DATEV Online APIs (Sandbox)",
"type": "REST",
"baseUrl": "https://accounting-clients.api.datev.de/platform-sandbox/v2",
"authType": "OAUTH2",
"authConfig": {
"clientId": "{{DATEV_CLIENT_ID}}",
"clientSecret": "{{DATEV_CLIENT_SECRET}}",
"authorizationUrl": "https://login.datev.de/openidsandbox/authorize",
"tokenUrl": "https://sandbox-api.datev.de/token",
"tokenAuthMethod": "basic",
"scopes": "datev:accounting:clients accounting:clients:read accounting:documents"
},
"headers": {
"X-DATEV-Client-Id": "{{DATEV_CLIENT_ID}}"
}
},
"tools": [
{
"name": "datev_list_clients",
"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.",
"parameters": {
"type": "object",
"properties": {
"skip": {
"type": "integer",
"description": "Pagination offset (>= 0)."
},
"top": {
"type": "integer",
"description": "Max items per page (1-100)."
}
}
},
"endpointMapping": {
"method": "GET",
"path": "https://accounting-clients.api.datev.de/platform-sandbox/v2/clients",
"queryParams": {
"skip": "$skip",
"top": "$top"
},
"headers": {
"Accept": "application/json"
}
}
},
{
"name": "datev_get_client",
"description": "Get one Mandant's master data (consultant/client number, name, address) from accounting-clients v2.",
"parameters": {
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "Mandant UUID (RFC4122 8-4-4-4-12). Get this from datev_list_clients."
}
},
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
"path": "https://accounting-clients.api.datev.de/platform-sandbox/v2/clients/{clientId}",
"headers": {
"Accept": "application/json"
}
}
},
{
"name": "datev_documents_list_clients",
"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.",
"parameters": {
"type": "object",
"properties": {}
},
"endpointMapping": {
"method": "GET",
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients",
"headers": {
"Accept": "application/json"
}
}
},
{
"name": "datev_get_documents_client",
"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).",
"parameters": {
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "Mandant UUID (from datev_list_clients or datev_documents_list_clients)."
}
},
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients/{clientId}",
"headers": {
"Accept": "application/json"
}
}
},
{
"name": "datev_list_document_types",
"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.",
"parameters": {
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "Mandant UUID (from datev_list_clients)."
}
},
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients/{clientId}/document-types",
"headers": {
"Accept": "application/json"
}
}
},
{
"name": "datev_get_duo_version",
"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.",
"parameters": {
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "Mandant UUID (from datev_list_clients)."
}
},
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
"path": "https://accounting-documents.api.datev.de/platform-sandbox/v2/clients/{clientId}/duo-version",
"headers": {
"Accept": "application/json"
}
}
}
]
}
23 changes: 17 additions & 6 deletions packages/backend/src/adapters/de/datev.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
"slug": "datev",
"name": "DATEV Online APIs",
"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).",
"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.",
"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.",
"region": "de",
"category": "accounting",
"icon": "datev",
"docsUrl": "https://developer.datev.de/en/products/988700e4-e0c5-40bf-814b-cd7327dad67a",
"requiredEnvVars": ["DATEV_CLIENT_ID", "DATEV_CLIENT_SECRET"],
"requiredEnvVars": [
"DATEV_CLIENT_ID",
"DATEV_CLIENT_SECRET"
],
"connector": {
"name": "DATEV Online APIs",
"type": "REST",
Expand Down Expand Up @@ -65,7 +68,9 @@
"description": "Mandant UUID (RFC4122 8-4-4-4-12). Get this from datev_list_clients."
}
},
"required": ["clientId"]
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
Expand Down Expand Up @@ -101,7 +106,9 @@
"description": "Mandant UUID (from datev_list_clients or datev_documents_list_clients)."
}
},
"required": ["clientId"]
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
Expand All @@ -122,7 +129,9 @@
"description": "Mandant UUID (from datev_list_clients)."
}
},
"required": ["clientId"]
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
Expand All @@ -143,7 +152,9 @@
"description": "Mandant UUID (from datev_list_clients)."
}
},
"required": ["clientId"]
"required": [
"clientId"
]
},
"endpointMapping": {
"method": "GET",
Expand Down
25 changes: 25 additions & 0 deletions packages/backend/src/connectors/connectors.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,31 @@ export class ConnectorsService {
});
}

/**
* Merges `patch` into the connector's existing (decrypted) authConfig and
* re-encrypts it, instead of replacing the whole object. Used by the OAuth
* callback so storing the freshly-issued tokens does not drop static config
* such as `authorizationUrl` and `scopes` (needed for later re-authorization).
*/
async updateAuthConfigMerge(
id: string,
patch: Record<string, unknown>,
): Promise<Connector> {
const connector = await this.findByIdInternal(id);
const existing = connector.authConfig
? (JSON.parse(
decrypt(connector.authConfig, this.encryptionKey),
) as Record<string, unknown>)
: {};
const merged = { ...existing, ...patch };
return this.prisma.connector.update({
where: { id },
data: {
authConfig: encrypt(JSON.stringify(merged), this.encryptionKey),
},
});
}

async remove(id: string): Promise<void> {
await this.findById(id);
await this.prisma.connector.delete({ where: { id } });
Expand Down
Loading
Loading