
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.
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:
- Global level
- Consumer level
- Service level
- 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