Skip to main content
Glama
ivantagesam

OpsBridge MCP

by ivantagesam

OpsBridge MCP

Сервер Model Context Protocol (MCP), который предоставляет AI-клиенту контролируемый и аудируемый доступ к данным о клиентах и тикетах поддержки бизнеса — включая одно реальное действие записи, ограниченное проверкой одобрения на стороне сервера, а не инструкцией в промпте.

Это целенаправленная техническая демонстрация, а не продукт. Это портфолио-проект, созданный, чтобы показать одну вещь хорошо: корректно реализованный MCP-сервер на TypeScript с той инженерной дисциплиной, которая отличает демо, которое просто работает, от того, которое действительно безопасно направить на LLM — валидация схем, параметризованный SQL, шлюз одобрения, реализованный в коде приложения, и журнал аудита, всё проверено против реального SDK и реального протокола, а не предположено. Он не развёрнут нигде, у него нет реальных клиентов, и он не претендует на готовность к продакшену — см. Ограничения и Что бы я изменил для продакшена для точного указания границы.

Какую проблему это решает

AI-клиенты всё чаще ожидают, что они будут совершать реальные действия в реальных системах, а не просто отвечать на вопросы. Это создаёт конкретную инженерную проблему: как позволить модели читать живые бизнес-данные и выполнять значимое действие, не давая ей (а) неограниченный доступ к базе данных или (б) полагаясь на то, что промпт — единственное, что стоит между «модель предложила это» и «это действительно произошло»?

OpsBridge — это небольшой, полный ответ на эту проблему для одного конкретного случая: системы тикетов поддержки. Он предоставляет ровно те данные, которые нужны AI-ассистенту (клиенты, тикеты), и ровно один способ что-либо изменить (создать тикет) — и этот единственный путь записи не может быть выполнен, если вызывающий код явно не укажет approved: true, что проверяется в серверном коде, который выполняется независимо от того, что «решает» модель. Всё остальное в проекте — схемы, обработка ошибок, журнал аудита — существует для того, чтобы сделать эту единственную гарантию действительно надёжной.

Related MCP server: Customer Support MCP Server

Что MCP делает в этой архитектуре

Протокол Model Context Protocol — это слой, который позволяет AI-клиенту (Claude Code, Claude Desktop, MCP Inspector или любому другому, кто говорит на MCP) обнаруживать, что может делать этот сервер, и вызывать его без какого-либо пользовательского интеграционного кода для каждого клиента. Конкретно в этом проекте MCP отвечает за:

  • Обнаружение инструментов — сервер рекламирует search_customers, get_customer, list_customer_tickets и create_support_ticket, каждый с входными и выходными данными, описанными в JSON Schema, автоматически генерируемыми из Zod-схем этого проекта.

  • Структурированный контракт запроса/ответа — каждый вызов инструмента проверяется на соответствие его схеме до того, как код этого проекта вообще выполнится, и каждый ответ — либо обычный результат, либо корректно сформированный результат isError: true — никогда не сырое исключение или некорректный ответ.

  • Транспорт — JSON-RPC 2.0 через stdio. Клиент запускает node dist/index.js как подпроцесс и общается с ним через stdin/stdout; сетевого порта нет.

MCP не выполняет никакой реальной работы — это причина, по которой универсальный AI-клиент может использовать этот сервер вообще без специального связующего кода. Бизнес-логика, валидация и гарантии безопасности — это собственная работа этого проекта.

Архитектура

flowchart TD
    Client["Claude Code / MCP Client"]
    Protocol["MCP Protocol<br/>(JSON-RPC over stdio)"]
    Server["OpsBridge MCP Server<br/>src/server.ts · src/index.ts"]
    Tools["Tool Layer<br/>src/tools/*.ts"]
    Approval["Approval / Validation<br/>src/domain/*.ts"]
    DB[("SQLite Database<br/>src/db/*.ts")]
    Audit["Audit Log (stderr)<br/>src/lib/audit.ts"]

    Client --> Protocol --> Server --> Tools --> Approval --> DB
    Tools -.->|every call, success or failure| Audit
src/
  db/        SQLite schema, synthetic seed data, idempotent seeding
  domain/    Repository functions (customers, tickets) — plain TS, no MCP knowledge
  tools/     One file per MCP tool: Zod schema, audit-log wrapper, thin handler
  lib/       Audit logging (lib/audit.ts) and typed error classes (lib/errors.ts)
  server.ts  Builds the McpServer and registers all tools
  index.ts   Entrypoint — opens/seeds the DB, connects stdio transport

Слои продуманы и однонаправленны: каждый слой знает только о слое под ним, и domain/ не имеет импорта ничего из @modelcontextprotocol/sdk — это чистый TypeScript, работающий с базой данных better-sqlite3. Именно это позволяет тестовому набору проверять реальный сквозной путь вызова инструмента (реальный MCP Client, общающийся с реальным McpServer) вместо мокирования границ слоёв. Полное описание, включая точные пути кода: docs/architecture.md.

Предоставляемые инструменты

Инструмент

Тип

Назначение

search_customers

чтение

Поиск клиентов по имени или email (частичное, без учёта регистра)

get_customer

чтение

Получение деталей одного клиента по id

list_customer_tickets

чтение

Список тикетов клиента, опционально фильтруется по статусу

create_support_ticket

запись

Создание нового тикета — требует явного approved: true

На основе SQLite с синтетическими, вымышленными данными: 10 клиентов, 18 предзаполненных тикетов поддержки.

Технологический стек

Слой

Выбор

Почему

Язык

TypeScript, строгий режим + noUncheckedIndexedAccess / exactOptionalPropertyTypes

Ловит реальные ошибки на границах слоёв, которые важны для этого проекта (необязательные поля, индексированный доступ)

MCP SDK

@modelcontextprotocol/sdk 1.30.0

Текущая опубликованная мажорная версия — на момент написания v2 не существует; проверено по собственным .d.ts файлам установленного пакета, а не по туториалам

Валидация схем

zod ^4

Единый источник истины и для валидации во время выполнения, и для JSON Schema, отправляемой клиентам

База данных

better-sqlite3 ^12 (синхронная)

Нет сложности с асинхронным драйвером/пулом для однопроцессного локального сервера; ^12, а не более новый 13.x, потому что 13.x требует Node 22+, а этот проект нацелен на Node 20+

Среда выполнения

Node.js 20+

Заявленный базовый уровень проекта

Тесты

vitest ^4

Подключает реальный MCP Client к реальному McpServer через InMemoryTransport — см. Тестирование

Линтер

eslint ^10 + typescript-eslint ^8

typescript-eslint пока не поддерживает TypeScript 7 (новый компилятор на Go), поэтому TypeScript закреплён на ветке 5.9.x — осознанный выбор совместимости, а не упущение

Dev-раннер

tsx

Запускает src/index.ts напрямую без шага сборки во время разработки

Механизм одобрения

create_support_ticket — единственное значимое действие в системе, поэтому именно здесь проект добавляет жёсткий шлюз:

// src/domain/tickets.ts
export function createSupportTicket(db, input: CreateTicketInput): Ticket {
  if (input.approved !== true) {
    throw new ApprovalRequiredError(
      "Ticket creation was not approved. Set approved=true to confirm this action before it is created.",
    );
  }
  // ... only reaches the INSERT after this point
}

Две вещи делают это реальным механизмом принуждения, а не предложением:

  1. Он выполняется в доменном слое, ниже слоя MCP-инструментов, до выполнения любого SQL — нет пути кода от обработчика инструмента к INSERT в базу данных, который обходит его.

  2. approved — обязательное логическое значение в схеме входа инструмента, не необязательное. Если его опустить, вызов не пройдёт валидацию схемы до того, как этот код вообще выполнится; если передать false, он будет отклонён здесь.

Описание инструмента также просит модель сначала подтвердить с пользователем — но это рекомендательный текст для поведения модели, а не то, что делает систему безопасной. Гарантия сохраняется, даже если модель игнорирует описание и вызывает инструмент напрямую; сервер, а не промпт, является последней линией обороны.

Что это не гарантирует: что флаг действительно установил человек — approved: true — это просто ещё один аргумент, который модель может предоставить по собственной инициативе, и человек никогда не увидит запрос. Полное закрытие этого пробела потребовало бы, чтобы сервер принудительно выполнял интерактивный цикл подтверждения обратно к человеку (elicitation в MCP); этот проект намеренно не добавляет этого, поскольку это реальное изменение модели взаимодействия для гарантии, которую этот проект не заявляет. См. Ограничения.

Вопросы безопасности

  • Одобрение реализовано в коде приложения, а не в промпте — см. выше.

  • Каждый вызов инструмента записывается в журнал аудита в stderr (src/lib/audit.ts, применяется на уровне инструментов через обёртку withAudit() вокруг всех четырёх инструментов): имя инструмента, временная метка, успех/неудача и нечувствительный идентификатор (customer_id, где применимо); строки create_support_ticket также записывают, было ли одобрено действие. Никогда не записывается чувствительное содержимое вызова — ни темы/описания тикетов, ни сырой текст поискового запроса, ни email/телефон/имя.

  • Весь SQL параметризован через подготовленные выражения better-sqlite3 — нет конкатенации строк, поэтому нет поверхности для SQL-инъекций, даже несмотря на то, что входные данные в конечном итоге исходят от LLM. Паттерн LIKE в search_customers также экранирует %/_, чтобы поисковый текст сопоставлялся буквально, а не как подстановочный знак (иначе запрос просто "%" вернул бы все строки).

  • Входные данные проверяются с помощью Zod до того, как они достигнут любой бизнес-логики — ограничения длины, ограничения перечислений для priority/status — отклоняя некорректный ввод с понятной ошибкой вместо передачи его дальше.

  • Сохранённый текст тикета рассматривается как данные, а не как инструкции. subject/description — это свободный текст, и тикет, созданный сейчас, будет прочитан дословно последующим вызовом list_customer_tickets — это вектор инъекции второго порядка. Текст ответа явно отмечает, что это содержимое является сохранённым пользовательским вводом, а не директивами. Это смягчение, а не гарантия.

  • Нет аутентификации или авторизации. Это локальное демо для одного пользователя — любой, кто может запустить процесс, имеет полный доступ ко всем инструментам, включая полные PII клиентов. Явно вне области действия здесь; это должно измениться, прежде чем этот паттерн коснётся реальных многопользовательских данных.

  • В проекте нет секретов. Никаких API-ключей, токенов или учётных данных; единственная внешняя зависимость — локальный файл SQLite, который находится в .gitignore.

Примеры взаимодействия с Claude

Промпты для чтения, после подключения:

  • «Найди клиента по имени Chen.»

  • «Получи полные данные для клиента cust_004.»

  • «Какие открытые тикеты у cust_005?»

Интересный случай — путь записи:

Вы: «Создай приоритетный тикет поддержки для cust_002 о том, что их трекинг-номера не синхронизируются — но сначала спроси меня, прежде чем реально создавать его.»

Ожидаемое поведение: модель вызывает search_customers/get_customer по мере необходимости, затем либо просит вас подтвердить перед вызовом create_support_ticket, либо вызывает его один раз с approved false/опущенным, получает отказ и возвращает вам предложенный тикет. В любом случае ничего не записывается, пока вы действительно не согласитесь и модель не вызовет его снова с approved: true.

Более подробные сценарии, включая принудительное отклонение напрямую, чтобы увидеть исходное сообщение о принуждении: docs/demo-script.md.

Локальная настройка

Требуется Node.js 20+.

npm install
npm run db:seed     # creates and seeds data/opsbridge.db (10 customers, 18 tickets)
npm run build        # compiles TypeScript to dist/
npm run dev           # runs src/index.ts directly with tsx (auto-seeds on first run)
# or, after `npm run build`:
npm start              # runs dist/index.js

Сервер общается через stdio — нет HTTP-порта, нечего открывать в браузере напрямую.

Подключение к Claude Code: этот репозиторий содержит файл .mcp.json в скоупе проекта (сгенерирован через claude mcp add opsbridge --scope project -- node dist/index.js — то есть ровно то, что создаёт сам CLI, а не написанное вручную). Сначала выполните сборку, затем один раз одобрите его:

npm run build
claude          # prompts to trust this project's .mcp.json server on first run — approve it
claude mcp list # should show: opsbridge: node dist/index.js - ✔ Connected

Подключение любого другого MCP-клиента (Claude Desktop и т. п.) — большинство читают JSON-конфиг с парой command/args:

{
  "mcpServers": {
    "opsbridge": {
      "command": "node",
      "args": ["/absolute/path/to/opsbridge-mcp/dist/index.js"]
    }
  }
}

Ручная проверка без полноценного клиента — MCP Inspector с намеренно зафиксированной версией (без указания версии npx @modelcontextprotocol/inspector может срезолвиться в устаревшую кэшированную сборку вместо текущего релиза):

npx @modelcontextprotocol/inspector@2.3.0 node dist/index.js       # web UI
npx @modelcontextprotocol/inspector@2.3.0 --cli node dist/index.js -- --method tools/list   # headless

Тестирование

npm test        # vitest — 33 tests across 6 files
npm run typecheck
npm run lint

Тесты соединяют реальный Client MCP с реальным McpServer через InMemoryTransport из SDK и используют свежую in-memory базу данных SQLite для каждого теста (tests/helpers.ts) — проверяется реальный путь, который проходит настоящий клиент: запрос → валидация Zod → обработчик инструмента → ответ, а не только доменные функции по отдельности. Покрытие включает: успешный и пустой поиск, клиент не найден, список тикетов с фильтром по статусу и без него, некорректный ввод для каждого инструмента, отклонение создания тикета и при approved: false, и при полностью отсутствующем approved, успешное создание, защиту от повторной отправки, экранирование LIKE-подстановочных символов, попытку промпт-инъекции текстом и содержимое журнала аудита (включая то, что PII никогда не появляется в строке лога) для каждого инструмента.

Ограничения

Намеренные сокращения области для сфокусированного демо, а не упущения:

  • Нет аутентификации, авторизации или разграничения данных по пользователю — см. Security considerations.

  • Флаг одобрения — не подтверждённый человеческий сигнал — это булево значение, которое модель может выставить по своей инициативе; см. Approval mechanism.

  • Нет пагинации — поиск ограничен 10 результатами; списки тикетов ничем не ограничены, но набор данных крошечный.

  • Нет инструментов обновления и удаления — запись выполняется только при создании тикета.

  • Только stdio-транспорт — нет HTTP/SSE, нет сценария удалённого развёртывания.

  • Нет ограничения частоты запросов и ключа идемпотентности у create_support_ticket — повторный вызов создаёт второй независимый тикет, а не отбрасывается как дубликат.

  • SQLite, один процесс — нет пула соединений; из инструментов миграции только CREATE TABLE IF NOT EXISTS.

  • Журнал аудита — локальный поток stderr — никуда не отправляется, его нельзя запрашивать, нет политики хранения.

Что я бы изменил для продакшена

Если бы этот паттерн был нацелен на реальных клиентов, а не на синтетические демо-данные:

  • Уйти со stdio на Streamable HTTP с OAuth bearer-аутентификацией, разграниченной по тенанту/клиенту — SDK уже поддерживает этот транспорт; текущая stdio-модель неявно доверяет любому, кто может запустить процесс, что нормально только для локального демо и ни для чего больше.

  • Добавить настоящую авторизацию — сопоставлять аутентифицированного вызывающего с тем, к каким клиентам и тикетам он может обращаться; сейчас каждый инструмент не имеет никакого разграничения.

  • Сделать одобрение проверяемым, а не просто формально присутствующим — использовать MCP elicitation, чтобы потребовать реального обратного подтверждения от человека, либо требовать короткоживущий токен, выпущенный отдельным шагом подтверждения вне контроля модели.

  • Заменить SQLite на Postgres с пулом соединений и настоящим инструментом миграций.

  • Отправлять журнал аудита в долговечное и доступное для запросов хранилище (не в stderr) с политикой хранения и контроля доступа, подходящей тому, что в нём аудитируется.

  • Добавить rate limiting и ключ идемпотентности на путь записи.

  • Добавить пагинацию в search_customers и list_customer_tickets.

  • Добавить наблюдаемость — задержку, частоту ошибок и объём вызовов по каждому инструменту.

  • Запускать typecheck/тесты/линтер в CI при каждом изменении, а не только локально по требованию.

Ничего из этого здесь не реализовано — цель проекта в том, чтобы корректно продемонстрировать паттерн в малом масштабе, а не заранее собирать инфраструктуру, которая нужна реальному развёртыванию, но не нужна демо.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    A secure MCP server that exposes a SQLite database to AI agents with Role-Based Access Control, supporting authentication, customer/order/user management, and audit logging.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables interaction with a local SQLite-backed issue tracker, offering full CRUD operations (search, fetch, summarize, create, comment, close/reopen) with team-scoped visibility, authorization, rate limiting, and audit logging.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to safely work with SQLite databases by enforcing read/write separation, dry-run writes with confirmation, automatic backups, and an audit trail.
    MIT