· 9 分で読めます

安全な DynamoDB MCP サーバーを構築するには何が必要だったか

を使えば、 に自分のデータベースを渡すのは造作もありません。それこそが問題です。ほとんどの DynamoDB MCP サーバーは、レビューのステップもなく書き込み経路を持ったまま、あなたの 生の AWS 認証情報 を握っている薄いプロセスにすぎません — なぜそのパターンが危険で、どう安全に選ぶかについては、ユーザー側の視点ですでに書いています。

この記事はその反対側 — 安全なバージョンを実際に構築するには何が必要だったのか、についてです。DynoTable は、そのゲートで守られた AI ツールキットを外部エージェント — Claude Code、Cursor、Codex — に公開するデスクトップ DynamoDB クライアントで、そのMCP サーバーは、エージェントが AWS 認証情報に一切触れることなくツールを利用できるようにします。そこにたどり着くには、Electron アプリの 内部 認可サーバーを出荷し、認証情報の取り違えを表現不可能にするアイデンティティ型を設計し、そして便利なデフォルトこそ間違った選択だと、一度ならず判断する必要がありました。

もしあなたが機密性の高い何かを中心に MCP サーバーを構築しているなら、これらの判断はあなたも直面することになるはずです。

ループバック HTTP は自動的に安全なわけではない

このサーバーは 127.0.0.1 にバインドします。安全そうに聞こえますが、それだけでは不十分です。あなたが訪れるどんなウェブページも localhost に向けたリクエストを試みることができ、DNS リバインディングを使えば、敵対的なページはそれらしい Host を装ってそれを仕立て上げられます。そこで最初の層はオリジンガードです — 本物のウェブページの Origin を持つリクエストは拒否し、ループバックでない Host ヘッダーも拒否する — ただし Origin がまったくないリクエストは許可する のです。なぜなら Claude Code のような正規の CLI クライアントは Origin を送らないからです。

その最後の一節が正直な部分です — オリジンガードはブラウザ攻撃のクラスをブロックできるだけです。誰かを認証することはできません。本物のゲートはベアラートークンであり、それがトークンはどこから来るのかという問いを生みます。

OAuth 2.1 認可サーバーをアプリの内部に出荷した

MCP のリモートサーバー認証の物語は OAuth 2.1 であり、ローカルのデスクトップサーバーには頼れる上流のアイデンティティプロバイダーが存在しません。そこでアプリ 自体 が認可サーバーになります — 動的クライアント登録、(S256)、認可コード、リフレッシュトークン、失効、そしてクライアントがそれらすべてを発見できるようにする RFC 8414 / RFC 9728 のメタデータドキュメントです。

早い段階で定め、守り続けたルール:

  • プロトコルを手で組まない。 MCP SDK は完全な認可サーバーの表面をルーターとして同梱しており、私たちはその下にあるストレージ/ポリシーのプロバイダーだけを実装します。受け入れた帰結が一つあります — 私たちの MCP フレームワークは内部で一つの HTTP スタックを話し、SDK のルーターは別のスタックを話すため、認可サーバーは2つ目のループバックリスナーとして動き、リソースサーバーは protected-resource メタデータ経由でクライアントをそこへ向けます。2つのリスナーは、範囲が限定された明示的なコストです。PKCE とトークンの文法を手で再導出するのは、際限のないコストです。
  • リダイレクトはループバックのみ(ネイティブアプリ向けの RFC 8252 に従う)で、無効なリダイレクトがエラーリダイレクトを受け取ることは決してありません — 直接 400 を返すので、攻撃者の URI が行き先になることは決してありません。
  • 認可コードは単回使用 — 再生されたコードは無効なグラントであり、それで終わりです。
  • リフレッシュはスコープを狭められても決して広げられず、同じアイデンティティのペイロードを一字一句そのまま再発行します — リフレッシュされたトークンがこっそり別のリージョンを手に入れることはできません。
  • 失効はすべてのリクエストで再チェックされ、接続時にキャッシュされることはありません — 設定で「失効」をクリックすれば、そのクライアントの次の呼び出しを止められます。

開発ビルドは代わりに、リリースバイナリには存在し得ないように作られた、よりシンプルな認証経路を使います — 出荷されるソフトウェアの中に、認証なしのフルスコープトークンがあれば、それは Origin なしのリクエストをあえて通すオリジンガードの背後にあるローカルなバックドアになってしまいます。

1つのコネクションに1つの AWS アイデンティティ — まるごと運ばれ、決して推測されない

核となる安全性の性質は — 外部エージェントは「DynoTable」にではなく、あるプロファイル に接続する、というものです。各コネクションは1つの AWS プロファイルと1つのリージョンにバインドされ、それぞれ専用の共有されない DynamoDB クライアントに支えられているので、異なるプロファイル上の2つのエージェントが互いに混ざり合うことはありません。

このバインディングは当初、緩いフィールドの集まりとして生まれました — awsProfileregionprofileId、そして任意のローカルポート — が、経路上のあらゆるレコードで再宣言されていました。それが今では 1つの判別可能なユニオンのアイデンティティ型 になり、2つのアームを持ちます — online(プロファイル + リージョン + id)と offline(ローカルポート + id、DynamoDB Local プロファイル向け)です。オフラインかどうかは判別子そのものなので、認証情報のないオンラインアイデンティティや、リージョンを持つオフラインアイデンティティは、実行時の驚きではなく 型エラー になります。その1つの型が、consent → トークン → グラント → あらゆるリクエストへと、そっくりそのまま渡っていきます。

MCP server (tools)User (consent modal)In-app OAuth ASClaude Code / CursorMCP server (tools)User (consent modal)In-app OAuth ASClaude Code / Cursorregister + authorize (PKCE)which profile? which scope?profile P, read-onlycode → token (identity {P, region}pinned)tool call + bearer tokenverify, re-check revocation,clamp scope to license, resolve P's credsresult (agent never sees AWS keys)

私たちが土台を支えると考えている帰結が2つあります:

  • リージョンは consent 時に凍結される。 ユーザーは「リージョン Y のプロファイル X」を承認し、そのタプルがトークンに固定されます — リフレッシュを生き延び、ホットパス上で可変な状態から再解決されることは決してありません。プロファイルのリージョンを編集するには再 consent が必要です。もう一方の道 — リクエストごとにリージョンを解決したり、デフォルトにフォールバックしたり — は、エージェントがこっそり us-east-1 にクエリを投げる羽目になる仕組みです。
  • 不正な形式のトークンは 401 であり、決して強制変換ではない。 トークンのアイデンティティのクレームが有効なアームにパースできなければ、そのリクエストは認可されません。'default' プロファイルも、デフォルトのリージョンも、空文字列もありません。古いコードなら推測していたであろうあらゆる箇所が、今ではハードな失敗になります。

最初の consent プロンプトはプラットフォームネイティブのメッセージボックスでした。それは開いているあいだ Electron のメインプロセスを凍結させ、アプリのデザインシステムに合わせられず、ドキュメント用にスクリーンショットを撮ることすらできませんでした。それが今ではアプリ内のモーダルになっています — 接続してきたクライアントの名前、ユーザーが公開することを選んだプロファイルだけを対象とする プロファイルピッカー、そして最も狭いものから順に提示される3つのスコープ — read-only、read + stage、full です。期限切れや重複した consent リクエストは、OAuth フローをハングさせるのではなく、拒否として決着します。

アプリ内の MCP consent モーダル: 接続してきたクライアント、プロファイルピッカー、そして最も狭いものから順に並ぶ3つのスコープ — read-only、read + stage、full アクセス。
アプリ内の MCP consent モーダル: 接続してきたクライアント、プロファイルピッカー、そして最も狭いものから順に並ぶ3つのスコープ — read-only、read + stage、full アクセス。

ピッカーには、それ自身のささやかな信頼境界があります — クライアントは authorize URL でプロファイルを ヒント として示せますが、グラントはユーザーが実際に選択したものから鋳造され、彼らに示されたスナップショットに照らして検証されます — 偽造されたヒントが、画面に一度も現れなかったプロファイルをバインドすることはできません。

私たちは consent が 与えないもの についても、同じくらい明確です — 認可はスコープのみで、コネクションごとに一度だけ承認されます。アプリ内アシスタントの呼び出しごとの権限プロンプトは、MCP のトラフィックには適用されません — 漏洩したフルスコープトークンは、人間が各呼び出しを目にすることなく(ステージングへ)書き込みができます。短いトークンの有効期間、クライアントごとの失効、あらゆる MCP 由来のアクションを記録する常時オンの監査証跡、そしてデフォルトの提示としての read-only が、その緩和策です。それを残余リスクとして書き留めることは、consent モーダルがそれを閉じたと装うよりも有用でした。

ヘッドレスな作業のためのガードレール

外部エージェントにはレンダラーも、確認ポップアップも、見ているユーザーもいません。インタラクティブなものはすべて、ヘッドレスな等価物を必要としました:

  • 呼び出しごとの、ライブなライセンスのクランプ。 グラントはユーザーが承認したスコープを保存します。実効的な スコープは、あらゆるツール呼び出しでアプリの現在のライセンス状態から導出されます — read-only のライセンスはフルのグラントをクランプで狭め、サインアウトの状態はどのトークンの分岐よりも前に 401 で拒否し(ゆえに開発トークンでもそれを回避できません)、セッション途中の再アクティベーションは再 consent なしで再び広げます。あえて設けた非対称性が1つあります — アプリ内エクスポートは read-only のライセンス下でも利用可能なまま(あなたのデータはあなたのものです)ですが、MCP 越しのヘッドレスな一括エクスポートはクランプで無効化されます — インタラクティブな可搬性の権利と、エージェント駆動の流出チャネルは、異なる信頼の表面だからです。
  • 確認ポップアップの代わりにスキャン予算。 アプリ内では、テーブル全体の読み取りはまず尋ねます。ヘッドレスでは、アイテム/バイト/時間の予算があらゆるスキャンを制限し、テーブルに対する SQL はメモリ内の上限ではなく、ディスクを裏付けとしたエンジンを通してストリーミングされます。
  • 切断は正直に中断させる。 クライアントがスキャンの途中で切れた場合、サーバーは次のページ境界で中断し — ステートメントの途中では決して中断せず — 一時的な状態を破棄します。
  • はワイヤー越しに完了する。 AWS 認証情報が MFA コードを必要とし、クライアントが MCP elicitation をサポートしている場合、サーバーは エージェント自身の UI を通じて 6桁のコードを引き出し — Claude Code が人間に尋ね — 再試行します。SSO はこの経路からあえて除外しています — それにはブラウザのデバイスフローが必要で、そうでないふりをすればより粗末なエラーメッセージになるだけだからです。同じ認証情報を必要とする同時の呼び出しは、1つのプロンプトにまとめられます。

そもそもデータベースを MCP 越しに公開するのは安全なのか?

私たちの正直な答えは — サーバーが、エージェントはよくて混乱しており、最悪の場合は敵対的であるという前提を中心に設計されているときに限る、というものです。はエージェントの 意図 を信頼できないものにします。だからこそこのアーキテクチャは、エージェントが行儀よくふるまうことに決して依存しません — 認証情報はアプリの中に留まり、書き込みは常にレビュー可能なステージングエリアにのみ着地し、スコープはモデルが何を求めようと呼び出しにできることをクランプし、あらゆるアクションが監査されます。接続側のガイドは、それが各クライアントにとって実際に何を意味するのかを扱っています。

あなたのデータベースに触れる MCP サーバーに要求すべきこと

  • 認証情報はどこにあるのか — エージェントのプロセスの中か、それともサーバーの背後か?
  • 本物のスコープを伴うコネクションごとの consent があるのか、それとも1つの設定ファイルがすべてを許可するのか?
  • 書き込みは人間のレビューのステップなしに実行できるのか?
  • アイデンティティ(プロファイル、リージョン)は consent 時に固定されるのか、それとも呼び出し時にデフォルトから解決されるのか?
  • 不正な形式の、あるいは失効したトークンでは何が起こるのか — ハードな失敗か、それともフォールバックか?
  • エージェント由来のアクションを区別する監査証跡はあるのか?
  • 暴走したスキャンを制限するものは何で、クライアントが操作の途中で切断したときには何が起こるのか?

サーバーがそれらに答えられないなら、その便利な統合こそが危険な統合です。制限のないスキャンが実際にどれだけのコストになるかを見積もるなら、料金計算ツールは目を覚まさせてくれます — そして、安全なバージョンについて読むより動いているのを見たいなら、DynoTable をダウンロードして、MCP サーバーをオンにし、1分もかからずに read-only スコープでクライアントを接続してみてください。

Console なしで DynamoDB を扱う

DynoTable は DynamoDB 向けの高速なデスクトップクライアントです — テーブルを閲覧し、SQL スタイルのクエリを実行し、アイテムをローカルで編集できます。