> ## Documentation Index
> Fetch the complete documentation index at: https://docs.barndoor.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Note>
  **Estimated time**: 20–30 minutes. Complete [Getting Started](/terraform/getting-started) first — this guide assumes a working provider configuration.
</Note>

## Before You Begin

* A configured `provider "barndoor"` block (see [Getting Started](/terraform/getting-started))
* An API key for the service your MCP server fronts (this guide uses a non-OAuth server)
* The **directory entry ID** of the MCP server you want to onboard, and the **directory entry ID** of the agent you want to register — both visible in the Barndoor app when browsing the MCP server and agent catalogs

## 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.

<Tabs>
  <Tab title="New server">
    ```hcl theme={null}
    resource "barndoor_mcp_server" "search" {
      name                    = "Search"
      mcp_server_directory_id = var.search_directory_id
    }
    ```
  </Tab>

  <Tab title="Existing server (data source)">
    ```hcl theme={null}
    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"
    }
    ```
  </Tab>
</Tabs>

References: [`barndoor_mcp_server` resource](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/resources/mcp_server), [data source](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/data-sources/mcp_server)

<Note>
  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.
</Note>

## 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:

```hcl theme={null}
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`](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/resources/connection)

<Warning>
  **OAuth servers can't be connected by Terraform** — the interactive browser consent can't happen inside a declarative apply. Terraform supports the non-OAuth credential providers (`api_key`, `bearer_token`, `basic_auth`, `generic`); connect OAuth servers in the Barndoor app. Also note: an organization can have at most **one tenant-wide connection per server**, and changing any credential attribute replaces the connection.
</Warning>

## 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:

```hcl theme={null}
resource "barndoor_agent" "assistant" {
  application_directory_id = var.assistant_directory_id

  llm_gateway_enabled = true
}
```

Reference: [`barndoor_agent`](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/resources/agent)

An agent directory entry can have one live registration per organization. To reference an agent registered outside Terraform, use the [`barndoor_agent` data source](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/data-sources/agent).

## 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:

```hcl theme={null}
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`](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/resources/policy)

<Warning>
  **Always set `actions` and `roles` explicitly on every rule.** If you omit either list, the platform API defaults it to `["*"]` — *everything*. An "allow" rule you meant to scope narrowly becomes an allow-all. Write `["*"]` only when you mean it.
</Warning>

### 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 = [...]`:

```hcl theme={null}
rules = [
  {
    name    = "allow small reads"
    effect  = "ALLOW"
    actions = ["tools/call:search"]
    roles   = ["role:analyst"]

    condition = jsonencode({
      all = {
        of = [
          { expr = { expr = "request.size < 100" } },
        ]
      }
    })
  },
]
```

<Tip>
  Start new policies in `DRAFT` (the default), review the result in the Barndoor app under **MCP Gateway → Policies**, then flip `status` to `ACTIVE` in a follow-up apply — the same draft-then-activate flow the portal uses.
</Tip>

## Lifecycle notes

* **Destroy archives, it doesn't delete.** Destroying a `barndoor_policy` moves it to `ARCHIVED` — the platform's terminal lifecycle state, kept for audit history. The name is freed for non-archived reuse.
* **`mcp_server_id` is 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](/terraform/best-practices#importing-existing-configuration).

## Complete example

<Expandable title="Full configuration from this guide">
  ```hcl theme={null}
  resource "barndoor_mcp_server" "search" {
    name                    = "Search"
    mcp_server_directory_id = var.search_directory_id
  }

  resource "barndoor_connection" "search" {
    server_id = barndoor_mcp_server.search.id
    api_key   = var.search_api_key
  }

  resource "barndoor_agent" "assistant" {
    application_directory_id = var.assistant_directory_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_directory_id" {
    type = string
  }

  variable "assistant_directory_id" {
    type = string
  }

  variable "search_api_key" {
    type      = string
    sensitive = true
  }
  ```
</Expandable>
