Skip to main content

@Authenticated

Decorator to protect MCP tools, prompts, resources, or entire services with authentication.
This decorator uses AsyncLocalStorage for concurrency safety. Each request has its own isolated authUser context, preventing race conditions in high-concurrency scenarios.

Signature

function Authenticated(
  authProvider: AuthProviderBase,
  options?: AuthenticatedOptions
): MethodDecorator | ClassDecorator
authProvider
AuthProviderBase
required
Instance of AuthProvider or custom AuthProviderBase implementation
options
AuthenticatedOptions
Optional configuration object:
getUser
boolean
default:"true"
Whether to fetch and attach user information to authUser variable
projectId
string
Project ID for fetching user environment variables. Required when using @RequireEnv or getEnv().

Usage Patterns

1. Protect Individual Methods

Protect a single tool/prompt/resource with automatic user info:
import { Tool } from '@leanmcp/core';
import { Authenticated, AuthProvider } from '@leanmcp/auth';

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

await authProvider.init();

export class SentimentService {
  @Tool({ description: 'Analyze sentiment of text' })
  @Authenticated(authProvider, { getUser: true })
  async analyzeSentiment(args: { text: string }) {
    // authUser is automatically available in method scope
    console.log('User:', authUser.email);
    console.log('User ID:', authUser.sub);
    
    // Your implementation
    return { sentiment: 'positive', score: 0.8 };
  }
}

2. Protect Without User Info

Only verify token, don’t fetch user info:
@Tool({ description: 'Public tool' })
@Authenticated(authProvider, { getUser: false })
async publicTool(args: { query: string }) {
  // Token is verified, but authUser is undefined
  return { result: 'success' };
}

3. Protect Entire Service

Apply authentication to all methods in a class:
import { Authenticated } from '@leanmcp/auth';
import { Tool, Prompt } from '@leanmcp/core';

@Authenticated(authProvider)
export class SecureService {
  @Tool({ description: 'Protected tool 1' })
  async tool1(args: any) {
    // authUser automatically available
    console.log('Current user:', authUser.email);
    return { data: 'secure' };
  }

  @Tool({ description: 'Protected tool 2' })
  async tool2(args: any) {
    // All methods require authentication
    return { data: 'also secure' };
  }

  @Prompt({ name: 'securePrompt' })
  async securePrompt() {
    return {
      messages: [{ role: 'user', content: `Hello ${authUser.email}` }]
    };
  }
}

4. With User Environment Variables

Fetch user-specific environment variables (LeanMCP provider only):
import { Authenticated, AuthProvider } from '@leanmcp/auth';
import { getEnv } from '@leanmcp/env-injection';

const authProvider = new AuthProvider('leanmcp', {
  apiKey: process.env.LEANMCP_API_KEY,
});

await authProvider.init();

@Tool({ description: 'Send email via user\'s API key' })
@Authenticated(authProvider, { projectId: 'your-project-id' })
async sendEmail(args: { to: string; subject: string; body: string }) {
  // Access user's SendGrid API key
  const apiKey = getEnv('SENDGRID_API_KEY');
  
  // Use the user's credentials
  await sendEmailViaSendGrid(apiKey, args);
  
  return { success: true };
}

authUser Global Variable

When getUser: true (default), the authUser variable is automatically available in method scope:
declare global {
  const authUser: any;
}

Common Fields

authUser.sub
string
User ID (subject)
authUser.email
string
User’s email address
authUser.email_verified
boolean
Email verification status
authUser.attributes
object
Full decoded token payload with all claims

Provider-Specific Fields

Cognito:
  • authUser['cognito:username'] - Cognito username
  • authUser.username - Username
Clerk:
  • authUser.first_name - First name
  • authUser.last_name - Last name
Auth0:
  • authUser.name - Full name

Token Format

The decorator expects the authentication token in the MCP request _meta field:
{
  "method": "tools/call",
  "params": {
    "name": "analyzeSentiment",
    "arguments": {
      "text": "This product is amazing!"
    },
    "_meta": {
      "authorization": {
        "type": "bearer",
        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
      }
    }
  }
}

Helper Functions

getAuthUser()

Get the current authenticated user from async context:
import { getAuthUser } from '@leanmcp/auth';

const user = getAuthUser();
console.log('User:', user?.email);

isAuthenticationRequired()

Check if a method or class requires authentication:
import { isAuthenticationRequired } from '@leanmcp/auth';

const requiresAuth = isAuthenticationRequired(myMethod);

getAuthProvider()

Get the auth provider for a method or class:
import { getAuthProvider } from '@leanmcp/auth';

const provider = getAuthProvider(myMethod);

Error Responses

When authentication fails, the decorator throws an AuthenticationError:
{
  "error": {
    "code": "MISSING_TOKEN",
    "message": "Authentication required. Please provide a valid token in _meta.authorization.token"
  }
}

Error Codes

MISSING_TOKEN
string
No token provided in _meta.authorization.token
INVALID_TOKEN
string
Token is invalid or expired
VERIFICATION_FAILED
string
Token verification failed (e.g., network error, invalid signature)

TypeScript Configuration

Ensure your tsconfig.json has these settings:
tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2022",
    "lib": ["ES2022"]
  }
}

Concurrency Safety

The decorator uses AsyncLocalStorage to ensure each request has its own isolated authUser context:
import { AsyncLocalStorage } from 'async_hooks';

const authUserStorage = new AsyncLocalStorage<any>();

// Each request gets its own context
await authUserStorage.run(user, async () => {
  // authUser is scoped to this request only
  return await originalMethod.apply(this, [args]);
});
This prevents race conditions when handling concurrent requests with different users.

Build docs developers (and LLMs) love