SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor

Як self-host Open Connector для AI-агентів

Запустіть Open Connector на власному VPS: pinned image, TLS origin, OAuth callbacks і резервні копії, щоб AI-агенти не зберігали SaaS-токени.

Що Open Connector робить для AI-агента

Self-hosting Open Connector створює єдиний auth gateway між вашими AI-агентами та всіма SaaS API, до яких вони звертаються. Тому агент ніколи не зберігає токен провайдера. Це gateway із доступним вихідним кодом від OOMOL Lab. Наразі він поширюється за модифікованою ліцензією Apache 2.0 із додатковими умовами. Версії до v1.9.4 включно залишаються під ліцензією Apache 2.0. У v1.9.5 ліцензію було змінено. Надання hosted або managed service, основна цінність якого полягає у функціональності OpenConnector, потребує комерційної ліцензії від OOMOL. Open Connector працює як один container, зберігає стан в одному SQLite-файлі та надає дії провайдерів через HTTP і MCP (model context protocol).

Проблеми починаються вже під час другої інтеграції. Кожен провайдер має власний OAuth (open authorization) flow, власний час життя refresh token і власні назви scope. Ручне підключення п’яти провайдерів до агента означає створення п’яти redirect handler, п’яти сховищ облікових даних і п’яти циклів оновлення, які мають виконуватися до завершення строку дії токена. Майже ніхто не пише такий код. Замість цього для кожного сервісу створюють один довгоживучий personal access token і вставляють його в конфігурацію агента, environment file або сам prompt. Тоді цей токен може читати кожен tool, який запускає агент, а також він потрапляє до transcript. Саме цю проблему описано в матеріалі як не зберігати секрети в AI-агентах.

Auth gateway розділяє облікові дані на дві частини. Gateway зберігає облікові дані провайдера та виконує OAuth flow. Агент отримує runtime token, дійсний лише для звернень до gateway. Коли агент викликає дію, gateway завантажує збережені облікові дані, додає їх до outbound request на стороні сервера та повертає лише тіло відповіді. Агент ніколи не отримує access token провайдера. Тому витік transcript агента коштує вам один відкличний runtime token, а не весь обліковий запис GitHub.

У каталозі заявлено понад 1,000 провайдерів і 10,000 готових дій. Це власні дані проєкту, які неможливо перевірити ззовні. Натомість можна перевірити саму структуру: один HTTP endpoint для кожної дії, одне збережене connection для кожного провайдера і один токен для кожного агента. Якщо робота з агентами для вас ще нова, а такі терміни, як tool call або MCP server, ще не стали звичними, поетапний шлях у матеріалі як навчитися працювати з AI-агентами з нуля допоможе послідовно опанувати цикл роботи, tools і практики безпеки, які gateway на кшталт цього припускає як уже відомі.

Чому варто self-host Open Connector, а не користуватися hosted connector service

Hosted connector service виконує ту саму роботу та зберігає refresh tokens для кожного підключеного до нього провайдера. Refresh token для Google або GitHub — це довгоживучий ключ до вашої пошти та репозиторіїв. Їхній злам означає злам вашої системи. Self-hosting переносить ці записи в SQLite на машину, яку ви орендуєте й адмініструєте. Дані захищені ключем, який ніколи не залишає ваш сервер.

До початку роботи оцініть витрати та ризики. Цей VPS стане найціннішим сервером у вашій інфраструктурі. В одному файлі він зберігає робочі облікові дані для десятка сервісів. Тому поводьтеся з ним так само, як із хостом password manager: firewall має відкривати лише 443, спільні облікові записи не використовуйте, резервну копію хоча б один раз потрібно реально відновити, а коли сервер перестає відповідати, має надходити сповіщення. Якщо ви не стали б розміщувати на цій машині своє password vault, не розміщуйте на ній і connector.

Зафіксуйте версію перед встановленням

Open Connector — молодий проєкт. У цьому посібнику зафіксовано v1.3.3, опубліковану 30 July 2026, як версію для встановлення станом на 1 August 2026. Це вже не найновіший реліз. Реєстр також публікує тег tip, зібраний із найновішого коміту в main.

У такому новому проєкті рухомі теги часто змінюються. docker compose pull, який перескакує через два релізи, може змінити endpoint, від якого залежить ваш agent, і ви витратите вечір на налагодження, помилково вважаючи проблему пов’язаною з agent. Зафіксуйте image на тезі релізу та оновлюйте його тоді, коли вирішите самі, попередньо прочитавши release notes.

Розгортання Open Connector за TLS на власному VPS

Перш ніж запускати контейнер, переконайтеся, що у вас є:

  • Docker Engine 28.0.0 або новішої версії з Compose plugin на Ubuntu 24.04 або близькій до неї системі
  • hostname, A-запис якого вказує на цей VPS, наприклад connect.example.com
  • reverse proxy, який уже завершує TLS (transport layer security) для цього hostname
  • два випадкові секрети, згенеровані нижче

У матеріалі Reverse proxy Traefik для кількох застосунків Docker Compose описано налаштування проксі. Повний процес підключення сертифікатів для одного застосунку наведено в посібнику n8n на VPS із Docker і HTTPS.

Спочатку згенеруйте секрети. Ключ шифрування захищає збережені облікові дані. Токен адміністратора захищає вебконсоль і всю поверхню /api. Для жодного з них немає значення за замовчуванням, але runtime без них запускається без помилок.

mkdir -p ~/open-connector && cd ~/open-connector
umask 077
printf 'OOMOL_CONNECT_ENCRYPTION_KEY=%s\n' "$(openssl rand -base64 32)" > .env
printf 'OOMOL_CONNECT_ADMIN_TOKEN=%s\n' "$(openssl rand -base64 32)" >> .env
chmod 600 .env

Скопіюйте обидва значення до менеджера паролів зараз, до першого запуску. Для ключа шифрування не передбачено відновлення. Причину наведено нижче у списку помилок.

Тепер compose.yaml. Він відрізняється від прикладу upstream у двох місцях, і обидві зміни важливі.

services:
  connector:
    image: ghcr.io/oomol-lab/open-connector:v1.3.3
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - connector-data:/app/data
    environment:
      OOMOL_CONNECT_DATA_DIR: /app/data
      OOMOL_CONNECT_ORIGIN: "https://connect.example.com"
      OOMOL_CONNECT_ENCRYPTION_KEY: "${OOMOL_CONNECT_ENCRYPTION_KEY:?set this in .env}"
      OOMOL_CONNECT_ADMIN_TOKEN: "${OOMOL_CONNECT_ADMIN_TOKEN:?set this in .env}"

volumes:
  connector-data:

Перша зміна — зафіксований tag замість latest. Друга — порт. Upstream-файл публікує 3000:3000, який прив’язує порт до всіх інтерфейсів хоста. Docker записує опубліковані порти до таблиці NAT (network address translation) ще до того, як пакет потрапляє до ланцюжка фільтрації ufw. Тому ufw deny 3000 не закриває цей порт. Цю типову помилку описано в матеріалі чому порти Docker обходять ufw. Запис 127.0.0.1:3000:3000 публікує порт лише на loopback-інтерфейсі, а reverse proxy підключається з того самого хоста. До Docker Engine 28.0.0 хости в тому самому L2-сегменті могли отримувати доступ до портів, опублікованих на localhost. Саме тому цей посібник вимагає версію 28.0.0 або новішу.

:? позначає кожну змінну як обов’язкову. Тому stack відмовляється запускатися, якщо .env відсутній, замість запуску з незашифрованими обліковими даними. Зберігання значень у .env, а не у compose-файлі, відповідає підходу з матеріалу env-файли та секрети Docker Compose. Зберігайте /v1 і /mcp приватними в reverse proxy, доки нижче не створите перший runtime-токен.

docker compose up -d
docker compose logs -n 30 connector
curl -s http://127.0.0.1:3000/health
sudo ss -tlnp | grep 3000

/health повертає { "ok": true } після запуску runtime. ss має вивести 127.0.0.1:3000. Рядок із 0.0.0.0:3000 означає, що listener прив’язаний до всіх IPv4-інтерфейсів, але не підтверджує доступність з Інтернету: зовнішні firewall або правила в ланцюжку Docker DOCKER-USER все одно можуть блокувати зовнішній трафік. Відмова в підключенні під час health check означає, що контейнер ще не слухає порт. Перегляньте журнали, перш ніж змінювати proxy.

Мітки Traefik для того самого сервісу
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.connector.rule=Host(`connect.example.com`)"
      - "traefik.http.routers.connector.entrypoints=websecure"
      - "traefik.http.routers.connector.tls.certresolver=le"
      - "traefik.http.services.connector.loadbalancer.server.port=3000"

Якщо Traefik працює в Docker на тому самому хості, підключіть цей сервіс до мережі Traefik і видаліть блок ports:. Traefik підключатиметься до контейнера через внутрішню мережу, тому публікувати порт на хості взагалі не потрібно. certresolver=le має відповідати імені resolver у статичній конфігурації Traefik, інакше router запуститься без сертифіката.

Чому для OAuth callback потрібен правильний origin

OOMOL_CONNECT_ORIGIN — це параметр, який часто пропускають. Через це OAuth не працює, а помилка виглядає як проблема на стороні провайдера. Runtime формує redirect URI на основі цього origin у форматі <origin>/oauth/callback. Якщо origin не задано, за замовчуванням використовується http://localhost:3000. Тому runtime передає провайдеру redirect URI http://localhost:3000/oauth/callback, тоді як у вашому OAuth-застосунку зареєстровано https://connect.example.com/oauth/callback. Ці два рядки відрізняються, тому GitHub повертає:

The redirect_uri MUST match the registered callback URL for this application.

OAuth-провайдер перенаправляє браузер назад на цей URI, тому браузер має мати до нього доступ. OAuth не завжди потребує публічного hostname: у документації OpenConnector описано callback GitHub за адресою http://localhost:3000/oauth/callback. У цьому розгортанні на VPS використовується hostname і сертифікат, щоб браузер міг підключитися до gateway через його HTTPS origin. Задайте origin до першого запуску, оскільки значення зчитується під час запуску: після редагування .env або compose.yaml знову виконайте docker compose up -d, щоб застосувати зміни.

Підключіть першого провайдера через OAuth

Спочатку створіть OAuth app у провайдера. У GitHub відкрийте Settings, потім Developer settings, OAuth Apps і New OAuth App. Укажіть URL callback для авторизації: https://connect.example.com/oauth/callback. Збережіть client ID і client secret.

Кожен виклик /api передає admin token, тому експортуйте його один раз для поточної shell-сесії.

export ADMIN_TOKEN='paste-the-admin-token'
curl -s https://connect.example.com/api/oauth/configs \
  -H "authorization: Bearer $ADMIN_TOKEN"

У цьому списку показано redirect URI, який runtime очікує для кожного провайдера. Це найшвидший спосіб перевірити, що origin застосовано. Якщо там і далі вказано localhost, контейнер працює зі старим значенням, і OAuth flow завершиться помилкою на останньому кроці.

Збережіть client credentials і запустіть авторизацію.

curl -s -X PUT https://connect.example.com/api/oauth/configs/github \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"clientId":"...","clientSecret":"..."}'

curl -s -X POST https://connect.example.com/api/oauth/authorizations \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"service":"github"}'

Другий виклик повертає authorizationUrl. Відкрийте його в браузері, підтвердьте scopes, і провайдер поверне браузер на /oauth/callback. Там runtime обмінює code і зберігає credential. Вебконсоль за вашим origin виконує ті самі кроки через форму, використовуючи той самий admin token. Провайдери, які використовують звичайний API key, пропускають усі ці кроки: PUT /api/connections/<service> із {"authType":"api_key","values":{"apiKey":"..."}} безпосередньо зберігає key.

Надавайте кожному agent runtime token, а не credential

Agent автентифікується на gateway за допомогою runtime token, який створює admin API.

curl -s -X POST https://connect.example.com/api/runtime-tokens \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"research-agent"}'

У відповіді міститься token, що починається з oct_. Створюйте окремий token для кожного agent і називайте його відповідно до цього agent, оскільки відкликання token, який неможливо ідентифікувати, означає відкликання всіх token. Після цього agent викликає actions через звичайний HTTP.

curl -s -X POST https://connect.example.com/v1/actions/github.get_current_user \
  -H "authorization: Bearer oct_..." \
  -H 'content-type: application/json' \
  -d '{"input":{}}'

Коректна відповідь — це envelope, у якому поле success має значення true, а payload провайдера міститься в data. GitHub token у цій відповіді відсутній. Для MCP client укажіть https://connect.example.com/mcp і використовуйте той самий bearer header. Gateway надає discovery tools, зокрема search_actions і execute_action, замість окремого tool для кожного API. Це дає змогу зменшити список tools agent. У матеріалі Запуск MCP servers на VPS описано налаштування клієнтської частини.

Перед завершенням виконайте ще одну перевірку. Повторіть виклик action, видаливши header authorization. У власному quickstart проєкту /v1 викликається взагалі без bearer, тому інсталяція без налаштованої runtime authentication виконуватиме actions для будь-кого, хто має доступ до порту. Якщо виклик без автентифікації успішний, є два варіанти: налаштувати runtime tokens і переконатися, що анонімний виклик тепер завершується помилкою, або обмежити /api, /v1 і /mcp на reverse proxy адресами, з яких підключаються ваші agents. /oauth/callback має бути доступним для браузера, який завершує авторизацію. Відкривати його для всього інтернету не потрібно.

Скоротіть список дій до необхідного агенту

Шлюз із тисячею провайдерів за ним має велику поверхню атаки для мовної моделі. Вона стає ще більшою, коли модель починає читати текст, створений не нею, оскільки сторінка, яку повертає ваш власний екземпляр SearXNG у відповідь на вебпошук агента, може містити інструкції, спрямовані на будь-які дії, доступні агенту. Те саме обмеження, завдяки якому агент для роботи з кодом вносить найменшу працездатну зміну, має поширюватися і на його дозволи: надавайте лише кілька дій, фактично потрібних для завдання, і нічого більше. Є два елементи керування, які звужують цей набір.

OOMOL_CONNECT_ALLOWED_ACTIONS приймає список дозволених дій через кому та підтримує service.* і *. OOMOL_CONNECT_BLOCKED_ACTIONS — це список заборонених дій, і він має пріоритет. Якщо задати для списку дозволених дій значення github.get_current_user,github.list_issues, усі інші дії буде відхилено незалежно від запиту агента. Саме це відрізняє помилку від інциденту. Токени середовища виконання мають власні правила дій на додаток до глобальних правил, а їхній список allowedProxies за замовчуванням порожній. Тому POST /v1/proxy/:service буде відхилено, доки ви явно не надасте цей дозвіл. Ця кінцева точка проксі пересилає необроблений запит провайдеру разом із вашими обліковими даними, тому залишайте її порожньою, якщо вона не потрібна конкретному агенту.

OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK за замовчуванням має значення false. Це не дає підключенню до self-hosted провайдера звертатися до приватної адреси, наприклад до сервісу метаданих хмари на 169.254.169.254 або до вашої бази даних у тій самій мережі. Залишайте цей параметр вимкненим. Умикайте його лише для провайдера, який ви розгорнули самостійно.

Резервне копіювання сервера, на якому зберігаються всі токени

Важливі дві речі, і кожна з них без іншої непридатна. База даних у /app/data/connect.sqlite всередині тому connector-data містить зашифровані облікові дані. Ключ шифрування у .env розшифровує їх. Резервна копія тому без ключа нічого не відновить, а ключ без тому також нічого не відновить, тому ключ зберігайте у password manager, а том додайте до звичайної ротації резервних копій.

Зупиніть контейнер, поки копіюєте файл SQLite, оскільки копія, створена під час запису, може відновитися як пошкоджена база даних.

docker volume ls | grep connector-data
docker compose stop connector
docker run --rm -v open-connector_connector-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/connector-data.tgz -C /data .
docker compose start connector

Назва тому — це назва каталогу проєкту плюс _connector-data, тому потрібна перша команда: вставте фактичну назву в третю команду. Передайте архів із VPS за допомогою резервного копіювання restic із VPS. restic шифрує його перед передаванням, оскільки цей архів містить сховище облікових даних.

Середовище виконання зберігає останні запуски дій як записи аудиту — за замовчуванням 5,000 записів. Тому в консолі можна побачити, який агент, що саме і коли запускав. Цей журнал слід читати насамперед, коли агент поводиться нетипово. Також налаштуйте сторінку стану Uptime Kuma для перевірки https://connect.example.com/health. Коли gateway перестає відповідати, агенти починають поводитися непередбачувано, а інформація про недоступність gateway заощаджує годину читання виводу агентів.

Що може зламатися і яке повідомлення ви побачите

redirect_uri_mismatch у провайдера. Джерело та зареєстрована URL-адреса callback відрізняються. Порівняйте точний рядок із /api/oauth/configs з налаштуваннями застосунку у провайдера, зокрема https зі http, а також перевірте кінцевий слеш.

Кожен виклик /api повертає 401. Заголовок із токеном адміністратора відсутній або має неправильну назву. Заголовок має вигляд Authorization: Bearer <token>, а вебконсоль запитує той самий токен.

Контейнер працює, а облікові дані зберігаються у відкритому вигляді. Це відбувається, коли OOMOL_CONNECT_ENCRYPTION_KEY не потрапляє до контейнера, оскільки runtime зберігає записи облікових даних у незашифрованому вигляді, замість того щоб відмовитися від запуску. Перевірте це у власній інсталяції: підключіть провайдера за допомогою API key, який можна розпізнати, а потім виконайте пошук цього ключа в базі даних.

docker compose cp connector:/app/data/connect.sqlite /tmp/connect.sqlite
grep -c 'github_pat_' /tmp/connect.sqlite
shred -u /tmp/connect.sqlite

Значення понад 0 означає, що ключ не застосовано. Перевірте, що .env розташований в одному каталозі з compose.yaml і що docker compose config виводить це значення. Якщо ключ налаштовано, той самий пошук поверне 0, оскільки запис захищено за допомогою AES-256-GCM (advanced encryption standard, 256-bit key, Galois/counter mode).

Після відновлення нічого не розшифровується. Ключ шифрування змінився або був втрачений. За задумом його ніколи не записують поруч із даними, тому шляху відновлення немає, і звернення до служби підтримки не допоможе. Підключіть усіх провайдерів повторно. Ротація підтримується через окрему змінну ключа та команду роботи з даними в runtime, тому перед ротацією ознайомтеся з нотатками до поточного релізу.

Agent повертає помилку із зазначенням action, який він бачить у каталозі. Виявлення та виконання — окремі процеси. Action може відображатися в search_actions, але його все одно може відхилити OOMOL_CONNECT_ALLOWED_ACTIONS, denylist або власні правила цього runtime token.

Оновлення. Створіть резервну копію тому, змініть image tag на новий реліз, а потім docker compose pull && docker compose up -d. Моніторте docker compose logs -n 50 connector на наявність повідомлення про міграцію, після чого повторно виконайте перевірку працездатності та одну реальну action, перш ніж знову довірити системі роботу. Відкат означає повернення старого tag. Це працює лише тому, що tag було зафіксовано.

FAQ

Чи потрібен публічний домен для self-hosting Open Connector?

Для провайдерів, які використовують API key, ні: достатньо gateway на 127.0.0.1. OAuth також не завжди його потребує: OpenConnector документує локальний callback GitHub як http://localhost:3000/oauth/callback. Браузер, у якому завершується авторизація, має отримати доступ до callback URL. Для цього розгортання на VPS перед першим запуском задайте OOMOL_CONNECT_ORIGIN як hostname https:// і зареєструйте <origin>/oauth/callback в OAuth app провайдера.

Що станеться, якщо я втрачу encryption key Open Connector?

Збережені облікові дані неможливо розшифрувати, і відновлення не передбачене. Ключ навмисно не зберігається разом із даними, тому ніхто, хто має доступ до database, не може їх прочитати, зокрема й ви. Єдиний варіант — задати новий ключ і повторно підключити кожного провайдера. Зберігайте ключ у password manager, а database включіть до циклу резервного копіювання, оскільки для відновлення потрібні обидва компоненти.

Чи може мій AI agent бачити access token провайдера?

Ні, якщо він викликає провайдера через gateway. Agent автентифікується за допомогою runtime token, що починається з oct_, а gateway додає облікові дані провайдера до вихідного запиту на сервері та повертає лише відповідь. Цю властивість порушують дві ситуації: endpoint /v1/proxy/:service, який пересилає необроблені запити з прикріпленими обліковими даними і навмисно має порожній набір grants, а також вставлення API key в agent вручну, що повністю обходить gateway.

Чи має gateway бути доступним із публічного інтернету?

OAuth callback має бути доступним браузеру, у якому завершується авторизація. Для цього не потрібен доступ із усього інтернету. У Docker Engine 28.0.0 або новішої версії зі стандартною bridge network опублікуйте порт контейнера на 127.0.0.1, щоб опублікований порт залишався доступним лише з host, і розмістіть перед ним reverse proxy. Старіші версії Docker дозволяли host у тому самому сегменті L2 отримувати доступ до портів, опублікованих на localhost. Після цього перевірте один action call без заголовка authorization. Якщо виклик успішний, обмежте /api, /v1 і /mcp на proxy адресами, з яких працюють ваші agents, доки доступними не залишаться лише автентифіковані виклики.

Чи готовий Open Connector до використання в production?

Поточні релізи використовують змінену ліцензію Apache 2.0 з додатковими умовами. Релізи до v1.9.4 включно, зокрема зафіксований v1.3.3, залишаються під ліцензією Apache 2.0. Hosted або managed connector service, основною цінністю якого є функціональність OpenConnector, за поточною ліцензією потребує commercial license від OOMOL. Команди встановлення v1.3.3 залишаються snapshot станом на 1 August 2026, а цей реліз вийшов 30 July 2026. Запускайте його з фіксацією на release tag, ніколи не використовуйте latest або tip, перед кожним оновленням читайте release notes і зберігайте резервну копію volume, яку ви вже один раз відновили. Така архітектура підходить для сервера, який належить вам. Ризик пов’язаний зі змінами версій, а не з архітектурою.

Чи була ця стаття корисною?

#ai-agents#oauth#self-hosting#integrations#source-available