How to Add a New Tool to the Auth0 MCP Server: Step-by-Step Implementation Guide

To add a new tool to the Auth0 MCP server, define a Tool metadata object and handler function in a new category file (e.g., src/tools/<category>.ts), then register both in src/tools/index.ts by spreading them into the TOOLS array and allHandlers object.

The Auth0 MCP server exposes Auth0 Management API endpoints as Model Context Protocol (MCP) tools through a standardized architecture. Adding a new tool—whether for a new resource server operation or a custom endpoint—requires implementing a consistent pattern involving tool definitions, handler implementations, and centralized registration in the auth0/auth0-mcp-server repository.

Architecture Overview

The MCP server uses a modular architecture with clear separation between tool definitions and their implementations:

  • Tool Definition (Tool[]): Declares the tool name, description, input JSON schema, required scopes, and UI hints. Located in src/tools/<category>.ts.
  • Handler (Record<string, (req, cfg) => Promise<HandlerResponse>>): Implements the Auth0 SDK call, validates tokens, and formats responses. Co-located with the tool definition.
  • Tool Registry (TOOLS): A flat array in src/tools/index.ts that aggregates all tools for server discovery.
  • Handler Registry (HANDLERS): Wrapped with analytics tracking and exported from src/tools/index.ts.

When you add a new tool, you only need to create or extend a category file and plug the exported constants into src/tools/index.ts. The server automatically discovers the tool without additional configuration.

Step-by-Step Implementation Process

Follow this canonical workflow used by existing tool groups such as applications, resource-servers, and logs.

Create a New Category File

Create a TypeScript file at src/tools/<new-category>.ts (for example, src/tools/custom-endpoint.ts). This file will contain both the tool metadata and the handler implementation for your new Auth0 Management API endpoint.

Define Tool Metadata

Export a constant array of Tool objects that declare the interface for your new MCP tool:

export const CUSTOM_ENDPOINT_TOOLS: Tool[] = [
  {
    name: 'auth0_custom_action',
    description: 'Perform the custom Auth0 Management-API action',
    inputSchema: {
      type: 'object',
      properties: {
        id: { type: 'string', description: 'The ID of the resource' },
        foo: { type: 'string' },
      },
      required: ['id'],
    },
    _meta: {
      requiredScopes: ['read:custom_scope'],
      readOnly: true,
    },
    annotations: {
      title: 'Custom Auth0 Action',
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
  },
];

The _meta field specifies the exact Auth0 scope required (e.g., read:custom_scope) and whether the operation is read-only. The annotations object provides UI hints for MCP clients regarding the tool's behavior.

Implement the Handler Function

Export a handler map that implements the actual API call using the Auth0 Management SDK:

export const CUSTOM_ENDPOINT_HANDLERS: Record<
  string,
  (req: HandlerRequest, cfg: HandlerConfig) => Promise<HandlerResponse>
> = {
  auth0_custom_action: async (request, config) => {
    if (!request.token) return createErrorResponse('Error: Missing authorization token');
    if (!config.domain) return createErrorResponse('Error: Auth0 domain is not configured');

    const mgmtCfg: Auth0Config = { domain: config.domain, token: request.token };
    const client = await getManagementClient(mgmtCfg);

    const { id, foo } = request.parameters;

    try {
      const result = await client.customEndpoint.perform({ id }, { foo });
      return createSuccessResponse(result);
    } catch (sdkError: any) {
      let msg = `Failed to run custom action: ${sdkError.message || 'Unknown error'}`;
      if (sdkError.statusCode === 401) {
        msg += '\nError: Unauthorized – check token and required scopes.';
      } else if (sdkError.statusCode === 404) {
        msg = `Resource with id '${id}' not found.`;
      }
      return createErrorResponse(msg);
    }
  },
};

Every handler must validate the request.token and config.domain before proceeding. Use getManagementClient from src/utils/auth0-client.ts to instantiate the SDK, and return responses via createSuccessResponse or createErrorResponse from src/utils/http-utility.ts.

Register in the Global Registry

Import and spread your new constants into src/tools/index.ts:

import { CUSTOM_ENDPOINT_HANDLERS, CUSTOM_ENDPOINT_TOOLS } from './custom-endpoint.js';

export const TOOLS: Tool[] = [
  ...APPLICATION_TOOLS,
  ...RESOURCE_SERVER_TOOLS,
  ...ACTION_TOOLS,
  ...LOG_TOOLS,
  ...FORM_TOOLS,
  ...APPLICATION_GRANTS_TOOLS,
  ...CUSTOM_ENDPOINT_TOOLS,
];

const allHandlers = {
  ...APPLICATION_HANDLERS,
  ...RESOURCE_SERVER_HANDLERS,
  ...ACTION_HANDLERS,
  ...LOG_HANDLERS,
  ...FORM_HANDLERS,
  ...APPLICATION_GRANTS_HANDLERS,
  ...CUSTOM_ENDPOINT_HANDLERS,
};

The createHandlersWithAnalytics wrapper automatically applies analytics tracking to your new handler—no additional code required.

Test Your Implementation

Run the test suite to ensure your tool integrates correctly:

npm test

If you created a new category, add unit tests under test/tools/<category>.test.ts that mock the Auth0 SDK call, send a synthetic HandlerRequest, and assert the HandlerResponse shape. Verify that error handling works correctly for 401 and 404 status codes.

Concrete Example: Adding a User Blocks Tool

Below is a complete implementation adding auth0_list_user_blocks, which maps to the Auth0 Management API endpoint GET /api/v2/user-blocks.

Create src/tools/user-blocks.ts:

import type {
  HandlerConfig,
  HandlerRequest,
  HandlerResponse,
  Tool,
} from '../utils/types.js';
import { log } from '../utils/logger.js';
import { createErrorResponse, createSuccessResponse } from '../utils/http-utility.js';
import type { Auth0Config } from '../utils/config.js';
import { getManagementClient } from '../utils/auth0-client.js';

export const USER_BLOCK_TOOLS: Tool[] = [
  {
    name: 'auth0_list_user_blocks',
    description: 'List user blocks in the tenant (e.g. after too many failed logins).',
    inputSchema: {
      type: 'object',
      properties: {
        page: { type: 'number', description: '0-based page number' },
        per_page: { type: 'number', description: 'Items per page' },
        include_totals: { type: 'boolean', description: 'Include total count' },
      },
    },
    _meta: {
      requiredScopes: ['read:user_blocks'],
      readOnly: true,
    },
    annotations: {
      title: 'List Auth0 User Blocks',
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
  },
];

export const USER_BLOCK_HANDLERS: Record<
  string,
  (req: HandlerRequest, cfg: HandlerConfig) => Promise<HandlerResponse>
> = {
  auth0_list_user_blocks: async (request, config) => {
    if (!request.token) return createErrorResponse('Error: Missing authorization token');
    if (!config.domain) return createErrorResponse('Error: Auth0 domain is not configured');

    const options: Record<string, any> = {};
    if (request.parameters.page !== undefined) options.page = request.parameters.page;
    if (request.parameters.per_page !== undefined) options.per_page = request.parameters.per_page;
    if (request.parameters.include_totals !== undefined) options.include_totals = request.parameters.include_totals;

    try {
      const client = await getManagementClient({ domain: config.domain, token: request.token });
      log('Fetching user blocks...');
      const { data } = await client.userBlocks.getAll(options);
      return createSuccessResponse(data);
    } catch (sdkError: any) {
      let msg = `Failed to list user blocks: ${sdkError.message || 'Unknown error'}`;
      if (sdkError.statusCode === 401) msg += '\nError: Unauthorized – check token / scopes.';
      if (sdkError.statusCode === 429) msg += '\nError: Rate limited.';
      return createErrorResponse(msg);
    }
  },
};

export { USER_BLOCK_TOOLS, USER_BLOCK_HANDLERS };

Then register in src/tools/index.ts:

import { USER_BLOCK_HANDLERS, USER_BLOCK_TOOLS } from './user-blocks.js';

export const TOOLS: Tool[] = [
  ...USER_BLOCK_TOOLS,
];

const allHandlers = {
  ...USER_BLOCK_HANDLERS,
};

Key Files and Their Roles

File Purpose
src/tools/<category>.ts Contains the Tool array and handler implementations for your new endpoint.
src/tools/index.ts Aggregates all tools and handlers; you must import and spread your new constants here.
src/utils/auth0-client.ts Provides getManagementClient for SDK instantiation.
src/utils/http-utility.ts Supplies createSuccessResponse and createErrorResponse for standardized responses.
src/utils/types.ts Defines HandlerRequest, HandlerConfig, and HandlerResponse interfaces.
test/tools/<category>.test.ts Unit tests mocking SDK calls and verifying response shapes.

Summary

  • Define tool metadata in a new category file with name, description, JSON schema, and required Auth0 scopes.
  • Implement the handler to validate tokens, instantiate the Management client, call the Auth0 SDK, and normalize errors.
  • Export both the Tool array and handler map from your category file.
  • Register the exports in src/tools/index.ts by spreading into TOOLS and allHandlers.
  • Test using the existing Jest/Vitest suite and add unit tests for your specific endpoint logic.
  • Document the new tool in the README or documentation to inform end-users of availability.

Frequently Asked Questions

Do I need to modify server configuration files to add a new tool?

No. Once you register your tool and handler in src/tools/index.ts, the server automatically discovers them through the TOOLS and HANDLERS exports. The analytics wrapper and RPC endpoint require no additional configuration.

What Auth0 scopes should I specify in the _meta field?

Specify the exact Management API scope required by the endpoint you are exposing. For example, use read:user_blocks for listing user blocks or update:resource_servers for modifying resource servers. These scopes are enforced by the Auth0 API, not the MCP server itself.

How should I handle SDK errors in the handler?

Catch SDK errors and normalize them using createErrorResponse. Check for specific statusCode values (such as 401 for unauthorized or 404 for not found) to provide actionable error messages. Always include the original error message for debugging purposes.

Yes. Define multiple objects in your Tool array and multiple entries in your handler map within the same file. Follow the pattern in src/tools/applications.ts or src/tools/resource-servers.ts where related operations are grouped logically.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →