Головна / Статті

Enterprise MCP на Bedrock AgentCore з Okta SSO та Trino

Lambda OAuth-мозок, PKCE-ретранслятор, шлюз та середовище виконання AgentCore, а також окремі підключення Trino для кожного користувача без витоку спільних токенів.

Видавець: REACTAPP.TOP13 хв читання2982 слів

Джерела: Building Enterprise MCP on AWS Bedrock AgentCore: Okta SSO + Trino

Цей посібник описує процес створення системи, яка починається з сировини та закінчується функціональною системою для: створення Enterprise MCP на AWS Bedrock AgentCore з використанням Okta SSO + Trino. Основна увага приділяється крокам виконання, чітким перевіркам та коду, який можна просто додати до репозиторію без необхідності здогадуватися щодо його призначення. Для загального огляду необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан системи. Необхідно фіксувати час виконання та витрати на токени чи запити разом із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовища до спільних середовищ.

Коротко

Коротко кажучи, перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь алгоритм. Необхідно надавати інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж вони автоматично схвалюють їх.

MCP Client → Lambda OAuth Proxy → Bedrock AgentCore Gateway → AgentCore Runtime → Trino

Чому передача необробленого токена інструменту порушує вимоги корпоративних систем

Щоб зрозуміти, чому передача необработаного токена інструменту призводить до невдачі перевірок у корпоративних системах, необхідно перед зміною коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Інструменти з вузькими схемами та чіткими позначеннями побічних ефектів мають бути відкриті для використання. Хостам потрібно знати, які виклики змінюють стан, перш ніж автоматично схвалити їх.

Архітектура в одному погляді

Для стислого огляду архітектури необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестовані одиниці замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану систему обробки. Використовуйте інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж автоматично схвалити їх. Для стислого огляду архітектури необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні.

1. Клієнт MCP

Для клієнта MCP необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь граф. Аутентифікація має відбуватися на шлюзі, а повторна авторизація — на рівні обробки даних. Один лише токен не є межею окремого тенанту.

2. Проксі Lambda MCP (мозок OAuth)

Для пункту 2. Проксі Lambda MCP („мозок“ OAuth) – необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Аутентифікація відбувається на шлюзі, а повторна авторизація – на рівні обробки даних. Один лише токен-носій не є межею окремого тенанта.

3. AWS Bedrock AgentCore Gateway

Для пункту 3. AWS Bedrock AgentCore Gateway необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестовані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Аутентифікуйтеся біля шлюзу та повторно авторизуйтесь на рівні обробки даних. Одного лише токена не достатньо для визначення меж користувацького облікового запису. Для пункту 3. AWS Bedrock AgentCore Gateway необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити разом із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних середовищ.

4. AWS Bedrock AgentCore Runtime

Для компонента 4. AWS Bedrock AgentCore Runtime необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Для кожного виклику потрібно записувати назву інструменту, хеш аргументів, час виконання та результат. Без цих даних налагодження циклів агента займає години.

Потік автентифікації від початку до кінця

Для повного циклу автентифікації необхідно перед змінами коду визначити вхідні дані, власника кроку та критерії завершення. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Для кожного виклику необхідно фіксувати назву інструменту, хеш аргументів, час виконання та результат. Без цих даних налагодження працює без кінця, що марнує години.

┌─────────────────────┐
│    MCP Client       │
│ Claude / Claude Code│
└──────────┬──────────┘
           │
           │ OAuth 2.1 + PKCE
           ▼
┌─────────────────────┐
│   Lambda MCP Proxy  │◄──────────────► Okta
│ OAuth / Identity    │   Login + JWT
└──────────┬──────────┘
           │
           │ Bearer JWT
           ▼
┌─────────────────────────────┐
│ Bedrock AgentCore Gateway   │
│ CUSTOM_JWT validation       │
└────────────┬────────────────┘
             │ SigV4
             ▼
┌─────────────────────────────┐
│ Bedrock AgentCore Runtime   │
│ FastMCP Server              │
│                             │
│ list_catalogs               │
│ describe_table              │
│ execute_query               │
└────────────┬────────────────┘
             │
             │ User JWT
             ▼
┌─────────────────────────────┐
│ Enterprise Query Engine     │
│ Per-user authorization      │
└─────────────────────────────┘

Детальний аналіз 1: Навчання проксі Lambda роботі з OAuth 2.1

Для детального огляду 1: Навчання проксі Lambda роботі з OAuth 2.1 — перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори мають мати змогу перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестовані одиниці коду замість об’ємних скриптів. Якщо крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Для кожного виклику необхідно фіксувати назву інструменту, хеш аргументів, час виконання та результат. Без цих даних налагодження проблем займає години. Для детального огляду 1: Навчання проксі Lambda роботі з OAuth 2.1 — перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори мають мати змогу перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Реєструйте час виконання та витрати на токени чи запити разом із функціональними результатами. Відображення витрат заздалегідь запобігає несподіваним рахункам під час виконання процесу.

переходить від демо-середовищ до спільних середовищ.

# GET /.well-known/protected-resource
{
  "resource": f"{BASE_URL}/mcp",
  "authorization_servers": [BASE_URL],
}
# GET /.well-known/oauth-authorization-server
{
  "issuer": BASE_URL,
  "authorization_endpoint": f"{BASE_URL}/authorize",
  "token_endpoint":         f"{BASE_URL}/token",
  "registration_endpoint":  f"{BASE_URL}/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
}

Передача PKCE без порушення його функціоналу

Щоб передавати PKCE без порушення його функціоналу, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь алгоритм. Необхідно надавати інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж вони автоматично схвалюють їх.

relay_payload = json.dumps({"u": original_redirect, "s": original_state}).encode()
relay_state   = base64.urlsafe_b64encode(relay_payload).decode().rstrip("=")
params = {
    "client_id":             OKTA_CLIENT_ID,
    "response_type":         "code",
    "redirect_uri":          f"{BASE_URL}/callback",   # <- proxy's callback
    "state":                 relay_state,               # <- client's state, wrapped
    "code_challenge":        qs["code_challenge"],      # <- untouched
    "code_challenge_method": "S256",
}

Динамічна реєстрація клієнтів

Для динамічної реєстрації клієнтів необхідно визначити параметри введення, власника кроку та критерії завершення ще до змін у коді. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Необхідно надавати інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж вони автоматично схвалюють операцію.

def handle_register(event):
    req = json.loads(event.get("body") or "{}")
    return _json_resp(201, {
        "client_id": OKTA_CLIENT_ID,
        "redirect_uris": req.get("redirect_uris", []),
        "grant_types":   req.get("grant_types", ["authorization_code"]),
        "response_types": req.get("response_types", ["code"]),
        "token_endpoint_auth_method": "none",
    })

Детальний аналіз 2: Проблеми, які виникали, коли FastMCP знаходився після AgentCore

Для глибокого аналізу 2: спостерігалося злам при розташуванні FastMCP після AgentCore; перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Краще використовувати невеликі, тестирувані одиниці замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану систему обробки даних. Запроваджуйте інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж автоматично схвалити їх. Для глибокого аналізу 2: спостерігалося злам при розташуванні FastMCP після AgentCore; перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітка відомість про витрати допомагає запобігти проблемам на ранніх етапах.

Несподівані рахунки при переході з демо-середовища у спільні середовища.

Гостра проблема №1: жодні користувацькі заголовки HTTP не потрапляють до контейнера

Щодо гострої проблеми №1: жодні користувацькі заголовки HTTP не потрапляють до контейнера, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Зберігайте конфігурацію поза кодом додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь граф. Аутентифікуйтеся біля шлюзу та знову авторизуйтесь на рівні обробки даних. Один лише токен-носій не є межею тенантства.

meta = payload.setdefault("params", {}).setdefault("_meta", {})
meta["okta_token"]   = token         # the validated user identity
meta["trino_target"] = routing_hint  # optional routing metadata
class CredentialExtractMiddleware:
    async def __call__(self, scope, receive, send):
        # buffer the whole body
        body = await _read_full_body(receive)
        # pull identity out of params._meta
        meta = json.loads(body).get("params", {}).get("_meta", {})
        token = meta.get("okta_token")        # AgentCore also injects the validated caller token as a header —
        # prefer that when present, fall back to _meta / Authorization otherwise
        token = token or _caller_token_from_headers(scope)        _req_okta_token.set(token)        # replay the buffered body so the MCP app can read it too
        await self.app(scope, _replay(body, receive), send)

Гостра проблема №2: AgentCore видаляє заголовок Accept

Для гострого краю №2: AgentCore видаляє заголовок Accept, визначає вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Необхідно задокументувати як шлях успішного виконання, так і шлях відновлення одночасно. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Аутентифікація відбувається на шлюзі, а повторна авторизація — на рівні обробки даних. Один лише токен-носій не є межею окремого тенантства.

class InjectAcceptMiddleware:
    async def __call__(self, scope, receive, send):
        if scope["type"] == "http":
            headers = [(k, v) for k, v in scope["headers"] if k != b"accept"]
            headers.append((b"accept", b"application/json, text/event-stream"))
            scope = {**scope, "headers": headers}
        await self.app(scope, receive, send)

Детальний аналіз 3: Пов’язування токенів доступу Okta з ізольованими сеансами Trino

Для розділу «Глибоке дослідження 3: Пов’язування токенів доступу Okta з ізольованими сеансами Trino» необхідно перед змінами коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестирувані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну структуру процесу. Аутентифікуйтеся біля шлюзу та знову надайте дозволи на рівні обробки даних. Один лише токен-носій не є межею окремого тенанту. Для розділу «Глибоке дослідження 3: Пов’язування токенів доступу Okta з ізольованими сеансами Trino» необхідно перед змінами коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Відомі заздалегідь витрати запобігають несподіваним рахункам.

Це відбувається, коли шлях змінюється від демо-середовищ до спільних середовищ.

def get_connection():
    token = _req_okta_token.get()
    user  = _jwt_email(token)          # e.g. "alice@company.com"
    return backend.connect(
        host=BACKEND_HOST,
        user=user,                      # identity for attribution/authorization
        auth=JWTAuthentication(token),  # the backend re-validates this itself
        http_scheme="https",
    )

Авторизація та забезпечення режиму лише для читання належать у кінцевому підсумку до рівня даних.

Щоб визначити вхідні дані, власника кроку та критерії завершення перед зміною коду, необхідно спочатку це визначити. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевіряти, не читаючи весь граф. Фіксуйте назву інструменту, хеш аргументів, затримку та результат кожного виклику. Без цих записів дебагування агента займає години.

forbidden = ("INSERT", "UPDATE", "DELETE", "DROP", "CREATE",
             "ALTER", "TRUNCATE", "MERGE", "REPLACE", "GRANT", "REVOKE")
if any(query.strip().upper().startswith(kw) for kw in forbidden):
    raise ValueError("Write operations are not allowed.")

Два способи підключення клієнта

Для двох способів підключення клієнта необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення одночасно. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Фіксуйте назву інструменту, хеш аргументів, час відгуку та результат кожного виклику. Без цих записів дебагування агента займає години.

{ "mcpServers": { "data": { "type": "http", "url": "https://<lambda-url>/mcp" } } }
{ "mcpServers": { "data": { "type": "http", "url": "http://localhost:8766/mcp" } } }

Що ми дізналися

Згідно з тим, що ми дізналися, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестовані одиниці замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Фіксуйте назву інструменту, хеш аргументів, час виконання та результат кожного виклику. Без цих даних налагодження працює без користі та забирає години часу. Згідно з тим, що ми дізналися, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Відомі заздалегідь витрати запобігають несподіваним рахункам під час переходу з демо-середовища у спільні.

Підсумок

На завершення, перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь архітектурний план. Необхідно надавати інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж вони автоматично схвалюють їх.

Чек-лист для експлуатації

Для чек-листу експлуатації також необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан.

Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви елементів, визначте критерії успіху та не допускайте мовчазного часткового виконання.

Аутентифікуйтеся біля шлюзу та повторно авторизуйтесь на рівні обробки даних. Один лише токен-носій не є межею окремого тенантства.

Створюйте контрольні точки після дорогих кроків. Система відновлення не повинна знову стягувати плату за один і той самий виклик LLM, коли оператор перезапускає пізнішу ланку.

Переносьте ідентичність від клієнта до нижчорівневого сховища, не зводячи всіх до одного облікового запису сервісу.

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

Перш ніж запускати оновлення, заморозьте версії, створіть остаточну копію даних для критичного шляху виконання та підтвердьте кроки для скасування змін. У спільних середовищах необхідні обмеження на кількість запитів, перевірки прив’язки користувачів та чіткий власник для зміни секретних даних. Віддавайте перевагу надійності перед креативними одноразовими демонстраціями.

Примітка щодо розгортання 1: фіксуйте версії зображень, встановлюйте ліміти на кількість запитів та перевіряйте ізоляцію користувачів на тестовому середовищі перед більш масштабним розгортанням.

Примітка щодо розгортання 2: фіксуйте версії зображень, встановлюйте ліміти на кількість запитів та перевіряйте ізоляцію користувачів на тестовому середовищі перед більш масштабним розгортанням.

Примітка щодо розгортання 3: фіксуйте версії зображень, встановлюйте ліміти на кількість запитів та перевіряйте ізоляцію користувачів на тестовому середовищі перед більш масштабним розгортанням.

Примітка щодо розгортання 4: фіксуйте версії зображень, встановлюйте ліміти на кількість запитів та перевіряйте ізоляцію користувачів на тестовому середовищі перед більш масштабним розгортанням.

Примітка щодо розгортання 5: фіксуйте версії зображень, встановлюйте ліміти на кількість запитів та перевіряйте ізоляцію користувачів на тестовому середовищі перед більш масштабним розгортанням.

Примітка щодо розгортання 6: фіксуйте зображення, встановлюйте ліміти запитів та перевіряйте ізоляцію користувачів у режимі canary перед більш масштабним розгортанням.