Connected Apps

When people connect an AI agent to your app, mint a headless key for automation, or simply sign in from a new device, they’re trusting your app to keep track of those connections — and to let them pull the plug when something looks wrong. Connected Apps is the platform API that makes that possible. From inside your own app, on your own profile or settings page, you can show a signed-in user everything connected to their account and let them revoke any of it.

Three kinds of connection show up here, all for the currently signed-in user:

  • Connected agents — the AI agents and OAuth apps the user has authorized against your MCP server, with the scopes they were granted.
  • API keys — the headless keys the user minted for automation (background jobs, CI, scheduled assistants).
  • Login sessions — where the user is currently signed in.

Everything is reached through platform.auth.*. There’s nothing to deploy and no separate dashboard — it’s the same platform client you already use for KV, Database, and Storage.

Scoped to the signed-in user — always

These methods expose security-sensitive information, so they operate only on the user behind the current request. You never pass a user id; the runtime resolves the signed-in user server-side and answers for exactly that person. There is no way to ask “what’s connected to someone else’s account” — an app can only ever show a user their own connections. If no user is signed in, the call simply fails.

This is the same identity your authorization rules already use, so a Connected Apps page inherits the same guarantee the rest of your app relies on: a user sees their own data and no one else’s.

Showing what’s connected

Read the three lists in any server-rendered route, the same way you’d read any other platform service:

// SvelteKit +page.server.ts / Nuxt server route / React Router loader
import { getPlatform } from '@maravilla-labs/platform';

export async function load() {
  const platform = getPlatform();

  const [agents, apiKeys, sessions] = await Promise.all([
    platform.auth.listConnectedClients(), // authorized agents + OAuth apps
    platform.auth.listApiKeys(),          // headless keys for automation
    platform.auth.listSessions(),         // where the user is signed in
  ]);

  return { agents, apiKeys, sessions };
}

Each entry tells the user what they need to make a decision: an agent’s name and the scopes it holds, when a key was last used and when it expires, which device a session belongs to and whether it’s the one they’re using right now. Render them as three sections of a profile page and you have the equivalent of the “Authorized apps” screen people already know from GitHub or Google — built on your own accounts.

Revoking access

Each list has a matching revoke call. Wire them to buttons (or your framework’s form actions) so a user can cut off anything they don’t recognize:

const platform = getPlatform();

// Disconnect an agent / OAuth app entirely.
await platform.auth.revokeClient(clientId);

// Revoke a headless API key.
await platform.auth.revokeApiKey(keyId);

// Sign out a specific login session.
await platform.auth.revokeSession(sessionId);

Revocation takes effect immediately. Revoking an agent withdraws the user’s consent and ends its connection, so it can’t continue acting on the user’s behalf. Revoking a key stops it on its next use. Revoking a session signs that device out. The user stays in control, from your UI, without ever touching a separate console.

Minting an API key

A user can create a new headless key directly from your settings page — handy when they want to drive your app from a script or connect a non-interactive agent. You choose the name and which scopes it carries (never more than the user themselves can do), and optionally an expiry:

const platform = getPlatform();

const { id, key } = await platform.auth.mintApiKey({
  name: 'CI deploy bot',
  scopes: ['notes:read', 'notes:write'],
  expiresInDays: 90, // optional
});

// Show `key` to the user once — it isn't retrievable again.

The full key value is returned once at creation; show it to the user immediately and store only what you need to identify it later (the id). The key behaves exactly like the headless keys described in MCP Servers: the holder is treated as that user, with exactly the granted scopes, until the key expires or the user revokes it.

The full surface

All on platform.auth, all scoped to the current signed-in user:

MethodWhat it does
listConnectedClients()List the agents / OAuth apps the user has authorized, with granted scopes and when each was last active.
revokeClient(clientId)Disconnect an agent entirely — immediate.
listApiKeys()List the user’s headless API keys (name, scopes, last used, expiry).
mintApiKey({ name, scopes, expiresInDays? })Create a new headless key; returns the secret once.
revokeApiKey(id)Revoke a headless key.
listSessions()List the user’s active login sessions (device, when, and which is current).
revokeSession(id)Sign a specific session out.

How it fits together

  1. In a server-rendered route, read listConnectedClients(), listApiKeys(), and listSessions() for the signed-in user.
  2. Render the three lists as a profile or security page.
  3. Wire each item’s revoke button to revokeClient / revokeApiKey / revokeSession.
  4. Optionally let the user mint a new key with mintApiKey.

Because everything is scoped server-side to the signed-in user, you write the UI and the platform handles the trust boundary.

Next Steps

  • MCP Servers — the agents and headless keys that show up here, and how users connect them
  • Authentication — the accounts and sessions Connected Apps manages
  • Authorization — the per-user rules that scope every call