manage mcp access.md
Manage MCP Access
Onboard an MCP server, connect it tenant-wide, register an AI Agent, and govern access with a policy — all in Terraform
This guide builds the core Barndoor workflow as code: an MCP server your organization can reach, a credential connection for it, a registered AI Agent, and an access policy that governs what that agent may do.
Estimated time: 20–30 minutes. Complete Getting Started first — this guide assumes a working provider configuration.
Before You Begin
- A configured
provider "barndoor"block (see Getting Started) - An API key for the service your MCP server fronts (this guide uses a non-OAuth server)
- The catalog slug or name of the MCP server you want to onboard and the name of the agent you want to register — directory entries are looked up by data source, so no IDs need to be copied from the app
Step 1: Onboard the MCP server
If the server is new to your organization, create it from its directory entry. If it was already onboarded in the portal, look it up with a data source instead — don't manage the same server from both places.
New server
# Look up the catalog entry by its connector slug (or name, or id).
data "barndoor_mcp_server_directory" "search" {
slug = "search"
}
resource "barndoor_mcp_server" "search" {
name = "Search"
mcp_server_directory_id = data.barndoor_mcp_server_directory.search.id
}
Existing server (data source)
data "barndoor_mcp_server" "search" {
# Look up by name, slug, or id. Name matching is case- and
# whitespace-insensitive; an ambiguous name fails with candidates.
name = "Search"
}
References: barndoor_mcp_server resource, data source, barndoor_mcp_server_directory data source
Server names are unique within an organization (case- and whitespace-insensitively), and destroying a barndoor_mcp_server soft-deletes it: its connections and stored credentials are torn down, and the name is freed for reuse.
Step 2: Connect it tenant-wide
A barndoor_connection stores a service-account-owned credential for the server, so every authorized agent can use it without each user connecting individually:
resource "barndoor_connection" "search" {
server_id = barndoor_mcp_server.search.id
# Write-only: stored in Barndoor's secret store, never returned by the API.
api_key = var.search_api_key
}
variable "search_api_key" {
type = string
sensitive = true
}
Reference: barndoor_connection
Step 3: Register the AI Agent
Registration attaches the agent's directory entry to your organization and creates the service account that policies bind to:
data "barndoor_agent_directory" "assistant" {
name = "Assistant"
}
resource "barndoor_agent" "assistant" {
application_directory_id = data.barndoor_agent_directory.assistant.id
llm_gateway_enabled = true
}
References: barndoor_agent, barndoor_agent_directory data source
An agent directory entry can have one live registration per organization. To reference an agent registered outside Terraform, use the barndoor_agent data source.
Step 4: Govern access with a policy
The policy is where the three pieces meet: it binds the agent (application_ids) to the server (mcp_server_id) and defines what's allowed:
resource "barndoor_policy" "search_read_only" {
name = "Search read-only for the assistant"
mcp_server_id = barndoor_mcp_server.search.id
description = "The assistant may run searches; everything else is blocked."
support_contact = "platform@example.com"
status = "ACTIVE" # DRAFT (default), ACTIVE, or INACTIVE
application_ids = [barndoor_agent.assistant.id]
tags = ["search", "terraform-managed"]
rules = [
{
name = "allow searches"
effect = "ALLOW"
actions = ["tools/call:search", "tools/call:get_document"]
roles = ["*"]
},
]
}
Reference: barndoor_policy
Conditional rules
Rules accept an optional condition tree, passed as JSON. Each node has exactly one of expr or a combinator (all / any / none) with of = [...]:
rules = [
{
name = "allow small reads"
effect = "ALLOW"
actions = ["tools/call:search"]
roles = ["role:analyst"]
condition = jsonencode({
all = {
of = [
{ expr = { expr = "request.size < 100" } },
]
}
})
},
]
Lifecycle notes
- Destroy archives, it doesn't delete. Destroying a
barndoor_policymoves it toARCHIVED— the platform's terminal lifecycle state, kept for audit history. The name is freed for non-archived reuse. mcp_server_idis immutable on a policy; changing it forces a replacement (a new policy is created and the old one archived).- Importing existing objects: all four resources support
terraform import. Policies, servers, and agents import by ID; connections import by their server ID (or slug). See Best Practices → Importing.
Complete example
data "barndoor_mcp_server_directory" "search" {
slug = "search"
}
resource "barndoor_mcp_server" "search" {
name = "Search"
mcp_server_directory_id = data.barndoor_mcp_server_directory.search.id
}
resource "barndoor_connection" "search" {
server_id = barndoor_mcp_server.search.id
api_key = var.search_api_key
}
data "barndoor_agent_directory" "assistant" {
name = "Assistant"
}
resource "barndoor_agent" "assistant" {
application_directory_id = data.barndoor_agent_directory.assistant.id
llm_gateway_enabled = true
}
resource "barndoor_policy" "search_read_only" {
name = "Search read-only for the assistant"
mcp_server_id = barndoor_mcp_server.search.id
description = "The assistant may run searches; everything else is blocked."
support_contact = "platform@example.com"
status = "ACTIVE"
application_ids = [barndoor_agent.assistant.id]
tags = ["search", "terraform-managed"]
rules = [
{
name = "allow searches"
effect = "ALLOW"
actions = ["tools/call:search", "tools/call:get_document"]
roles = ["*"]
},
]
}
variable "search_api_key" {
type = string
sensitive = true
}