Skip to main content
Glama
bhargavlukka

SecureAgentServer

by bhargavlukka

Secure MCP-Based Agent System

このリポジトリは、サポートチケットと顧客アカウントを扱うMCPサーバーとクライアントを、エンドツーエンドで構築・保護したものです。署名付きトークン認証、プロンプトインジェクションとツールポイズニングに対する2つの独立したガードレール、文書化された脅威モデル、そして唯一の破壊的なツール呼び出しに対するヒューマン・イン・ザ・ループのゲートを備えています。'Secure an MCP-Based Agent System' ラボ用に構築されています。

アーキテクチャ

flowchart TD
    HOST["Host application"] --> CLIENT["client.py\n(fastmcp.Client + elicitation_handler)"]
    CLIENT <-->|"Streamable HTTP\nAuthorization: Bearer <signed JWT>"| SERVER

    subgraph Server["server.py — FastMCP('SecureAgentServer')"]
        AUTH["JWTVerifier (HS256)\nissuer + audience + signature checked"]
        G1["Guardrail 1: sanitize_untrusted_text()\napplied to ticket body/subject"]
        G2["Guardrail 2: verify_tool_manifest()\nchecked at startup, refuses to start on mismatch"]
        TOOLS["Tools: search_tickets, lookup_customer_account,\nclose_ticket (elicitation-gated)"]
        AUTH --> TOOLS
        TOOLS --> G1
    end
    G2 -.->|startup check| SERVER

    TOOLS --> TICKETS[("data/tickets.json\n(untrusted customer text)")]
    TOOLS --> CUSTOMERS[("data/customers.json\n('internal API')")]

Related MCP server: enterprise-agent-lab

セットアップ

pip install -r requirements.txt

# 1. Set the JWT signing secret (never commit the real value; see .env.example)
export MCP_JWT_SECRET="a-long-random-secret-at-least-32-characters"

# 2. Generate the pinned tool-integrity manifest (a deliberate, manual step —
#    see docs/threat-model.md Risk #2)
python generate_manifest.py

# 3. Run the server
python server.py

# 4. In another terminal (same MCP_JWT_SECRET exported)
python client.py --auto-confirm   # non-interactive demo
python client.py                  # interactive: real yes/no confirmation prompts

セキュリティ対策の再現

対策

確認方法

署名付きJWT認証

client.py の最後の2つのデモ: 読み取り専用スコープのトークンは close_ticket で拒否され、偽造・未署名トークンは、どのツールも実行される前に 401 で拒否されます。どちらも demo/session_log.txt に記録されています。

ガードレール1: プロンプトインジェクションのサニタイズ

クライアントを実行し、search_tickets の出力で TICKET-2002 を確認してください(その本文には"仕込まれた"IGNORE ALL PREVIOUS INSTRUCTIONS..." ペイロードが含まれています)。返されるテキストはラップされ、トリガーフレーズは伏せ字にされます。サーバー自身のstdoutには、これが発火したときに [SECURITY] 行が記録されます。

ガレートール2: ツールポイズニングの検出

python demo/verify_tampering_detection.py を実行する — メモリ上で close_ticket の説明文を改ざんし、verify_tool_manifest() が不一致を検出することを示します。実行例は demo/tampering_detection_log.txt に記録されています。

破壊的操作に対するヒューマン・イン・ザ・ループ

デモログでは close_ticket は [ELICITATION] ... -> accepting の後にのみ完了します。--auto-confirm なしで client.py を実行すると、実際に対話的な確認プロンプトが表示されます。

最小権限のスコープ

add_ticket_note/close_ticket は、接続レベルの要件だけではなく、get_access_token()(server.py::_require_scope)を介して write:tickets を明示的にチェックします。これは、上記の読み取り専用トークン拒否によって実証されています。

脅威モデル

5つの特定済みリスクと、その緩和策および残存リスクの注記を、ごまかさず明示的に書いた詳細な文書です: docs/threat-model.md。

意図的にスコープ外としていること(明示)

  • mint_token.py は、実在のOAuth 2.1アイデンティティプロバイダーの代わりをするものです。本番展開では、ローカルでのトークン発行スクリプトではなく、実際のトークン発行・ローテーション・失効のしくみが必須です。

  • レート制限やネットワーク層の堅牢化(TLS終端、WAF)は行っていません。これはアプリケーション層のセキュリティデモであり、本番向けの完全な堅牢化ガイドではありません。

  • ガードレール1のうち正規表現ベースの部分は、二次的でベストエフォートなレイヤーとして明示しています。デミリタによるラッピングが実際に依拠している制御である理由は、docs/threat-model.md のリスク番号を参照してください。

シークレットはコミットされない

MCP_JWT_SECRET は環境から取得され(.env.example 参照)、ハードコードもコミットも一切行われません。tool_manifest.json は意図的にコミットされています。これは固定されたハッシュマニフェスト(lockfile と同様)であり、シークレットではありません。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables controlled AI-agent access to enterprise-shaped tools with a deny-by-default gated write path, human approval, dry-run execution, and append-only audit logging.
    1
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enforces authenticated identity on every tool call and SSE frame, rotates vaulted credentials in place, and restricts tools via allowlists.
    71 npm
    MIT