# Enmasse - GitOps for AI integrations

Keep the whole AI configuration as one YAML file in version control.

Everything the AI layer consists of takes part in [enmasse](https://zato.io/docs/admin/enmasse.html), the platform's configuration export and import tool - [LLM connections](https://zato.io/docs/ai/llm/index.html) under the `llm` section and [MCP gateways](https://zato.io/docs/ai/mcp/index.html) under `mcp_gateway`. What you define in the Dashboard exports to YAML, the YAML is stored in git, and importing it recreates the configuration in another environment - dev, test and production differing only in their environment variables.

## The llm section {#the-llm-section}

```yaml
llm:

  - name: My OpenAI
    model: gpt-4o-mini
    address: https://api.openai.com/v1
    secret: Zato_Enmasse_Env.My_OpenAI_API_Key
    timeout: 30
    max_tokens: 2048
    max_history_turns: 10
    chat_expiry: 3600

  - name: My Claude
    model: Sonnet 5
    address: https://api.anthropic.com
    secret: Zato_Enmasse_Env.My_Claude_API_Key
```

| Key | Required | Meaning |
| --- | --- | --- |
| `name` | Yes | The connection's name - what services look up through `self.llm[name]` |
| `model` | Yes | A catalog name, e.g. `Sonnet 5`, or a wire id entered by hand, exactly as in [the Dashboard](https://zato.io/docs/ai/llm/connections.html) |
| `address` | Yes | The base URL of the API to call |
| `secret` | No | The [API key](https://zato.io/docs/security/api-keys.html) - self-hosted endpoints may not need one |
| `is_active` | No | Whether the connection can be used, `true` when omitted |
| `timeout` | No | Seconds to wait for the provider's response (default: 60) |
| `max_tokens` | No | The most tokens the model may generate per reply (default: 1024) |
| `max_history_turns` | No | How many past [chat](https://zato.io/docs/ai/llm/chat.html) turns are sent to the provider (default: 20) |
| `chat_expiry` | No | Seconds a chat's history is kept after its last message (default: 86400) |

Every key the second connection above omits gets its default, so a minimal definition is a name, a model and an address.

## The mcp\_gateway section {#the-mcp_gateway-section}

```yaml
mcp_gateway:

  - name: billing
    url_path: /mcp/billing
    services:
      - billing.get-invoice
      - billing.list-invoices
    security_groups:
      - partner-agents
    oauth: true
    oauth_scopes: openid profile
    skills:
      - invoice-analysis
    validate_input: true
    is_audit_log_active: true
    max_response_size: 2000
    safeguards_pii_enabled: true
    safeguards_pii_lands:
      - international
    alerts:
      invalid_calls: 10
      repeat_calls: 30
      volume_budget: 500000000
```

Importing a gateway also creates the REST channel that makes it reachable under its URL path - there is nothing separate to define. The only required key is `name`, every other key gets its documented default:

| Key | Default | Meaning |
| --- | --- | --- |
| `url_path` | `/mcp` | The path the endpoint is exposed under |
| `services` | `[]` | The services exposed as [tools](https://zato.io/docs/ai/mcp/tool-schemas.html) |
| `security_groups` | `[]` | The names of the [security groups](https://zato.io/docs/ai/mcp/security.html) whose members may call the gateway |
| `oauth` | `false` | Whether people sign in through your identity provider, as described under [OAuth](https://zato.io/docs/ai/mcp/oauth.html) - needs a bearer token definition with an issuer and an audience among the groups' members |
| `oauth_scopes` | `''` | With `oauth` on, the scopes the gateway's metadata document tells clients to ask for, separated with spaces |
| `skills` | `[]` | The [skills](https://zato.io/docs/ai/skills/index.html) served as [MCP prompts](https://zato.io/docs/ai/mcp/prompts.html) |
| `is_active` | `true` | Whether the gateway accepts requests |
| `is_audit_log_active` | `false` | The per-gateway [audit log](https://zato.io/docs/ai/mcp/audit-log.html) toggle |
| `validate_input` | `false` | [Argument validation](https://zato.io/docs/ai/mcp/argument-validation.html) against each tool's schema |
| `allow_agent_filters` | `false` | The per-call JSONata [response filter](https://zato.io/docs/ai/mcp/agent-filters.html) |
| `session_ttl` | `0` | Idle [session](https://zato.io/docs/ai/mcp/index.html#endpoint-behavior) TTL in seconds - zero keeps the default 30 minutes |
| `invoke_timeout` | `0` | Seconds one `tools/call` may run for - zero keeps the default 90 |

The response shaping family matches the wizard's step 02, all documented under [response controls](https://zato.io/docs/ai/mcp/response-controls.html):

| Key | Default |
| --- | --- |
| `max_response_size` | `0` - no cap |
| `size_cap_mode` | `truncate`, the other value is `block` |
| `min_size_threshold` | `0` - no threshold |
| `characters_per_token` | `4.0` |
| `safeguards_strip_nulls`, `safeguards_collapse_whitespace`, `safeguards_strip_base64` | `false` |
| `safeguards_pii_enabled` | `false` |
| `safeguards_pii_lands`, `safeguards_pii_detectors`, `safeguards_pii_exclude` | `[]` |
| `safeguards_pii_validate` | `true` |
| `safeguards_pii_stable_replacements` | `false` |
| `safeguards_secrets_enabled` | `false` |
| `safeguards_normalize_unicode`, `safeguards_sanitize_markup` | `false` |
| `safeguards_unicode_mode`, `safeguards_markup_mode` | `clean`, the other value is `reject` |
| `safeguards_url_policy_enabled` | `false` |
| `safeguards_url_allow_list` | `[]` |
| `safeguards_url_mode` | `remove` - the other values are `neutralize` (the Defang mode) and `reject` |

The `alerts` mapping holds the gateway's own [alert](https://zato.io/docs/ai/mcp/alerts.html) settings - every key it omits keeps its default. The three above raise the invalid-call threshold to ten in the window, the repeated-call one to thirty and the response volume to 500 megabytes, typed as a plain byte count.

## Keep secrets out of git {#keep-secrets-out-of-git}

The `Zato_Enmasse_Env.` prefix makes enmasse read the value from an environment variable of the given name where the import runs - `My_OpenAI_API_Key` above. The YAML in git then contains no secrets, and each environment supplies its own keys. The mechanism is the same one all enmasse sections use and it is described in the [enmasse chapter](https://zato.io/docs/admin/enmasse.html).

The gateways themselves hold no secrets - their `security_groups` name groups whose member definitions, with their passwords and keys, are defined in the `security` section the same YAML file can include. A gateway with `oauth` on needs a `bearer_token` definition there with an `issuer`, an `audience` and, to tell people apart, an `identity_claim` - the [OAuth](https://zato.io/docs/ai/mcp/oauth.html) page shows the whole file for a gateway of that kind.

## Export and import {#export-and-import}

- To export, in the Dashboard click **System** > **Config** > **Export enmasse** - the resulting `enmasse.yaml` has the `llm` and `mcp_gateway` sections with the rest of the configuration, without any secrets, which never leave the server.
- To import, click **System** > **Config** > **Import enmasse** and select the file, or mount it under `/opt/hot-deploy/enmasse/enmasse.yaml` inside a container for imports that run on startup, as the [DevOps guide](https://zato.io/tutorials/devops/deployment.html) describes.

An import creates what does not exist yet and updates what does, matching by name - running it again is safe.

## See also {#see-also}

- [Enmasse overview](https://zato.io/docs/admin/enmasse.html) - The export and import tool across every configuration section
- [Enmasse reference](https://zato.io/docs/admin/enmasse-reference.html) - Every section and attribute enmasse supports
- [LLM connections](https://zato.io/docs/ai/llm/connections.html) - The same connections defined in the Dashboard
- [MCP gateways](https://zato.io/docs/ai/mcp/index.html) - The gateways the mcp\_gateway section declares
