Documentation Index
Fetch the complete documentation index at: https://mcp-use.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
This documentation covers the Better Auth OAuth Provider plugin (@better-auth/oauth-provider).If you are using the older Better Auth MCP plugin, note that it is deprecated in favor of the OAuth Provider approach. For legacy MCP adapter users, Better Auth provides a dedicated mcp-use adapter.
The Better Auth provider turns your mcp-use server into an OAuth 2.1 authorization server using Better Auth’s OAuth Provider plugin. Better Auth handles the full OAuth flow (authorization, token issuance, JWKS) — mcp-use only verifies the resulting JWTs.
Install
npm install better-auth @better-auth/oauth-provider better-sqlite3
Setup
Create an auth.ts file that initializes Better Auth with the OAuth Provider plugin and the JWT plugin (required for token signing):
// auth.ts
import { betterAuth } from "better-auth";
import { jwt } from "better-auth/plugins";
import { oauthProvider } from "@better-auth/oauth-provider";
import Database from "better-sqlite3";
export const auth = betterAuth({
authURL: "http://localhost:3000",
basePath: "/api/auth",
secret: process.env.BETTER_AUTH_SECRET!,
database: new Database("./sqlite.db"),
// Social login providers (add your own here)
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
plugins: [
jwt(), // Required: signs and verifies access tokens
oauthProvider({
loginPage: "/sign-in",
consentPage: "/consent",
allowDynamicClientRegistration: true,
allowUnauthenticatedClientRegistration: true,
// Must include your MCP endpoint so Better Auth accepts it as a valid audience
validAudiences: ["http://localhost:3000/mcp"],
// Expose user profile claims in access token JWTs
customAccessTokenClaims: async ({ user }) => ({
email: user?.email,
name: user?.name,
picture: user?.image,
}),
}),
],
});
2. Generate and migrate the database
npx auth@latest generate
npx auth@latest migrate
// server.ts
import { MCPServer, oauthBetterAuthProvider } from "mcp-use/server";
import { auth } from "./auth.js";
import {
oauthProviderAuthServerMetadata,
oauthProviderOpenIdConfigMetadata,
} from "@better-auth/oauth-provider";
const server = new MCPServer({
name: "my-server",
version: "1.0.0",
oauth: oauthBetterAuthProvider({
authURL: "http://localhost:3000/api/auth",
}),
});
// Mount Better Auth routes on the MCP server's Hono app
server.app.on(["GET", "POST"], "/api/auth/**", (c) => auth.handler(c.req.raw));
// OAuth authorization server metadata (RFC 8414)
// CORS headers are required for browser-based MCP clients like MCP Inspector.
const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET" };
const authServerMetadataHandler = oauthProviderAuthServerMetadata(auth, { headers: corsHeaders });
server.app.get("/.well-known/oauth-authorization-server", (c) => authServerMetadataHandler(c.req.raw));
server.app.get("/.well-known/oauth-authorization-server/api/auth", (c) => authServerMetadataHandler(c.req.raw));
// OpenID Connect configuration metadata
const openIdConfigHandler = oauthProviderOpenIdConfigMetadata(auth, { headers: corsHeaders });
server.app.get("/.well-known/openid-configuration", (c) => openIdConfigHandler(c.req.raw));
server.app.get("/.well-known/openid-configuration/api/auth", (c) => openIdConfigHandler(c.req.raw));
await server.listen(3000);
4. Add login and consent pages
Better Auth requires a login page (where users authenticate) and a consent page (where users approve requested scopes):
// Sign-in page — redirects to your social provider
server.app.get("/sign-in", (c) => {
const queryString = new URL(c.req.url).search;
return c.html(`<!DOCTYPE html>
<html>
<body>
<button onclick="signIn()">Sign in with GitHub</button>
<script>
async function signIn() {
const res = await fetch('/api/auth/sign-in/social', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
provider: 'github',
callbackURL: '/api/auth/oauth2/authorize${queryString}',
}),
});
const data = await res.json();
if (data.url) window.location.href = data.url;
}
</script>
</body>
</html>`);
});
// Consent page — lets the user approve or deny requested scopes
server.app.get("/consent", (c) => {
const url = new URL(c.req.url);
const scope = url.searchParams.get("scope") || "openid";
return c.html(`<!DOCTYPE html>
<html>
<body>
<p>Requested scopes: ${scope}</p>
<button onclick="handleConsent(true)">Allow</button>
<button onclick="handleConsent(false)">Deny</button>
<script>
async function handleConsent(accept) {
const oauthQuery = window.location.search.slice(1);
const res = await fetch('/api/auth/oauth2/consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ accept, oauth_query: oauthQuery }),
});
const data = await res.json();
if (data.url) window.location.href = data.url;
}
</script>
</body>
</html>`);
});
Environment variables
# Better Auth secret (used for signing cookies and tokens)
BETTER_AUTH_SECRET=your-secret-change-in-production
# GitHub OAuth credentials (https://github.com/settings/developers)
# Set callback URL to: http://localhost:3000/api/auth/callback/github
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
Configuration options
oauthBetterAuthProvider({
// Required: URL of your Better Auth instance (including basePath)
authURL: "https://yourapp.com/api/auth",
// Optional: disable JWT verification (development only)
verifyJwt: process.env.NODE_ENV === "production",
// Optional: override advertised scopes
// Default: ['openid', 'profile', 'email', 'offline_access']
scopesSupported: ['openid', 'profile', 'email', 'offline_access'],
// Optional: custom user info extraction from the JWT payload
getUserInfo: (payload) => ({
userId: payload.sub as string,
email: payload.email as string,
name: payload.name as string,
roles: (payload.roles as string[]) || [],
permissions: (payload.permissions as string[]) || [],
}),
})
server.tool(
{
name: "get-user-info",
description: "Get information about the authenticated user",
},
async (_args, ctx) => ({
userId: ctx.auth.user.userId,
email: ctx.auth.user.email,
name: ctx.auth.user.name,
scopes: ctx.auth.scopes,
permissions: ctx.auth.permissions,
})
);
Resources