Vercel LogoWorkOS Logo

Secure YourMCP📱apps🖥️servers⚡️tools🤖usersWith the Vercel MCP Adapter and WorkOS

The authHandler pattern transforms any MCP server into an enterprise-ready service. Test both public and authenticated endpoints below, then explore the complete implementation.

Test the AuthHandler Pattern

This demo implements the exact authHandler pattern described above. Test both endpoints to see how authentication works in practice—one is public, one requires WorkOS login.

Public Health Check

Works for everyone • No login required

This calls the MCP ping tool, which demonstrates a public endpoint in your MCP server. Notice it works whether you're signed in or not—ideal for health checks and monitoring.

MCP Response:
Click "Test Now" to see the response
Available to all users

Authenticated Profile Access

Enterprise security • WorkOS verification required

This calls the MCP getUserProfile tool, demonstrating the authHandler pattern. It validates your JWT, fetches your profile from WorkOS, and returns authenticated user data.

MCP Response:
Sign in first to test this endpoint
Sign in to test authenticated tools
Sign In with WorkOS

MCP Inspector

Official testing tool • Test connectivity

Use the official MCP Inspector to list available tools and verify your server is responding. This tests basic connectivity without requiring authentication.

Command:
npx @modelcontextprotocol/inspector --cli http://localhost:3000/mcp --method tools/list --transport http
Available to all users
Full Testing Guide

Inspector + Auth

Test with authentication tokens

Test authenticated MCP tools using the Inspector with your auth token.Sign in first to get a valid token.

Command:
npx @modelcontextprotocol/inspector --cli http://localhost:3000/mcp --method tools/call --tool-name getUserProfile --transport http --header "Authorization=Bearer YOUR_TOKEN"
Sign in to get auth token
Sign In First

This demonstrates the core value proposition: add authentication to any MCP server with minimal code changes using the authHandler pattern.

Implementation Guide

Transform any MCP server into an enterprise-ready, authenticated service

1

Build your MCP handler

Create your normal MCP server with tools using `createMcpHandler()` - mix public and authenticated tools

app/mcp/route.ts
import { createMcpHandler } from '@vercel/mcp-adapter';

// Create your regular MCP handler with tools
const handler = createMcpHandler((server) => {
  // Public tool - works without authentication
  server.tool(
    'ping',
    'Ping the MCP server to check if it is alive.',
    {},
    async () => {
      return {
        content: [{ type: 'text', text: JSON.stringify({ result: 'pong' }, null, 2) }],
      };
    },
  );

  // Authenticated tool - requires user context
  server.tool(
    'getUserProfile',
    "Returns the authenticated user's profile information.",
    {},
    async (args, { authInfo }) => {
      // authInfo.extra contains user data from auth wrapper
      const user = getUserFromAuthInfo(authInfo);
      return {
        content: [{ type: 'text', text: JSON.stringify(user, null, 2) }],
      };
    },
  );
});
2

Wrap with `experimental_withMcpAuth`

Transform your handler into an `authHandler` that optionally validates JWTs—public tools work without auth, secured tools get user data

Authentication wrapper
// 🔐 THE AUTHHANDLER PATTERN 🔐
// Wrap your MCP handler with optional enterprise authentication
const authHandler = experimental_withMcpAuth(
  handler, // Your regular MCP handler from step 1
  async (request, token) => {
    // No token? Return undefined (allows public tools like ping)
    if (!token) {
      return undefined;
    }

    // Verify the JWT using WorkOS JWKS
    const { payload } = await jwtVerify(token, JWKS);
    
    if (!payload.sub) {
      throw new Error('Invalid token: missing sub claim');
    }

    // Fetch user profile from WorkOS
    const userProfile = await workos.userManagement.getUser(payload.sub);
    
    const user = {
      id: userProfile.id,
      email: userProfile.email,
      firstName: userProfile.firstName,
      lastName: userProfile.lastName,
      profilePictureUrl: userProfile.profilePictureUrl,
    };

    // Return AuthInfo with user data in extra field
    return {
      token,
      clientId: clientId!,
      scopes: [],
      extra: { user, claims: payload }, // Available in authInfo.extra
    };
  },
  { required: false } // Optional auth: tools decide if they need it
);
3

Expose authenticated MCP server

Export the `authHandler` as HTTP endpoints to expose your authenticated MCP server

Expose authenticated MCP server
// Expose the authenticated MCP server as HTTP endpoints
export { authHandler as GET, authHandler as POST };

// How it works:
// • ping tool → works for everyone (no auth required)
// • getUserProfile tool → requires authentication
// • authHandler validates JWT → calls handler with user in authInfo.extra