Build MiniApps for the SuperApp.
Everything you need: Silent SSO with no login screen, container control, deep links, the platform APIs for chat, signature & payment — and an MCP server that lets your AI agent get going right away.
Introduction — SuperApp & MiniApps
The KOBIL SuperApp is a native container (a mobile app). Individual features — appointment booking, AI chat, dealings with public authorities — are MiniApps: standalone web apps that run in a WebView inside the SuperApp.
For the end user it feels native:
- Sign-in without typing — the user is signed in to the SuperApp and your MiniApp takes over their identity automatically (Silent SSO).
- System UI controlled by the container — the SuperApp controls full-screen mode, status bar color and navigation via your
miniApp.json. - Overlay navigation — your MiniApp can open another one via a deep link.
sub per user across every app. Cross-app features build on this.Quickstart — your first MiniApp in 5 minutes
Create an account in the Partner Hub (email + password).
Enter a URL + name in the dashboard. We register it as an OIDC service and publish it in the store. You receive your service ID (= sID = OIDC client_id) and a client_secret.
Open the pre-filled docs in the dashboard or use MCP scaffold_miniapp — .env and the OIDC flow come filled in with your values.
Host your web app at the MiniApp URL you specified.
Silent SSO runs automatically — no login screen.
miniApp.json — container control
A static file in your web root: public/miniApp.json (→ /miniApp.json). The SuperApp reads it when your MiniApp loads.
// 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" } } } }
| Field | Meaning |
|---|---|
| fullScreen | true = the MiniApp draws all the way under the status bar (full screen). |
| theme | app-default adopts the SuperApp theme. |
| navigationBar.visible | false = no container navigation; you build the UI yourself. |
| statusBar.theme | Status bar color per light/dark mode. |
Design & Native Feel
With fullScreen: true, your MiniApp draws from the very top (under the status bar) all the way to the bottom. But the native SuperApp menu sits on top of your web UI: a floating pill in the top right with two buttons — Options (grid ⊞) and Close (✕). That area belongs to the SuperApp — you must keep it clear.
Safe areas & the reserved zone — how to do it
Enable viewport-fit=cover and use the env() safe-area insets. Keep space for the pill clear in the top right.
<!-- 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 */ }
Disable every kind of zoom (required for a native feel)
Nothing gives a web app away faster than zoomable content. There are three sources of zoom — turn them all off:
| Type of zoom | Disable with |
|---|---|
| Pinch-to-zoom (two fingers) | maximum-scale=1, user-scalable=no in the viewport meta (see above). The SuperApp WebView respects this. |
| Double-tap zoom | touch-action: manipulation (globally, e.g. on html). |
| Auto-zoom on focusing an input/select | Field font size ≥ 16px — otherwise iOS zooms in automatically when tapped. |
/* 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 feel — the must-do checklist
The MiniApp has to feel like a native screen, not like a website in a browser. Mobile-first (design for 360–430 px width first):
| Topic | Rule |
|---|---|
| Height | 100svh/100dvh instead of 100vh — otherwise the layout jumps with the address bar. |
| Touch targets | At least 44×44 px for anything tappable. |
| Type | System stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif. |
| No hover | No hover-only UI — touch has no hover. Use clear :active states for instant feedback. |
| Zoom | All three kinds of zoom off (pinch, double-tap, focus auto-zoom) — see the section above. Plus touch-action: manipulation also kills the 300 ms tap delay. |
| Scroll | Momentum scrolling, overscroll-behavior: contain against “pull-through”. No horizontal overflow wobble. |
| Selection | user-select: none on buttons/nav; allow text selection only where it makes sense. |
| Navigation | Native patterns: a bottom tab bar (with safe-area-inset-bottom), bottom sheets instead of desktop modals, swipe where it fits. |
| Motion | Short, soft transitions (cubic-bezier(.2,.8,.2,1)), no layout shifts. Optimistic UI + skeletons instead of spinners. |
| Color | The status bar & tab bar are white (miniApp.json) → resolve top/bottom into white; respect prefers-color-scheme. |
Authentication — Silent SSO (OIDC + PKCE)
Your MiniApp authenticates via Authorization Code + PKCE (confidential client) against the KOBIL IDP. Because the SuperApp injects the Keycloak session, this happens with no login screen.
The flow
- No
user_sessioncookie → redirect to/api/auth/login?next=<path>. /api/auth/login: generatesstate,nonce, the PKCEverifier, stores them in a short-livedoidc_txcookie (HttpOnly, 10 min) and redirects to theauthorization_endpoint(code_challenge=S256).- IDP → Silent SSO → redirect to
/api/auth/callback?code=…&state=…. /api/auth/callback: checksstate, exchanges the code (withclient_secret+code_verifier), verifies theid_tokenvia JWKS, extracts claims and sets your own signeduser_sessioncookie.- Follow-up requests only check the
user_sessioncookie (no IDP round-trip).
// 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 | Contents · TTL · Flags |
|---|---|
| oidc_tx | state/nonce/PKCE verifier · 10 min · HttpOnly, SameSite=Lax |
| user_session | signed claims (sub, name, …) · e.g. 8 h · HttpOnly, SameSite=Lax |
Strict would block your cookies.OIDC_CLIENT_ID is not set locally, skip the OIDC layer (sub = "dev-user") — that way you develop without an IDP.Deep Links & Navigation
One MiniApp opens another via the share host. The SuperApp intercepts the URL and places the target MiniApp as an overlay over the current WebView.
location.href / location.replace() is discarded by the host. The only reliable way is a synthetic click on an <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(); }
- Don't show your own loading/launch overlay — the SuperApp places its own overlay on top; yours would stay stuck when you return.
- Watchdog (optional): if the page is still visible ~1.5 s after an auto-open, show a tappable card — a real user tap always goes through.
mPower — Basics & Authentication
mPower is the platform's server-to-server interface. With it, your backend sends messages into the citizen's SuperApp chat, has them tap buttons, sign PDFs and trigger payments — all without the citizen logging in anywhere separately. The following sections (Chat & Choice, PDF Signature, Payment) all build on these basics.
Two APIs, two clients, one realm
There are two separate APIs. Chat, choice & signature run over your regular OIDC client; payment runs over a dedicated merchant client. Don't mix the two up — that's the most common source of errors.
| API | What for | Client |
|---|---|---|
| mPower (mercury) | Chat, choice messages, PDF signature, media | your OIDC client (sID + client_secret) |
| Payment (mpay-merchant) | Trigger a payment + status | separate merchant client (its own merchantId + secret + tenantId) |
sID, your client_secret and all endpoint URLs come pre-filled in the dashboard. You receive the merchant credentials for payment separately from your 1DE contact.Step 1: get an access token (client_credentials)
Both APIs authenticate via client_credentials against the same token endpoint of the realm. The token is valid for ~300 seconds — fetch it once and cache it instead of requesting a new one on every call.
# 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; }
Key terms
| Term | Meaning |
|---|---|
| userId | The citizen's OIDC sub — exactly the value you get at sign-in from user_session or /api/auth/me. No login needed (S2S). |
| serviceUuid | Your sID — identifies your service as the sender. |
| version | Schema version of the mPower message. Currently 3. |
| {tenant} | The realm/tenant in the URL (for mPower, the same as your OIDC realm). |
The callback model (very important)
Outgoing calls (you → platform) get a small receipt { messageId, instanceId } back immediately. The citizen's actual response (they signed, tapped a button, typed something) arrives asynchronously as a callback.
- One global service callback URL (configured in your service config) receives ALL mPower callbacks for your service — signature, choice and chat all land at the same endpoint. You tell them apart via
messageType. - Payment has its own callback (
merchantCallback), which you pass per transaction.
?applicationId=… to the callbackUrl, it comes back empty. So correlate via the file name you chose (for signature) or via from.userId + internal status. The payment callback, by contrast, does echo the query param.Chat & Choice Messages
With two message types you reach the citizen directly in the SuperApp chat: plain text (processChatMessage) and a choice with buttons (choiceRequest). Both go to the same endpoint:
# Authorization: Bearer <access_token> (siehe Grundlagen)
POST <MPOWER_BASE>/auth/realms/<TENANT>/mpower/v1/users/{userId}/messagePlain-text message
A simple text message that appears in the chat — e.g. a confirmation.
{
"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 } }
Choice message (buttons)
Shows the citizen tappable buttons. Their tap comes back as a choiceResponse callback.
{
"serviceUuid": "<DEINE_SID>",
"messageType": "choiceRequest",
"version": 3,
"messageContent": {
"messageText": "Möchtest du jetzt bezahlen?",
"choices": [{ "text": "Bezahlen" }, { "text": "Später" }]
}
}The callback (citizen responds)
When the citizen taps or types, the platform calls your global service callback URL. Envelope:
{
"message": {
"content": {
"messageType": "choiceResponse", // oder "processChatMessage"
"messageContent": { "messageText": "Bezahlen" } // gewählter Button-Text
},
"from": { "ecoId": "<TENANT>", "userId": "<sub>" },
"messageId": "…"
}
}| messageType | Meaning |
|---|---|
| choiceResponse | messageContent.messageText = the button text the citizen chose. |
| processChatMessage | Normal chat text from the user (often simply ignorable). |
from.userId + your internal status (e.g. “this user is waiting on a payment decision”). Buttons have no ID — you recognize the choice only from the messageText.PDF Signature
You send a PDF to the platform, the citizen signs it directly in the SuperApp, and you get the signed PDF back. The flow in four steps:
Fill/flatten the PDF, specify the signature box as percentage coordinates, post it to /signature.
The SuperApp shows the PDF with the signature field. The citizen signs.
You receive signatureStatus: "signed" + a mediaId.
Use the mediaId to download the finished PDF and process it further.
Step 1 — signature request
A multipart/form-data POST with two parts: signatureFile (the PDF) and signatureData (a 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": "…" }⚠️ Understanding the coordinates (most common mistake)
| Field | Meaning |
|---|---|
| pageNumber | The page that gets signed (1-based). |
| bottomLeftX / Y | Bottom-left corner of the signature box, in % of the page (from the bottom left). |
| topRightX / Y | Top-right corner, in % of the page. |
Converting millimeters to percent (PDF point = 1/72 inch, 1 inch = 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() is not enough — it leaves an empty AcroForm dictionary behind, and mPower then shows no signature box (and the form widgets cover your overlay text). Fix: flatten first, then copy the pages into a fresh PDFDocument — that removes the AcroForm completely.Step 3 — signatureResponse callback
{
"message": {
"content": {
"messageType": "signatureResponse",
"signatureStatus": "signed",
"mediaContent": { "mediaId": "…", "fileName": "…",
"fileSize": 12345, "contentType": "application/pdf" }
},
"from": { "ecoId": "<TENANT>", "userId": "<sub>" }
}
}<templateId>-<antragId>.pdf) — via mediaContent.fileName or from.userId you can find the request again.Step 4 — download the signed PDF
GET <MPOWER_BASE>/v1/mpower/tenants/<TENANT>/media/{mediaId}/download
# Authorization: Bearer <token>
# → 301 Redirect auf eine presigned S3-URL.fetch follows the redirect automatically and strips the Authorization header cross-origin in the process (as the Fetch spec requires) — S3 authorizes via the signature in the URL. You don't need to do anything special, just allow the redirect.Payment
Payment triggers a real payment. It runs over a dedicated merchant client — not over your OIDC client. You fetch the token the same way via client_credentials (see Basics), but with the merchant credentials.
merchantId === merchantServiceUUID === the client_id of your payment merchant client — both fields get the same value, and that is NOT the OIDC sID of this MiniApp, but the separate payment client (credentials from 1DE). tenantId = the tenant of your merchant credentials, not the mPower tenant.Step 1 — start a transaction
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.
| Field | Watch out |
|---|---|
| amount | In cents, as an integer. €10.00 = 1000. Never a decimal. |
| idempotencyId | A real UUID. Protects against double-charging on retries. |
| merchantCallback | Your webhook for the result. Query params are echoed here (unlike with mPower). |
Step 2 — result callback
The result arrives asynchronously at your merchantCallback URL once the citizen has paid (or cancelled).
{
"transactionId": "…",
"status": "finished",
"transactionStatus": "finished",
"message": "Payment complete.",
"cardGatewayType": "credit_stripe"
}finished or complete — not “success” or “paid” (this is empirically verified). So check for finished/complete, not for other words.End to end: form → signature → payment
Here is how the three mPower parts mesh together in a typical public-authority flow:
A proven state machine for it:
draft → ready → awaiting_signature → awaiting_payment → paying → sent
Discovery — Store & Findability
When you create it, your MiniApp is registered as a service. Two flags control its visibility:
- searchable — findable in the store.
- webFlowEnabled — WebFlow active.
Via the sID, your MiniApp is reachable by deep link from other MiniApps and from the SuperApp's AI agent.
Deploy & Domain
Your MiniApp is a normal web app — host it wherever you like (Vercel, your own server, a container …). Required:
Reachable at /miniApp.json.
https://<your-domain>/api/auth/callback. Set when you create the app; re-register it if you change domains, otherwise the OIDC callback fails.
no-store — otherwise the user only sees updates after force-closing the SuperApp.
Need a *.temmuz.uk subdomain (Cloudflare, proxied)? Ask your 1DE contact.
Auto-deploy to your own subdomain (the easiest path)
You don't have to host it yourself at all: upload your project as a ZIP — in the dashboard on your MiniApp, or via the MCP tool deploy_miniapp. We build it as a dedicated container and put it live at <name>-<code>.temmuz.uk (with HTTPS).
Next.js or static. package.json at the root, without node_modules, .next, .git (the server builds it). Max. 25 MB.
Dashboard → MiniApp → “Upload & deploy ZIP”, or agent: deploy_miniapp({ service_id, zip_base64 }).
After a 1–3 min build your MiniApp is live at the subdomain.
deploy_miniapp with the same service_id — same subdomain, the container is replaced. Do NOT create a new MiniApp for this (otherwise you get duplicates). create_miniapp is only needed once per app.Dockerfile (npm install → next build → next start). If your ZIP has its own Dockerfile, we use yours. No package.json → it's served as a static site.Test in the browser (WebFlow)
Inside the SuperApp, sign-in runs via Silent SSO automatically. To test outside the SuperApp (in a normal browser):
Set the “WebFlow active” switch when you create the app (dashboard) — then web login is possible.
On the IDP login page, sign in with the test account:
| Test IDP account | Value |
|---|---|
hello@kobil.com | |
| Password | TestAccount57428 |
MCP for AI agents
Connect your AI agent (Claude, Cursor, …) directly so it develops against your real MiniApp config right away — read the docs, fetch the config, generate code, even create new MiniApps.
| Tool | Effect |
|---|---|
| get_docs | These docs (optional: just one section). |
| list_my_miniapps | Your MiniApps. |
| get_miniapp_config | OIDC config, client_secret, redirect URIs, deep link, miniApp.json, .env, endpoints. |
| scaffold_miniapp | Ready-made starter files (Next.js), prefilled. |
| create_miniapp | Create & publish a new MiniApp (optionally with a logo data URI). |
| deploy_miniapp | Host a project (Next.js/static) as a dedicated container on a subdomain (ZIP base64). |
| delete_miniapp | Delete your own MiniApp & publish the change. |
Connection details
| Field | Value |
|---|---|
| Endpoint | https://deutschlandappwebpage.temmuz.uk/mcp |
| Transport | Streamable HTTP |
| Auth header | x-api-key: <YOUR_MCP_TOKEN> |
Step 1 — get a token: in the dashboard under “Connect an AI agent via MCP”, generate a token. Step 2 — set up the client (pick your agent):
Fastest way: one session, one command (Claude Code)
No setup, no config file. The command connects Claude Code to your MCP for this session only (insert your token):
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). A plain chat prompt cannot connect — the client always handles the MCP connection. For a permanent setup or other agents, see below.Permanent / other agents
# 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!
Get going right away — the first prompt
Give your agent this task first. It uses it to read everything in before it builds anything:
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.
Reference — Endpoints & Glossary
Glossary
| Term | Meaning |
|---|---|
| sID | Service ID of your MiniApp = OIDC client_id = serviceUuid for mPower. |
| Silent SSO | Sign-in without typing; identity from the SuperApp. |
| Deep link | <SHARE_BASE><sID>, opens a MiniApp as an overlay. |
| user_session | Your own signed session cookie after the OIDC callback. |
Endpoints
| Purpose | URL (placeholders from your 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 |
| Signature | <MPOWER_BASE>/…/users/{userId}/signature |
| Payment | <PAYMENT_BASE>/mpay-merchant/create/transaction |
client_secret) in the dashboard — pre-filled and ready to copy.
1DE