Skip to main content
LeanMCP provides built-in authentication support with the @Authenticated decorator. Protect your MCP tools, prompts, and resources with JWT-based authentication using popular providers.

Quick Start

import { AuthProvider, Authenticated } from '@leanmcp/auth';
import { Tool } from '@leanmcp/core';

const authProvider = new AuthProvider('auth0', {
  domain: 'your-tenant.auth0.com',
  clientId: 'your-client-id',
  audience: 'your-api-identifier'
});

export class SecureService {
  @Tool({ description: 'Analyze sentiment' })
  @Authenticated(authProvider, { getUser: true })
  async analyzeSentiment(args: { text: string }) {
    // authUser is automatically available
    console.log('User ID:', authUser.sub);
    console.log('Email:', authUser.email);
    
    return { sentiment: 'positive' };
  }
}

Authentication Providers

LeanMCP supports multiple authentication providers out of the box:
import { AuthProvider } from '@leanmcp/auth';

const authProvider = new AuthProvider('auth0', {
  domain: 'your-tenant.auth0.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret', // Optional
  audience: 'your-api-identifier',
  scopes: 'openid profile email offline_access'
});
Required Configuration:
  • domain: Your Auth0 tenant domain
  • clientId: Application client ID
  • audience: API identifier
Token Verification: Auth0 tokens are verified using JWKS from https://{domain}/.well-known/jwks.jsonSource: packages/auth/src/providers/auth0.ts:6

Using @Authenticated Decorator

Protect Individual Methods

Apply to specific tools, prompts, or resources:
@Tool({ description: 'Send Slack message' })
@Authenticated(authProvider, { getUser: true })
async sendMessage(args: { channel: string; text: string }) {
  console.log('Authenticated user:', authUser.email);
  // Send message
}

Protect Entire Service

Apply to the class to protect all methods:
@Authenticated(authProvider)
export class SlackService {
  @Tool({ description: 'Send message' })
  async sendMessage(args: SendMessageInput) {
    // All methods require authentication
    console.log('User:', authUser);
  }

  @Tool({ description: 'List channels' })
  async listChannels() {
    // Also protected
    return { channels: [] };
  }
}

Skip User Fetching

For performance, you can verify tokens without fetching user info:
@Tool({ description: 'Public tool' })
@Authenticated(authProvider, { getUser: false })
async publicTool(args: PublicInput) {
  // Only verifies token, doesn't fetch user
  // authUser will be undefined
}

The authUser Global

When getUser: true (default), the authenticated user is available as a global variable:
@Authenticated(authProvider, { getUser: true })
async myTool(args: any) {
  // Access user properties
  console.log('User ID:', authUser.sub);
  console.log('Email:', authUser.email);
  console.log('Name:', authUser.name);
  
  // Full user object
  console.log('All attributes:', authUser.attributes);
}
The authUser global is concurrency-safe using AsyncLocalStorage. Each request has its own isolated context, preventing race conditions in high-concurrency scenarios.Source: packages/auth/src/decorators.ts:25

Token Format

Clients must send tokens in the MCP request _meta field:
{
  "method": "tools/call",
  "params": {
    "name": "sendMessage",
    "arguments": {
      "channel": "#general",
      "text": "Hello!"
    },
    "_meta": {
      "authorization": {
        "type": "bearer",
        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
      }
    }
  }
}

OAuth Flow (Client-Side)

LeanMCP provides an OAuth client for browser-based authentication:
import { OAuthClient } from '@leanmcp/auth';

const client = new OAuthClient({
  serverUrl: 'https://mcp.example.com',
  scopes: ['read', 'write'],
  clientName: 'My MCP Client'
});

// Start browser-based OAuth flow
await client.authenticate();

// Get token for API calls
const token = await client.getValidToken();

// Auto-refresh is enabled by default
const freshToken = await client.getValidToken();

PKCE Support

PKCE (Proof Key for Code Exchange) is enabled by default for secure OAuth flows:
const client = new OAuthClient({
  serverUrl: 'https://mcp.example.com',
  pkceEnabled: true, // Default
  autoRefresh: true,  // Default
  refreshBuffer: 60   // Refresh 60s before expiry
});
Source: packages/auth/src/client/oauth-client.ts:86

Error Handling

import { AuthenticationError } from '@leanmcp/auth';

try {
  await myAuthenticatedTool(args);
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Auth error:', error.code);
    // error.code: 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'VERIFICATION_FAILED'
  }
}

Environment Variables

Providers support environment variables for configuration:
# Auth0
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
AUTH0_AUDIENCE=your-api-identifier

# Clerk
CLERK_FRONTEND_API=clerk.example.com
CLERK_SECRET_KEY=sk_test_...

# Cognito
AWS_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
COGNITO_CLIENT_ID=your-client-id
COGNITO_CLIENT_SECRET=your-client-secret

Next Steps

Multi-Tenancy

Isolate data per user or organization

Env Injection

Request-scoped environment variables

Build docs developers (and LLMs) love