OpsBridge MCP
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| Auditsrc/
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。
公開されているツール
ツール | タイプ | 目的 |
| 読み取り | 名前またはメールアドレスで顧客を検索(部分一致、大文字小文字を区別しない) |
| 読み取り | IDで1人の顧客の詳細を取得 |
| 読み取り | 顧客のチケットを一覧表示(ステータスでフィルタリング可能) |
| 書き込み | 新しいチケットを作成 — 明示的な |
SQLiteに、合成された架空のデータ(10人の顧客、18件のシード済みサポートチケット)を保持しています。
技術スタック
レイヤー | 選択 | 理由 |
言語 | TypeScript、strictモード + | このプロジェクトが注目するレイヤー境界で実際のバグを検出する(オプションフィールド、インデックス付きアクセス) |
MCP SDK |
| 現在公開されているメジャーバージョン — 本書執筆時点でv2はありません。チュートリアルではなく、インストール済みパッケージ自身の |
スキーマ検証 |
| ランタイム検証とクライアントに送信されるJSONスキーマの両方のための単一の情報源 |
データベース |
| シングルプロセスのローカルサーバーに非同期ドライバー/プールの複雑さがない。 |
ランタイム | Node.js 20+ | プロジェクトの基本要件として明記されている |
テスト |
| 実際のMCP |
リンター |
|
|
開発ランナー |
| 開発中にビルドステップなしで |
承認メカニズム
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つです:
MCPツールレイヤーの下のドメインレイヤーで、SQLが実行される前に実行されます — ツールハンドラーからデータベースの
INSERTに至るまで、これをスキップするコードパスはありません。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 で型チェック/テスト/リントを実行 — オンデマンドのローカル実行だけでなく。
これらはここでは実装されていません — このプロジェクトの目的は、実際のデプロイに必要なインフラを事前に構築することではなく、パターンを小規模で正しくデモンストレーションすることです。
This server cannot be deployed
Maintenance
Related MCP Connectors
Runtime permission, approval, and audit layer for AI agent tool execution.
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Guard AI agents' PostgreSQL/MySQL access via MCP: SQL audit, auth, masking, write approval
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceA 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.-
- FlicenseAqualityCmaintenanceEnables AI assistants like Cursor to manage customer support tickets in SQLite through MCP tools, supporting creation, retrieval, search, and updates via natural language.4-
- AlicenseNot gradedqualityCmaintenanceEnables 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
- AlicenseNot gradedqualityCmaintenanceEnables 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