Skip to main content
Glama
ivantagesam

OpsBridge MCP

by ivantagesam

OpsBridge MCP

AIクライアントに、企業の顧客データとサポートチケットデータへの制御された監査可能なアクセスを提供するModel Context Protocol (MCP)サーバーです。プロンプトの指示ではなく、サーバー側で強制される承認チェックによってゲートされた、1つの実際の書き込みアクションを含みます。

これは焦点を絞った技術デモであり、製品ではありません。 1つのことをうまく示すために作られたポートフォリオ作品です:TypeScriptで正しく実装されたMCPサーバーであり、単に動くデモと、実際にLLMを向けても安全なデモを分ける特定のエンジニアリング規律を備えています — スキーマ検証、パラメータ化SQL、アプリケーションコードで強制される承認ゲート、および監査証跡。これらはすべて、推測ではなく実際のSDKと実際のプロトコルに対して検証されています。どこにもデプロイされておらず、実際の顧客はいませんし、本番準備完了を主張するものではありません — その線が正確にどこにあるかは、制限事項と本番向けに変更する点を参照してください。

これが解決する問題

AIクライアントは、質問に答えるだけでなく、実際のシステム上で実際のアクションを実行することがますます期待されています。これにより特定のエンジニアリング上の問題が生じます:モデルがライブの業務データを読み取り、重要なアクションを実行できるようにするには、どうすればよいのか。その際、(a) 無制限のデータベースアクセスを与えることなく、(b) 「モデルがこれを提案した」と「これが実際に発生した」の間に立つ唯一のものがプロンプトであると信頼することなく、実現するにはどうすればよいのか。

OpsBridgeは、1つの具体的なケース、つまりサポートチケットシステムに対する、その問題への小さく完全な答えです。AIアシスタントが必要とするデータ(顧客、チケット)と、何かを変更するための正確に1つの方法(チケットの作成)だけを公開します — そして、その1つの書き込みパスは、呼び出し元が明示的に approved: true を指定しない限り実行できません。これは、モデルが何を「決定」するかに関係なく実行されるサーバーコードでチェックされます。プロジェクト内の他のすべて — スキーマ、エラーハンドリング、監査ログ — は、その1つの保証を実際に信頼できるものにするために存在します。

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スキーマで記述された入力と出力があり、このプロジェクトのZodスキーマから自動生成されます。

  • 構造化されたリクエスト/レスポンス契約 — すべてのツール呼び出しは、このプロジェクトのコードが実行される前にスキーマに対して検証され、すべてのレスポンスは通常の結果または整形式の isError: true の結果のいずれかです — 生の例外や不正な形式の応答は決してありません。

  • トランスポート — stdio上のJSON-RPC 2.0。クライアントは 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 から何もインポートしません — better-sqlite3 データベース上で動作するプレーンなTypeScriptです。これにより、テストスイートはレイヤー境界をモックする代わりに、実際のエンドツーエンドのツール呼び出しパス(実際のMCP Client が実際の McpServer と通信する)を実行できます。正確なコードパスを含む完全な解説は:docs/architecture.md。

公開されているツール

ツール

タイプ

目的

search_customers

読み取り

名前またはメールアドレスで顧客を検索(部分一致、大文字小文字を区別しない)

get_customer

読み取り

IDで1人の顧客の詳細を取得

list_customer_tickets

読み取り

顧客のチケットを一覧表示(ステータスでフィルタリング可能)

create_support_ticket

書き込み

新しいチケットを作成 — 明示的な approved: true が必要

SQLiteに、合成された架空のデータ(10人の顧客、18件のシード済みサポートチケット)を保持しています。

技術スタック

レイヤー

選択

理由

言語

TypeScript、strictモード + noUncheckedIndexedAccess / exactOptionalPropertyTypes

このプロジェクトが注目するレイヤー境界で実際のバグを検出する(オプションフィールド、インデックス付きアクセス)

MCP SDK

@modelcontextprotocol/sdk 1.30.0

現在公開されているメジャーバージョン — 本書執筆時点でv2はありません。チュートリアルではなく、インストール済みパッケージ自身の .d.ts ファイルに対して検証済み

スキーマ検証

zod ^4

ランタイム検証とクライアントに送信されるJSONスキーマの両方のための単一の情報源

データベース

better-sqlite3 ^12(同期)

シングルプロセスのローカルサーバーに非同期ドライバー/プールの複雑さがない。^12 であり新しい 13.x ではない。なぜなら 13.x はNode 22+を必要とし、このプロジェクトはNode 20+を対象としているため

ランタイム

Node.js 20+

プロジェクトの基本要件として明記されている

テスト

vitest ^4

実際のMCP Client を InMemoryTransport 経由で実際の McpServer に接続する — テスト を参照

リンター

eslint ^10 + typescript-eslint ^8

typescript-eslint はまだTypeScript 7(新しいGoベースのコンパイラ)をサポートしていないため、TypeScriptは 5.9.x 系に固定されている — 見落としではなく、意図的な互換性の選択

開発ランナー

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
}

これを提案ではなく実際の強制メカニズムにしているのは、次の2つです:

  1. MCPツールレイヤーの下のドメインレイヤーで、SQLが実行される前に実行されます — ツールハンドラーからデータベースの INSERT に至るまで、これをスキップするコードパスはありません。

  2. approved はツールの入力スキーマで必須のブール値であり、オプションではありません。省略すると、このコードが実行される前に呼び出しはスキーマ検証に失敗します。false を渡すと、ここで拒否されます。

ツールの説明文も、モデルにまずユーザーと確認するよう求めています — しかし、それはモデルの動作に関する助言的なテキストであり、システムを安全にしているものではありません。モデルが説明を無視してツールを直接呼び出しても、この保証は成立します。最後の防衛線はプロンプトではなくサーバーです。

これが保証しないこと: 人間が実際にフラグを設定したことです — approved: true はモデルが自らの判断で提供し得る単なる別の引数にすぎず、人間がリクエストを見ることはありません。そのギャップを完全に埋めるには、サーバーが人間へのインタラクティブな確認ラウンドトリップ(MCP elicitation)を強制する必要があります。このプロジェクトは意図的にそれを追加していません。なぜなら、これはこのプロジェクトが提供を主張しない保証のための実際のインタラクションモデルの変更になるからです。制限事項 を参照してください。

セキュリティに関する考慮事項

  • 承認はプロンプトではなくアプリケーションコードで強制される — 上記を参照。

  • すべてのツール呼び出しが監査ログに記録される — stderrに出力されます(src/lib/audit.ts。4つのツールすべてをラップする withAudit() ラッパーを介してツールレイヤーで適用されます):ツール名、タイムスタンプ、成功/失敗、および機密性の低い識別子(該当する場合は customer_id)。create_support_ticket の行には、呼び出しが承認されたかどうかも記録されます。呼び出しの機密性の高い内容は決して記録されません — チケットの件名/説明、生の検索クエリテキスト、メールアドレス/電話番号/名前は記録されません。

  • すべてのSQLはパラメータ化されている — better-sqlite3 のプリペアドステートメントを使用します。文字列連結がないため、入力が最終的にLLM由来であってもSQLインジェクションの表面がありません。search_customers の LIKE パターンは %/_ もエスケープするため、検索テキストはワイルドカードとしてではなくリテラルとして一致します(そうでなければ、単に "%" というクエリで全行が返ってしまいます)。

  • 入力はZodで検証される — ビジネスロジックに到達する前に、長さの制限、priority/status に対するenum制約などにより、不正な入力はそのまま通すのではなく明確なエラーで拒否されます。

  • 保存されたチケットテキストは命令ではなくデータとして扱われる。 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 など)— ほとんどのクライアントは command/args のペアを含む JSON 設定を読み取ります:

{
  "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

テストは、実際の MCP Client を実際の McpServer に SDK の InMemoryTransport 経由で接続し、テストごとに新しいインメモリ SQLite データベース(tests/helpers.ts)をバックエンドとして使用します。これにより、実際のクライアントが通過する「リクエスト → Zod バリデーション → ツールハンドラー → レスポンス」のパスを実際に実行します。単にドメイン関数を単体でテストするだけではありません。カバレッジには以下が含まれます: 成功および空結果の検索、顧客が見つからない場合、ステータスフィルターあり/なしのチケット一覧、全ツールにわたる不正入力、approved: false と approved を完全に省略した場合の両方で拒否されるチケット作成、作成の成功、重複送信の安全性、LIKE ワイルドカードのエスケープ、プロンプトインジェクションのフレーミングテキスト、および全ツールの監査ログ内容(PII がログ行に一切現れないことを含む)。

制限事項

意図的なスコープ削減であり、フォーカスしたデモのためのもので、見落としではありません:

  • 認証、認可、ユーザーごとのデータスコープなし — セキュリティの考慮事項を参照。

  • 承認フラグは検証済みの人間のシグナルではない — モデルが自らの判断で設定できるブール値です。承認メカニズムを参照。

  • ページネーションなし — 検索は最大 10 件に制限されています。チケット一覧は無制限ですが、データセットは非常に小さいです。

  • 更新・削除ツールなし — チケットの作成のみが書き込み操作です。

  • stdio トランスポートのみ — HTTP/SSE なし、リモートデプロイのストーリーなし。

  • create_support_ticket にレート制限や冪等性キーなし — 再試行された呼び出しは重複排除されず、2 つ目の独立したチケットを作成します。

  • SQLite、シングルプロセス — コネクションプーリングなし、CREATE TABLE IF NOT EXISTS を超えるマイグレーションツールなし。

  • 監査ログはローカルの stderr ストリーム — どこにも送信されず、クエリ不可、保持ポリシーなし。

本番環境で変更する点

このパターンが合成デモデータではなく実際の顧客に向けられる場合:

  • stdio から OAuth ベアラー認証付きの Streamable HTTP に移行 — テナント/顧客ごとにスコープ設定。SDK はすでにこのトランスポートをサポートしています。現在の stdio モデルは、プロセスを起動できる人を暗黙的に信頼します。これはローカルデモには問題ありませんが、それ以外では問題があります。

  • 認証済み呼び出し元を、アクセス可能な顧客/チケットにマッピングする実際の認可を追加 — 現在、すべてのツールがスコープなしです。

  • 承認を「存在するだけ」ではなく検証可能にする — MCP のエリシテーションを使用して実際のラウンドトリップ確認を人間に強制するか、モデルの制御外にある別の確認ステップで発行された短命トークンを要求します。

  • SQLite を Postgres に置き換え、プールされた接続と実際のマイグレーションツールを使用。

  • 監査ログを永続的でクエリ可能な場所に送信(stderr ではなく)— 監査対象に適した保持とアクセス制御を備えて。

  • 書き込みパスにレート制限と冪等性キーを追加。

  • search_customers と list_customer_tickets にページネーションを追加。

  • 可観測性を追加 — ツールごとのレイテンシ、エラーレート、呼び出しボリューム。

  • すべての変更で 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