Skip to main content
This example demonstrates how to build an MCP server with LeanMCP authentication. While this example uses LeanMCP’s built-in auth, the same pattern can be adapted for AWS Cognito by using the generic JWT verification capabilities.

Features

  • LeanMCP Authentication: Uses @leanmcp/auth to verify user tokens
  • Token Management: Refresh tokens and manage user sessions
  • Protected Endpoints: Demonstrate authenticated tool access
  • Zero-Config Service Discovery: Services are automatically discovered from the ./mcp directory
  • Concurrency-Safe: The authUser variable is implemented using AsyncLocalStorage for safe concurrent request handling
For AWS Cognito integration, you can use the same AuthProvider pattern with a custom JWT verification strategy. Contact LeanMCP support for AWS Cognito integration guidance.

Prerequisites

  • Node.js 18+ installed
  • A LeanMCP account with API key (or AWS Cognito credentials for custom setup)

Setup

1

Install dependencies

npm install @leanmcp/core @leanmcp/auth dotenv
Install dev dependencies:
npm install -D @leanmcp/cli @types/node tsx typescript
2

Set up environment variables

Create a .env file:
PORT=3000
LEANMCP_API_KEY=your_api_key_here
You can generate an API Key with the SDK scope from the LeanMCP dashboard or via the Orchestration API.
3

Create the configuration file

Create mcp/config.ts to initialize the auth provider:
mcp/config.ts
import { AuthProvider } from "@leanmcp/auth";

// Initialize authentication provider
// The API key is used by the SDK to authorize verification of user tokens
export const authProvider = new AuthProvider('leanmcp', {
    apiKey: process.env.LEANMCP_API_KEY,
    orchestrationApiUrl: 'http://localhost:3001',
    authUrl: 'http://localhost:3003'
});

// Initialize the provider
await authProvider.init();
For production AWS Cognito integration, replace the LeanMCP provider configuration with Cognito-specific JWT verification settings.
4

Create the demo service

Create mcp/demo/index.ts with authenticated endpoints:
mcp/demo/index.ts
import { Tool, SchemaConstraint } from "@leanmcp/core";
import { Authenticated } from "@leanmcp/auth";
import { authProvider } from "../config.js";

class EchoInput {
    @SchemaConstraint({
        description: 'Message to echo back',
        minLength: 1
    })
    message!: string;
}

class RefreshTokenInput {
    @SchemaConstraint({
        description: 'The refresh token to use for obtaining a new ID token',
        minLength: 1
    })
    refreshToken!: string;
}

export class DemoService {
    /**
     * Get the authenticated user's profile
     */
    @Authenticated(authProvider)
    @Tool({
        description: 'Get the authenticated user profile information'
    })
    async getUserProfile(): Promise<{
        userId: string;
        email: string;
        name?: string;
        picture?: string;
    }> {
        // authUser is automatically available - injected by @Authenticated decorator
        return {
            userId: authUser.uid || authUser.sub,
            email: authUser.email,
            name: authUser.name,
            picture: authUser.picture
        };
    }

    /**
     * Echo back a message with user information
     */
    @Tool({
        description: 'Echo back a message with authenticated user information',
        inputClass: EchoInput
    })
    async echo(args: EchoInput): Promise<{
        message: string;
        timestamp: string;
        userId: string;
        userEmail: string;
    }> {
        return {
            message: args.message,
            timestamp: new Date().toISOString(),
            userId: authUser.uid || authUser.sub,
            userEmail: authUser.email
        };
    }

    /**
     * Refresh a user token
     * This tool is NOT protected by authentication
     */
    @Tool({
        description: 'Refresh a user token using a refresh token',
        inputClass: RefreshTokenInput,
    })
    async refreshToken(args: RefreshTokenInput): Promise<any> {
        return await authProvider.refreshToken(args.refreshToken);
    }
}
5

Create the server entry point

Create main.ts:
main.ts
import 'dotenv/config';
import { createHTTPServer } from "@leanmcp/core";

await createHTTPServer({
  name: 'leanmcp-auth',
  version: '1.0.0',
  port: parseInt(process.env.PORT || '3000'),
  cors: true,
  logging: true
});

console.log('\nLeanMCP Auth Server Example');

Running the Server

npm run dev
The server will start with auto-reload on http://localhost:3000

Authentication Flow

1

Obtain user token

Authenticate with your provider (LeanMCP or AWS Cognito) to obtain a JWT token.
2

Use protected tools

Pass the token via _meta.authorization.token in your MCP request:
{
  "_meta": {
    "authorization": {
      "token": "your-jwt-token"
    }
  }
}
3

Refresh expired tokens

When tokens expire, use the refreshToken tool to obtain new tokens.

Available Tools

DemoService

getUserProfile (Protected)

Get the authenticated user’s profile information. Output:
{
  "userId": "user_123",
  "email": "[email protected]",
  "name": "John Doe",
  "picture": "https://example.com/avatar.jpg"
}

echo (Protected)

Echo back a message with user information. Input:
{
  "message": "Hello, World!"
}
Output:
{
  "message": "Hello, World!",
  "timestamp": "2024-01-01T12:00:00.000Z",
  "userId": "user_123",
  "userEmail": "[email protected]"
}

refreshToken

Refresh a user token using a refresh token. Input:
{
  "refreshToken": "your-refresh-token"
}
Output:
{
  "access_token": "new-access-token",
  "id_token": "new-id-token",
  "refresh_token": "new-refresh-token"
}

AWS Cognito Adaptation

To adapt this example for AWS Cognito:
1

Install AWS SDK

npm install @aws-sdk/client-cognito-identity-provider
2

Configure Cognito environment variables

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
3

Update config.ts

Replace the LeanMCP AuthProvider with a custom Cognito JWT verifier:
import { CognitoJwtVerifier } from "aws-jwt-verify";

export const verifier = CognitoJwtVerifier.create({
  userPoolId: process.env.COGNITO_USER_POOL_ID!,
  tokenUse: "id",
  clientId: process.env.COGNITO_CLIENT_ID!,
});
4

Custom authentication decorator

Create a custom authentication implementation using the Cognito verifier for JWT validation.
For detailed AWS Cognito integration guidance, please contact LeanMCP support or refer to the AWS Cognito documentation.

How It Works

Service Discovery

The MCP server automatically discovers and registers all services exported from files in the ./mcp directory.

Authentication

  • The @Authenticated decorator protects tools that require authentication
  • JWT tokens are verified using the configured auth provider
  • The authUser variable is automatically available in protected methods
  • Concurrency Safe: authUser is implemented as a getter that reads from AsyncLocalStorage
  • Access user information directly via authUser.uid, authUser.email, etc.

Package.json Scripts

package.json
{
  "scripts": {
    "dev": "leanmcp dev",
    "start": "leanmcp start",
    "build": "leanmcp build"
  },
  "dependencies": {
    "@leanmcp/auth": "^0.4.0",
    "@leanmcp/core": "^0.4.0",
    "dotenv": "^16.4.5"
  },
  "devDependencies": {
    "@leanmcp/cli": "^0.4.0",
    "@types/node": "^22.10.1",
    "tsx": "^4.19.2",
    "typescript": "^5.7.2"
  }
}

Security Notes

  • Never commit your .env file or expose your API keys
  • Always use HTTPS in production
  • Implement proper token storage and refresh logic in your client
  • Consider implementing rate limiting for production use
  • For AWS Cognito, follow AWS security best practices

Learn More

Build docs developers (and LLMs) love