## Documentation Index

Fetch the complete documentation index at: [/llms.txt](https://docs.barndoor.ai/llms.txt)

Use this file to discover all available pages before exploring further.

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](https://docs.barndoor.ai/terraform/getting-started) first — this guide assumes a working provider configuration.

## Before You Begin

- A configured `provider "barndoor"` block (see [Getting Started](https://docs.barndoor.ai/terraform/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

- Existing server (data source)

```
# 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
}
```

```
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](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), [`barndoor_mcp_server_directory` data source](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/data-sources/mcp_server_directory)

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

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

## 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`](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/resources/agent), [`barndoor_agent_directory` data source](https://registry.terraform.io/providers/barndoor-ai/barndoor/latest/docs/data-sources/agent_directory)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:

```
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"\\n\
      actions = ["tools/call:search", "tools/call:get_document"]\
      roles   = ["*"]\
    },\
  ]
}
```

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

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

### 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" } },\
        ]\
      }\
    })\
  },\
]
```

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.

## 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](https://docs.barndoor.ai/terraform/best-practices#importing-existing-configuration).

## Complete example

Show Full configuration from this guide

```
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
}
```
