How Auth0 MCP Server Filters Write Operations at the Tool Level in Read-Only Mode

The Auth0 MCP server removes write-capable tools from the available set by filtering for _meta.readOnly === true when the --read-only flag is enabled, preventing AI assistants from invoking any non-read operations.

The auth0/auth0-mcp-server implements a strict security boundary that prevents accidental or malicious modifications to Auth0 tenants when operating in restricted environments. When administrators enable read-only mode, the server does not rely on runtime permission checks or API-level blocks; instead, it surgically removes write-capable tools from the Model Context Protocol (MCP) tool registry before the AI assistant can even discover them.

How Read-Only Filtering Works

The filtering mechanism operates through three coordinated stages that transform the full tool registry into a sanitized, read-only subset. According to the source code in auth0/auth0-mcp-server, this happens before the server responds to any ListTools MCP requests.

  1. CLI Parsing: The --read-only flag is captured in the RunOptions object.
  2. Server Startup: The startServer function forwards the flag to the tool selection logic.
  3. Tool Filtering: The getAvailableTools function applies pattern filtering first, then strictly filters by the _meta.readOnly metadata field.

Step-by-Step Implementation Details

CLI Flag Parsing

The security chain begins in src/commands/run.ts (lines 12-15, 101-107), where the command-line interface parses user input. When an administrator invokes:

npx @auth0/auth0-mcp-server run --read-only --tools '*'

The parser instantiates a RunOptions object with readOnly: true and the specified tool patterns. This configuration object travels through the application boundary to the server initialization code.

Server Initialization

In src/server.ts (line 65), the startServer function receives the RunOptions and immediately calls getAvailableTools with three arguments: the complete TOOLS array imported from src/tools/index.ts, the pattern filters, and the readOnly boolean.

const availableTools = getAvailableTools(TOOLS, options?.tools, options?.readOnly);

This single call determines which tools the AI assistant will be allowed to see for the entire session lifecycle.

Tool Filtering Logic

The core enforcement happens in src/utils/tools.ts within the getAvailableTools function (lines 57-60). The logic applies two sequential filters:

Pattern Filtering: If specific tool patterns are provided (e.g., --tools 'list-*'), the function first calls filterToolsByPatterns to match against Glob objects.

Read-Only Filtering: When readOnly is true, the function invokes filterToolsByReadOnly:

if (readOnly) {
  filteredTools = filterToolsByReadOnly(filteredTools);
}

The filterToolsByReadOnly implementation (lines 112-115) performs a strict equality check:

const readOnlyTools = tools.filter((tool) => tool._meta?.readOnly === true);

This excludes every tool capable of write operations because such tools declare readOnly: false or omit the flag entirely. The resulting array contains only safe tools like list-clients, get-resource-servers, and list-logs, while removing create-client, delete-resource-server, and patch-application.

Before returning the filtered set via the MCP protocol, the server sanitizes the response by stripping the internal _meta fields (lines 77-82 in src/server.ts):

const sanitizedTools = availableTools.map(({ _meta, ...rest }) => rest);
return { tools: sanitizedTools };

The Critical Role of _meta.readOnly vs readOnlyHint

Some tool definitions include readOnlyHint: true, but this property only affects UI hints in command-line output and documentation. It provides zero security enforcement. The actual protection relies entirely on the _meta.readOnly boolean field, which is the only value checked by filterToolsByReadOnly.

Tools that perform read operations explicitly declare:

_meta: {
  readOnly: true
}

Write-capable tools either set this to false or omit the property, causing the filter to exclude them from the sanitized tool list.

Practical Examples

Running in Read-Only Mode

Start the server with write protection enabled:

npx @auth0/auth0-mcp-server run --read-only --tools '*'

Console output:


Starting server in read-only mode
Auth0 MCP Server version 1.2.3 running on stdio with 7/23 tools available

The count shows only 7 tools because 16 write-capable tools were filtered out.

Inspecting Available Tools

When an AI client requests the tool list via the MCP protocol:

const response = await mcpClient.request({ type: 'list-tools' });
console.log(response.tools.map(t => t.name));

Result:

[
  "list-clients",
  "get-resource-servers",
  "list-logs",
  "list-forms",
  "list-applications",
  "list-action-triggers",
  "list-actions"
]

Write operations like create-client or delete-resource-server are absent from the response.

Attempting a Write Operation

If the AI attempts to invoke a filtered tool:

const result = await mcpClient.callTool('create-client', { name: 'new-app' });

The server responds with:


Error: Unknown tool: create-client

This occurs because create-client was removed from the available tools during initialization, making it invisible to the MCP protocol.

Summary

  • The --read-only CLI flag sets RunOptions.readOnly = true in src/commands/run.ts.
  • startServer forwards this flag to getAvailableTools in src/server.ts.
  • getAvailableTools applies glob patterns first, then calls filterToolsByReadOnly to keep only tools where _meta.readOnly === true.
  • The filtered tool list is sanitized (stripped of _meta) and exposed via the MCP protocol; write-capable tools return "Unknown tool" errors if invoked.
  • readOnlyHint provides UI information but offers no security protection; only _meta.readOnly enforces restrictions.

Frequently Asked Questions

What happens if I try to call a write operation while in read-only mode?

The server returns an "Unknown tool" error because write-capable tools are removed from the MCP tool registry during startup. The tool never appears in the ListTools response, so the AI assistant cannot construct valid requests for it.

How is read-only mode different from using readOnlyHint in tool definitions?

readOnlyHint is a cosmetic property that only affects CLI help text and logging output. It does not prevent tool execution. The _meta.readOnly boolean is the enforcement mechanism used by filterToolsByReadOnly in src/utils/tools.ts to physically exclude tools from the available set.

Can I combine read-only mode with specific tool patterns?

Yes. The server applies pattern filtering first via filterToolsByPatterns, then applies read-only filtering. In src/utils/tools.ts (lines 57-60), the code checks if (readOnly) after pattern matching, ensuring that even if a pattern matches a write-capable tool, it will be removed if the read-only flag is set.

Where are the read-only flags defined for each tool?

Tool definitions across the codebase (such as in src/tools/resource-servers.ts, src/tools/logs.ts, and other tool files) declare the _meta.readOnly property. Read-only tools set this to true, while management tools that create, update, or delete resources omit the flag or set it to false.

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 →