MCP Gateway Access Controls: Defining Permissions for LLM Agents

MCP Gateway Access Controls: Defining Permissions for LLM Agents

MCP Gateway enables fine-grained access controls for LLM agents interacting with third-party APIs. Scoped permissions, auditability, and enforcement mechanisms ensure secure, compliant, and production-ready API usage by autonomous agents.

Eliav Lavi, Software Engineer

Eliav Lavi, Software Engineer

June 4, 2025

MCP

MCPX

AI Gateways

Why We Needed Access Controls for MCP

As discussed in our previous post on MCPX, taking agentic workflows to production introduces a range of new challenges. Chief among them is access control: How do you define and enforce who can use which tools, and under what context?

Without safeguards, an LLM could invoke critical or sensitive tools unintentionally. For example, we saw a marketing automation agent trigger a delete_channel command on Slack when it should have only been permitted to read analytics or post content. This wasn’t malicious—it was a failure of boundaries. That’s why the OWASP Top Risks for LLMs includes Excessive Agency: AI agents must be constrained in their authority.

Enter ACLs for MCPX.

Introducing MCPX's ACL Feature

ACLs (Access Control Lists) are a powerful mechanism we've added to MCPX to help teams scope and enforce what tools and services are available to which consumers. In this blog post, we'll walk through the motivations behind this feature, how it was designed, and how it works in practice.

The goal was to build a system that allows developers and architects to configure access across multiple levels of granularity, while still being intuitive and manageable in production environments.

At the heart of the ACL feature lies a declarative YAML file parsed at boot time. This file allows you to define permissions on four distinct levels:

  1. Global level
  2. Consumer level
  3. Service level
  4. Tool level

Let’s walk through how that works, using real configurations and examples from our implementation.

Defining Base Permissions

The most basic configuration starts by setting a global policy for tool access. The following YAML blocks all tools by default:

permissions:
  base: "block"

This configuration instructs MCPX to hide all integrated tools and services from all consumers. Obviously, not particularly practical on its own.

Let’s say we want to allow access only to users or agents identifying as "developers". We can override the global policy for them specifically:

  permissions:
  base: "block"
  consumers:
	  developers:
		  base: "allow"

This grants "developers" full access while continuing to block other consumers. But how does MCPX know who is a "developer"?

Identifying the Consumer

Consumer identity is passed in as part of the connection header. Here’s an example of how this is done from a client application:

import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

const transport = new StreamableHTTPClientTransport(
  new URL(`${MCPX_HOST}/mcp`),
  {
    requestInit: {
      headers: {
        "x-lunar-consumer-tag": "developers", // A consumer-tag of your choice!
      },
    },
  }
);
const client = new Client({ name: "mcpx-client", version: "1.0.0" });
await client.connect(transport);

This snippet shows an MCPX client passing the x-lunar-consumer-tag header to identify as a "developer". MCPX uses this tag to resolve permissions and enforce access control throughout the session.

Creating Granular Control with Tool Groups

To fine-tune access, MCPX supports defining toolGroups that bundle tools across services into meaningful categories. Here is a more advanced example combining consumers and toolGroups:

permissions:
  base: "block"
  consumers:
    developers:
      base: "allow"
      profiles:
        block:
          - "admin"
  marketing:
    profiles:
      allow:
        - "reads"

toolGroups:
  - name: "writes"
    services:
      slack: # marks specific tools from this service
        - "post_message"
        - "post_reaction"
      gmail: # marks specific tools from this service
        - "send_email"
        - "send_attachment"
      github: "*" # marks all the tools from this service

  - name: "reads"
    services:
      slack:
        - "read_messages"
        - "read_comments"
      gmail:
        - "read_email"
        - "read_attachment"

  - name: "admin"
    services:
      slack:
        - "create_channel"
        - "delete_channel"

This YAML configuration sets a default block policy but defines two consumers: developers and marketing. Developers are allowed access to all tools except those in the admin group. Marketing is allowed only tools in the reads group.

Visualizing ACL Resolution Flow

Here is a simplified view of how the ACL enforcement works inside MCPX:

[Client App (Agent)]
    |
    | -- x-lunar-consumer-tag: marketing
    |
[MCPX Gateway]
    |
    | -- Parses app.yaml:
    |     - Check global defaults
    |     - Match consumer tag
    |     - Resolve profiles/toolGroups
    |
    v
[Allowed Tools + Services Returned]

ACL resolution is handled in code here: mcpx/packages/mcpx-server/src/services/permissions.ts.

Connecting It to Your MCP Config

ACL toolGroup entries are matched to tool names as defined in your config/mcp.json file. Here’s a simplified version:

{
  "mcpServers": {
    "google-maps": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-google-maps"]
      }
  }
}

Once connected, the server can respond to a listTools call (mcpx does this after connection) and only then will mcpx learn about the existing tools

Benefits: Secure, Clear, and Scalable

With this ACL feature, you can:

  • Define scoped permissions per team or application
  • Bundle tools into profiles that match business context (e.g. "reads", "writes", "admin", or anything else!)
  • Easily onboard new teams by assigning them to a custom consumption profile
  • Centralize enforcement without coupling it to agent-side logic

All of this is declarative, easy to understand, and backed by open-source logic.

What’s Next: Enforcing Runtime Behavior

Access control is only one part of secure LLM infrastructure. Now that we've enabled scoped access, our next steps include enforcing traffic shaping policies:

  • Prioritization queues by consumer role
  • Rate limiting and circuit breakers
  • Per-consumer audit logs and tracing

These will tie into Lunar Gateway, helping teams enforce not just what can be used, but how and when. Stay tuned for more.

Ready to Start your journey?

Manage a single service and unlock API management at scale