Skip to main content
Glama
i1i1i1i1i1i11

bitrix-mcp

README.md
# Bitrix MCP: Keycloak identity and Vault credentials

This is a Streamable HTTP MCP server for working with Bitrix24 from OpenWebUI.
It does **not** accept Bitrix tokens from OpenWebUI, tool arguments, headers,
or the LLM. Every MCP request must carry a Keycloak access token. The server
verifies it and uses its verified `sub` as the only user identity. Bitrix access
and refresh tokens stay in Vault and are never returned to OpenWebUI or a model.

## Security model

```text
OpenWebUI -- Bearer Keycloak access token --> MCP
                                         | verify signature/JWKS, iss, aud, exp, nbf
                                         v
                                    verified sub
                                         |
                                         v
                                    Vault KV v2
                                         |
                                         v
                                   Bitrix24 REST API
```

- Keycloak issuer: `https://sso.rucortex.ru/realms/rucortex`
- Required MCP audience: `https://147.45.38.205/mcp`. The JWT `aud` claim may
  be an array; the server requires this value to be a member of it.
- Vault uses AppRole and keeps its short-lived Vault token only in memory.
- Local SQLite contains only one-time OAuth state and task-form data; it never
  stores Bitrix access or refresh tokens.
- Audit logs contain request ID, verified Keycloak subject, external Bitrix
  user ID (when known), tool, and status—never secrets or tokens.

## Prerequisites

1. Open TCP ports **80** and **443** to the VPS and set the public IP in
   `PUBLIC_HOST` (without protocol or path).
2. Put the DevOps-provided AppRole values in the VPS-only `.env` file as
   `VAULT_ROLE_ID` and `VAULT_SECRET_ID`. Do not commit `.env`, print either
   value, or put either value in logs or chat messages.
3. Vault must contain the shared Bitrix local-app configuration at the
   read-only API path `/v1/mcp/data/config/bitrix`:

   ```json
   {
     "client_id": "...",
     "client_secret": "...",
     "redirect_uri": "https://147.45.38.205/bitrix/oauth/callback",
     "portal_url": "https://electrosteel.bitrix24.ru"
   }
   ```

4. In the Bitrix local app, configure this exact redirect URL:

   ```text
   https://147.45.38.205/bitrix/oauth/callback
   ```

   The user who opens the connection link completes the official Bitrix OAuth
   flow. The callback state is server-generated, short-lived, and already tied
   to the verified Keycloak `sub`; the resulting credentials are written only
   to `/v1/mcp-tokens/data/users/<sub>/bitrix` in Vault.

## VPS environment

Copy `.env.example` to `.env` and set the non-secret values, for example:

```dotenv
PUBLIC_HOST=147.45.38.205
ACME_EMAIL=admin@example.com
KEYCLOAK_ISSUER=https://sso.rucortex.ru/realms/rucortex
KEYCLOAK_DISCOVERY_URL=https://sso.rucortex.ru/realms/rucortex/.well-known/openid-configuration
KEYCLOAK_AUDIENCE=https://147.45.38.205/mcp
VAULT_ADDR=https://vault-staging.rucortex.ru
VAULT_ROLE_ID=replace_with_role_id
VAULT_SECRET_ID=replace_with_secret_id
SQLITE_ADMIN_USERNAME=sqlite_admin
SQLITE_ADMIN_PASSWORD=use_a_long_unique_password
```

Docker Compose passes `VAULT_ROLE_ID` and `VAULT_SECRET_ID` from the VPS-only
`.env` into the MCP container. Keep `.env` restricted to the VPS and out of
Git.

### Optional shared-account Static Bearer mode

The same `/mcp` endpoint can also accept one shared bearer token for a separate
OpenWebUI connection. Calls made through that connection act as the owner of a
single Bitrix incoming webhook and do not use Keycloak identity or per-user
Vault credentials. Set both values only in the VPS `.env`:

```dotenv
MCP_STATIC_BEARER_TOKEN=a-long-random-secret
BITRIX_STATIC_WEBHOOK_URL=https://electrosteel.bitrix24.ru/rest/USER_ID/WEBHOOK_CODE
```

Keep this connection restricted to trusted administrators: every OWU user who
can use it acts with the webhook owner's Bitrix permissions. Keycloak bearer
authentication remains available for the existing per-user connection.

Start or update the stack:

```bash
cd /tmp/mcp-bitrix && git pull
```

```bash
cd /tmp/mcp-bitrix && docker compose up -d --build
```

The public MCP endpoint is:

```text
https://147.45.38.205/mcp
```

`/sqlite` exposes the current non-secret local state database
(`data/mcp-state.sqlite3`) through sqlite-web, behind Caddy basic auth. It is
not a Vault browser and cannot show Bitrix tokens.

## Inline Visualizer v2 task drafts

With Classic298 Inline Visualizer v2 enabled, `create_task` creates a short-
lived draft bound to the verified Keycloak subject. It returns exact HTML for
Visualizer v2 to stream between its `@@@VIZ-START` and `@@@VIZ-END` markers.
The form has no iframe, no MCP HTTP request, and no access to Vault or Bitrix
credentials.

After **Принять**, the form sends a strict follow-up prompt through Visualizer
v2's `sendPrompt` bridge. The model must pass its opaque `confirmation_payload`
unchanged to `confirm_task_draft`; MCP checks draft ownership, expiry, and the
selected employee before reading Vault and creating the Bitrix task. That tool
returns a static task preview. **Отклонить** similarly calls
`reject_task_draft`, which creates no Bitrix task.

Enable **Allow iframe same origin** for Visualizer v2, as required by its
streaming observer. Its default Strict CSP is sufficient here: the task form
does not make outbound fetches and must not use `security_level=none`.

## Keycloak configuration for OpenWebUI

Create a confidential Keycloak client for the OpenWebUI MCP connection.

1. In realm `rucortex`, create a client such as `openwebui-bitrix-mcp`.
   Enable **Standard Flow**, enable **Client authentication**, and use the
   client secret generated by Keycloak.
2. Add this exact valid redirect URI, replacing the host and connection ID with
   the values of your OpenWebUI installation and connection:

   ```text
   https://<openwebui-host>/oauth/clients/<mcp-connection-id>/callback
   ```

   The MCP connection ID is the `ID` field in OpenWebUI, e.g. `bitrix-test`.
   If your OpenWebUI is served from the same host, the concrete URI is likely
   `https://<openwebui-host>/oauth/clients/bitrix-test/callback`.
3. Add an Audience mapper (or attach a client scope containing one) so the
   access token has `https://147.45.38.205/mcp` in its `aud` claim. It is valid
   and expected for `aud` to contain several values.
4. Ensure the assigned users can obtain this client’s access token. The default
   `openid` scope is sufficient for MCP authentication; add your normal profile
   scopes only if your organisation requires them.

## OpenWebUI MCP connection

Use **MCP Streamable HTTP** and set:

| Field | Value |
| --- | --- |
| URL | `https://147.45.38.205/mcp` |
| Auth | **OAuth 2.1 (Static)** |
| OAuth server URL | `https://sso.rucortex.ru/realms/rucortex` |
| Client ID | Keycloak client ID from the prior section |
| Client secret | Keycloak client secret |
| OAuth scopes | `openid` (plus your organisation’s required scopes) |
| OAuth resource parameter | `https://147.45.38.205/mcp` |

Then save/register the connection and authenticate each user with Keycloak.
Choose static OAuth because this Keycloak client is pre-created. Do **not** use
OpenWebUI’s Dynamic Client Registration mode unless Keycloak DCR has separately
been enabled and secured by your Keycloak administrator.

When a user’s Vault path has no Bitrix credentials, any Bitrix tool returns the
controlled `BITRIX_NOT_CONNECTED` result. Call `connect_bitrix`, open its
one-time link, and complete the official Bitrix login. Repeat the tool call
after the confirmation page appears.

## Tools

- `users_list` — employees with ID, full name, position, and departments.
- `get_user_info` — full current Bitrix profile.
- `create_task` — returns an embedded task form; the task is created only after
  its **Принять** button is pressed.
- `connect_bitrix` — provides a short-lived user Bitrix OAuth connection link.

The Bitrix local application needs permissions for users and tasks. In typical
Bitrix24 terminology that means at least `user`, `department`, and `task` /
`tasks`; retain `crm` only if future MCP tools will call CRM methods.

## Migrating from the old OAuth bridge

The old bridge database may contain historic OAuth material. This code no longer
reads it. After confirming the new flow works, take an encrypted backup and
remove/archive any legacy `data/tokens.json`, `data/clients.json`, and old
`data/templates.sqlite3` files from the VPS. Do not delete `data/mcp-state.sqlite3`:
it contains only active short-lived UI/OAuth state for this version.