MCP server
Let AI clients like Claude use your app through an MCP endpoint, with OAuth sign-in and team-scoped tools.
Emberstack serves a Model Context Protocol endpoint at /api/mcp, so AI clients such as Claude can read and create data for a signed-in user. Sign-in uses the Better Auth MCP plugin, which makes Better Auth an OAuth 2.1 server.
How a client connects
- The client calls
/api/mcp, gets a401, and finds the authorization server through/.well-known/oauth-protected-resource. - It registers itself and sends the user to sign in at
/login, with a password or a passkey. - The user approves access on
/oauth/consent. The grant is for the team that's active at that moment. - The client gets an access token for
/api/mcpand calls the tools with it. Refresh tokens rotate on every use.
Tools
The starter ships four tools in src/pages/api/mcp.ts, all scoped to the user and the team in the token:
list_notesandget_noteread the user's notes.create_notecreates a draft note.list_activitylists the user's recent activity.
Membership is checked on every call, so someone removed from the team loses access straight away.
Adding a tool
Register it in buildServer() in src/pages/api/mcp.ts with a Zod input schema, and scope every query by userId and organizationId:
server.registerTool(
'list_projects',
{
description: 'List the projects in this team.',
inputSchema: z.object({ limit: z.number().int().min(1).max(100).default(20) }),
annotations: { readOnlyHint: true },
},
async ({ limit }) =>
text(await db.select().from(projects).where(eq(projects.organizationId, organizationId)).limit(limit)),
);Tokens carry the mcp scope. If a tool needs more access, add a scope to MCP_SCOPES in src/lib/betterauth.ts and check it in the handler.
Settings
- The endpoint and token audience come from
PUBLIC_URL(<PUBLIC_URL>/api/mcp), so set it to your real domain. - The OAuth tables are created by the app migrations; there's nothing else to configure.
- Deleting an account revokes its refresh tokens and consents.
Testing locally
Run the Worker with bunx wrangler dev --port 3000 --local-upstream localhost:3000. Without --local-upstream, Wrangler rewrites the host to your production domain and Better Auth rejects the origin. Point an MCP client or the MCP Inspector at http://localhost:3000/api/mcp.