Skip to main content
Docs

Build an MCP server in your Next.js application with Clerk

If you're building an application and want to allow large language model (LLM) apps to securely access user data within your app, you'll need to set up a Model Context Protocol (MCP) service. MCP enables external tools like LLM-based apps to request user permission to access specific data from your service.

There are two parties involved in MCP - "client" and "server". In web development, the terms "client" and "server" often refer to the frontend (browser) and backend (web server). However, in this context, these terms have different meanings:

  • The "client" is the LLM application that wants to access another service on a user's behalf. For example, Claude would be the "client" if it wants to get access to Gmail.
  • The "server" is the system that hosts the protected resources the client wants to access. In this example, this would be Gmail. This is sometimes referred to as the "resource server" or "MCP server".

This guide demonstrates how to build an MCP server using Clerk's OAuth server in your Next.js app. This example is written for Next.js App Router, but can be adapted for Next.js Pages Router. It assumes that you have already integrated Clerk into your app by following the quickstart.

Install dependencies

To get started, this implementation requires the following packages to be installed in your project in addition to @clerk/nextjs:

  • @vercel/mcp-adapter: A utility library that simplifies building an MCP server by handling the core protocol logic for you. It also includes an authentication wrapper that allows you to plug in your own token validation - in this case, using Clerk's OAuth tokens.
  • @clerk/mcp-tools: A helper library built on top of the MCP TypeScript SDK used to connect Clerk OAuth with MCP easily.
terminal
npm install @vercel/mcp-adapter @clerk/mcp-tools
terminal
yarn add @vercel/mcp-adapter @clerk/mcp-tools
terminal
pnpm add @vercel/mcp-adapter @clerk/mcp-tools
terminal
bun add @vercel/mcp-adapter @clerk/mcp-tools

Set up your Next.js app with Clerk and MCP imports

Let's begin creating your MCP server.

  1. In your app/ directory, create a [transport] folder. This dynamic segment allows the MCP server to support different transport protocols used by the LLM tool. Streamable HTTP is the recommended default transport in the current MCP spec, and uses /mcp as the base path. SSE is also supported, and uses /sse as the base path.
  2. Inside this directory, create a route.ts file with the following code. This is the starting point for your MCP server. It includes the imports and setup needed to implement an MCP server with Clerk.
app/[transport]/route.ts
import { verifyClerkToken } from '@clerk/mcp-tools/next'
import { createMcpHandler, experimental_withMcpAuth as withMcpAuth } from '@vercel/mcp-adapter'
import { auth, clerkClient } from '@clerk/nextjs/server'

const clerk = await clerkClient()

Create your MCP server and define tools

To let external LLM-powered tools securely interact with your app, you need to define an MCP server, and expose one or more resources, prompts, and/or tools.

For this guide, you'll implement a single, example tool called get_clerk_user_data that retrieves information about the authenticated Clerk user. For more documentation on how to build MCP tools, see the MCP documentation.

Vercel's createMcpHandler() function is used to handle the connection and transports required by the MCP protocol. Within its callback function, you can define tools that external LLM-based apps can invoke using server.tool(). Each tool includes:

  • A name (get-clerk-user-data).
  • A description of what the tool does.
  • Input parameters (none in this case).
  • A function that represents the implementation of the tool. In this case, it extracts the user ID from authInfo.extra.userId, which is provided by Clerk's OAuth authentication, and then fetches the user's data using Clerk's getUser() method. The response is then returned in MCP's expected response format.
app/[transport]/route.ts
import { verifyClerkToken } from '@clerk/mcp-tools/next'
import { createMcpHandler, experimental_withMcpAuth as withMcpAuth } from '@vercel/mcp-adapter'
import { auth, clerkClient } from '@clerk/nextjs/server'

const clerk = await clerkClient()

const handler = createMcpHandler((server) => {
  server.tool(
    'get-clerk-user-data',
    'Gets data about the Clerk user that authorized this request',
    {},
    async (_, { authInfo }) => {
      const userId = authInfo!.extra!.userId! as string
      const userData = await clerk.users.getUser(userId)

      return {
        content: [{ type: 'text', text: JSON.stringify(userData) }],
      }
    },
  )
})

Secure your MCP server

Now that your MCP server and tools are defined, the next step is to secure your endpoints with OAuth. This ensures only authenticated clients with valid Clerk-issued tokens can access your tools.

Add the following code to your route.ts file. This uses Vercel's withMcpAuth() function to wrap the MCP handler in authentication logic and uses Clerk's auth() helper to parse the incoming OAuth token and extract the session context. This data is then passed into Clerk's verifyClerkToken() helper method, which verifies the OAuth token, extracts key metadata, and makes the current user'd ID available to tool call functions. To learn more about verifying OAuth tokens in Next.js apps, see the dedicated guide.

The authHandler is then exported for both GET and POST methods. The GET method is required for SSE support only - if you do not need to support SSE, you can export only POST (and the [transport] part of the route).

app/[transport]/route.ts
// The rest of your code...

const authHandler = withMcpAuth(
  handler,
  async (_, token) => {
    const clerkAuth = await auth({ acceptsToken: 'oauth_token' })
    return verifyClerkToken(clerkAuth, token)
  },
  {
    required: true,
    resourceMetadataPath: '/.well-known/oauth-protected-resource/mcp',
  },
)

export { authHandler as GET, authHandler as POST }

Expose MCP metadata endpoints

To comply with the MCP specification, your server must expose OAuth protected resource metadata at a specific endpoint (.well-known/oauth-protected-resource).

Older versions of the MCP spec required that you also expose OAuth authorization server metadata at a specific endpoint (.well-known/oauth-authorization-server). This is no longer required by the current MCP spec, but it may be necessary for some clients that only support older versions of the spec.

These metadata endpoints allow clients to discover where to authenticate, and some details about how the authentication service works. Clerk provides prebuilt helpers via @clerk/mcp-tools that handle this for you:

  • protectedResourceHandlerClerk: Next.js route handler that serves OAuth protected resource metadata in the format expected by MCP clients. This handler lets you define specific supported OAuth scopes to declare what access levels your resource requires.
  • authServerMetadataHandlerClerk: Next.js route handler that serves OAuth authorization server metadata in the format expected by MCP clients.
  • metadataCorsOptionsRequestHandler: Handles CORS preflight (OPTIONS) requests for OAuth metadata endpoints. Required to ensure public, browser-based MCP clients can access these endpoints.

Note

For a more in-depth explanation of these helpers, see the MCP Next.js reference.

To expose your MCP metadata endpoints:

  1. In your app/ directory, create a .well-known folder.
  2. Inside this directory, create two subdirectories called oauth-protected-resource and oauth-authorization-server.
  3. Inside the oauth-protected-resource directory, create a mcp subdirectory.
  4. In the mcp subdirectory, create a route.ts file with the following code for that specific route.
  5. In the oauth-authorization-server directory, create a route.ts file with the following code for that specific route.
app/.well-known/oauth-authorization-server/route.ts
import {
  authServerMetadataHandlerClerk,
  metadataCorsOptionsRequestHandler,
} from '@clerk/mcp-tools/next'

const handler = authServerMetadataHandlerClerk()

export { handler as GET, metadataCorsOptionsRequestHandler as OPTIONS }
app/.well-known/oauth-protected-resource/mcp/route.ts
import {
  metadataCorsOptionsRequestHandler,
  protectedResourceHandlerClerk,
} from '@clerk/mcp-tools/next'

const handler = protectedResourceHandlerClerk({
  // Specify which OAuth scopes this protected resource supports
  scopes_supported: ['profile', 'email'],
})

export { handler as GET, metadataCorsOptionsRequestHandler as OPTIONS }

Finished 🎉

Once this is complete, clients that support the latest MCP spec can now invoke the get-clerk-user-data tool to securely fetch user data from your app, assuming the request is authorized with a Clerk OAuth token. To test this out, learn how to connect your client LLM to the MCP server.

The next step is to replace the demo tool with your own tools, resources, and/or prompts that are relevant to your app. You can learn more about how to do this in the MCP SDK documentation.

Feedback

What did you think of this content?

Last updated on