cd /news/ai-infrastructure/enterprise-mcp-gateway-with-built-in… · home topics ai-infrastructure article
[ARTICLE · art-88216] src=dev.to ↗ pub= topic=ai-infrastructure verified=true sentiment=· neutral

Enterprise MCP Gateway with Built-In Security: OAuth 2.0, RBAC, and Tool Access Control

Maxim's Bifrost project introduces an enterprise MCP gateway with built-in security, including OAuth 2.0, RBAC, and tool access control. The gateway ensures that LLM tool calls are not automatically executed, requiring explicit approval, and supports deny-by-default virtual key filtering to prevent accidental production access.

read8 min views1 publishedAug 5, 2026

MCP servers are powerful, but they can expose production systems if anyone on the team can connect and run tools without guardrails.

Imagine a new hire testing the app on their laptop and accidentally granting an MCP server access to the production database. Without governance, that is a realistic path to data leakage.

Bifrost addresses this with three layers:

POST /v1/mcp/tool/execute

.mcp_configs

gets zero MCP tools. Unlisted clients are implicitly blocked.Bifrost covers virtual keys, budgets, rate limits, routing, and MCP tool filtering, RBAC, SSO, audit logs, and MCP Tool.

First, let's open the app and set up the MCP server. To do this, I'll enter the following line in the terminal:

npx -y @maximhq/bifrost

After that, you will see the following interface (similar, depending on the version):

Go to the "MCP Library" tab and you will see a huge list of pre-configured MCP servers that you can use in your projects.

If you want to set up your own MCP server, go to MCP Gateway and click New MCP Server:

Here you can specify the connection URL, auth type, tool allowlists, and other settings including Code Mode, which can significantly reduce token usage when orchestrating many MCP servers.

This is the most important security property for the scenario in the introduction.

When an LLM returns tool calls, Bifrost does not automatically execute them. Tool calls are suggestions only. Your application must explicitly approve and execute each one:

1. POST /v1/chat/completions   → LLM returns tool call suggestions (NOT executed)
2. Your app reviews tool calls → Apply security rules, get user approval if needed
3. POST /v1/mcp/tool/execute   → Execute approved tool calls explicitly
4. POST /v1/chat/completions   → Continue the conversation with tool results

Example execution call:

curl -X POST http://localhost:8080/v1/mcp/tool/execute \
  -H "Content-Type: application/json" \
  -d '{
    "id": "call_xyz789",
    "type": "function",
    "function": {
      "name": "database_query",
      "arguments": "{\"sql\": \"SELECT 1\"}"
    }
  }'

So even if a new hire's agent requests a dangerous database operation, nothing happens until your application deliberately executes it. Combined with deny-by-default virtual key filtering (below), this is Bifrost's real three-layer answer to accidental production access.

You can opt into autonomous execution for specific tools via Agent Mode, but that must be explicitly configured, it is not the default.

Authentication is declared on the MCP client itself as a top-level auth_type

field, posted to /api/mcp/client

. There is no nested auth

object.

auth_type | Who authenticates | When to use | |---|---|---| none | — | Public MCP servers, local STDIO tools | headers | Admin, once | Shared API keys, bearer tokens, custom headers | oauth | Admin, once | Shared third-party service the whole team uses | per_user_oauth | Each end-user, lazily | Per-user services like Notion, GitHub, Sentry | per_user_headers | Each end-user, lazily | Per-user API keys, signed tokens |

OAuth (oauth

and per_user_oauth

) is only valid for HTTP and SSE connections. Bifrost implements the Authorization Code flow, there is no client-credentials / service-account mode.

{
  "name": "local-tools",
  "connection_type": "stdio",
  "stdio_config": {
    "command": "npx",
    "args": ["-y", "@anthropic/mcp-filesystem"]
  },
  "auth_type": "none",
  "tools_to_execute": ["read_file", "list_directory"]
}
curl -X POST http://localhost:8080/api/mcp/client \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web_search",
    "connection_type": "http",
    "connection_string": "https://mcp.example.com/mcp",
    "auth_type": "headers",
    "headers": {
      "Authorization": "Bearer your-api-key",
      "X-Tenant-ID": "acme-corp"
    },
    "tools_to_execute": ["*"]
  }'

The admin authenticates once during setup. Every subsequent request to that MCP server uses the same stored token, regardless of which caller hit Bifrost.

curl -X POST http://localhost:8080/api/mcp/client \
  -H "Content-Type: application/json" \
  -d '{
    "name": "authenticated_service",
    "connection_type": "http",
    "connection_string": "https://api.example.com/mcp",
    "auth_type": "oauth",
    "oauth_config": {
      "client_id": "your-client-id",
      "client_secret": "your-client-secret",
      "authorize_url": "https://auth.example.com/oauth/authorize",
      "token_url": "https://auth.example.com/oauth/token",
      "scopes": ["mcp:read", "mcp:write"]
    },
    "tools_to_execute": ["*"]
  }'

The oauth_config

object accepts client_id

, client_secret

, authorize_url

, token_url

, scopes

, or registration_url

/ server_url

for Dynamic Client Registration. After the admin completes the authorize step, finalize with POST /api/mcp/client/{id}/complete-oauth

.

Use auth_type: "per_user_oauth"

when each end-user must connect under their own account. Bifrost stores one OAuth token per (identity, mcp_client)

and reuses it on later calls. Identity is required via virtual key, signed-in SSO user, or x-bf-mcp-session-id

.

curl -X POST http://localhost:8080/api/mcp/client \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme_api",
    "connection_type": "http",
    "connection_string": "https://api.acme.example.com/mcp",
    "auth_type": "per_user_headers",
    "per_user_header_keys": ["X-API-Key", "X-Tenant-ID"],
    "tools_to_execute": ["*"]
  }'

Identity matters: With auth_type: "oauth"

or auth_type: "headers"

, all callers share the same upstream credential. Bifrost does not attach a per-user identity to MCP requests. To know exactly who performed an action upstream, use per_user_oauth

or per_user_headers

.

RBAC does not govern which MCP tools an agent can invoke at runtime. That is controlled by virtual keys and three stacked levels of tool filtering:

tools_to_execute

on each MCP client (baseline)x-bf-mcp-include-clients

and x-bf-mcp-include-tools

per requestmcp_configs

array (takes precedence over request headers)This is built-in behavior, not a config setting: a virtual key with no mcp_configs gets zero MCP tools, and clients not listed in

mcp_configs

are implicitly blocked.

curl -X POST http://localhost:8080/api/governance/virtual-keys \
  -H "Content-Type: application/json" \
  -d '{
    "name": "new-dev-key",
    "mcp_configs": [
      {
        "mcp_client_name": "internal_api",
        "tools_to_execute": ["search", "get_article"]
      },
      {
        "mcp_client_name": "staging_database",
        "tools_to_execute": ["query"]
      }
    ]
  }'

tools_to_execute | Result | |---|---| ["*"] | All tools from this client | ["a", "b"] | Only specified tools | [] | No tools from this client | Client not in mcp_configs | All tools blocked from that client |

This is where you enforce patterns like "backend devs can hit staging APIs but not production databases" by giving different virtual keys different mcp_configs

, not by RBAC permission strings.

For one-off restrictions within a virtual key's allowlist:

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer vk_new_dev" \
  -H "x-bf-mcp-include-tools: staging_database-query" \
  -d '...'

Note: when a virtual key has mcp_configs

, it auto-generates x-bf-mcp-include-tools

and overrides any manually sent header.

Bifrost does not parse SQL or block operations like DELETE

/ DROP

at the query level. Restrict access by allowing only specific tool names (for example, a read-only query

tool instead of an execute

tool).

Bifrost provides Role-Based Access Control for the administrative surface who can edit MCP gateway configs, read logs, configure guardrails, manage virtual keys, and so on. RBAC is not runtime authorization for agents invoking MCP tools.

Permissions are Resource × Operation pairs, not permission strings like mcp:tool:invoke

.

Role Permissions Description
Admin
42 Full access to all resources and operations
Developer
27 CRUD on technical resources, view access to logs and cluster
Viewer
14 Read-only access to all resources

You can also create custom roles (for example, an Auditor role with AuditLogs:View

and Logs:View

only).

Logs

, VirtualKeys

, MCPGateway

, MCPToolGroups

, MCPLogs

, GuardrailsConfig

, AuditLogs

, Cluster

, and others.

View

, Create

, Update

, Delete

, Download

, Reveal

, and inference operations.

Example: a custom Auditor role might grant AuditLogs:View

and AuditLogs:Download

, but not MCPGateway:Update

. That controls who can configure the gateway in the dashboard, not which tools an agent executes at runtime.

Roles and permissions are managed via Governance → Roles & Permissions in the dashboard or the /api/roles

endpoints:

curl -X GET http://localhost:8080/api/roles/{role_id}/permissions \
  -H "Authorization: Bearer <admin_token>"

There is no role_sync

config block. Role assignment comes from User Provisioning over OIDC, supported for Okta, Microsoft Entra and others.

When SSO is configured:

Configuration lives under scim_config

in config.json

. See the User Provisioning docs for provider-specific setup guides.

Audit logs in Bifrost record administrative activity who changed what, when, and which resource was affected. They do not use a log_level

/ capture

/ export_to

block.

Real configuration shape:

{
  "audit_logs": {
    "disabled": false,
    "hmac_key": "env.AUDIT_HMAC_KEY",
    "retention_days": 365,
    "object_storage": {
      "type": "s3",
      "bucket": "acme-audit-archive",
      "prefix": "acme-prod",
      "compress": true,
      "region": "us-east-1",
      "access_key_id": "env.AUDIT_S3_KEY",
      "secret_access_key": "env.AUDIT_S3_SECRET"
    }
  }
}

Key features:

AuditLogs:Download

permission)retention_days

controls database retentionView audit entries at Governance → Audit Logs in the dashboard.

Do not look for a "policy": "default_deny"

setting. It does not exist. Instead:

mcp_configs

for each team or environmenttools_to_execute

to the minimum neededOnly enable Agent Mode auto-execution for tools you have explicitly reviewed. The default flow — chat → review → /v1/mcp/tool/execute

— is your strongest safety net.

mcp_configs

  • request headers
{
  "name": "production-readonly",
  "mcp_configs": [
    { "mcp_client_name": "production_database", "tools_to_execute": ["query"] }
  ]
}
{
  "name": "staging-full",
  "mcp_configs": [
    { "mcp_client_name": "staging_database", "tools_to_execute": ["*"] }
  ]
}

Enable HMAC signing, set retention_days

comfortably above your archival window, and optionally mirror to object storage for compliance.

Schedule quarterly reviews to answer:

Use the dashboard and /api/roles

endpoints, there is no bifrost audit

CLI command. The @maximhq/bifrost-cli

package is an interactive launcher for coding agents (Claude Code, Codex CLI, Gemini CLI, Opencode), not an audit tool.

With Bifrost, you can configure your company's MCP server much more securely. This ready-made solution will save you not only money but also time, which can be spent on product development.

npx -y @maximhq/bifrost-cli

Thanks for reading this article! ❤️

I'd love to hear your thoughts on this mode in the comments!

── more in #ai-infrastructure 4 stories · sorted by recency
── more on @bifrost 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/enterprise-mcp-gatew…] indexed:0 read:8min 2026-08-05 ·