Expose an MCP server from your app
Your app can be an MCP server: AI assistants (Claude as a connector, Claude Code, LM Studio…) connect to it and call tools you define, authenticated per end-user via OAuth. The platform handles the whole transport (JSON-RPC 2.0 over Streamable HTTP) and the OAuth — you just register tools.
One call: mountMcp
import { mountMcp, currentUser } from "./.onvibe/helpers.ts";
export default async function handler(req: Request): Promise<Response> {
const mcp = await mountMcp(req, {
auth: "oauth", // authenticate each MCP client per end-user
tools: [
{
name: "list_notes",
description: "List the signed-in user's notes",
inputSchema: { type: "object", properties: {} },
handler: async (_args, ctx) => await listNotes(ctx.user!.id),
},
{
name: "create_note",
description: "Create a note",
inputSchema: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
handler: async (args, ctx) => await createNote(ctx.user!.id, String(args.text)),
},
],
});
if (mcp) return mcp; // it was an MCP request (/mcp) — handled
// ...the rest of your app (normal web UI)
}
mountMcp returns a Response for MCP requests (path /mcp) and null otherwise, so it composes
with your existing routes. It handles initialize, ping, tools/list, tools/call,
notifications, batching, CORS and the session headers for you.
Each tool handler(args, ctx) receives the call arguments and ctx.user — the authenticated
end-user { id, email } (or null if auth: "none"). Use ctx.user.id as the stable key for that
person's data. Return a string, a JSON-serializable value, or an MCP content object; mountMcp
wraps it.
Turning on OAuth (required for auth: "oauth")
MCP OAuth authenticates against your app's managed users, so the app must have user accounts:
set_access_policy({ project_id: "my-app", policy: "signup" }) // or "allowlist"
That's it. The platform then acts as the OAuth Authorization Server for your app:
- It serves discovery (
/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server), Dynamic Client Registration, and the authorize/token endpoints on your app's domain. - When a user adds your app's
/mcpURL in their AI client, they're redirected to your app's login (the same managed-auth login), approve access, and a scoped token is issued — no API keys to copy by hand. - That token arrives at your
/mcpverified;currentUser()/ctx.userresolves to that end-user.
The client's connection URL is https://<your-app-domain>/mcp (works on the .onvibe.run URL and
on custom domains).
Notes
- The
/mcppath is reserved for this — keep your MCP endpoint there. auth: "none"exposes tools with no authentication (public data only). Preferoauthfor anything user-specific.- Manage who can connect with the usual account tools (
create_app_user,add_allowed_email,remove_app_user) — MCP access follows the same accounts as the rest of your app.