---
title: "User-Level Permission Controls for MCP Tool Access with an Enterprise MCP Gateway"
description: "MCP gives your AI agents real power — reading files, calling APIs, moving money. But who decides which user or agent can reach which tool? Here is how an enterprise MCP gateway turns that question into a policy you can actually enforce, from the gateway down to every laptop."
author: Aleksei Aleinikov
date: 2026-07-06
lang: en
tags: [mcp, ai-gateway, access-control, bifrost, platform-engineering, ai-security]
canonical: https://www.alekseialeinikov.com/en/blog/topics/security/user-level-permission-controls-mcp-tool-access-gateway-2026
source: alekseialeinikov.com
---

# User-Level Permission Controls for MCP Tool Access with an Enterprise MCP Gateway

The Model Context Protocol solved a real problem: it gave AI models a clean, standard way to reach the outside world. Instead of gluing each model to each API by hand, you point a client at an **MCP server** and the model can suddenly list files, query a database, open a pull request, or issue a refund.

That is also the problem.

An MCP tool is not read-only context. It is a **capability** — a live button the model can press on someone's behalf. The moment you connect a payments server, "create an invoice" and "issue a refund" are one tool call away. And in most organizations the honest answer to *"which user or agent can reach which of those tools?"* is: **nobody knows, and nothing stops them.**

This article is about turning that question into an answer you can enforce. The short version: you do it at a **gateway**, and — for the tools that never pass through one — at the **endpoint**.

## Why This Is a Permissions Problem, Not a Prompt Problem

The tempting fix is to handle tool access in the prompt: tell the agent "only use the read-only tools." That is not a control. It is a suggestion to a probabilistic system, and it collapses under a jailbreak, a confused-deputy attack, or a simple mistake.

Tool access is an **authorization** problem, and it has the same shape as every other authorization problem you already solve:

- A **subject** — a user, a team, or an autonomous agent.
- A **resource** — a specific MCP tool (`create-invoice`, not just "the billing server").
- A **decision** — allow or deny, made by something the subject cannot talk its way past.

The mistake is to push that decision *into* each AI app. Every app has its own config, users can edit it, and there is a new app every month — Claude Desktop, Cursor, Codex, an internal agent someone wrote on Friday. Trusting each of them to police itself is the "every service does its own auth" anti-pattern, reborn for AI.

The fix is the same one platform teams have reached for every time before: **move the decision to a choke point that every request has to pass through.**

## The Gateway: One Choke Point for Every Tool Call

An **MCP gateway** sits between your AI clients and your MCP servers. Every client — desktop app, IDE, custom agent — connects to the gateway instead of to the tools directly. The gateway is simultaneously an **MCP client** (to the real tool servers) and an **MCP server** (to your apps), so it can see and rule on every tool call in flight.

That single position gives you the thing per-app config can never provide: **one place where the answer to "can this caller use this tool?" is decided, the same way, every time.**

I'll use [Bifrost](https://www.getbifrost.ai) as the concrete example — an open-source [AI gateway](https://www.getbifrost.ai) that fronts 20+ model providers and includes an MCP gateway with per-request tool filtering — but the pattern applies to any gateway built on the same principles.

![An MCP gateway as a single choke point: AI clients authenticate with a virtual key, the gateway applies a per-key allow-list of MCP tools, and enforces it at both inference and tool-execution time.](https://www.alekseialeinikov.com/blog/mcp-gateway-tool-filtering-2026.webp)

### Virtual keys: attaching permissions to a caller

The unit of control is a **virtual key**. Instead of handing out raw provider keys, you issue each user, team, or agent a virtual key (`sk-bf-…`) that they send on every request:

```bash
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-vk: sk-bf-your-virtual-key" \
  -H "Content-Type: application/json" \
  -d '{ "model": "gpt-4o-mini", "messages": [{"role":"user","content":"Hello"}] }'
```

The virtual key is where governance hangs: which models and providers it may use, its budget, its rate limits — and, the part we care about here, **which MCP tools it is allowed to touch.**

### Deny-by-default tool filtering

This is the design decision that makes the whole thing a real control: a virtual key with **no** MCP configuration gets **no** tools. Access is not something you take away; it is something you explicitly grant.

For each MCP client attached to a key, you choose exactly what it may reach:

- **Specific tools** — only the tools you name are available.
- **`*` wildcard** — all current and future tools from that client.
- **Empty list** — everything from that client is blocked.
- **Not configured at all** — that client is invisible to the key.

A worked example. Say two MCP clients are connected to the gateway:

- `billing-client` with tools `[create-invoice, check-status, issue-refund]`
- `support-client` with tools `[create-ticket, get-faq]`

A read-only support agent's virtual key can be scoped like this:

```json
{
  "mcp_clients": {
    "billing-client": ["check-status"],
    "support-client": ["*"]
  }
}
```

That key can check an invoice's status and use every support tool — but `create-invoice` and `issue-refund` simply do not exist for it. A different key, for the finance team, grants `billing-client: ["*"]`. Same gateway, same tools, **different reach per caller.**

### Enforced twice, so the model can't cheat

Here is the subtle part that separates a control from a filter. It would be easy to only hide the disallowed tools when presenting the toolset to the model. But a model that hallucinates a tool name — or an injected instruction that names one directly — would slip through.

So the allow-list is applied in **two** places:

1. **At inference time** — the model is only ever offered the tools the key allows. It cannot choose what it cannot see.
2. **At tool-execution time** — even if a call for a disallowed tool arrives anyway, the gateway rejects it before it reaches the server.

The gateway does this by translating the key's configuration into a strict `x-bf-mcp-include-tools` allow-list on the request, and re-checking it at execution. A tool that is off the list is not merely hidden — it is **unreachable**.

## Binding Permissions to Real People, Not Anonymous Keys

A virtual key is only as good as your knowledge of who holds it. Scope a key perfectly and then email it around, and you are back to shared secrets.

That is why per-user MCP control has to connect to your identity provider. In [Bifrost's](https://www.getbifrost.ai) enterprise tier this is **OIDC + SCIM**: users sign in with their corporate account, and their Bifrost role, team, and business-unit membership are provisioned automatically from the IdP — Okta, Entra ID, Keycloak, Google Workspace, and others.

The mapping is attribute-driven, so a claim from the IdP becomes a Bifrost role:

```json
{
  "attribute": "department",
  "value": "Finance",
  "role": "developer",
  "team": "Finance"
}
```

Now the chain is complete and self-maintaining:

**corporate identity → team → virtual key → MCP tool allow-list.**

When someone joins Finance, they inherit the finance team's tool access. When they leave, SCIM deprovisions them and the access evaporates — no orphaned key still able to issue refunds. Permissions follow the *person*, which is the only version of "user-level control" that survives contact with a real org.

> This is the same principle that killed long-lived cloud credentials. If you still hand out static service-account keys, the identity-bound approach applies there too — see [Kill Your Service Account Keys: Workload Identity Federation in 2026](https://www.alekseialeinikov.com/en/blog/topics/security/kill-service-account-keys-workload-identity-federation-2026).

## The Blind Spot: Tools That Never Touch the Gateway

A gateway governs everything that flows through it. The uncomfortable truth is that a growing share of MCP usage **doesn't**.

A developer adds a filesystem MCP server to Claude Desktop. Another wires a database server straight into Cursor. A third installs something they found on GitHub into Codex. None of it points at your gateway. Each is a local process on a laptop with real capabilities — reading files, hitting internal APIs, taking actions — and it is completely invisible to your central policy. This is **shadow MCP**, and it is exactly where the gateway model runs out of road.

You cannot solve this from the server side, because these tools were never configured to go through a server. You have to solve it **on the machine.**

![A single control plane, enforced everywhere: Bifrost defines identity, virtual keys and MCP policy centrally; an Edge agent on each machine syncs that policy and enforces allow or deny on local MCP servers directly on the device.](https://www.alekseialeinikov.com/blog/mcp-gateway-edge-enforcement-2026.webp)

### Extending policy to the endpoint

This is what an endpoint agent like [Bifrost Edge](https://www.getbifrost.ai) is for. It runs on every machine — macOS, Windows, Linux — and does two things that a gateway alone cannot:

- **Discovery.** It reads the MCP configuration inside each AI app on the device and reports it back, building a fleet-wide inventory. For the first time you can answer *"which MCP servers are actually running across our org?"* with data instead of a guess.
- **On-device enforcement.** Allowing or denying a server is not advisory. When you deny one, Edge enforces that decision on the machine, so the tool cannot be used **even by an app that had it configured before the policy existed.**

The governance model does not change — it *extends*. The same identities, the same virtual keys, the same allow/deny decisions defined centrally are now enforced on traffic that never would have reached the gateway: desktop chat apps, browser AI, coding agents in the terminal, and the local MCP servers they connect to. A new server appearing on a laptop can trigger an approval request in the admin console instead of silently gaining access.

Gateway and endpoint are the two halves of one control: the gateway is the **policy plane** for everything that routes through it; the endpoint agent is the **last mile** for everything that doesn't.

## Putting It Together

Here is the whole progression, from no control to real least-privilege for agents:

| | No control | Gateway only | Gateway + endpoint |
|---|---|---|---|
| **Who decides tool access** | Each app, each user | Central allow-list per virtual key | Central allow-list, enforced everywhere |
| **Default posture** | Allow-all | Deny-by-default | Deny-by-default |
| **Tied to identity** | No | Via OIDC/SCIM → key | Via OIDC/SCIM → key |
| **Enforcement point** | None | At the gateway (inference + execution) | Gateway *and* on the device |
| **Local IDE MCP servers** | Invisible | Invisible | Inventoried + allow/deny |
| **Leaver offboarding** | Manual, easy to miss | Key deprovisioned | Key deprovisioned + device enforced |

The principles underneath are not new — they are the ones platform and security engineers have applied to every capable system before this one:

1. **Least privilege.** Deny-by-default; grant the specific tools a caller needs and nothing more.
2. **One choke point.** Decide access in a single place every request passes through, not scattered across apps.
3. **Tie capability to identity.** Permissions belong to a person or an agent with an owner, not to a floating secret.
4. **Enforce where the action happens.** At the gateway for routed traffic; on the device for everything else.

MCP made AI agents genuinely capable of *doing things*. That is exactly why "which user or agent can reach which tool" stops being a nice-to-have and becomes the same kind of question you already ask about database roles and cloud IAM. An enterprise MCP gateway — deny-by-default, virtual-key scoped, identity-bound, and extended to the endpoint — is how you get an answer you can actually enforce.

If you want to see the mechanics in detail: [Bifrost](https://www.getbifrost.ai) is the AI gateway used throughout this article, the [gateway docs](https://docs.getbifrost.ai/overview) cover virtual keys and MCP tool filtering, and the [Bifrost Edge docs](https://docs.getbifrost.ai/edge/overview) cover endpoint discovery and on-device enforcement.

You can explore Bifrost on [GitHub](https://github.com/maximhq/bifrost) or visit the [official website](https://www.getbifrost.ai/) to learn more.
