# Config by hand (YAML)

> The full reference of the settings file: providers, the event receiver and integration connections.

For automated installations the config is written directly. This is an equal path:
the [local page](/en/docs/connector/web-setup) and the [commands](/en/docs/connector/cli-setup)
edit the same file, under the same check.

## Example

```yaml
# middleware_url can be omitted — the relay address is built into the agent.
state_dir: /var/lib/connector

providers:
  - kind: gitlab
    base_url: https://gitlab.corp.local
    token_env: GITLAB_TOKEN            # the token from an environment variable
    repos:
      - group/app
      - group/lib
  - kind: bitbucket-server
    base_url: https://bitbucket.corp.local
    token_file: /run/secrets/bb.token  # …or from a file
    repos:
      - PROJ/service

# The webhook receiver is one for the whole connector. Empty — off, polling works.
# Required for GitLab commit comments and for Figma without a list of files and projects
# (for Figma — an address reachable from the internet).
webhooks:
  listen: 0.0.0.0:8766
  host: connector.corp.local:8766

plugins:
  - id: 8GXN8BECS24D392A          # GUID; the connector assigns it itself if omitted
    plugin: jira
    version: 1.4.1
    alias: Jira prod
    slug: jira_prod               # derived from alias if not given
    dev_project: backend
    enabled: true
    config:                       # checked against the integration's schema
      base_url: https://jira.corp.local
      project_keys: [PROJ, OPS]
      mention_alias: buff
    credentials:
      - name: api
        kind: bearer
        token_file: /var/lib/connector/secrets/jira-8GXN8BECS24D392A-api.token
    webhook: true
    webhook_secret: 1BXYNH8PA7ZYNZGN
```

## Top level

| Field | Description |
|------|----------|
| `middleware_url` | **Optional.** The Buff relay address — built into the agent; set it only for your own installation. |
| `state_dir` | The directory for the agent’s key, secrets, installed integrations and their working directories. Must survive restarts. |
| `providers` | The git providers the bridge serves. |
| `webhooks` | The event receiver: `listen` — the address and port on this machine, `host` — how the tracker sees this port. Empty — the receiver is off. |
| `plugins` | Integration connections. |

## `providers[]`

| Field | Description |
|------|----------|
| `kind` | `github`, `ghe`, `gitlab`, `gitea`, `bitbucket`, `bitbucket-server`. |
| `base_url` | The address of your git server (or cloud provider). |
| `repos` | Repositories (`owner/name`, for Bitbucket Server — `PROJECT/slug`). |
| `token_env` / `token_file` | Where to take the token from: an environment variable **or** a file. Exactly one of the two. |

## `plugins[]` — connections

**An integration is code, a connection is a link.** Two Jiras are two connections
of one integration: the same code, different credentials, different allowed addresses, a different
webhook link, a different OS user.

| Field | Description |
|------|----------|
| `id` | The connection’s GUID. The webhook link, the working directory and charges are tied to it, so it does not change on renaming. If not given, the connector assigns one. |
| `plugin` | The integration’s name in the catalogue (`jira`, `github`, `redmine`, …). Empty for [your own server](#mcp--your-own-server): instead of `plugin`/`version` it has an `mcp` block. |
| `version` | The version the connection is pinned to. Required: “latest” is not a configuration but a lottery. |
| `alias` | A human-readable name. Changes freely and breaks nothing. |
| `slug` | A short name: it goes into the names of the tools the model sees. Derived from `alias` if not set; must be unique. |
| `dev_project` | The development project or [space](/en/docs/quickstart/space) in Buff where the work goes. The name **within the organisation**, without the organisation and without a slash. |
| `enabled` | `false` — the connection is configured but not started. On by default. |
| `config` | The integration’s own settings. Checked against its schema: an extra key or a wrong value is an error, not a silent save. A field that has a default value in the manifest (for example, Figma’s API address) can be omitted — the connector fills it in itself, the same way for the form, the command line and the outbound rules. |
| `credentials[]` | References to secrets (see below). Values never go into the YAML. |
| `webhook` | Whether the connection accepts calls. Where the connector listens — in `webhooks` above. |
| `webhook_secret` | The secret in the link. The connector mints it itself; it survives renaming, so the link pasted into the tracker does not have to change. |
| `poll_seconds` | How often to read this connection. Empty — as the integration suggests, otherwise the connector’s default. |
| `ai_notes` | Notes for the AI: free text about what the model should know about this system. Goes to the model together with the description of the connection’s tools. Up to 2048 characters unless the limit is lifted. |
| `ai_notes_unbounded` | `true` lifts the length limit on `ai_notes`. This is your acceptance of the risk: excess text makes the model work worse and costs more; the connector truncates nothing. A file with `ai_notes` over the limit and without this flag will not be loaded. |

### `credentials[]`

| Field | Description |
|------|----------|
| `name` | The name the integration asks for (usually `api`). |
| `kind` | `bearer`, `basic` or `header` — how the tracker accepts the secret; `service_account` (a service account’s JSON key) or `oauth2` (your own OAuth client and a refresh token) — kinds for which the connector **issues tokens itself**. Taken from the integration’s manifest; where the integration offers several kinds, it is chosen in the form or with the `--kind` flag. |
| | For [your own server](#mcp--your-own-server): `env` — the secret reaches the server as an environment variable named `name`; `secret` — only where there is a `${user_config.name}` for it; `header` — the remote server’s `Authorization` header. |
| `username` | The account for `basic` (for example, the Jira Cloud email). Not a secret. |
| `header` | The header name for the `header` kind (for example, `X-Redmine-API-Key`). |
| `token_env` / `token_file` | Where to take the secret from. For `service_account` — the whole JSON key; for `oauth2` — JSON `{"client_id","client_secret","refresh_token"}`, where the connector itself adds the refresh token after sign-in. |
| `password_env` / `password_file` | The password for `basic`, if it is kept separately from the token. |

**Secrets are not stored in the YAML** — only a reference to an environment variable or a file. This way
the config can be copied, compared and kept in version control.

The simplest way to write a secret is with a command — it sets the right kind of access from the
manifest itself:

```bash
connector instance secret jira_prod --secret api \
  --config /etc/connector/connector.yaml < /run/secrets/jira-token
```

### `mcp` — your own server

A connection to [an MCP server you brought yourself](/en/docs/connector/custom-mcp). Such
a connection does not name `plugin` and `version`; what to run is described here. Exactly one of
`command` and `url`.

```yaml
plugins:
  - id: 7QK3M9XBWZ0FVT2H
    alias: Weather
    slug: weather_mcp
    dev_project: backend
    config:
      units: metric
    credentials:
      - name: api_key
        kind: secret
        token_file: /var/lib/connector/secrets/mcp-7QK3M9XBWZ0FVT2H-api_key.token
    mcp:
      source: archive
      origin: weather-mcp.mcpb
      name: weather-mcp
      version: 1.0.0
      command: node
      args: ["${__dirname}/server/index.js"]
      env:
        API_KEY: ${user_config.api_key}
        UNITS: ${user_config.units}
      read_only: [forecast]
      accepted: true
```

| Field | Description |
|------|----------|
| `source` | Where the server comes from: `archive`, `github`, `url`, `command`. |
| `origin` | The archive name or the repository address — for you; the connector does not use it. |
| `name`, `version` | What its packaging called the server. A started server introduces itself, and the report carries its own words. |
| `command`, `args`, `env` | What to run. `node`, `npx`, `python`, `uvx` are replaced by the connector with the ones found on the machine; `${__dirname}` is the code directory, `${user_config.name}` is a value from `config` or a secret from `credentials`. A missing setting is a start-up error with its name. |
| `url` | The address of a remote server (Streamable HTTP) instead of `command`. The `Authorization` header is a credential of the `header` kind. |
| `read_only` | The tools **you** marked as read-only. Only they reach the model; the server’s own word is a hint on the page, not a decision. Changes without a restart. |
| `accepted` | `true` — your consent: the code has not been checked, the network is not limited. Without it the connection does not start, even if written in by hand. |

## What the connector adds itself

The file is not only yours: the agent normalises it on save. It assigns `id`, derives
`slug`, mints `webhook_secret` and turns secret references into files it created itself.
Edits apply **on the fly**: the agent watches the file, no restart is needed.

Two integrations named the same do not conflict: the slug is made unique
automatically, and each has its own GUID.

<Callout type="warn">
A section the agent does not know **is kept untouched** and not used — so that
a config from a newer version is not lost on a rollback. That is exactly why the old
`jira:` section stays in the file and the local page mentions it: it does not work and does not
get in the way.
</Callout>

## Pairing and running

```bash
connector pair --config /etc/connector/connector.yaml --code bufc_…
connector run  --config /etc/connector/connector.yaml --admin 127.0.0.1:8765
```
