IN DIESEM ARTIKEL7 ABSCHNITTE
Der MCP-Server von Cord verbindet Assistenten wie Claude oder Cursor mit den echten Daten eines Unternehmens: Angebote, Rechnungen, überfällige Forderungen. Mit einem Schlüssel mit Schreibrechten sendet er außerdem Angebote und erfasst Zahlungen. Anders gesagt: ein Sprachmodell, das mit den Zugangsdaten eines Unternehmens handelt. Wir haben ihn von Anfang bis Ende auditiert, und das ist dabei herausgekommen.
1. Ein Leseschlüssel konnte schreiben
Cord stellt MCP über zwei Transporte bereit: zustandsloses HTTP (POST /api/mcp) und das ältere SSE (GET /api/mcp/sse, um die Sitzung zu öffnen, und POST /api/mcp/message, um Nachrichten zu senden). Die SSE-Sitzung speichert die Organisation, den Schlüssel und die Berechtigung, mit der sie geöffnet wurde.
Das Problem lag in /api/mcp/message. Der Endpoint prüfte, ob der Schlüssel, der die Nachricht sendet, zu derselben Organisation gehört wie die Sitzung, und führte das Tool anschließend mit der in der Sitzung gespeicherten Berechtigung aus. Ein Schlüssel mit reinem Lesezugriff desselben Unternehmens, der eine sessionId kannte, konnte so Schreib-Tools mit der Sitzung eines anderen Schlüssels ausführen.
Die Korrektur ist eine einzige Zeile, aber die richtige: Eine Sitzung gehört zu einem Schlüssel, nicht zu einer Organisation.
if (session.orgId !== auth.orgId || session.keyId !== auth.keyId) {
return rpcErrRes(null, -32001, 'Esta API key no corresponde a la sesión.', 403);
}
// und ausgeführt wird mit auth.scope, der Berechtigung des Aufrufers, nicht der der Sitzung
Dafür gibt es einen Test: Ein Leseschlüssel mit der sessionId eines Schreibschlüssels erhält 403, und das Tool wird nicht ausgeführt.
2. Ein Ausgabevertrag, der nicht eingehalten wurde
Alle 19 Tools deklarierten ein outputSchema. In der Protokollversion 2025-06-18 verpflichtet diese Deklaration dazu, structuredContent zurückzugeben, das dem Schema entspricht. Cord gab nur Text zurück. Außerdem hatten die Schemas additionalProperties: false und einen als String typisierten next_cursor, der tatsächlich null sein kann: Ein validierender Client hätte korrekte Antworten abgelehnt.
Jetzt antwortet jedes Tool mit beidem: structuredContent für Clients, die validieren, und demselben kompakten JSON als Text für die, die es nicht tun. Die Ausgabeschemas sind offen: Ein neues Feld bricht bei niemandem etwas. Und der Text ist nicht mehr eingerückt, was bei jedem Aufruf Tokens spart.
3. Beträge ohne Währung
Cord hat eine Regel: Ein Betrag ohne Währung ist eine Zahl, kein Geld. Die Tools für Angebote, Produkte und Kunden gaben total: 12500 zurück, ohne zu sagen, in welcher Währung. Ein Agent, der von Pesos ausgeht, meldet einem Unternehmen, das in Dollar verkauft, falsche Zahlen. Heute wird jeder Betrag zusammen mit seiner moneda übermittelt.
4. Fehler, die das Modell nicht erreichten
Ein Tool für das Forderungsmanagement warf einen generischen Fehler, wenn der Tarif es nicht enthielt. Der Server behandelte ihn als internen Fehler, und das Modell erhielt „Interner Serverfehler“, ohne dem Nutzer erklären zu können, was los war. Obendrein nannte die Meldung den falschen Tarif. Jetzt werden Tariflimits als fachliche Fehler gemeldet, mit dem Namen des Tarifs, der tatsächlich benötigt wird – gelesen aus demselben Vertrag, den auch der Rest von Cord verwendet.
Nebenbei stellten wir fest, dass der Index der Dokumentation bei einem Fehler den vollständigen Stacktrace zurückgab. Jetzt nicht mehr.
5. Sieben neue Tools
Wir sind von 19 auf 26 Tools gewachsen. Die neuen sind dafür gedacht, dass ein Agent Cord integrieren kann, nicht nur bedienen:
| Tool | Wofür |
|---|---|
contexto_cuenta |
Unternehmen, Land, Währung, Tarif und ob der Schlüssel ein Test- oder Live-Schlüssel ist. Der erste Aufruf. |
proponer_configuracion |
Schlägt Profil, Steuern und Katalog anhand der Website des Unternehmens vor. Gibt einen Link zurück; eine Person gibt frei. |
estado_configuracion |
Ob dieser Vorschlag bereits freigegeben wurde und was übernommen wurde. |
validar_datos_fiscales |
RFC, Steuerregime und CFDI-Verwendung, NIF oder EIN – mit denselben Regeln wie bei der Rechnungsstellung. |
validar_apariencia |
Prüft ein appearance von Cord Elements, bevor Sie es in den Code übernehmen. |
simular_evento |
Löst einen Test-Webhook über die echte Engine aus. Nur mit Testschlüssel. |
buscar_documentacion |
Durchsucht die Dokumentation und gibt die Seiten mit Auszug und URL zurück. |
Keines dieser Tools ändert Kontoeinstellungen direkt. proponer_configuracion hinterlässt einen Vorschlag, den eine Person in Cord freigibt – genau wie den aus dem Onboarding.
6. Eine API, die sich selbst beschreibt
Ein Agent, der Code gegen eine API schreibt, liest ihre Spezifikation. Von den 343 Feldern unserer OpenAPI-Spezifikation hatten 279 keine Beschreibung: Der Agent musste raten, was terminos ist, ob total Steuern enthält oder in welcher Einheit rate geliefert wird.
Jetzt gibt es ein Glossar, FIELD_DOCS, das jeden Feldnamen der API beschreibt, und der Generator wendet es auf jede Eigenschaft ohne eigene Beschreibung an. Der CI-Schritt, der die Spezifikation mit dem Code vergleicht, schlägt fehl, wenn eine Eigenschaft oder ein Parameter ohne Beschreibung bleibt. Ein Feld hinzuzufügen, ohne es zu erklären, kommt nicht mehr durch.
Was sich bewusst nicht geändert hat
- Kein Tool stempelt Rechnungen. Einen Entwurf anlegen: ja. Ihn bei der Steuerbehörde auszustellen, ist unwiderruflich und wird von einer Person ausgelöst.
- Text, den der Endkunde im öffentlichen Link schreibt, erreicht das Modell als
cliente_externomarkiert und abgegrenzt, damit es ihn meldet und nicht als Anweisung befolgt. - Aktionen, die beim Kunden ankommen (senden, bestätigen, ablehnen, Zahlung erfassen), tragen
destructiveHint, sodass ein gut konfigurierter MCP-Client vor der Ausführung um Bestätigung bittet.
Wenn Sie Cord mit Ihrem Assistenten verbinden, lassen Sie ihn zuerst contexto_cuenta aufrufen. Alles Weitere ergibt sich daraus.
Der MCP-Server von Cord verbindet Assistenten wie Claude oder Cursor mit den echten Daten eines Unternehmens: Angebote, Rechnungen, überfällige Forderungen. Mit einem Schlüssel mit Schreibrechten sendet er außerdem Angebote und erfasst Zahlungen. Anders gesagt: ein Sprachmodell, das mit den Zugangsdaten eines Unternehmens handelt. Wir haben ihn von Anfang bis Ende auditiert, und das ist dabei herausgekommen.
## 1. Ein Leseschlüssel konnte schreiben
Cord stellt MCP über zwei Transporte bereit: zustandsloses HTTP (`POST /api/mcp`) und das ältere SSE (`GET /api/mcp/sse`, um die Sitzung zu öffnen, und `POST /api/mcp/message`, um Nachrichten zu senden). Die SSE-Sitzung speichert die Organisation, den Schlüssel und die Berechtigung, mit der sie geöffnet wurde.
Das Problem lag in `/api/mcp/message`. Der Endpoint prüfte, ob der Schlüssel, der die Nachricht sendet, zu **derselben Organisation** gehört wie die Sitzung, und führte das Tool anschließend mit der **in der Sitzung gespeicherten Berechtigung** aus. Ein Schlüssel mit reinem Lesezugriff desselben Unternehmens, der eine sessionId kannte, konnte so Schreib-Tools mit der Sitzung eines anderen Schlüssels ausführen.
Die Korrektur ist eine einzige Zeile, aber die richtige: Eine Sitzung gehört zu einem Schlüssel, nicht zu einer Organisation.
```ts
if (session.orgId !== auth.orgId || session.keyId !== auth.keyId) {
return rpcErrRes(null, -32001, 'Esta API key no corresponde a la sesión.', 403);
}
// und ausgeführt wird mit auth.scope, der Berechtigung des Aufrufers, nicht der der Sitzung
```
Dafür gibt es einen Test: Ein Leseschlüssel mit der sessionId eines Schreibschlüssels erhält 403, und das Tool wird nicht ausgeführt.
## 2. Ein Ausgabevertrag, der nicht eingehalten wurde
Alle 19 Tools deklarierten ein `outputSchema`. In der Protokollversion 2025-06-18 verpflichtet diese Deklaration dazu, `structuredContent` zurückzugeben, das dem Schema entspricht. Cord gab nur Text zurück. Außerdem hatten die Schemas `additionalProperties: false` und einen als String typisierten `next_cursor`, der tatsächlich `null` sein kann: Ein validierender Client hätte korrekte Antworten abgelehnt.
Jetzt antwortet jedes Tool mit beidem: `structuredContent` für Clients, die validieren, und demselben kompakten JSON als Text für die, die es nicht tun. Die Ausgabeschemas sind offen: Ein neues Feld bricht bei niemandem etwas. Und der Text ist nicht mehr eingerückt, was bei jedem Aufruf Tokens spart.
## 3. Beträge ohne Währung
Cord hat eine Regel: Ein Betrag ohne Währung ist eine Zahl, kein Geld. Die Tools für Angebote, Produkte und Kunden gaben `total: 12500` zurück, ohne zu sagen, in welcher Währung. Ein Agent, der von Pesos ausgeht, meldet einem Unternehmen, das in Dollar verkauft, falsche Zahlen. Heute wird jeder Betrag zusammen mit seiner `moneda` übermittelt.
## 4. Fehler, die das Modell nicht erreichten
Ein Tool für das Forderungsmanagement warf einen generischen Fehler, wenn der Tarif es nicht enthielt. Der Server behandelte ihn als internen Fehler, und das Modell erhielt „Interner Serverfehler“, ohne dem Nutzer erklären zu können, was los war. Obendrein nannte die Meldung den falschen Tarif. Jetzt werden Tariflimits als fachliche Fehler gemeldet, mit dem Namen des Tarifs, der tatsächlich benötigt wird – gelesen aus demselben Vertrag, den auch der Rest von Cord verwendet.
Nebenbei stellten wir fest, dass der Index der Dokumentation bei einem Fehler den vollständigen Stacktrace zurückgab. Jetzt nicht mehr.
## 5. Sieben neue Tools
Wir sind von 19 auf 26 Tools gewachsen. Die neuen sind dafür gedacht, dass ein Agent Cord integrieren kann, nicht nur bedienen:
| Tool | Wofür |
|---|---|
| `contexto_cuenta` | Unternehmen, Land, Währung, Tarif und ob der Schlüssel ein Test- oder Live-Schlüssel ist. Der erste Aufruf. |
| `proponer_configuracion` | Schlägt Profil, Steuern und Katalog anhand der Website des Unternehmens vor. Gibt einen Link zurück; eine Person gibt frei. |
| `estado_configuracion` | Ob dieser Vorschlag bereits freigegeben wurde und was übernommen wurde. |
| `validar_datos_fiscales` | RFC, Steuerregime und CFDI-Verwendung, NIF oder EIN – mit denselben Regeln wie bei der Rechnungsstellung. |
| `validar_apariencia` | Prüft ein `appearance` von Cord Elements, bevor Sie es in den Code übernehmen. |
| `simular_evento` | Löst einen Test-Webhook über die echte Engine aus. Nur mit Testschlüssel. |
| `buscar_documentacion` | Durchsucht die Dokumentation und gibt die Seiten mit Auszug und URL zurück. |
Keines dieser Tools ändert Kontoeinstellungen direkt. `proponer_configuracion` hinterlässt einen Vorschlag, den eine Person in Cord freigibt – genau wie den aus dem Onboarding.
## 6. Eine API, die sich selbst beschreibt
Ein Agent, der Code gegen eine API schreibt, liest ihre Spezifikation. Von den 343 Feldern unserer OpenAPI-Spezifikation hatten 279 keine Beschreibung: Der Agent musste raten, was `terminos` ist, ob `total` Steuern enthält oder in welcher Einheit `rate` geliefert wird.
Jetzt gibt es ein Glossar, `FIELD_DOCS`, das jeden Feldnamen der API beschreibt, und der Generator wendet es auf jede Eigenschaft ohne eigene Beschreibung an. Der CI-Schritt, der die Spezifikation mit dem Code vergleicht, schlägt fehl, wenn eine Eigenschaft oder ein Parameter ohne Beschreibung bleibt. Ein Feld hinzuzufügen, ohne es zu erklären, kommt nicht mehr durch.
## Was sich bewusst nicht geändert hat
- **Kein Tool stempelt Rechnungen.** Einen Entwurf anlegen: ja. Ihn bei der Steuerbehörde auszustellen, ist unwiderruflich und wird von einer Person ausgelöst.
- **Text, den der Endkunde im öffentlichen Link schreibt,** erreicht das Modell als `cliente_externo` markiert und abgegrenzt, damit es ihn meldet und nicht als Anweisung befolgt.
- **Aktionen, die beim Kunden ankommen** (senden, bestätigen, ablehnen, Zahlung erfassen), tragen `destructiveHint`, sodass ein gut konfigurierter MCP-Client vor der Ausführung um Bestätigung bittet.
Wenn Sie Cord mit Ihrem Assistenten verbinden, lassen Sie ihn zuerst `contexto_cuenta` aufrufen. Alles Weitere ergibt sich daraus.