Baue MiniApps für die SuperApp.
Alles, was du brauchst: Silent SSO ohne Login-Screen, Container-Steuerung, Deeplinks, die Plattform-APIs für Chat, Signatur & Payment — und ein MCP-Server, mit dem dein KI-Agent sofort loslegt.
Einführung — SuperApp & MiniApps
Die KOBIL SuperApp ist ein nativer Container (Mobile-App). Einzelne Funktionen — Terminbuchung, KI-Chat, Behördengänge — sind MiniApps: eigenständige Web-Apps, die in einem WebView innerhalb der SuperApp laufen.
Für den Endnutzer fühlt es sich nativ an:
- Login ohne Eintippen — der Nutzer ist in der SuperApp angemeldet, deine MiniApp übernimmt die Identität automatisch (Silent SSO).
- System-UI vom Container gesteuert — Vollbild, Status-Bar-Farbe und Navigation steuert die SuperApp über deine
miniApp.json. - Overlay-Navigation — deine MiniApp kann per Deeplink eine andere öffnen.
sub pro Nutzer über alle Apps. Darauf bauen App-übergreifende Features.Schnellstart — in 5 Minuten zur ersten MiniApp
Lege im Partner Hub einen Account an (E-Mail + Passwort).
Im Dashboard URL + Name eintragen. Wir registrieren sie als OIDC-Service und veröffentlichen sie im Store. Du erhältst deine Service-ID (= sID = OIDC client_id) und ein client_secret.
Im Dashboard die vorbefüllte Doku öffnen oder per MCP scaffold_miniapp — .env und OIDC-Flow sind mit deinen Werten gefüllt.
Hoste deine Web-App unter der angegebenen MiniApp-URL.
Silent SSO läuft automatisch — kein Login-Screen.
miniApp.json — Container-Steuerung
Eine statische Datei im Web-Root: public/miniApp.json (→ /miniApp.json). Die SuperApp liest sie beim Laden deiner MiniApp.
// public/miniApp.json { "version": "2.0", "fullScreen": true, "theme": "app-default", "statusBar": { "theme": { "light": { "backgroundColor": "#ffffff" }, "dark": { "backgroundColor": "#ffffff" } } }, "navigationBar": { "visible": false, "theme": { "light": { "backgroundColor": "#ffffff" }, "dark": { "backgroundColor": "#ffffff" } } } }
| Feld | Bedeutung |
|---|---|
| fullScreen | true = MiniApp zeichnet bis unter die Status-Bar (Vollbild). |
| theme | app-default übernimmt das SuperApp-Theme. |
| navigationBar.visible | false = keine Container-Navigation; du baust die UI selbst. |
| statusBar.theme | Status-Bar-Farbe je Light/Dark-Modus. |
Design & Native Feel
Mit fullScreen: true zeichnet deine MiniApp von ganz oben (unter der Status-Bar) bis ganz unten. Über deiner Web-UI liegt aber das native SuperApp-Menü: oben rechts eine schwebende Pille mit zwei Buttons — Optionen (Raster ⊞) und Schließen (✕). Dieser Bereich gehört der SuperApp — du musst ihn freihalten.
Safe Areas & reservierte Zone — so geht's konkret
Aktiviere viewport-fit=cover und nutze die env()-Safe-Area-Insets. Halte oben rechts Platz für die Pille frei.
<!-- Safe-Areas (viewport-fit) + ALLE Zoom-Arten aus --> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1, user-scalable=no">
/* Native SuperApp-Pille oben rechts */ :root{ --superapp-menu-w: 128px; /* Breite Pille + Abstand */ --superapp-menu-h: 52px; } /* Vollbild-Fläche inkl. Safe-Areas */ html, body{ min-height:100svh; margin:0; } .screen{ padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); } /* Eigene Kopfzeile: Titel links, rechts Platz für die Pille */ .appbar{ display:flex; align-items:center; height: var(--superapp-menu-h); padding-left: 20px; padding-right: var(--superapp-menu-w); /* ← Pille nicht überlappen */ }
Alle Zoom-Arten abschalten (Pflicht fürs Native-Gefühl)
Nichts verrät eine Web-App schneller als zoombarer Inhalt. Es gibt drei Zoom-Quellen — schalte alle ab:
| Zoom-Art | Abschalten mit |
|---|---|
| Pinch-to-Zoom (zwei Finger) | maximum-scale=1, user-scalable=no im Viewport-Meta (siehe oben). Im SuperApp-WebView wird das respektiert. |
| Doppeltipp-Zoom | touch-action: manipulation (global, z. B. auf html). |
| Auto-Zoom beim Fokus von Input/Select | Schriftgröße der Felder ≥ 16px — sonst zoomt iOS beim Antippen automatisch rein. |
/* gegen Doppeltipp-Zoom; Eingaben nie unter 16px */ html{ touch-action: manipulation; -webkit-text-size-adjust: 100%; } input, select, textarea{ font-size: 16px; }
document.addEventListener('gesturestart', e => e.preventDefault()).Native Feeling — Pflicht-Checkliste
Die MiniApp muss sich anfühlen wie ein nativer Screen, nicht wie eine Website im Browser. Mobile-first (entwirf zuerst für 360–430 px Breite):
| Thema | Regel |
|---|---|
| Höhe | 100svh/100dvh statt 100vh — sonst springt das Layout mit der Adressleiste. |
| Touch-Targets | Mindestens 44×44 px für alles Tippbare. |
| Schrift | System-Stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif. |
| Kein Hover | Keine reine Hover-UI — Touch hat kein Hover. Nutze klare :active-States für sofortiges Feedback. |
| Zoom | Alle drei Zoom-Arten aus (Pinch, Doppeltipp, Fokus-Auto-Zoom) — siehe Abschnitt oben. Plus touch-action: manipulation killt zugleich das 300 ms-Tap-Delay. |
| Scroll | Momentum-Scroll, overscroll-behavior: contain gegen „Durchziehen". Keine horizontalen Overflow-Wackler. |
| Auswahl | user-select: none auf Buttons/Nav; Text-Auswahl nur wo sinnvoll. |
| Navigation | Native Muster: Bottom-Tab-Bar (mit safe-area-inset-bottom), Bottom-Sheets statt Desktop-Modals, Swipe wo passend. |
| Bewegung | Kurze, weiche Transitions (cubic-bezier(.2,.8,.2,1)), keine Layout-Shifts. Optimistische UI + Skeletons statt Spinner. |
| Farbe | Status-Bar & Tab-Bar sind weiß (miniApp.json) → oben/unten in Weiß auflösen; prefers-color-scheme respektieren. |
Authentifizierung — Silent SSO (OIDC + PKCE)
Deine MiniApp authentifiziert per Authorization Code + PKCE (confidential client) gegen den KOBIL-IDP. Weil die SuperApp die Keycloak-Session injiziert, läuft das ohne Login-Screen.
Der Flow
- Kein
user_session-Cookie → Redirect auf/api/auth/login?next=<pfad>. /api/auth/login: erzeugtstate,nonce, PKCE-verifier, legt sie in ein kurzlebigesoidc_tx-Cookie (HttpOnly, 10 min) und redirectet zumauthorization_endpoint(code_challenge=S256).- IDP → Silent SSO → Redirect auf
/api/auth/callback?code=…&state=…. /api/auth/callback: prüftstate, tauscht den Code (mitclient_secret+code_verifier), verifiziert dasid_tokenvia JWKS, extrahiert Claims und setzt dein eigenes signiertesuser_session-Cookie.- Folge-Requests prüfen nur noch das
user_session-Cookie (kein IDP-Roundtrip).
// PKCE + Authorize-URL import { createHash, randomBytes } from "crypto"; export function pkce() { const verifier = randomBytes(32).toString("base64url"); const challenge = createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } export function authUrl(state, nonce, challenge) { const p = new URLSearchParams({ client_id: process.env.OIDC_CLIENT_ID, response_type: "code", scope: "openid profile email", redirect_uri: BASE + "/api/auth/callback", state, nonce, code_challenge: challenge, code_challenge_method: "S256", }); return ISSUER + "/protocol/openid-connect/auth?" + p; }
// app/api/auth/login/route.ts export async function GET(req) { const next = req.nextUrl.searchParams.get("next") || "/"; const { verifier, challenge } = pkce(); const state = randomBytes(16).toString("hex"); const res = NextResponse.redirect(authUrl(state, state, challenge)); res.cookies.set("oidc_tx", JSON.stringify({ state, verifier, next }), { httpOnly: true, sameSite: "lax", maxAge: 600, }); return res; }
// middleware.ts — schützt alles außer /api/auth/* export function middleware(req) { const { pathname } = req.nextUrl; if (pathname.startsWith("/api/auth")) return NextResponse.next(); if (!process.env.OIDC_CLIENT_ID) return NextResponse.next(); // Dev ohne IDP if (req.cookies.get("user_session")) return NextResponse.next(); const url = req.nextUrl.clone(); url.pathname = "/api/auth/login"; url.searchParams.set("next", pathname); return NextResponse.redirect(url); }
| Cookie | Inhalt · TTL · Flags |
|---|---|
| oidc_tx | state/nonce/PKCE-verifier · 10 min · HttpOnly, SameSite=Lax |
| user_session | signierte Claims (sub, name, …) · z. B. 8 h · HttpOnly, SameSite=Lax |
Strict würde deine Cookies blocken.OIDC_CLIENT_ID lokal nicht gesetzt, überspringe die OIDC-Ebene (sub = "dev-user") — so entwickelst du ohne IDP.Deeplinks & Navigation
Eine MiniApp öffnet eine andere über den Share-Host. Die SuperApp fängt die URL ab und legt die Ziel-MiniApp als Overlay über die aktuelle WebView.
location.href / location.replace() wird vom Host verworfen. Zuverlässig ist nur ein synthetischer Klick auf ein <a href> (linkActivated).function openMiniApp(url) { const a = document.createElement("a"); a.href = url; // <SHARE_BASE><ZIEL_SID> a.rel = "noreferrer"; a.style.display = "none"; document.body.appendChild(a); a.click(); // ← linkActivated, vom Host abgefangen a.remove(); }
- Kein eigenes Lade-/Launch-Overlay zeigen — die SuperApp legt ihr Overlay selbst darüber; deins bliebe beim Zurückkehren hängen.
- Watchdog (optional): bleibt die Seite ~1,5 s nach Auto-Open sichtbar, eine antippbare Karte zeigen — ein echter User-Tap geht immer durch.
mPower — Grundlagen & Authentifizierung
mPower ist die Server-zu-Server-Schnittstelle der Plattform. Damit schickt dein Backend Nachrichten in den SuperApp-Chat des Bürgers, lässt ihn Buttons antippen, PDFs unterschreiben und Zahlungen auslösen — alles, ohne dass der Bürger sich irgendwo separat einloggt. Die folgenden vier Abschnitte (Chat & Auswahl, PDF-Signatur, Payment) bauen alle auf diesem Grundlagen-Teil auf.
Zwei APIs, zwei Clients, ein Realm
Es gibt zwei getrennte APIs. Chat, Auswahl & Signatur laufen über deinen normalen OIDC-Client; Payment läuft über einen eigenen Merchant-Client. Verwechsle die beiden nicht — das ist die häufigste Fehlerquelle.
| API | Wofür | Client |
|---|---|---|
| mPower (mercury) | Chat, Auswahl-Nachrichten, PDF-Signatur, Medien | dein OIDC-Client (sID + client_secret) |
| Payment (mpay-merchant) | Zahlung anstoßen + Status | separater Merchant-Client (eigene merchantId + Secret + tenantId) |
sID, dein client_secret und alle Endpoint-URLs findest du fertig vorbefüllt im Dashboard. Die Merchant-Credentials für Payment bekommst du separat von deinem 1DE-Ansprechpartner.Schritt 1: Access-Token holen (client_credentials)
Beide APIs authentifizieren per client_credentials gegen denselben Token-Endpoint des Realms. Das Token ist ~300 Sekunden gültig — hole es einmal und cache es, statt es bei jedem Call neu anzufragen.
# Token fuer mPower (mit deinem OIDC-Client) curl -X POST "<ISSUER>/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=<DEINE_SID>" \ -d "client_secret=<DEIN_CLIENT_SECRET>" # → { "access_token": "eyJ…", "expires_in": 300, … }
// Token holen + cachen (~300s). Fuer Payment denselben Code mit // den Merchant-Credentials nutzen. let cached = { token: "", exp: 0 }; export async function mpowerToken() { if (cached.token && Date.now() < cached.exp) return cached.token; const body = new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.OIDC_CLIENT_ID, client_secret: process.env.OIDC_CLIENT_SECRET, }); const r = await fetch(`${ISSUER}/protocol/openid-connect/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); const j = await r.json(); cached = { token: j.access_token, exp: Date.now() + (j.expires_in - 30) * 1000 }; return cached.token; }
Wichtige Begriffe
| Begriff | Bedeutung |
|---|---|
| userId | Das OIDC-sub des Bürgers — exakt der Wert, den du beim Login aus dem user_session bzw. /api/auth/me bekommst. Kein Login nötig (S2S). |
| serviceUuid | Deine sID — identifiziert deinen Service als Absender. |
| version | Schema-Version der mPower-Nachricht. Aktuell 3. |
| {tenant} | Der Realm/Tenant in der URL (für mPower derselbe wie dein OIDC-Realm). |
Das Callback-Modell (sehr wichtig)
Ausgehende Calls (du → Plattform) bekommen sofort eine kleine Quittung { messageId, instanceId } zurück. Die eigentliche Antwort des Bürgers (er hat unterschrieben, einen Button getippt, geschrieben) kommt asynchron als Callback.
- Eine globale Service-Callback-URL (in deiner Service-Config hinterlegt) bekommt ALLE mPower-Callbacks deines Service — Signatur, Auswahl und Chat landen am selben Endpoint. Du unterscheidest sie über
messageType. - Payment hat einen eigenen Callback (
merchantCallback), den du pro Transaktion mitgibst.
callbackUrl ein ?applicationId=…, kommt es leer zurück. Korreliere deshalb über den von dir gewählten Dateinamen (bei Signatur) bzw. über from.userId + internen Status. Der Payment-Callback spiegelt den Query-Param dagegen schon.Chat & Auswahl-Nachrichten
Mit zwei Nachrichtentypen sprichst du den Bürger direkt im SuperApp-Chat an: Klartext (processChatMessage) und Auswahl mit Buttons (choiceRequest). Beide gehen an denselben Endpoint:
# Authorization: Bearer <access_token> (siehe Grundlagen)
POST <MPOWER_BASE>/auth/realms/<TENANT>/mpower/v1/users/{userId}/messageKlartext-Nachricht
Eine einfache Textnachricht, die im Chat erscheint — z. B. eine Bestätigung.
{
"serviceUuid": "<DEINE_SID>",
"messageType": "processChatMessage",
"version": 3,
"messageContent": { "messageText": "Danke! Dein Antrag ist eingegangen." }
}
{ "messageId": "a1b2c3…", "instanceId": "d4e5f6…" }
// Quittung, dass die Nachricht zugestellt wurde — NICHT die Antwort des Nutzers.
async function sendChat(userId, text) { const token = await mpowerToken(); const r = await fetch( `${MPOWER_BASE}/auth/realms/${TENANT}/mpower/v1/users/${userId}/message`, { method: "POST", headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify({ serviceUuid: process.env.MPOWER_SERVICE_UUID, messageType: "processChatMessage", version: 3, messageContent: { messageText: text }, }), }); return r.json(); // { messageId, instanceId } }
Auswahl-Nachricht (Buttons)
Zeigt dem Bürger antippbare Buttons. Sein Tipp kommt als choiceResponse-Callback zurück.
{
"serviceUuid": "<DEINE_SID>",
"messageType": "choiceRequest",
"version": 3,
"messageContent": {
"messageText": "Möchtest du jetzt bezahlen?",
"choices": [{ "text": "Bezahlen" }, { "text": "Später" }]
}
}Der Callback (Bürger antwortet)
Wenn der Bürger tippt oder schreibt, ruft die Plattform deine globale Service-Callback-URL auf. Envelope:
{
"message": {
"content": {
"messageType": "choiceResponse", // oder "processChatMessage"
"messageContent": { "messageText": "Bezahlen" } // gewählter Button-Text
},
"from": { "ecoId": "<TENANT>", "userId": "<sub>" },
"messageId": "…"
}
}| messageType | Bedeutung |
|---|---|
| choiceResponse | messageContent.messageText = der vom Bürger gewählte Button-Text. |
| processChatMessage | Normaler Chat-Text des Nutzers (oft einfach ignorierbar). |
from.userId + deinen internen Status (z. B. „dieser Nutzer wartet auf eine Zahl-Entscheidung"). Buttons haben keine ID — du erkennst die Wahl nur am messageText.PDF-Signatur
Du schickst ein PDF an die Plattform, der Bürger unterschreibt es direkt in der SuperApp, und du bekommst das signierte PDF zurück. Der Ablauf in vier Schritten:
PDF füllen/flatten, Signatur-Kasten als Prozent-Koordinaten angeben, an /signature posten.
Die SuperApp zeigt das PDF mit dem Signatur-Feld. Der Bürger unterschreibt.
Du bekommst signatureStatus: "signed" + eine mediaId.
Über die mediaId das fertige PDF herunterladen und weiterverarbeiten.
Schritt 1 — Signatur-Request
Ein multipart/form-data-POST mit zwei Parts: signatureFile (das PDF) und signatureData (ein JSON-String).
POST <MPOWER_BASE>/auth/realms/<TENANT>/mpower/v1/users/{userId}/signature
# Content-Type: multipart/form-data; Authorization: Bearer <token>
# Part "signatureData" (JSON-String):
{
"version": 3,
"pageNumber": 1,
"bottomLeftXCoordinate": 12,
"bottomLeftYCoordinate": 3,
"topRightXCoordinate": 39,
"topRightYCoordinate": 5,
"serviceUuid": "<DEINE_SID>",
"messageText": "Bitte unterschreiben",
"callbackUrl": "https://deine-app.de/api/webhooks/mpower-signature"
}
# → Antwort: { "messageId": "…", "instanceId": "…" }⚠️ Die Koordinaten verstehen (häufigster Fehler)
| Feld | Bedeutung |
|---|---|
| pageNumber | Seite, auf der unterschrieben wird (1-basiert). |
| bottomLeftX / Y | Untere-linke Ecke des Signatur-Kastens, in % der Seite (von unten-links). |
| topRightX / Y | Obere-rechte Ecke, in % der Seite. |
Umrechnung von Millimetern in Prozent (PDF-Punkt = 1/72 Zoll, 1 Zoll = 25,4 mm):
// X / Breite — direkt von links: const MM = 72 / 25.4; // mm → pt const pctX = Math.round(mmVonLinks * MM / seiteBreitePt * 100); // Y — Ursprung ist UNTEN, also von oben umrechnen: const pctY = Math.round((seiteHoehePt - mmVonOben * MM) / seiteHoehePt * 100);
flatten() nicht — es lässt ein leeres AcroForm-Dictionary zurück, und mPower zeigt dann keinen Signatur-Kasten an (außerdem überdecken die Formular-Widgets deinen Overlay-Text). Lösung: erst flatten, dann die Seiten in ein frisches PDFDocument kopieren — das entfernt das AcroForm vollständig.Schritt 3 — Callback signatureResponse
{
"message": {
"content": {
"messageType": "signatureResponse",
"signatureStatus": "signed",
"mediaContent": { "mediaId": "…", "fileName": "…",
"fileSize": 12345, "contentType": "application/pdf" }
},
"from": { "ecoId": "<TENANT>", "userId": "<sub>" }
}
}<templateId>-<antragId>.pdf) — über mediaContent.fileName bzw. from.userId findest du den Antrag wieder.Schritt 4 — Signiertes PDF herunterladen
GET <MPOWER_BASE>/v1/mpower/tenants/<TENANT>/media/{mediaId}/download
# Authorization: Bearer <token>
# → 301 Redirect auf eine presigned S3-URL.fetch folgt dem Redirect automatisch und entfernt dabei den Authorization-Header cross-origin (so will es die Fetch-Spec) — S3 autorisiert über die Signatur in der URL. Du musst nichts Spezielles tun, nur den Redirect zulassen.Payment
Payment löst eine echte Zahlung aus. Es läuft über einen eigenen Merchant-Client — nicht über deinen OIDC-Client. Das Token holst du genauso per client_credentials (siehe Grundlagen), aber mit den Merchant-Credentials.
merchantId === merchantServiceUUID === die client_id deines Payment-Merchant-Clients — beide Felder bekommen denselben Wert, und das ist NICHT die OIDC-sID dieser MiniApp, sondern der separate Payment-Client (Credentials von 1DE). tenantId = Tenant deiner Merchant-Credentials, nicht der mPower-Tenant.Schritt 1 — Transaktion anstoßen
POST <PAYMENT_BASE>/mpay-merchant/create/transaction # Authorization: Bearer <merchant_token> { "version": 1, "idempotencyId": "<UUID>", // echte UUID, gegen Doppelzahlung "userId": "<sub>", "merchantId": "<MERCHANT_ID>", "merchantServiceUUID": "<MERCHANT_ID>", "merchantName": "Stadt-Service", "merchantCallback": "https://deine-app.de/api/webhooks/payment?applicationId=42", "transactionTimeout": 10, "amount": 1000, // 10,00 € — in CENT (Ganzzahl!) "tenantId": "<MERCHANT_TENANT>", "currency": "EUR", "paymentContent": [[{ "key": "Antrag", "value": "10,00 €" }]] }
{ "transactionId": "…", "status": "new" }
// Transaktion ist nur ANGELEGT — noch NICHT bezahlt.
| Feld | Achtung |
|---|---|
| amount | In Cent als Ganzzahl. 10,00 € = 1000. Niemals Dezimal. |
| idempotencyId | Echte UUID. Schützt vor Doppelbuchung bei Retries. |
| merchantCallback | Dein Webhook für das Ergebnis. Query-Params werden hier gespiegelt (anders als bei mPower). |
Schritt 2 — Ergebnis-Callback
Das Ergebnis kommt asynchron an deine merchantCallback-URL, sobald der Bürger gezahlt (oder abgebrochen) hat.
{
"transactionId": "…",
"status": "finished",
"transactionStatus": "finished",
"message": "Payment complete.",
"cardGatewayType": "credit_stripe"
}finished bzw. complete — nicht „success" oder „paid" (das ist empirisch verifiziert). Prüfe also auf finished/complete, nicht auf andere Wörter.End-to-End: Formular → Signatur → Zahlung
So greifen die drei mPower-Teile in einem typischen Behörden-Flow ineinander:
Eine bewährte Status-Maschine dazu:
draft → ready → awaiting_signature → awaiting_payment → paying → sent
Discovery — Store & Auffindbarkeit
Beim Anlegen wird deine MiniApp als Service registriert. Über zwei Flags steuerst du die Sichtbarkeit:
- searchable — im Store auffindbar.
- webFlowEnabled — WebFlow aktiv.
Über die sID ist deine MiniApp per Deeplink aus anderen MiniApps und vom KI-Agenten der SuperApp erreichbar.
Deploy & Domain
Deine MiniApp ist eine normale Web-App — hoste sie, wo du willst (Vercel, eigener Server, Container …). Pflicht:
Unter /miniApp.json erreichbar.
https://<deine-domain>/api/auth/callback. Wird beim Anlegen gesetzt; bei Domain-Wechsel neu registrieren, sonst scheitert der OIDC-Callback.
no-store — sonst sieht der Nutzer Updates erst nach Force-Close der SuperApp.
Brauchst du eine *.temmuz.uk-Subdomain (Cloudflare, proxied)? Frag deinen 1DE-Ansprechpartner.
Auto-Deploy auf eine eigene Subdomain (am einfachsten)
Du musst gar nicht selbst hosten: Lade dein Projekt als ZIP hoch — im Dashboard bei deiner MiniApp oder per MCP-Tool deploy_miniapp. Wir bauen es als eigenen Container und schalten es live auf <name>-<code>.temmuz.uk (mit HTTPS).
Next.js oder statisch. package.json im Root, ohne node_modules, .next, .git (baut der Server). Max. 25 MB.
Dashboard → MiniApp → „ZIP hochladen & deployen", oder Agent: deploy_miniapp({ service_id, zip_base64 }).
Nach 1–3 Min Build ist deine MiniApp unter der Subdomain live.
deploy_miniapp mit derselben service_id auf — gleiche Subdomain, der Container wird ersetzt. Lege dafür KEINE neue MiniApp an (sonst Duplikate). create_miniapp ist nur einmal pro App nötig.Dockerfile (npm install → next build → next start). Hat dein ZIP ein eigenes Dockerfile, nutzen wir deins. Kein package.json → wird als statische Seite ausgeliefert.Im Browser testen (WebFlow)
Innerhalb der SuperApp läuft das Login per Silent SSO automatisch. Zum Testen außerhalb der SuperApp (im normalen Browser):
Beim Anlegen den Schalter „WebFlow aktiv" setzen (Dashboard) — dann ist Web-Login möglich.
Auf der IDP-Login-Seite mit dem Test-Account anmelden:
| Test-IDP-Account | Wert |
|---|---|
hello@kobil.com | |
| Passwort | TestAccount57428 |
MCP für KI-Agenten
Binde deinen KI-Agenten (Claude, Cursor, …) direkt an, damit er mit deiner echten MiniApp-Config sofort entwickelt — Doku lesen, Config abrufen, Code generieren, sogar neue MiniApps anlegen.
| Tool | Wirkung |
|---|---|
| get_docs | Diese Doku (optional: nur ein Abschnitt). |
| list_my_miniapps | Deine MiniApps. |
| get_miniapp_config | OIDC-Config, client_secret, Redirect-URIs, Deeplink, miniApp.json, .env, Endpoints. |
| scaffold_miniapp | Fertige Starter-Dateien (Next.js), prefilled. |
| create_miniapp | Neue MiniApp anlegen & veröffentlichen (optional mit Logo-Data-URI). |
| deploy_miniapp | Projekt (Next.js/statisch) als eigenen Container auf eine Subdomain hosten (ZIP base64). |
| delete_miniapp | Eigene MiniApp löschen & Änderung veröffentlichen. |
Verbindungsdaten
| Feld | Wert |
|---|---|
| Endpoint | https://deutschlandappwebpage.temmuz.uk/mcp |
| Transport | Streamable HTTP |
| Auth-Header | x-api-key: <DEIN_MCP_TOKEN> |
Schritt 1 — Token holen: Im Dashboard unter „KI-Agent per MCP anbinden" einen Token erzeugen. Schritt 2 — Client einrichten (wähle deinen Agenten):
Schnellster Weg: eine Session, ein Befehl (Claude Code)
Kein Setup, keine Config-Datei. Der Befehl verbindet Claude Code nur für diese Session mit deiner MCP (Token einsetzen):
claude --strict-mcp-config --mcp-config '{"mcpServers":{"1de-partner":{"type":"http","url":"https://deutschlandappwebpage.temmuz.uk/mcp","headers":{"x-api-key":"<DEIN_MCP_TOKEN>"}}}}'--mcp-config inline). Ein reiner Chat-Prompt kann sich nicht verbinden — die MCP-Anbindung macht immer der Client. Für dauerhaftes Setup oder andere Agenten siehe unten.Dauerhaft / andere Agenten
# Variante A — ein Befehl (Transport http + Custom-Header): claude mcp add --transport http 1de-partner \ https://deutschlandappwebpage.temmuz.uk/mcp \ --header "x-api-key: <DEIN_MCP_TOKEN>" # Variante B — projektweit per .mcp.json (ins Repo committen, Token via Env): { "mcpServers": { "1de-partner": { "type": "http", "url": "https://deutschlandappwebpage.temmuz.uk/mcp", "headers": { "x-api-key": "${MCP_TOKEN}" } } } } # Prüfen: claude mcp list · in der Session: /mcp
// Settings → Developer → "Edit Config" öffnet claude_desktop_config.json // (macOS: ~/Library/Application Support/Claude/ · Windows: %APPDATA%\Claude\) // Danach Claude Desktop neu starten. { "mcpServers": { "1de-partner": { "type": "http", "url": "https://deutschlandappwebpage.temmuz.uk/mcp", "headers": { "x-api-key": "<DEIN_MCP_TOKEN>" } } } } // Ältere Versionen ohne HTTP-Header-Support? → Tab "Andere KIs" (mcp-remote).
// Datei: ~/.cursor/mcp.json (global) oder .cursor/mcp.json (Projekt) // Cursor erkennt Streamable HTTP automatisch am "url"-Feld (kein "type" nötig). { "mcpServers": { "1de-partner": { "url": "https://deutschlandappwebpage.temmuz.uk/mcp", "headers": { "x-api-key": "<DEIN_MCP_TOKEN>" } } } } // Prüfen: Settings → MCP → grüner Punkt + Tool-Liste.
// Datei: .vscode/mcp.json — Schlüssel heißt "servers", "type" ist PFLICHT. // Der inputs-Block fragt den Token sicher ab (statt Klartext im Repo). { "servers": { "1de-partner": { "type": "http", "url": "https://deutschlandappwebpage.temmuz.uk/mcp", "headers": { "x-api-key": "${input:onede-key}" } } }, "inputs": [ { "type": "promptString", "id": "onede-key", "description": "1DE MCP-Token", "password": true } ] } // Start: CodeLens "Start" über der Definition · prüfen: "MCP: List Servers".
// Datei: ~/.codeium/windsurf/mcp_config.json — Feld heißt "serverUrl". { "mcpServers": { "1de-partner": { "serverUrl": "https://deutschlandappwebpage.temmuz.uk/mcp", "headers": { "x-api-key": "<DEIN_MCP_TOKEN>" } } } } // Cascade → Plugins → Refresh; verbundener Server zeigt seine Tools.
# Jeder andere MCP-Client: zuerst nativen Streamable-HTTP-Eintrag versuchen # (url + headers). Kann der Client NUR stdio? → mcp-remote-Bridge: { "mcpServers": { "1de-partner": { "command": "npx", "args": [ "-y", "mcp-remote", "https://deutschlandappwebpage.temmuz.uk/mcp", "--header", "x-api-key:<DEIN_MCP_TOKEN>" ] } } } # ⚠ Bei mcp-remote KEIN Leerzeichen nach dem Doppelpunkt im Header-Wert!
Sofort loslegen — der erste Prompt
Gib deinem Agenten als erstes diesen Auftrag. Er liest sich damit komplett ein, bevor er irgendetwas baut:
Du bist mein Entwickler für eine MiniApp im 1DE-/KOBIL-SuperApp-Ökosystem. Lies dich ZUERST vollständig ein — rufe `get_docs` für ALLE Abschnitte auf und verstehe: Was ist eine MiniApp, Schnellstart, miniApp.json, Design & Native Feel, Authentifizierung (Silent SSO), Deeplinks, mPower (Chat, Signatur, Payment), Discovery, Deploy — und wie man eine MiniApp anlegt (`create_miniapp`) und deployt (`deploy_miniapp`). WICHTIG zum Hosting: Soll auf 1DE gehostet werden, rufe `create_miniapp` OHNE `miniapp_url` auf — es wird automatisch eine feste Subdomain vergeben und als URL/Redirect/Callback registriert. Danach `deploy_miniapp` auf genau diese MiniApp → die URL stimmt immer (kein Mismatch). WebFlow bleibt aus. Bei ÄNDERUNGEN/Updates: KEINE neue MiniApp anlegen! Dieselbe MiniApp per `deploy_miniapp` mit derselben service_id erneut deployen (gleiche Subdomain, Container wird ersetzt). `create_miniapp` nur EINMAL. Wenn du ALLES verstanden hast, frag NICHTS weiter ab, sondern melde dich bei mir: „Ich bin bereit, deine MiniApp zu erstellen — was sollen wir bauen?" und warte auf meine Idee. Erst danach legst du an, baust, und deployst.
Referenz — Endpoints & Glossar
Glossar
| Begriff | Bedeutung |
|---|---|
| sID | Service-ID deiner MiniApp = OIDC client_id = serviceUuid für mPower. |
| Silent SSO | Login ohne Eintippen; Identität von der SuperApp. |
| Deeplink | <SHARE_BASE><sID>, öffnet eine MiniApp als Overlay. |
| user_session | Dein eigenes signiertes Session-Cookie nach dem OIDC-Callback. |
Endpoints
| Zweck | URL (Platzhalter aus deiner Config) |
|---|---|
| Authorize | <ISSUER>/protocol/openid-connect/auth |
| Token | <ISSUER>/protocol/openid-connect/token |
| JWKS | <ISSUER>/protocol/openid-connect/certs |
| mPower Msg | <MPOWER_BASE>/auth/realms/<TENANT>/mpower/v1/users/{userId}/message |
| Signatur | <MPOWER_BASE>/…/users/{userId}/signature |
| Payment | <PAYMENT_BASE>/mpay-merchant/create/transaction |
client_secret) findest du im Dashboard — fertig vorbefüllt zum Kopieren.
1DE