From a598e2a383d6213a558b809b161774768de82734 Mon Sep 17 00:00:00 2001 From: DoctoDre Date: Fri, 10 Jul 2026 12:22:31 +0200 Subject: [PATCH] chore(bahn): remove beam-mcp (educational only, not actively used) --- bahn/beam-mcp/.env.example | 25 - bahn/beam-mcp/.gitignore | 6 - bahn/beam-mcp/.kiro/steering/add-beam-tool.md | 137 ----- bahn/beam-mcp/.kiro/steering/beam-domain.md | 76 --- .../.kiro/steering/coding-standards.md | 86 ---- bahn/beam-mcp/CODEOWNERS | 2 - bahn/beam-mcp/COMPONENT.md | 16 - bahn/beam-mcp/LICENSE | 306 ----------- bahn/beam-mcp/README.md | 369 -------------- bahn/beam-mcp/package.json | 21 - bahn/beam-mcp/scm-info.yaml | 6 - bahn/beam-mcp/src/api.ts | 306 ----------- bahn/beam-mcp/src/auth.ts | 127 ----- bahn/beam-mcp/src/index.ts | 480 ------------------ bahn/beam-mcp/src/keychain.ts | 169 ------ bahn/beam-mcp/src/login.ts | 34 -- bahn/beam-mcp/tsconfig.json | 13 - 17 files changed, 2179 deletions(-) delete mode 100644 bahn/beam-mcp/.env.example delete mode 100644 bahn/beam-mcp/.gitignore delete mode 100644 bahn/beam-mcp/.kiro/steering/add-beam-tool.md delete mode 100644 bahn/beam-mcp/.kiro/steering/beam-domain.md delete mode 100644 bahn/beam-mcp/.kiro/steering/coding-standards.md delete mode 100644 bahn/beam-mcp/CODEOWNERS delete mode 100644 bahn/beam-mcp/COMPONENT.md delete mode 100644 bahn/beam-mcp/LICENSE delete mode 100644 bahn/beam-mcp/README.md delete mode 100644 bahn/beam-mcp/package.json delete mode 100644 bahn/beam-mcp/scm-info.yaml delete mode 100644 bahn/beam-mcp/src/api.ts delete mode 100644 bahn/beam-mcp/src/auth.ts delete mode 100644 bahn/beam-mcp/src/index.ts delete mode 100644 bahn/beam-mcp/src/keychain.ts delete mode 100644 bahn/beam-mcp/src/login.ts delete mode 100644 bahn/beam-mcp/tsconfig.json diff --git a/bahn/beam-mcp/.env.example b/bahn/beam-mcp/.env.example deleted file mode 100644 index 59589f3..0000000 --- a/bahn/beam-mcp/.env.example +++ /dev/null @@ -1,25 +0,0 @@ -# BEAM MCP Server - Konfiguration -# Kopiere diese Datei nach .env und passe die Werte an (optional) - -# ── BEAM URLs (optional, Standardwerte sind vorbelegt) ─────────────────────── -# Basis-URL der BEAM Instanz -BEAM_BASE_URL=https://db.leanix.net - -# Login-URL (für automatischen Browser-Login) -BEAM_LOGIN_URL=http://db.de/beam - -# Workspace-Name (für Token-Key im localStorage) -BEAM_WORKSPACE=beam - -# ── Browser-Login (optional) ───────────────────────────────────────────────── -# Headless-Modus für automatischen Browser-Login (true/false) -# Bei Anmeldeproblemen auf false setzen, um den Browser sichtbar zu starten -BEAM_HEADLESS=true - -# ── Authentifizierung ──────────────────────────────────────────────────────── -# Das BEAM-Token wird automatisch im OS-Keychain gespeichert: -# - macOS: Keychain Access (via `security` CLI) -# - Windows: DPAPI-verschlüsselte Datei in %LOCALAPPDATA%/beam-mcp/ -# -# Erstmaliger Login: `npm run login` oder automatisch beim ersten API-Aufruf. -# Danach wird das Token aus dem Keychain geladen — kein erneuter Login nötig. diff --git a/bahn/beam-mcp/.gitignore b/bahn/beam-mcp/.gitignore deleted file mode 100644 index 72b994b..0000000 --- a/bahn/beam-mcp/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.DS_Store -.env -package-lock.json -node_modules -dist -.idea diff --git a/bahn/beam-mcp/.kiro/steering/add-beam-tool.md b/bahn/beam-mcp/.kiro/steering/add-beam-tool.md deleted file mode 100644 index 8b62d4f..0000000 --- a/bahn/beam-mcp/.kiro/steering/add-beam-tool.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -inclusion: manual ---- - -# Neues BEAM Tool hinzufügen - -Schritt-für-Schritt Anleitung zum Erweitern des beam-mcp-servers um ein neues Tool. - -## 1. API-Funktion in `src/api.ts` - -Neue Datenabfragen gehören in `api.ts`. Nutze die bestehende `gql()`-Funktion für GraphQL-Queries: - -```typescript -export async function myNewFunction(param: string) { - // UUID-Erkennung: wenn keine UUID, per Suche auflösen - let uuid = param; - if (!param.match(/^[0-9a-f-]{36}$/i)) { - const results = await searchFactSheets(param); - if (!results.length) throw new Error(`No factsheet found for: ${param}`); - uuid = results[0].id as string; - } - - const data = (await gql( - `query MyQuery($id: ID!) { - factSheet(id: $id) { - id name type - ... on Application { - // gewünschte Felder - } - } - }`, - { id: uuid } - )) as { factSheet: Record }; - - return data.factSheet; -} -``` - -Für REST-Endpunkte (z.B. Bookmarks API) nutze `fetch` mit `getToken()`: - -```typescript -const token = await getToken(); -const res = await fetch(`${BEAM_BASE_URL}/services/pathfinder/v1/endpoint/${id}`, { - headers: { Authorization: `Bearer ${token}` }, -}); -``` - -## 2. Tool registrieren in `src/index.ts` - -Importiere die neue Funktion und registriere das Tool: - -```typescript -import { myNewFunction } from "./api.js"; - -server.registerTool( - "beam_my_new_tool", // beam_ Prefix + snake_case - { - title: "Kurzer deutscher Titel", - description: `Ausführliche Beschreibung was das Tool tut. - -Args: - - param1 (string, required): Beschreibung - - param2 (string, optional): Beschreibung - -Returns: - Was zurückgegeben wird. - -Examples: - - param1="Beispielwert" → Was passiert`, - inputSchema: { - param1: z.string() - .min(1, "Darf nicht leer sein") - .describe("Beschreibung des Parameters"), - param2: z.string() - .optional() - .describe("Optionaler Parameter"), - }, - annotations: { - readOnlyHint: true, // true wenn nur lesend - destructiveHint: false, // true wenn Daten gelöscht werden - idempotentHint: true, // true wenn wiederholbar ohne Seiteneffekte - openWorldHint: true, // true wenn externe Systeme angesprochen werden - }, - }, - async ({ param1, param2 }) => { - try { - const result = await myNewFunction(param1); - // Ergebnis formatieren... - const text = `## Ergebnis\n${JSON.stringify(result, null, 2)}`; - return { content: [{ type: "text", text: truncateIfNeeded(text) }] }; - } catch (error) { - return handleError(error); - } - } -); -``` - -## 3. Label-Mappings erweitern (falls nötig) - -Wenn neue BEAM-Werte gemappt werden müssen, die bestehenden Maps in `index.ts` erweitern: - -```typescript -const LC: Record = { - // ... bestehende Einträge - newValue: "Neuer Wert", -}; -``` - -## 4. Build & Test - -```bash -npm run build -``` - -Muss fehlerfrei durchlaufen. Danach MCP-Server neu starten. - -## 5. README aktualisieren - -Neues Tool in `README.md` unter "Verfügbare Tools" dokumentieren: -- Tool-Name mit `beam_` Prefix -- Parameter-Tabelle -- Annotations -- Beispiele -- Beispiel-Ausgabe - -## Checkliste - -- [ ] API-Funktion in `api.ts` (nicht in `index.ts`) -- [ ] `server.registerTool()` mit title, description, inputSchema, annotations -- [ ] Tool-Name: `beam_` + snake_case -- [ ] Description mit Args, Returns, Examples -- [ ] Zod-Schema mit `.describe()` und Validierung -- [ ] Alle 4 Annotations gesetzt -- [ ] try/catch mit `handleError()` -- [ ] `truncateIfNeeded()` für große Responses -- [ ] `npm run build` erfolgreich -- [ ] README.md aktualisiert diff --git a/bahn/beam-mcp/.kiro/steering/beam-domain.md b/bahn/beam-mcp/.kiro/steering/beam-domain.md deleted file mode 100644 index dc73652..0000000 --- a/bahn/beam-mcp/.kiro/steering/beam-domain.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -inclusion: always ---- - -# BEAM Domänenwissen - -## Was ist BEAM? - -BEAM (Business Enterprise Architecture Management) ist die Architektur-Plattform der Deutschen Bahn, basierend auf LeanIX. Sie dokumentiert die IT-Landschaft mit Anwendungen, Domänen, IT-Komponenten, Schnittstellen und Diagrammen. - -URL: https://db.leanix.net (intern: http://db.de/beam) - -## Zentrale Konzepte - -### Fact Sheets - -Fact Sheets sind die Kernobjekte in BEAM. Jedes hat eine UUID und optional eine externe Beam-ID. - -| Typ | Beam-ID Format | Beispiel | Beschreibung | -|-----|---------------|----------|--------------| -| Application | A-XXXXXX | A-106614 | Anwendung/Software-System | -| BusinessCapability | P.X.XX.XX.XX | P.F.05.05.04 | Fachliche Domäne/Capability | -| ITComponent | — | — | Technische Infrastruktur-Komponente | -| Interface | — | — | Schnittstelle zwischen Anwendungen | -| DataObject | — | — | Datenobjekt | - -### Lebenszyklus - -Jedes Fact Sheet hat einen Lebenszyklus-Status: -- `plan` → Plan -- `phaseIn` → Einführungsphase -- `active` → Aktiv -- `phaseOut` → Ausgliederungsphase -- `endOfLife` → Lebensende - -### Supporttyp (Domänen-Zuordnung) - -Anwendungen werden Domänen mit einem Supporttyp zugeordnet: -- `leading` → Führend (Hauptanwendung der Domäne) -- `supports` → Unterstützt (Hilfsanwendung) - -### Diagramme - -Diagramme in BEAM sind als "Bookmarks" gespeichert und enthalten mxGraph-XML (draw.io-kompatibel). Sie werden über die Documents-Relation eines Fact Sheets verknüpft. Typische Diagrammtypen: -- System Context (C4-Modell) -- Verteilungssicht (PROD/TST/ABN) -- Schnittstellen - -## BEAM GraphQL API - -Endpunkt: `https://db.leanix.net/services/pathfinder/v1/graphql` - -Wichtige Queries: -- `allFactSheets(filter, first)` — Suche mit Volltextfilter -- `factSheet(id)` — Einzelnes Fact Sheet mit Relationen -- Relationen: `relApplicationToBusinessCapability`, `relBusinessCapabilityToApplication`, `relApplicationToITComponent`, `relToSuccessor` - -## BEAM Bookmarks API (Diagramme) - -Endpunkt: `https://db.leanix.net/services/pathfinder/v1/bookmarks/{id}` - -Gibt ein JSON-Objekt zurück mit: -- `data.name` — Diagrammname -- `data.groupKey` — Typ (z.B. "freedraw") -- `data.state.graphXml` — mxGraph-XML (draw.io-kompatibel) -- `data.description` — Beschreibung -- `data.referencedFactSheetIds` — Verknüpfte Fact Sheets - -## Terminologie-Mapping - -Der MCP-Server mappt LeanIX-interne Begriffe auf BEAM-Terminologie: -- Application → Anwendung -- BusinessCapability → Domäne -- ITComponent → IT-Komponente -- Interface → Integration (Schnittstelle) -- lxState APPROVED → Bestätigt diff --git a/bahn/beam-mcp/.kiro/steering/coding-standards.md b/bahn/beam-mcp/.kiro/steering/coding-standards.md deleted file mode 100644 index 7091bb5..0000000 --- a/bahn/beam-mcp/.kiro/steering/coding-standards.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -inclusion: always ---- - -# Coding Standards — beam-mcp-server - -## Projektstruktur - -``` -src/ -├── index.ts # MCP Server, Tool-Registrierung, Label-Mappings, Error-Handling -├── api.ts # BEAM GraphQL/REST API Client, Diagramm-Funktionen -├── auth.ts # Token-Management (Keychain + Playwright Browser-Login) -└── keychain.ts # OS-Keychain Abstraktionsschicht (macOS/Windows) -``` - -## TypeScript-Konventionen - -- ESM (`"type": "module"` in package.json), alle Imports mit `.js`-Suffix -- Strict mode aktiviert, kein `any` — nutze `Record` oder konkrete Interfaces -- Target: ES2022, Module: Node16 -- Build: `npm run build` (tsc), Output in `dist/` - -## MCP Tool-Konventionen - -- Alle Tools nutzen `server.registerTool()` (NICHT `server.tool()`) -- Tool-Namen: `beam_` Prefix + snake_case (z.B. `beam_search_factsheets`) -- Jedes Tool braucht: - - `title`: Kurzer deutscher Titel - - `description`: Ausführliche Beschreibung mit Args, Returns, Examples - - `inputSchema`: Zod-Schemas mit `.describe()` und Validierung (`.min()`, `.max()`) - - `annotations`: `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` -- Jeder Tool-Handler ist in try/catch gewrappt und nutzt `handleError()` bei Fehlern -- Große Responses werden mit `truncateIfNeeded()` auf CHARACTER_LIMIT (25k) gekürzt - -## Error-Handling Pattern - -```typescript -async (params) => { - try { - // ... Logik ... - return { content: [{ type: "text", text: result }] }; - } catch (error) { - return handleError(error); - } -} -``` - -`handleError()` gibt `{ isError: true, content: [...] }` zurück mit kontextbezogenen Meldungen: -- 401/403 → Hinweis auf `beam_get_token` -- 404 → ID/Suchbegriff prüfen -- 429 → Rate-Limit, warten - -## API-Schicht (api.ts) - -- `gql()` — Shared GraphQL-Client mit Auth-Header -- Alle API-Funktionen sind `async` und werfen Errors mit HTTP-Status -- UUID-Erkennung: `/^[0-9a-f-]{36}$/i` — alles andere wird als Beam-ID/Name per Suche aufgelöst -- Neue API-Funktionen gehören in `api.ts`, nicht in `index.ts` - -## Label-Mappings (index.ts) - -BEAM-interne Werte werden auf deutsche Labels gemappt: -- `LC` — Lebenszyklus (active→Aktiv, phaseOut→Ausgliederungsphase, ...) -- `ST` — Supporttyp (leading→Führend, supports→Unterstützt) -- `FS_TYPE` — Fact-Sheet-Typen (Application→Anwendung, ...) -- `STATE` — Status (APPROVED→Bestätigt, ...) - -## Auth (auth.ts) & Keychain (keychain.ts) - -- Token-Priorität: In-Memory Cache → OS-Keychain → Playwright Browser-Login -- Token wird im OS-Keychain gespeichert (macOS: Keychain Access, Windows: DPAPI) -- Keine .env-Datei für Secrets — nur für Konfigurations-URLs -- Gültigkeit: JWT exp-Claim minus 60s Puffer -- Bei Invalidierung (401): Token aus Memory + Keychain gelöscht -- Logging nur auf stderr (stdio-Transport!) - -## Build & Test - -```bash -npm run build # TypeScript kompilieren -npm start # Server starten (stdio) -npm run login # Manueller Browser-Login + Token im Keychain speichern -``` - -Nach jeder Änderung: `npm run build` muss fehlerfrei durchlaufen. diff --git a/bahn/beam-mcp/CODEOWNERS b/bahn/beam-mcp/CODEOWNERS deleted file mode 100644 index 04273b4..0000000 --- a/bahn/beam-mcp/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -^[everything] -* @FlorianHofmann diff --git a/bahn/beam-mcp/COMPONENT.md b/bahn/beam-mcp/COMPONENT.md deleted file mode 100644 index 86698cf..0000000 --- a/bahn/beam-mcp/COMPONENT.md +++ /dev/null @@ -1,16 +0,0 @@ -# Component: beam-mcp - -## Description -MCP server for LeanIX/BEAM enterprise architecture (educational reference only) - -## Metadata -- **Deployment Target:** local-mcp-stdio -- **Upstream URL:** https://git.tech.rz.db.de/beta/ai/mcp/beam-mcp -- **Status:** active - -## Interconnections -- (to be documented) - -## Notes -- Part of bahn context in the Orchestrator monorepo - diff --git a/bahn/beam-mcp/LICENSE b/bahn/beam-mcp/LICENSE deleted file mode 100644 index 4ef0fa6..0000000 --- a/bahn/beam-mcp/LICENSE +++ /dev/null @@ -1,306 +0,0 @@ -DB Inner Source Lizenz Version 1.0 - -_Fachautoren: Cornelius Schumacher, Schlomo Schapiro (DB Systel GmbH)_ - -Diese Inner-Source-Lizenz für die Deutsche Bahn („DBISL“) gilt für Werke -(im Sinne der nachfolgenden Begriffsbestimmung), die unter -DBISL-Bedingungen zur Verfügung gestellt werden. Das Werk darf nur in -der durch diese Lizenz gestatteten Form genutzt werden (insoweit eine -solche Nutzung dem Urheber vorbehalten ist). - -Das Werk wird unter den Bedingungen dieser Lizenz zur Verfügung -gestellt, wenn der Lizenzgeber (im Sinne der nachfolgenden -Begriffsbestimmung) den folgenden Hinweis unmittelbar hinter dem -Urheberrechtshinweis dieses Werks anbringt: - -„Lizenziert unter der DBISL“ oder alternativ „Licensed under the DBISL“ - -oder in einer anderen Form zum Ausdruck bringt, dass er es unter der -DBISL lizenzieren möchte. - -== 1. Begriffsbestimmungen - -Für diese Lizenz gelten folgende Begriffsbestimmungen: - -* „Lizenz“: diese Lizenz. -* „Originalwerk“: das Werk oder die Software, die vom Lizenzgeber unter -dieser Lizenz verbreitet oder zugänglich gemacht wird, und zwar als -Quellcode und gegebenenfalls auch als ausführbarer Code. -* „Bearbeitungen“: die Werke oder Software, die der Lizenznehmer auf der -Grundlage des Originalwerks oder seiner Bearbeitungen schaffen kann. In -dieser Lizenz wird nicht festgelegt, wie umfangreich die Änderung oder -wie stark die Abhängigkeit vom Originalwerk für eine Einstufung als -Bearbeitung sein muss; dies bestimmt sich nach dem anwendbaren -Urheberrecht -* „Werk“: das Originalwerk oder seine Bearbeitungen. -* „Quellcode“: diejenige Form des Werkes, die zur Auffassung durch den -Menschen bestimmt ist und die am besten geeignet ist, um vom Menschen -verstanden und verändert zu werden. -* „Ausführbarer Code“: die — üblicherweise — kompilierte Form des Werks, -die von einem Computer als Programm ausgeführt werden soll. -* „Lizenzgeber“: die juristische Person innerhalb des DB Konzerns, die -das Werk unter der Lizenz verbreitet oder zugänglich macht. -* „Urheberrechtsinhaber/Autor“: jeder, der bestimmte von ihm selbst -entwickelte oder von Dritten vorgegebene Aufgabenstellungen in ein -Originalwerk umsetzt oder am Originalwerk eine Bearbeitung vornimmt. -* „Bearbeiter“: jeder, der das Werk unter der Lizenz verändert oder auf -andere Weise zur Schaffung einer Bearbeitung beiträgt. Jeder Autor ist -auch Bearbeiter. -* „Lizenznehmer“ („Sie“, „Ihnen“): jede juristische Person innerhalb des -DB Konzerns, die das Werk unter den Lizenzbedingungen nutzt. -* „Verbreitung“ oder „Zugänglichmachung“: alle Formen von Verkauf, -Überlassung, Verleih, Vermietung, Verbreitung, Weitergabe, Übermittlung -oder anderweitiger Online- oder Offline-Bereitstellung von -Vervielfältigungen des Werks oder Zugänglichmachung seiner wesentlichen -Funktionen für dritte natürliche oder juristische Personen. -* „Beitrag“: jedes urheberrechtliche Werk, einschließlich des -Originalwerks sowie jeglicher Änderungen, die der Bearbeiter vornimmt, -und die dem Lizenzgeber bewusst zur Aufnahme in das Werk eingereicht -werden. -* „DB“ oder „DB Konzern“: die Deutsche Bahn AG und alle mit ihr nach § -15 AktG verbundenen Unternehmen. - -== 2. Umfang der Lizenzrechte - -Der Lizenzgeber erteilt Ihnen hiermit eine weltweite, unentgeltliche, -nicht ausschließliche, unterlizenzierbare Lizenz, die Sie für -Geschäftszwecke des DB Konzerns berechtigt: - -* das Werk uneingeschränkt zu nutzen, -* das Werk zu vervielfältigen, -* das Werk zu verändern und Bearbeitungen auf der Grundlage des Werks zu -schaffen, -* das Werk oder Vervielfältigungen davon innerhalb der DB zu verbreiten, - -Für die Wahrnehmung dieser Rechte können beliebige, derzeit bekannte -oder künftige Medien, Träger und Formate verwendet werden, soweit das -geltende Recht dem nicht entgegensteht. - -Der Lizenzgeber erteilt dem Lizenznehmer ein nicht ausschließliches, -unentgeltliches Nutzungsrecht an seinen Patenten, sofern dies zur -Ausübung der durch die Lizenz erteilten Nutzungsrechte am Werk notwendig -ist. - -== 3. Zugänglichmachung des Quellcodes - -Der Lizenzgeber kann das Werk entweder als Quellcode oder als -ausführbaren Code zur Verfügung stellen. Stellt er es als ausführbaren -Code zur Verfügung, so stellt er darüber hinaus eine maschinenlesbare -Kopie des Quellcodes für jedes von ihm verbreitete -Vervielfältigungsstück des Werks zur Verfügung, oder er verweist in -einem Vermerk im Anschluss an den dem Werk beigefügten -Urheberrechtshinweis auf einen Speicherort, an dem problemlos und -unentgeltlich auf den Quellcode zugegriffen werden kann, solange der -Lizenzgeber das Werk verbreitet oder zugänglich macht. - -== 4. Einschränkungen des Urheberrechts - -Es ist nicht Zweck dieser Lizenz, Ausnahmen oder Schranken der -ausschließlichen Rechte des Urhebers am Werk, die dem Lizenznehmer -zugutekommen, einzuschränken. Auch die Erschöpfung dieser Rechte bleibt -von dieser Lizenz unberührt. - -== 5. Pflichten des Lizenznehmers - -Die Einräumung der oben genannten Rechte ist an mehrere Beschränkungen -und Pflichten für den Lizenznehmer gebunden: - -* Inner Source: Der Lizenznehmer darf das Werk ausschließlich für -Geschäftszwecke des DB Konzerns nutzen. -* Urheberrechtshinweis, Lizenztext, Nennung des Bearbeiters: Der -Lizenznehmer muss alle Urheberrechts-, Patent- oder Markenrechtshinweise -und alle Hinweise auf die Lizenz und den Haftungsausschluss unverändert -lassen. Jedem von ihm verbreiteten oder zugänglich gemachten -Vervielfältigungsstück des Werks muss der Lizenznehmer diese Hinweise -sowie diese Lizenz beifügen. Der Lizenznehmer muss auf jedem -abgeleiteten Werk deutlich darauf hinweisen, dass das Werk geändert -wurde, und das Datum der Bearbeitung angeben. -* „Copyleft“-Klausel: Der Lizenznehmer darf Vervielfältigungen des -Originalwerks oder Bearbeitungen nur unter den Bedingungen dieser DBISL -oder einer neueren Version dieser Lizenz innerhalb der DB verbreiten -oder zugänglich machen. Der Lizenznehmer (der zum Lizenzgeber wird) darf -für das Werk oder die Bearbeitung keine zusätzlichen Bedingungen -anbieten oder vorschreiben, die die Bedingungen dieser Lizenz verändern -oder einschränken. -* Bereitstellung des Quellcodes: Wenn der Lizenznehmer -Vervielfältigungsstücke des Werks verbreitet oder zugänglich macht, muss -er eine maschinenlesbare Fassung des Quellcodes mitliefern oder einen -Speicherort angeben, über den problemlos und unentgeltlich so lange auf -diesen Quellcode zugegriffen werden kann, wie der Lizenznehmer das Werk -verbreitet oder zugänglich macht. -* Rechtsschutz: Diese Lizenz erlaubt nicht die Benutzung von -Kennzeichen, Marken oder geschützten Namensrechten des Lizenzgebers, -soweit dies nicht für die angemessene und übliche Beschreibung der -Herkunft des Werks und der inhaltlichen Wiedergabe des -Urheberrechtshinweises erforderlich ist. - -== 6. Urheber und Bearbeiter - -Der ursprüngliche Lizenzgeber gewährleistet, dass er das Urheberrecht am -Originalwerk innehat oder dieses an ihn lizenziert wurde und dass er -befugt ist, diese Lizenz zu erteilen. - -Jeder Bearbeiter gewährleistet, dass er das Urheberrecht an den von ihm -vorgenommenen Änderungen des Werks besitzt und befugt ist, einen Beitrag -unter dieser Lizenz zu erstellen und beizutragen. - -Für jeden Fall, in dem der Lizenznehmer die Lizenz annimmt, erteilt der -ursprüngliche Lizenzgeber und alle folgenden Bearbeiter eine Befugnis -zur Nutzung der Beiträge zum Werk unter den Bedingungen dieser Lizenz. - -== 7. Gewährleistungsausschluss - -Falls die konzerninternen Leistungsbedingungen keine Anwendung finden, -gelten die folgenden Regelungen. - -Die Arbeit an diesem Werk wird laufend fortgeführt; es wird durch -unzählige Bearbeiter ständig verbessert. Das Werk ist nicht vollendet -und kann daher Fehler („Bugs“) enthalten, die dieser Art der Entwicklung -inhärent sind. - -Aus den genannten Gründen wird das Werk unter dieser Lizenz „so, wie es -ist“ ohne jegliche Gewährleistung zur Verfügung gestellt. Dies gilt -unter anderem — aber nicht ausschließlich — für Marktreife, -Verwendbarkeit für einen bestimmten Zweck, Mängelfreiheit, Richtigkeit -sowie Nichtverletzung von anderen Immaterialgüterrechten als dem -Urheberrecht (vgl. dazu Artikel 6 dieser Lizenz). - -Dieser Gewährleistungsausschluss ist wesentlicher Bestandteil der Lizenz -und Bedingung für die Einräumung von Rechten an dem Werk. - -== 8. Haftungsausschluss/Haftungsbeschränkung - -Falls die konzerninternen Leistungsbedingungen keine Anwendung finden, -gelten die folgenden Regelungen. - -Außer in Fällen von Vorsatz oder der Verursachung von Personenschäden -haftet der Lizenzgeber nicht für direkte oder indirekte, materielle oder -immaterielle Schäden irgendwelcher Art, die aus der Lizenz oder der -Benutzung des Werks folgen; dies gilt unter anderem, aber nicht -ausschließlich, für Firmenwertverluste, Produktionsausfall, -Computerausfall oder Computerfehler, Datenverlust oder wirtschaftliche -Schäden, und zwar auch dann, wenn der Lizenzgeber auf die Möglichkeit -solcher Schäden hingewiesen wurde. Unabhängig davon haftet der -Lizenzgeber im Rahmen der gesetzlichen Produkthaftung, soweit die -entsprechenden Regelungen auf das Werk anwendbar sind. - -== 9. Zusatzvereinbarungen - -Wenn der Lizenznehmer das Werk verbreitet, kann er Zusatzvereinbarungen -schließen, in denen Verpflichtungen oder Dienstleistungen festgelegt -werden, die mit dieser Lizenz vereinbar sind. - -Der Lizenznehmer darf Verpflichtungen nur in seinem eigenen Namen -eingehen, nicht jedoch im Namen des ursprünglichen Lizenzgebers oder -eines anderen Bearbeiters, und nur, wenn er sich gegenüber allen -Bearbeitern verpflichtet, sie zu entschädigen, zu verteidigen und von -der Haftung freizustellen, falls aufgrund der von ihm eingegangenen -Gewährleistungsverpflichtung oder Haftungsübernahme Forderungen gegen -sie geltend gemacht werden oder eine Haftungsverpflichtung entsteht. - -== 10. Annahme der Lizenz - -Der Lizenznehmer stimmt den Bestimmungen dieser Lizenz zu, indem er das -Symbol „Lizenz annehmen“ unter dem Fenster mit dem Lizenztext anklickt -oder indem er seine Zustimmung auf vergleichbare Weise gibt. Das -Anklicken des Symbols gilt als Anzeichen der eindeutigen und -unwiderruflichen Annahme der Lizenz und der darin enthaltenen Klauseln -und Bedingungen. - -In gleicher Weise gilt als Zeichen der eindeutigen und unwiderruflichen -Zustimmung die Ausübung eines Rechtes, das in Artikel 2 dieser Lizenz -angeführt ist, wie das Erstellen einer Bearbeitung oder die Verbreitung -oder Zugänglichmachung des Werks oder dessen Vervielfältigungen. - -== 11. Informationspflichten - -Wenn der Lizenznehmer das Werk verbreitet oder zugänglich macht -(beispielsweise, indem er es zum Herunterladen von einer Website -anbietet), muss der Lizenznehmer über den Vertriebskanal oder das -benutzte Verbreitungsmedium dem Adressatenkreis bzw. der Öffentlichkeit -Mindest-Informationen bereitstellen, üblicherweise bezüglich der -Lizenzgeber, der Lizenz und ihrer Zugänglichkeit, des Abschlusses des -Lizenzvertrags sowie darüber, wie die Lizenz durch den Lizenznehmer -gespeichert und vervielfältigt werden kann. - -== 12. Beendigung der Lizenz - -Die Lizenz und die damit eingeräumten Rechte erlöschen automatisch, wenn -der Lizenznehmer gegen die Lizenzbedingungen verstößt. - -Ein solches Erlöschen der Lizenz führt nicht zum Erlöschen der Lizenzen -von Dritten, denen das Werk vom Lizenznehmer unter dieser Lizenz zur -Verfügung gestellt worden ist, solange diese Personen die -Lizenzbedingungen erfüllen. - -== 13. Einreichung von Beiträgen - -Sofern nichts ausdrücklich anderes angegeben, unterliegt jeder Beitrag, -den der Lizenzgeber bewusst zur Aufnahme in das Werk eingereicht hat, -den Bedingungen dieser Lizenz, ohne dass zusätzliche Bedingungen gelten. -Ungeachtet des Vorstehenden ersetzt oder ändert keine der hierin -enthaltenen Bestimmungen die Bedingungen einer separaten -Lizenzvereinbarung, die der Auftraggeber möglicherweise mit dem -Auftragnehmer für solche Beiträge abgeschlossen hat. - -Für die Länder, in denen Urheberpersönlichkeitsrechte an einem Werk -entstehen können, verzichtet der Urheberrechtsinhaber/Autor im -gesetzlich zulässigen Umfang auf seine Urheberpersönlichkeitsrechte, um -die Lizenzierung der oben aufgeführten Verwertungsrechte wirksam -durchführen zu können. - -== 14. Sonstiges - -Unbeschadet des Artikels 9 stellt diese Lizenz die vollständige -Vereinbarung der Parteien über das Werk dar. - -Es gilt deutsches Recht. Sind einzelne Bestimmungen der Lizenz nach -geltendem Recht nichtig oder unwirksam, so berührt dies nicht die -Wirksamkeit oder Durchsetzbarkeit der Lizenz an sich. Solche -Bestimmungen werden vielmehr dergestalt ausgelegt oder modifiziert, dass -sie wirksam und durchsetzbar sind. - -== 15. Gesellschaftsrechtliche Veränderungen - -Bei gesellschaftsrechtlichen Veränderungen, z.B. dem Verkauf oder der -Abspaltung einer DB Gesellschaft, gilt folgende Regelungen in Anlehnung -an §12 Beendigungsunterstützung der konzerninternen -Leistungsbedingungen: - -Eine weitere Nutzung der lizenzierten Software durch ein nicht mehr dem -DB Konzern angehöriges Unternehmen unterliegt der Zustimmung durch die -Urheber bzw. das CIO Board. - -== 16. Lizenzänderungen - -Die Urheber eines Werks können gemeinsam eine Änderung der Lizenz -entscheiden, z.B. um das Werk als Open Source Software zu -veröffentlichen. Falls die Urheber nicht verfügbar sind oder sich nicht -einigen können, so kann das CIO Board stellvertretend für alle Urheber -innerhalb der DB die Änderung der Lizenz für ein Werk beschließen. - -== 17. Streitbeilegung - -Unbeschadet der Regelungen in den konzerninternen Leistungsbedingungen -zwischen den Parteien gilt zwischen den Parteien Folgendes: - -Bei Streitigkeiten im Zusammenhang mit der Auslegung und Anwendung -dieser Lizenz, bei denen mehr als ein Konzernunternehmen beteiligt ist, -dient das CIO Board des Konzerns als Entscheidungsgremium, welches von -jeder Partei angerufen werden kann. - -== 18. Lizenz der Lizenz - -Dieser Lizenztext ist lizenziert unter einer -„https://creativecommons.org/licenses/by/4.0/[Creative Commons -Namensnennung 4.0 International Lizenz]“ (CC-BY 4.0). - -Sie dürfen diesen Lizenztext für sich kopieren und anpassen, solange Sie -dabei die Deutsche Bahn Marke und „DB“ nur innerhalb der DB benutzen. -Falls Sie das Material für die Verwendung außerhalb der DB anpassen, so -müssen Sie alle Nennungen der Deutschen Bahn und DB ersetzen bzw. -entfernen. Geänderte Versionen des Lizenztextes müssen klar als -geänderte Versionen kenntlich gemacht werden. - -Teile des Textes dieser Lizenz basieren auf der EU Public License (EUPL) -v1.2. diff --git a/bahn/beam-mcp/README.md b/bahn/beam-mcp/README.md deleted file mode 100644 index 12d3bab..0000000 --- a/bahn/beam-mcp/README.md +++ /dev/null @@ -1,369 +0,0 @@ -*DIESER MCP SERVER DIENT NUR FÜR "EDUCATIONAL PURPOSES"! EINE AKTIVE VERWENDUNG IST NICHT GESTATTET AUFGRUND DATENSCHUTZRECHTLICHER BEDENKEN!* - -# BEAM MCP Server - -MCP Server für die Deutsche Bahn Enterprise Architektur Plattform **BEAM** (http://db.de/beam). -Ermöglicht den direkten Zugriff auf BEAM-Daten über die LeanIX GraphQL API — ohne Browser-Navigation. - -## Ansprechpartner - -| Kontakt | E-Mail | -|---------|--------| -| Team DT-BETA | DBS.BS.INFRA.DS4E.DT-BETA@deutschebahn.com | - -## Lizenz - -DB Inner Source License (DBISL) - DB Systel GmbH - ---- - -## Voraussetzungen - -- Node.js >= 18 -- Microsoft Edge (für automatischen Browser-Login) -- Zugang zu BEAM (DB-Netz oder VPN) - ---- - -## Installation - -```bash -cd beam-mcp -npm install -npm run build -``` - ---- - -## Konfiguration - -Optional: Kopiere `.env.example` nach `.env` um die Standard-URLs anzupassen: - -```bash -cp .env.example .env -``` - -### Umgebungsvariablen - -| Variable | Beschreibung | Standard | -|---|---|---| -| `BEAM_BASE_URL` | Basis-URL der BEAM-Instanz | `https://db.leanix.net` | -| `BEAM_LOGIN_URL` | Login-URL für automatischen Browser-Login | `http://db.de/beam` | -| `BEAM_WORKSPACE` | Workspace-Name | `beam` | -| `BEAM_HEADLESS` | Browser-Login im Headless-Modus (`true`/`false`). Bei Anmeldeproblemen auf `false` setzen, um den Browser sichtbar zu starten. | `true` | - ---- - -## Authentifizierung - -Das BEAM-Token wird sicher im **OS-Keychain** gespeichert: - -- **macOS**: Keychain Access (via `security` CLI) -- **Windows**: DPAPI-verschlüsselte Datei in `%LOCALAPPDATA%/beam-mcp/` - -### Automatischer Browser-Login (empfohlen) - -Beim ersten Aufruf eines Tools ohne gültiges Token startet der Server automatisch einen Microsoft Edge Browser, navigiert zu BEAM und meldet sich über "BEAM: DB User" an. Das Token wird anschließend im OS-Keychain gespeichert und für alle weiteren Anfragen (auch nach Server-Neustart) wiederverwendet. - -Falls der automatische Login fehlschlägt, kann der Browser mit `BEAM_HEADLESS=false` sichtbar gestartet werden, um das Problem zu diagnostizieren. - -Der Login kann auch manuell ausgelöst werden: - -```bash -npm run login -``` - -Oder über das Tool `beam_get_token` im MCP-Client. - -### Token-Lebensdauer - -BEAM-Tokens sind zeitlich begrenzt gültig. Der Server prüft die Gültigkeit automatisch und löst bei Bedarf einen neuen Login aus. Bei einem 401-Fehler wird das gespeicherte Token aus dem Keychain gelöscht und ein neuer Login getriggert. - ---- - -## Integration - -### Kiro - -In `.kiro/settings/mcp.json`: - -```json -{ - "mcpServers": { - "beam-mcp": { - "command": "node", - "args": ["/pfad/zu/beam-mcp/dist/index.js"], - "env": { - "BEAM_BASE_URL": "https://db.leanix.net", - "BEAM_WORKSPACE": "beam", - "BEAM_HEADLESS": "true" - } - } - } -} -``` - -### OpenCode - -In `.opencode/opencode.json`: - -```json -{ - "mcp": { - "beam-mcp": { - "enabled": true, - "type": "local", - "command": ["node", "/pfad/zu/beam-mcp/dist/index.js"], - "environment": { - "BEAM_BASE_URL": "https://db.leanix.net", - "BEAM_WORKSPACE": "beam", - "BEAM_HEADLESS": "true" - } - } - } -} -``` - ---- - -## Verfügbare Tools - -Alle Tools verwenden das Prefix `beam_` um Namenskonflikte mit anderen MCP-Servern zu vermeiden. - -### `beam_get_token` - -Meldet sich bei BEAM an und speichert das Token im OS-Keychain. -Nur nötig, wenn kein gültiges Token vorhanden ist oder das Token abgelaufen ist. - -**Parameter:** keine - -**Annotations:** `readOnlyHint: false` | `idempotentHint: true` - -**Beispiel:** -``` -beam_get_token() -``` - -**Ausgabe:** -``` -✓ BEAM Login erfolgreich! -Benutzer: florian.hofmann@deutschebahn.com -Token gültig bis: 20.04.2026, 11:30:00 -Token im Keychain gespeichert. -``` - ---- - -### `beam_search_factsheets` - -Sucht nach BEAM Fact Sheets anhand von Name, Beam-ID oder Freitext. - -**Parameter:** - -| Parameter | Typ | Pflicht | Beschreibung | -|---|---|---|---| -| `query` | String | ✓ | Suchbegriff, Beam-ID (z.B. `A-106614`) oder Name | -| `type` | Enum | — | Einschränkung auf Fact-Sheet-Typ (siehe unten) | - -**Mögliche Typen:** -- `Application` — Anwendung -- `BusinessCapability` — Domäne -- `ITComponent` — IT-Komponente -- `Interface` — Integration -- `DataObject` — Datenobjekt - -**Annotations:** `readOnlyHint: true` | `idempotentHint: true` - -**Beispiele:** -``` -beam_search_factsheets(query: "A-106614") -beam_search_factsheets(query: "RiM", type: "Application") -beam_search_factsheets(query: "Personaleinsatz", type: "BusinessCapability") -``` - ---- - -### `beam_get_factsheet` - -Ruft vollständige Details eines BEAM Fact Sheets ab. - -**Parameter:** - -| Parameter | Typ | Pflicht | Beschreibung | -|---|---|---|---| -| `id` | String | ✓ | Beam-ID (z.B. `A-106614`) oder UUID | - -**Unterstützte Fact-Sheet-Typen:** -- Anwendungen (`Application`) -- Domänen (`BusinessCapability`) -- IT-Komponenten (`ITComponent`) - -**Annotations:** `readOnlyHint: true` | `idempotentHint: true` - -**Beispiele:** -``` -beam_get_factsheet(id: "A-106614") -beam_get_factsheet(id: "33cb34f7-d6fe-481e-a650-8ca42884c4eb") -beam_get_factsheet(id: "P.F.05.05.04") -``` - -**Ausgabe (Anwendung):** -``` -## A-106614 - Fahrendes Personal RiM -Typ: Anwendung -Status: Bestätigt -Alias: RiM; Rail in Motion; Rim; rim -Lebenszyklus: Ausgliederungsphase - -Beschreibung: -Mit "Fahrendes Personal RiM" wird Fahrpersonalen und Triebfahrzeugführern... - -Tags: Bordservice, Mobile Solutions Bordpersonale - -### Domänen - • P.F.05.05.04 - Informationen bereitstellen | Supporttyp: Führend - -### IT-Komponenten - • Enterprise Cloud Managed - -### Nachfolger - • MERKUR (ID: 8c92a72a-...) -``` - ---- - -### `beam_get_domain_applications` - -Listet alle Anwendungen einer BEAM-Domäne auf, gruppiert nach Supporttyp. - -**Parameter:** - -| Parameter | Typ | Pflicht | Beschreibung | -|---|---|---|---| -| `domain` | String | ✓ | Domänen-ID (z.B. `P.F.05.05.04`), Name oder UUID | - -**Annotations:** `readOnlyHint: true` | `idempotentHint: true` - -**Beispiele:** -``` -beam_get_domain_applications(domain: "P.F.05.05.04") -beam_get_domain_applications(domain: "Informationen bereitstellen") -``` - ---- - -### `beam_get_diagram` - -Ruft Diagramme einer BEAM-Anwendung ab. Listet verfügbare Diagramme auf, zeigt eine lesbare Zusammenfassung (Systeme, Schnittstellen) und kann das Diagramm als `.drawio`-Datei speichern. - -**Parameter:** - -| Parameter | Typ | Pflicht | Beschreibung | -|---|---|---|---| -| `application` | String | ✓ | Anwendungsname, Beam-ID (z.B. `A-109410`) oder UUID | -| `diagram_name` | String | — | Name oder Teil des Diagrammnamens zum Abrufen | -| `save_path` | String | — | Dateipfad zum Speichern als `.drawio`-Datei | - -**Annotations:** `readOnlyHint: false` (kann Dateien schreiben) | `idempotentHint: true` - -**Beispiele:** - -Diagramme auflisten: -``` -beam_get_diagram(application: "KEP_KEO_Revision") -``` - -Diagramm lesen: -``` -beam_get_diagram(application: "KEP_KEO_Revision", diagram_name: "System Context") -``` - -Diagramm als draw.io speichern: -``` -beam_get_diagram(application: "KEP_KEO_Revision", diagram_name: "System Context", save_path: "./kep-system-context.drawio") -``` - -**Ausgabe (Lesen):** -``` -## A-109410 - KEP_KEO_Revision - System Context -Typ: freedraw -Beschreibung: Systemkontextdiagramm zu "Anwendung zur Integration..." - -### Systeme/Akteure (10) - • A-109410 - KEP_KEO_Revision [Software System] — Kraftwerkseinsatzoptimierung - • A-107939 - ETRM_Marktdatendatenbank [Software System] — Energiemarktrelevante Daten - ... - -### Schnittstellen/Verbindungen (12) - • Auswahl gewählter Produktionsvolumen für D und D+1 - • Marktpreise - ... -``` - -**Ausgabe (Speichern):** -``` -✓ Diagramm gespeichert: /pfad/zu/kep-system-context.drawio -Die Datei kann direkt in draw.io geöffnet werden. -``` - -**Hinweise:** -- Die `.drawio`-Datei enthält das originale mxGraph-XML aus BEAM und kann direkt in draw.io (Desktop oder Web) geöffnet werden. -- Die lesbare Zusammenfassung extrahiert automatisch C4-Modell-Elemente (Systeme, Personen) und Schnittstellen. - ---- - -## Fehlerbehandlung - -Alle Tools geben bei Fehlern strukturierte Fehlermeldungen mit `isError: true` zurück: - -| Fehler | Meldung | Lösung | -|--------|---------|--------| -| 401/403 | Authentifizierung fehlgeschlagen | `beam_get_token` aufrufen | -| 404 | Nicht gefunden | ID oder Suchbegriff prüfen | -| 429 | Rate-Limit erreicht | Kurz warten, erneut versuchen | - -Große Antworten werden automatisch auf 25.000 Zeichen gekürzt mit einem Hinweis auf spezifischere Filter. - ---- - -## Architektur - -``` -beam-mcp/ -├── src/ -│ ├── index.ts # MCP Server, Tool-Registrierung (registerTool), Error-Handling -│ ├── api.ts # BEAM GraphQL API Client, Diagramm-Export -│ ├── auth.ts # Authentifizierung (Keychain + Playwright Login) -│ └── keychain.ts # OS-Keychain Abstraktionsschicht (macOS/Windows) -├── dist/ # Kompilierter JavaScript-Code (nach npm run build) -├── .env.example # Vorlage für optionale Konfiguration -├── package.json -└── tsconfig.json -``` - -### Datenfluss - -``` -Kiro / OpenCode / KI-Assistent - │ - ▼ - beam-mcp-server (MCP über stdio) - │ - ├─► auth.ts ──► keychain.ts ──► OS-Keychain (Token lesen/schreiben) - │ └─► Playwright Edge Login (falls kein gültiges Token) - │ - └─► api.ts ──► BEAM GraphQL API (https://db.leanix.net/services/pathfinder/v1/graphql) - └─► BEAM Bookmarks API (Diagramme) -``` - ---- - -## Hinweise - -- Das Token läuft nach einigen Stunden ab. Der Server erkennt dies automatisch und löst einen neuen Login aus. -- Der Browser-Login benötigt Zugang zu `http://db.de/beam` (DB-Netz oder VPN). -- Alle internen Variablen- und Feldnamen folgen der LeanIX API-Konvention; die Anzeige ist auf BEAM-Terminologie gemappt. -- Der Server ist read-only — es werden keine Daten in BEAM verändert (außer `.drawio`-Dateien lokal speichern). -- Alle Tools nutzen `registerTool` mit Annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). -- Tool-Namen verwenden das `beam_`-Prefix zur Vermeidung von Namenskonflikten. -- Keine Secrets in Dateien — Token wird ausschließlich im OS-Keychain gespeichert. diff --git a/bahn/beam-mcp/package.json b/bahn/beam-mcp/package.json deleted file mode 100644 index 48550dd..0000000 --- a/bahn/beam-mcp/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "beam-mcp", - "version": "1.1.0", - "description": "MCP Server für BEAM (Deutsche Bahn Enterprise Architektur)", - "type": "module", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "start": "node dist/index.js", - "dev": "tsc --watch", - "login": "npm run build && node dist/login.js" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", - "playwright": "^1.59.1" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.0.0" - } -} diff --git a/bahn/beam-mcp/scm-info.yaml b/bahn/beam-mcp/scm-info.yaml deleted file mode 100644 index c2c6559..0000000 --- a/bahn/beam-mcp/scm-info.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -version: v3 -confidentiality: internal -contacts: florian.hofmann@deutschebahn.com -license: LicenseRef-DBPROPRIETARY -reference-ids: none diff --git a/bahn/beam-mcp/src/api.ts b/bahn/beam-mcp/src/api.ts deleted file mode 100644 index e1702ad..0000000 --- a/bahn/beam-mcp/src/api.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { getToken, invalidateToken, BEAM_BASE_URL } from "./auth.js"; -import { writeFileSync } from "fs"; -import { resolve } from "path"; - -const GRAPHQL_URL = `${BEAM_BASE_URL}/services/pathfinder/v1/graphql`; - -async function gql(query: string, variables?: Record, retry = true): Promise { - const token = await getToken(); - const res = await fetch(GRAPHQL_URL, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ query, variables }), - }); - if (res.status === 401 && retry) { - invalidateToken(); - return gql(query, variables, false); - } - if (!res.ok) throw new Error(`GraphQL HTTP ${res.status}: ${await res.text()}`); - const json = (await res.json()) as { data?: unknown; errors?: unknown[] }; - if (json.errors?.length) throw new Error(`GraphQL errors: ${JSON.stringify(json.errors)}`); - return json.data; -} - -// ── Search factsheets ──────────────────────────────────────────────────────── -export async function searchFactSheets(query: string, type?: string) { - const filter: Record = { fullTextSearch: query }; - if (type) filter.facetFilters = [{ facetKey: "FactSheetTypes", keys: [type] }]; - - const data = (await gql( - `query Search($filter: FilterInput) { - allFactSheets(filter: $filter, first: 20) { - edges { - node { - id name type - ... on Application { - lxState - externalId { externalId } - alias - lifecycle { asString } - } - ... on BusinessCapability { - externalId { externalId } - lifecycle { asString } - } - ... on ITComponent { - externalId { externalId } - lifecycle { asString } - } - } - } - } - }`, - { filter } - )) as { allFactSheets: { edges: { node: Record }[] } }; - - return data.allFactSheets.edges.map((e) => e.node); -} - -// ── Get full factsheet details ─────────────────────────────────────────────── -export async function getFactSheet(idOrBeamId: string) { - let uuid = idOrBeamId; - if (!idOrBeamId.match(/^[0-9a-f-]{36}$/i)) { - const results = await searchFactSheets(idOrBeamId); - if (!results.length) throw new Error(`No factsheet found for: ${idOrBeamId}`); - uuid = results[0].id as string; - } - - const data = (await gql( - `query GetFS($id: ID!) { - factSheet(id: $id) { - id name type - ... on Application { - lxState - externalId { externalId } - alias - description - lifecycle { asString } - tags { name } - relApplicationToBusinessCapability { - edges { - node { - supportType - functionalSuitability - factSheet { - id name - ... on BusinessCapability { externalId { externalId } } - } - } - } - } - relApplicationToITComponent { - edges { node { factSheet { id name } } } - } - relToSuccessor { - edges { node { factSheet { id name } } } - } - } - ... on BusinessCapability { - externalId { externalId } - description - lifecycle { asString } - relBusinessCapabilityToApplication { - edges { - node { - supportType - functionalSuitability - factSheet { - id name type - ... on Application { - lxState - externalId { externalId } - alias - lifecycle { asString } - } - } - } - } - } - } - ... on ITComponent { - externalId { externalId } - description - lifecycle { asString } - } - } - }`, - { id: uuid } - )) as { factSheet: Record }; - - return data.factSheet; -} - -// ── Get all applications of a domain ──────────────────────────────────────── -export async function getDomainApplications(domainIdOrExternalId: string) { - let uuid = domainIdOrExternalId; - if (!domainIdOrExternalId.match(/^[0-9a-f-]{36}$/i)) { - const results = await searchFactSheets(domainIdOrExternalId, "BusinessCapability"); - if (!results.length) throw new Error(`No domain found for: ${domainIdOrExternalId}`); - uuid = results[0].id as string; - } - - const data = (await gql( - `query DomainApps($id: ID!) { - factSheet(id: $id) { - id name - ... on BusinessCapability { - externalId { externalId } - relBusinessCapabilityToApplication { - edges { - node { - supportType - functionalSuitability - factSheet { - id name type - ... on Application { - lxState - externalId { externalId } - alias - lifecycle { asString } - tags { name } - } - } - } - } - } - } - } - }`, - { id: uuid } - )) as { factSheet: Record }; - - return data.factSheet; -} - -// ── Get diagram (bookmark) ────────────────────────────────────────────────── -export async function getDiagram(id: string) { - const token = await getToken(); - const res = await fetch( - `${BEAM_BASE_URL}/services/pathfinder/v1/bookmarks/${id}`, - { headers: { Authorization: `Bearer ${token}` } } - ); - if (!res.ok) throw new Error(`Bookmark HTTP ${res.status}: ${await res.text()}`); - return (await res.json()) as { data: Record }; -} - -// ── Get diagrams linked to a fact sheet via documents ─────────────────────── -export async function getFactSheetDiagrams(idOrName: string) { - let uuid = idOrName; - if (!idOrName.match(/^[0-9a-f-]{36}$/i)) { - const results = await searchFactSheets(idOrName); - if (!results.length) throw new Error(`No factsheet found for: ${idOrName}`); - uuid = results[0].id as string; - } - - const data = (await gql( - `query Docs($id: ID!) { - factSheet(id: $id) { id name documents { edges { node { id name } } } } - }`, - { id: uuid } - )) as { factSheet: { id: string; name: string; documents: { edges: { node: { id: string; name: string } }[] } } }; - - const diagramDocs = data.factSheet.documents.edges - .filter((e) => e.node.name.startsWith("Diagram ")) - .map((e) => e.node.name.replace("Diagram ", "")); - - const diagrams: { id: string; name: string; groupKey: string }[] = []; - for (const bmId of diagramDocs) { - try { - const bm = (await getDiagram(bmId)).data; - diagrams.push({ id: bmId, name: bm.name as string, groupKey: bm.groupKey as string }); - } catch { /* skip inaccessible */ } - } - - return { factSheet: data.factSheet, diagrams }; -} - - -// ── Extract graphXml from diagram bookmark data ───────────────────────────── -export function extractGraphXml(bookmarkData: Record): string | null { - const state = bookmarkData.state as Record | undefined; - if (!state?.graphXml) return null; - return state.graphXml as string; -} - -// ── Save diagram as .drawio file ──────────────────────────────────────────── -export function saveDiagramAsDrawio( - graphXml: string, - diagramName: string, - outputPath: string -): string { - // draw.io expects the mxGraphModel wrapped in a inside - const safeName = diagramName.replace(/[<>&"']/g, ""); - const drawioXml = ` - - - ${graphXml} - -`; - - const fullPath = resolve(outputPath); - writeFileSync(fullPath, drawioXml, "utf8"); - return fullPath; -} - -// ── Parse diagram into readable text summary ──────────────────────────────── -export function parseDiagramToText(bookmarkData: Record): string { - const lines: string[] = []; - const name = bookmarkData.name as string ?? "Unbekannt"; - const desc = bookmarkData.description as string ?? ""; - const groupKey = bookmarkData.groupKey as string ?? ""; - const updatedAt = bookmarkData.updatedAt as string ?? ""; - - lines.push(`## ${name}`); - if (groupKey) lines.push(`Typ: ${groupKey}`); - if (desc) lines.push(`Beschreibung: ${desc}`); - if (updatedAt) lines.push(`Zuletzt aktualisiert: ${updatedAt}`); - - // Parse graphXml to extract nodes and edges - const graphXml = extractGraphXml(bookmarkData); - if (!graphXml) { - lines.push("\nKein graphXml im Diagramm gefunden."); - return lines.join("\n"); - } - - // Extract nodes (factSheet objects with c4Name) - const nodeRegex = /c4Name="([^"]*)"[^>]*c4Type="([^"]*)"[^>]*c4Description="([^"]*)"[^>]*factSheetId="([^"]*)"/g; - const nodes: { name: string; type: string; desc: string; id: string }[] = []; - let match: RegExpExecArray | null; - while ((match = nodeRegex.exec(graphXml)) !== null) { - nodes.push({ - name: match[1].replace(/&/g, "&").replace(/ /g, " ").replace(/<[^>]*>/g, ""), - type: match[2], - desc: match[3].replace(/&/g, "&").replace(/ /g, " ").replace(/<[^>]*>/g, ""), - id: match[4], - }); - } - - // Extract edges (interface labels) - const edgeRegex = /label="([^"]*)"[^>]*factSheetType="Interface"[^>]*factSheetId="([^"]*)"/g; - const edges: { label: string; id: string }[] = []; - while ((match = edgeRegex.exec(graphXml)) !== null) { - edges.push({ - label: match[1].replace(/&/g, "&").replace(/ /g, " "), - id: match[2], - }); - } - - if (nodes.length) { - lines.push(`\n### Systeme/Akteure (${nodes.length})`); - nodes.forEach((n) => { - lines.push(` • ${n.name} [${n.type}]${n.desc ? " — " + n.desc : ""}`); - }); - } - - if (edges.length) { - lines.push(`\n### Schnittstellen/Verbindungen (${edges.length})`); - edges.forEach((e) => { - lines.push(` • ${e.label}`); - }); - } - - return lines.join("\n"); -} diff --git a/bahn/beam-mcp/src/auth.ts b/bahn/beam-mcp/src/auth.ts deleted file mode 100644 index e19a2b3..0000000 --- a/bahn/beam-mcp/src/auth.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Auth-Modul: Stellt ein gültiges BEAM JWT-Token bereit. - * - * Priorität: - * 1. In-Memory Cache (schnellster Zugriff) - * 2. OS-Keychain (macOS Keychain / Windows DPAPI) - * 3. Playwright Browser-Login (automatisch wenn kein Token vorhanden) - * - * Token wird nach erfolgreichem Login im OS-Keychain persistiert. - */ - -import { existsSync, mkdirSync } from "fs"; -import { join, dirname } from "path"; -import { fileURLToPath } from "url"; -import { tmpdir } from "os"; -import { getStoredToken, storeToken, deleteStoredToken } from "./keychain.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -export const BEAM_BASE_URL = process.env.BEAM_BASE_URL ?? "https://db.leanix.net"; -export const BEAM_LOGIN_URL = process.env.BEAM_LOGIN_URL ?? "http://db.de/beam"; -const WORKSPACE = process.env.BEAM_WORKSPACE ?? "beam"; -const TOKEN_KEY = `lxAccessToken:${WORKSPACE}`; - -// Temporäres Profil-Verzeichnis (kein Konflikt mit laufendem Edge) -const TEMP_PROFILE_DIR = join(tmpdir(), "beam-mcp-edge-profile"); - -// In-Memory Cache -let cachedToken: string | null = null; - -// ── Token-Gültigkeit prüfen ─────────────────────────────────────────────────── -function parseExpiry(token: string): number { - try { - const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString()); - return (payload.exp ?? 0) * 1000; - } catch { - return 0; - } -} - -function isValid(token: string): boolean { - return parseExpiry(token) > Date.now() + 60_000; -} - -// ── Playwright Login ────────────────────────────────────────────────────────── -export async function loginWithBrowser(): Promise { - process.stderr.write("[BEAM] Kein gültiges Token gefunden. Starte Edge Login...\n"); - - const { chromium } = await import("playwright"); - - if (!existsSync(TEMP_PROFILE_DIR)) mkdirSync(TEMP_PROFILE_DIR, { recursive: true }); - process.stderr.write(`[BEAM] Temporäres Profil: ${TEMP_PROFILE_DIR}\n`); - - const context = await chromium.launchPersistentContext(TEMP_PROFILE_DIR, { - channel: "msedge", - headless: false, - ignoreDefaultArgs: [ - "--disable-background-networking", - "--disable-extensions", - "--disable-sync", - "--no-sandbox", - ], - args: ["--no-first-run", "--no-default-browser-check"], - }); - - try { - const page = await context.newPage(); - - await page.goto(BEAM_LOGIN_URL, { waitUntil: "domcontentloaded", timeout: 30000 }); - - // Klicke auf "BEAM: DB User" falls Login-Seite erscheint - const loginLink = page.getByRole("link", { name: /BEAM.*DB User/i }) - .or(page.getByText(/BEAM.*DB User/i)) - .first(); - - const isLoginPage = await loginLink.isVisible({ timeout: 5000 }).catch(() => false); - if (isLoginPage) { - await loginLink.click({ timeout: 10000 }); - } - - // Warte auf erfolgreichen Login (URL enthält leanix.net) - await page.waitForURL(/leanix\.net/, { timeout: 180000 }); - - // Warte bis Token im localStorage liegt - await page.waitForFunction( - (key) => !!localStorage.getItem(key), - TOKEN_KEY, - { timeout: 30000 } - ); - - const token = await page.evaluate((key) => localStorage.getItem(key), TOKEN_KEY); - if (!token) throw new Error("Token nicht im localStorage gefunden nach Login."); - - // Token im Keychain persistieren - storeToken(token); - process.stderr.write("[BEAM] Login erfolgreich. Token im Keychain gespeichert.\n"); - return token; - } finally { - await context.close(); - } -} - -// ── Haupt-Funktion: Token bereitstellen ────────────────────────────────────── -export async function getToken(): Promise { - // 1. In-Memory Cache - if (cachedToken && isValid(cachedToken)) return cachedToken; - - // 2. OS-Keychain - const keychainToken = getStoredToken(); - if (keychainToken && isValid(keychainToken)) { - cachedToken = keychainToken; - process.stderr.write("[BEAM] Token aus Keychain geladen.\n"); - return cachedToken; - } - - // 3. Playwright Login - const token = await loginWithBrowser(); - cachedToken = token; - return token; -} - -// ── Token invalidieren (bei 401) ───────────────────────────────────────────── -export function invalidateToken(): void { - cachedToken = null; - deleteStoredToken(); - process.stderr.write("[BEAM] Token invalidiert (Memory + Keychain).\n"); -} diff --git a/bahn/beam-mcp/src/index.ts b/bahn/beam-mcp/src/index.ts deleted file mode 100644 index 7c396e1..0000000 --- a/bahn/beam-mcp/src/index.ts +++ /dev/null @@ -1,480 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -import { - searchFactSheets, - getFactSheet, - getDomainApplications, - getDiagram, - getFactSheetDiagrams, - extractGraphXml, - saveDiagramAsDrawio, - parseDiagramToText, -} from "./api.js"; -import { getToken, loginWithBrowser, invalidateToken } from "./auth.js"; - -const CHARACTER_LIMIT = 25_000; - -const server = new McpServer({ - name: "beam-mcp-server", - version: "1.1.0", -}); - -// ── Label-Mappings ──────────────────────────────────────────────────────────── -const LC: Record = { - active: "Aktiv", - plan: "Plan", - phaseIn: "Einführungsphase", - phaseOut: "Ausgliederungsphase", - endOfLife: "Lebensende", -}; -const lcLabel = (s: string | undefined) => (s ? (LC[s] ?? s) : "—"); - -const ST: Record = { - leading: "Führend", - supports: "Unterstützt", -}; -const stLabel = (s: string | null | undefined) => (s ? (ST[s] ?? s) : "—"); - -const FS_TYPE: Record = { - Application: "Anwendung", - BusinessCapability: "Domäne", - ITComponent: "IT-Komponente", - Interface: "Integration", - DataObject: "Datenobjekt", - TechnicalStack: "Technologie-Stack", - UserGroup: "Nutzergruppe", - Process: "Prozess", - Epic: "Initiative", -}; -const typeLabel = (s: string) => FS_TYPE[s] ?? s; - -const STATE: Record = { - APPROVED: "Bestätigt", - DRAFT: "Entwurf", - ARCHIVED: "Archiviert", -}; -const stateLabel = (s: string | undefined) => (s ? (STATE[s] ?? s) : "—"); - -// ── Shared error handler ────────────────────────────────────────────────────── -function handleError(error: unknown): { content: [{ type: "text"; text: string }]; isError: true } { - const msg = error instanceof Error ? error.message : String(error); - - if (msg.includes("HTTP 401") || msg.includes("HTTP 403")) { - return { - isError: true, - content: [{ type: "text", text: `Fehler: Authentifizierung fehlgeschlagen. Bitte 'beam_get_token' aufrufen, um ein neues Token zu erhalten.\nDetails: ${msg}` }], - }; - } - if (msg.includes("HTTP 404") || msg.includes("No factsheet found") || msg.includes("No domain found")) { - return { - isError: true, - content: [{ type: "text", text: `Fehler: Nicht gefunden. Bitte prüfe die ID oder den Suchbegriff.\nDetails: ${msg}` }], - }; - } - if (msg.includes("HTTP 429")) { - return { - isError: true, - content: [{ type: "text", text: `Fehler: Rate-Limit erreicht. Bitte kurz warten und erneut versuchen.` }], - }; - } - return { - isError: true, - content: [{ type: "text", text: `Fehler: ${msg}` }], - }; -} - -// ── Truncation helper ───────────────────────────────────────────────────────── -function truncateIfNeeded(text: string): string { - if (text.length <= CHARACTER_LIMIT) return text; - return text.slice(0, CHARACTER_LIMIT) + "\n\n⚠ Ausgabe wurde auf 25.000 Zeichen gekürzt. Nutze spezifischere Filter."; -} - - -// ── Tool: beam_get_token ────────────────────────────────────────────────────── -server.registerTool( - "beam_get_token", - { - title: "BEAM Token abrufen", - description: `Meldet sich automatisch bei BEAM an (headless Edge Browser) und hält das Token im Speicher (keine .env Persistenz). -Nur nötig wenn kein gültiges Token vorhanden ist oder ein 401/403-Fehler auftritt. - -Returns: - Bestätigung mit Benutzername und Token-Ablaufzeit. - -Examples: - - Nutze dieses Tool wenn andere BEAM-Tools mit Authentifizierungsfehlern fehlschlagen. - - Nutze es proaktiv vor der ersten BEAM-Abfrage.`, - inputSchema: {}, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - }, - async () => { - try { - const existing = await getToken().catch(() => null); - if (existing) { - return { content: [{ type: "text", text: "✓ Gültiges BEAM-Token bereits vorhanden. Kein Login nötig." }] }; - } - } catch { - // kein Token — Login starten - } - - try { - const token = await loginWithBrowser(); - const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString()); - const expiry = new Date((payload.exp ?? 0) * 1000).toLocaleString("de-DE"); - const user = payload.principal?.username ?? "Unbekannt"; - return { - content: [{ - type: "text", - text: `✓ BEAM Login erfolgreich!\nBenutzer: ${user}\nToken gültig bis: ${expiry}\nToken wird nur im Speicher gehalten.`, - }], - }; - } catch (error) { - return handleError(error); - } - } -); - -// ── Tool: beam_search_factsheets ────────────────────────────────────────────── -server.registerTool( - "beam_search_factsheets", - { - title: "BEAM Fact Sheets suchen", - description: `Sucht nach BEAM Fact Sheets anhand von Name, Beam-ID (z.B. 'A-106614') oder Freitext. -Gibt bis zu 20 Ergebnisse zurück mit Typ, Lebenszyklus und ID. - -Args: - - query (string, required): Suchbegriff, Beam-ID oder Name - - type (enum, optional): Einschränkung auf Fact-Sheet-Typ - -Returns: - Liste der gefundenen Fact Sheets mit Typ, Name, Lebenszyklus und UUID. - -Examples: - - query="A-106614" → Suche nach Beam-ID - - query="RiM", type="Application" → Anwendung nach Name suchen - - query="Personaleinsatz" → Freitextsuche über alle Typen`, - inputSchema: { - query: z.string() - .min(1, "Suchbegriff darf nicht leer sein") - .describe("Suchbegriff, Beam-ID (z.B. A-106614) oder Name"), - type: z.enum(["Application", "BusinessCapability", "ITComponent", "Interface", "DataObject"]) - .optional() - .describe("Optional: Einschränkung auf einen Fact-Sheet-Typ"), - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - }, - async ({ query, type }) => { - try { - const results = await searchFactSheets(query, type); - if (!results.length) { - return { content: [{ type: "text", text: `Keine Ergebnisse in BEAM für: ${query}` }] }; - } - - const text = results.map((r: Record) => { - const extId = (r.externalId as Record | null)?.externalId ?? ""; - const lc = lcLabel((r.lifecycle as Record | null)?.asString); - const alias = r.alias ? ` (${r.alias})` : ""; - return `• [${typeLabel(r.type as string)}] ${extId ? extId + " - " : ""}${r.name}${alias} | ${lc} | ID: ${r.id}`; - }).join("\n"); - - return { content: [{ type: "text", text: truncateIfNeeded(`BEAM Suchergebnisse für "${query}":\n\n${text}`) }] }; - } catch (error) { - return handleError(error); - } - } -); - - -// ── Tool: beam_get_factsheet ────────────────────────────────────────────────── -server.registerTool( - "beam_get_factsheet", - { - title: "BEAM Fact Sheet Details", - description: `Ruft vollständige Details eines BEAM Fact Sheets ab — per Beam-ID (z.B. 'A-106614') oder UUID. -Unterstützt Anwendungen, Domänen und IT-Komponenten mit Relationen. - -Args: - - id (string, required): Beam-ID (z.B. A-106614, P.F.05.05.04) oder UUID - -Returns: - Detaillierte Fact-Sheet-Informationen inkl. Beschreibung, Lebenszyklus, Tags, - verknüpfte Domänen, IT-Komponenten und Nachfolger. - -Examples: - - id="A-106614" → Anwendung per Beam-ID - - id="P.F.05.05.04" → Domäne per externer ID - - id="33cb34f7-..." → Fact Sheet per UUID`, - inputSchema: { - id: z.string() - .min(1, "ID darf nicht leer sein") - .describe("Beam-ID (z.B. A-106614) oder UUID des Fact Sheets"), - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - }, - async ({ id }) => { - try { - const fs = await getFactSheet(id) as Record; - const lines: string[] = []; - - const extId = (fs.externalId as Record | null)?.externalId ?? ""; - lines.push(`## ${extId ? extId + " - " : ""}${fs.name}`); - lines.push(`Typ: ${typeLabel(fs.type as string)}`); - lines.push(`Status: ${stateLabel(fs.lxState as string)}`); - - if (fs.alias) lines.push(`Alias: ${fs.alias}`); - - const lc = (fs.lifecycle as Record | null)?.asString; - if (lc) lines.push(`Lebenszyklus: ${lcLabel(lc)}`); - - if (fs.description) lines.push(`\nBeschreibung:\n${fs.description}`); - - const tags = (fs.tags as { name: string }[] | null); - if (tags?.length) lines.push(`\nTags: ${tags.map(t => t.name).join(", ")}`); - - // Domänen (für Anwendungen) - const domRel = fs.relApplicationToBusinessCapability as { edges: { node: Record }[] } | null; - if (domRel?.edges?.length) { - lines.push("\n### Domänen"); - domRel.edges.forEach(({ node }) => { - const dom = node.factSheet as Record; - const domExtId = (dom.externalId as Record | null)?.externalId ?? ""; - lines.push(` • ${domExtId ? domExtId + " - " : ""}${dom.name} | Supporttyp: ${stLabel(node.supportType as string)}`); - }); - } - - // Anwendungen (für Domänen) - const appRel = fs.relBusinessCapabilityToApplication as { edges: { node: Record }[] } | null; - if (appRel?.edges?.length) { - lines.push("\n### Anwendungen"); - appRel.edges.forEach(({ node }) => { - const app = node.factSheet as Record; - const appExtId = (app.externalId as Record | null)?.externalId ?? ""; - const appLc = lcLabel((app.lifecycle as Record | null)?.asString); - lines.push(` • ${appExtId ? appExtId + " - " : ""}${app.name} | Supporttyp: ${stLabel(node.supportType as string)} | ${appLc}`); - }); - } - - // IT-Komponenten - const itcRel = fs.relApplicationToITComponent as { edges: { node: Record }[] } | null; - if (itcRel?.edges?.length) { - lines.push("\n### IT-Komponenten"); - itcRel.edges.forEach(({ node }) => { - lines.push(` • ${(node.factSheet as Record).name}`); - }); - } - - // Nachfolger - const succRel = fs.relToSuccessor as { edges: { node: Record }[] } | null; - if (succRel?.edges?.length) { - lines.push("\n### Nachfolger"); - succRel.edges.forEach(({ node }) => { - const succ = node.factSheet as Record; - lines.push(` • ${succ.name} (ID: ${succ.id})`); - }); - } - - return { content: [{ type: "text", text: truncateIfNeeded(lines.join("\n")) }] }; - } catch (error) { - return handleError(error); - } - } -); - - -// ── Tool: beam_get_domain_applications ───────────────────────────────────────── -server.registerTool( - "beam_get_domain_applications", - { - title: "BEAM Domänen-Anwendungen", - description: `Listet alle Anwendungen einer BEAM-Domäne auf, gruppiert nach Supporttyp (Führend/Unterstützt). - -Args: - - domain (string, required): Domänen-ID (z.B. 'P.F.05.05.04'), Name oder UUID - -Returns: - Gruppierte Liste aller Anwendungen der Domäne mit Lebenszyklus und Tags. - -Examples: - - domain="P.F.05.05.04" → Domäne per externer ID - - domain="Informationen bereitstellen" → Domäne per Name - - domain="TO.I.01.01" → Technische Domäne`, - inputSchema: { - domain: z.string() - .min(1, "Domäne darf nicht leer sein") - .describe("Domänen-ID (z.B. 'P.F.05.05.04'), Name oder UUID"), - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - }, - async ({ domain }) => { - try { - const fs = await getDomainApplications(domain) as Record; - const extId = (fs.externalId as Record | null)?.externalId ?? ""; - - const lines: string[] = []; - lines.push(`## BEAM-Domäne: ${extId ? extId + " - " : ""}${fs.name}`); - - const appRel = fs.relBusinessCapabilityToApplication as { edges: { node: Record }[] } | null; - if (!appRel?.edges?.length) { - lines.push("Keine Anwendungen in dieser Domäne gefunden."); - return { content: [{ type: "text", text: lines.join("\n") }] }; - } - - lines.push(`${appRel.edges.length} Anwendung(en)\n`); - - const grouped: Record = {}; - appRel.edges.forEach(({ node }) => { - const app = node.factSheet as Record; - const appExtId = (app.externalId as Record | null)?.externalId ?? ""; - const appLc = lcLabel((app.lifecycle as Record | null)?.asString); - const st = stLabel(node.supportType as string); - const appTags = (app.tags as { name: string }[] | null)?.map(t => t.name).join(", ") ?? ""; - const entry = ` • ${appExtId ? appExtId + " - " : ""}${app.name} | ${appLc}${appTags ? " | " + appTags : ""}`; - if (!grouped[st]) grouped[st] = []; - grouped[st].push(entry); - }); - - const order = ["Führend", "Unterstützt"]; - const allTypes = [ - ...order.filter(k => grouped[k]), - ...Object.keys(grouped).filter(k => !order.includes(k)), - ]; - - allTypes.forEach((t) => { - lines.push(`### ${t} (${grouped[t].length})`); - grouped[t].forEach((e) => lines.push(e)); - lines.push(""); - }); - - return { content: [{ type: "text", text: truncateIfNeeded(lines.join("\n")) }] }; - } catch (error) { - return handleError(error); - } - } -); - - -// ── Tool: beam_get_diagram ──────────────────────────────────────────────────── -server.registerTool( - "beam_get_diagram", - { - title: "BEAM Diagramme abrufen", - description: `Ruft Diagramme einer BEAM-Anwendung ab. Ohne diagram_name werden alle verknüpften Diagramme aufgelistet. -Mit diagram_name wird eine lesbare Zusammenfassung (Systeme, Schnittstellen) angezeigt. -Optional als .drawio-Datei speichern zum Öffnen in draw.io. - -Args: - - application (string, required): Anwendungsname, Beam-ID (z.B. 'A-109410') oder UUID - - diagram_name (string, optional): Name oder Teil des Diagrammnamens zum Abrufen - - save_path (string, optional): Dateipfad zum Speichern als .drawio-Datei - -Returns: - Ohne diagram_name: Liste aller verfügbaren Diagramme. - Mit diagram_name: Lesbare Zusammenfassung mit Systemen/Akteuren und Schnittstellen. - Mit save_path: Zusätzlich wird eine .drawio-Datei gespeichert. - -Examples: - - application="KEP_KEO_Revision" → Alle Diagramme auflisten - - application="A-109410", diagram_name="System Context" → Diagramm lesen - - application="A-109410", diagram_name="System Context", save_path="./diagram.drawio" → Als draw.io speichern`, - inputSchema: { - application: z.string() - .min(1, "Anwendung darf nicht leer sein") - .describe("Anwendungsname, Beam-ID (z.B. 'A-109410') oder UUID"), - diagram_name: z.string() - .optional() - .describe("Optional: Name oder Teil des Diagrammnamens zum Abrufen"), - save_path: z.string() - .optional() - .describe("Optional: Dateipfad zum Speichern als .drawio-Datei (z.B. './diagram.drawio')"), - }, - annotations: { - readOnlyHint: false, // kann Dateien schreiben wenn save_path gesetzt - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - }, - async ({ application, diagram_name, save_path }) => { - try { - const { factSheet, diagrams } = await getFactSheetDiagrams(application); - - if (!diagrams.length) { - return { content: [{ type: "text", text: `Keine Diagramme bei ${factSheet.name} gefunden.` }] }; - } - - // Nur auflisten wenn kein diagram_name - if (!diagram_name) { - const lines = [`## Diagramme von ${factSheet.name}\n`]; - diagrams.forEach((d) => { - lines.push(` • ${d.name} [${d.groupKey}]\n ID: ${d.id}`); - }); - lines.push(`\nNutze diagram_name um ein Diagramm abzurufen.`); - lines.push(`Nutze save_path um ein Diagramm als .drawio-Datei zu speichern.`); - return { content: [{ type: "text", text: lines.join("\n") }] }; - } - - // Diagramm finden (Teilmatch) - const match = diagrams.find((d) => - d.name.toLowerCase().includes(diagram_name.toLowerCase()) || d.id === diagram_name - ); - if (!match) { - return { - content: [{ - type: "text", - text: `Kein Diagramm gefunden für "${diagram_name}". Verfügbar:\n${diagrams.map((d) => ` • ${d.name}`).join("\n")}`, - }], - }; - } - - const raw = await getDiagram(match.id); - const bm = raw.data; - - const summary = parseDiagramToText(bm); - const contentParts: { type: "text"; text: string }[] = [{ type: "text", text: truncateIfNeeded(summary) }]; - - // Optional als .drawio speichern - const graphXml = extractGraphXml(bm); - if (save_path && graphXml) { - const filePath = saveDiagramAsDrawio(graphXml, (bm.name as string) ?? match.name, save_path); - contentParts.push({ - type: "text", - text: `\n✓ Diagramm gespeichert: ${filePath}\nDie Datei kann direkt in draw.io geöffnet werden (Datei → Öffnen oder Doppelklick).`, - }); - } else if (save_path && !graphXml) { - contentParts.push({ - type: "text", - text: `\n⚠ Diagramm enthält kein graphXml — Speichern als .drawio nicht möglich.`, - }); - } - - return { content: contentParts }; - } catch (error) { - return handleError(error); - } - } -); - -// ── Start ───────────────────────────────────────────────────────────────────── -const transport = new StdioServerTransport(); -await server.connect(transport); diff --git a/bahn/beam-mcp/src/keychain.ts b/bahn/beam-mcp/src/keychain.ts deleted file mode 100644 index f1bd7a9..0000000 --- a/bahn/beam-mcp/src/keychain.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Keychain-Modul: Sichere Speicherung von Credentials im OS-Keychain. - * - * - macOS: Keychain Access via `security` CLI - * - Windows: DPAPI-verschlüsselte Datei im AppData-Verzeichnis - * - Fallback: Kein persistenter Speicher (nur In-Memory) - * - * Keine externen Dependencies — nur Node.js Built-ins + OS-Tools. - */ - -import { execSync } from "child_process"; -import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { join } from "path"; -import { homedir, platform } from "os"; - -const SERVICE_NAME = "beam-mcp"; -const ACCOUNT_NAME = "beam-token"; - -// ── Platform Detection ──────────────────────────────────────────────────────── -const OS = platform(); - -// ── macOS Keychain via `security` CLI ───────────────────────────────────────── - -function macosGet(): string | null { - try { - const result = execSync( - `security find-generic-password -a "${ACCOUNT_NAME}" -s "${SERVICE_NAME}" -w`, - { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] } - ); - return result.trim() || null; - } catch { - return null; - } -} - -function macosSet(token: string): void { - // -U flag updates existing entry or creates new one - execSync( - `security add-generic-password -a "${ACCOUNT_NAME}" -s "${SERVICE_NAME}" -w "${token.replace(/"/g, '\\"')}" -U`, - { stdio: ["pipe", "pipe", "pipe"] } - ); -} - -function macosDelete(): void { - try { - execSync( - `security delete-generic-password -a "${ACCOUNT_NAME}" -s "${SERVICE_NAME}"`, - { stdio: ["pipe", "pipe", "pipe"] } - ); - } catch { - // Ignore if entry doesn't exist - } -} - -// ── Windows DPAPI-encrypted file ────────────────────────────────────────────── - -function getWindowsStorePath(): string { - const appData = process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"); - const dir = join(appData, "beam-mcp"); - if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); - return join(dir, "credentials.enc"); -} - -function windowsGet(): string | null { - const storePath = getWindowsStorePath(); - if (!existsSync(storePath)) return null; - - try { - const encrypted = readFileSync(storePath, "utf8"); - // Decrypt using DPAPI via PowerShell - const psScript = ` - $bytes = [System.Convert]::FromBase64String('${encrypted}') - $decrypted = [System.Security.Cryptography.ProtectedData]::Unprotect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) - [System.Text.Encoding]::UTF8.GetString($decrypted) - `; - const result = execSync( - `powershell -NoProfile -NonInteractive -Command "${psScript.replace(/\n/g, " ")}"`, - { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] } - ); - return result.trim() || null; - } catch { - return null; - } -} - -function windowsSet(token: string): void { - const storePath = getWindowsStorePath(); - // Encrypt using DPAPI via PowerShell - const psScript = ` - Add-Type -AssemblyName System.Security - $bytes = [System.Text.Encoding]::UTF8.GetBytes('${token.replace(/'/g, "''")}') - $encrypted = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) - [System.Convert]::ToBase64String($encrypted) - `; - const result = execSync( - `powershell -NoProfile -NonInteractive -Command "${psScript.replace(/\n/g, " ")}"`, - { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] } - ); - writeFileSync(storePath, result.trim(), "utf8"); -} - -function windowsDelete(): void { - const storePath = getWindowsStorePath(); - try { - if (existsSync(storePath)) unlinkSync(storePath); - } catch { - // Ignore - } -} - -// ── Public API ──────────────────────────────────────────────────────────────── - -/** - * Liest das gespeicherte Token aus dem OS-Keychain. - * Gibt null zurück wenn kein Token gespeichert ist oder das OS nicht unterstützt wird. - */ -export function getStoredToken(): string | null { - switch (OS) { - case "darwin": - return macosGet(); - case "win32": - return windowsGet(); - default: - process.stderr.write(`[BEAM] Keychain nicht unterstützt auf ${OS}. Nur In-Memory Token.\n`); - return null; - } -} - -/** - * Speichert ein Token sicher im OS-Keychain. - * Überschreibt ein vorhandenes Token. - */ -export function storeToken(token: string): void { - switch (OS) { - case "darwin": - macosSet(token); - process.stderr.write("[BEAM] Token in macOS Keychain gespeichert.\n"); - break; - case "win32": - windowsSet(token); - process.stderr.write("[BEAM] Token verschlüsselt gespeichert (Windows DPAPI).\n"); - break; - default: - process.stderr.write(`[BEAM] Keychain nicht unterstützt auf ${OS}. Token nur im Speicher.\n`); - } -} - -/** - * Löscht das gespeicherte Token aus dem OS-Keychain. - */ -export function deleteStoredToken(): void { - switch (OS) { - case "darwin": - macosDelete(); - break; - case "win32": - windowsDelete(); - break; - default: - break; - } -} - -/** - * Prüft ob Keychain-Unterstützung auf diesem OS verfügbar ist. - */ -export function isKeychainSupported(): boolean { - return OS === "darwin" || OS === "win32"; -} diff --git a/bahn/beam-mcp/src/login.ts b/bahn/beam-mcp/src/login.ts deleted file mode 100644 index afe508c..0000000 --- a/bahn/beam-mcp/src/login.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Standalone Login-Skript - * Öffnet einen sichtbaren Edge-Browser, führt den BEAM-Login durch. - * Das Token wird im OS-Keychain gespeichert (macOS Keychain / Windows DPAPI). - * - * Verwendung: npm run login - */ - -import { loginWithBrowser } from "./auth.js"; -import { isKeychainSupported } from "./keychain.js"; - -console.log("🔐 BEAM Login wird gestartet..."); -console.log(" Ein Edge-Browser-Fenster öffnet sich gleich.\n"); - -if (!isKeychainSupported()) { - console.warn("⚠ Keychain wird auf diesem OS nicht unterstützt."); - console.warn(" Token wird nur im Speicher gehalten (nicht persistent).\n"); -} - -try { - const token = await loginWithBrowser(); - const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString()); - const expiry = new Date((payload.exp ?? 0) * 1000).toLocaleString("de-DE"); - const user = payload.principal?.username ?? payload.sub ?? "Unbekannt"; - - console.log("✅ Login erfolgreich!"); - console.log(` Benutzer: ${user}`); - console.log(` Token gültig bis: ${expiry}`); - console.log(` Gespeichert in: OS-Keychain (${process.platform === "darwin" ? "macOS Keychain" : "Windows DPAPI"})`); - console.log("\n Der MCP-Server nutzt das Token automatisch beim nächsten Start."); -} catch (err) { - console.error("❌ Login fehlgeschlagen:", err instanceof Error ? err.message : err); - process.exit(1); -} diff --git a/bahn/beam-mcp/tsconfig.json b/bahn/beam-mcp/tsconfig.json deleted file mode 100644 index b7418a0..0000000 --- a/bahn/beam-mcp/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["src/**/*"] -}