CLAUDE LABEN
SANDBOX — Claude Managed Agentsが自前のサンドボックスとプライベートMCPサーバー接続に対応(自己ホスト型ベータ・MCPトンネルはプレビュー)PLATFORM — Claude Developer Platformにcode execution・web search・web fetchのツール新版。長時間コードのセル90秒上限を明示しますCONTEXT — response_inclusionで消費済みの結果ブロックを切り詰め、エージェント処理のコンテキストを節約できますMCP — エンタープライズ管理型MCPコネクタ(Okta連携)が継続。Claude・Claude Code・Cowork横断でゼロタッチ利用できます(Team/Enterpriseベータ)CODE — Claude Codeに/cd・ポストセッションフック・安全モードが加わり、MCPポリシー強制が厳格化されましたMODEL — 主力はOpus 4.8・Sonnet 4.6・Haiku 4.5。Fable 5はClaude Codeから利用できますSANDBOX — Claude Managed Agentsが自前のサンドボックスとプライベートMCPサーバー接続に対応(自己ホスト型ベータ・MCPトンネルはプレビュー)PLATFORM — Claude Developer Platformにcode execution・web search・web fetchのツール新版。長時間コードのセル90秒上限を明示しますCONTEXT — response_inclusionで消費済みの結果ブロックを切り詰め、エージェント処理のコンテキストを節約できますMCP — エンタープライズ管理型MCPコネクタ(Okta連携)が継続。Claude・Claude Code・Cowork横断でゼロタッチ利用できます(Team/Enterpriseベータ)CODE — Claude Codeに/cd・ポストセッションフック・安全モードが加わり、MCPポリシー強制が厳格化されましたMODEL — 主力はOpus 4.8・Sonnet 4.6・Haiku 4.5。Fable 5はClaude Codeから利用できます
記事一覧/API & SDK
API & SDK/2026-06-21上級

公開したくない社内サービスに Managed Agents を繋ぐ — MCP トンネル設計の勘所

Claude Managed Agents から、インターネットに公開していない社内 MCP サーバーへ安全に接続する設計を扱います。MCP トンネル・自前サンドボックス・認可境界・障害時の縮退まで、実運用で必要な判断を整理しました。

Claude38MCP33Managed Agentsセキュリティ5API22

社内の在庫データベースに問い合わせるエージェントを作ろうとして、最初に手が止まるのは「その DB を外に出したくない」という一点だと思います。Managed Agents は便利ですが、ツールの呼び先が公開エンドポイントである限り、社内サービスはどうしても VPN 越しか踏み台越しになり、運用が重くなります。

2026 年 6 月の更新で、Managed Agents が自前のサンドボックスとプライベート MCP サーバーへ接続できるようになりました。自己ホスト型サンドボックスは Claude Platform でパブリックベータ、社内 MCP サーバーへ繋ぐ MCP トンネルはリサーチプレビューという段階です。公開したくないサービスをエージェントから扱うための接続設計を、認可と障害処理という実際に詰まりやすい点まで含めて具体的に整理していきます。

「公開しない」をどこで担保するか

まず誤解しやすい点を一つ整理します。MCP トンネルは「社内サーバーをインターネットに公開する仕組み」ではありません。発想は逆で、社内側からアウトバウンド接続を一本張り、その経路だけを通してエージェントのツール呼び出しを受け取ります。インバウンドのポートを開けずに済むのが要点です。

境界を整理すると次のようになります。

レイヤー公開範囲守るもの
サンドボックス自己ホスト(自社管理)コード実行の隔離・流出経路の限定
MCP トンネルアウトバウンドのみ社内サーバーの非公開性
MCP サーバートンネル経由のみ到達可ツールの認可・スコープ
バックエンド(DB 等)MCP サーバーからのみ実データへのアクセス制御

私自身、Dolice Labs の運用で個人開発のアプリ売上を集計するときも同じ考え方を採っています。集計用の内部 API は決して公開せず、手元のジョブからアウトバウンドで叩く。エージェントに渡すのは「公開エンドポイント」ではなく「アウトバウンド経路を持つ仲介役」だ、と割り切ると設計がぶれません。

最小構成のプライベート MCP サーバー

まず、社内側に置く MCP サーバーを用意します。ここでは在庫照会という一つのツールだけを公開する例にします。トンネルは「このサーバーへ外から到達させる」のではなく、「このサーバーが外へ繋ぎにいく」ことを思い出してください。

# inventory_mcp.py — 社内ネットワーク内でのみ動かす MCP サーバー
from mcp.server.fastmcp import FastMCP
import os
import asyncpg
 
mcp = FastMCP("inventory")
 
# 接続先はすべて社内アドレス。公開 IP を一切持たない
DB_DSN = os.environ["INTERNAL_DB_DSN"]  # 例: postgres://app@10.0.3.12:5432/inventory
 
@mcp.tool()
async def check_stock(sku: str) -> dict:
    """指定 SKU の在庫数と次回入荷予定を返す。"""
    if not sku.isalnum():
        # ツール側で入力を必ず検証する。エージェントの出力を信用しない
        raise ValueError("sku は英数字のみ受け付けます")
 
    conn = await asyncpg.connect(DB_DSN)
    try:
        row = await conn.fetchrow(
            "SELECT quantity, restock_date FROM stock WHERE sku = $1",
            sku,
        )
    finally:
        await conn.close()
 
    if row is None:
        return {"sku": sku, "found": False}
    return {
        "sku": sku,
        "found": True,
        "quantity": row["quantity"],
        "restock_date": row["restock_date"].isoformat() if row["restock_date"] else None,
    }
 
if __name__ == "__main__":
    mcp.run()

ここで意識的にやっているのは、check_stock の入力検証をツール側に置くことです。エージェントが生成する引数は、外部からの入力と同じ警戒度で扱うべきだと考えています。SKU を SQL に直接渡さずプレースホルダを使うのも同じ理由です。MCP サーバーは「エージェントの便利な手足」ではなく「最後の関所」だと位置づけると、権限設計が引き締まります。

トンネルを張り、エージェントから到達させる

次に、このサーバーへエージェントが届くようにします。リサーチプレビューの MCP トンネルでは、社内側でトンネルクライアントを起動し、アウトバウンドで Claude Platform に接続します。サーバーには到達用の識別子(トンネル ID)が割り当てられ、エージェント側はそれを MCP コネクタとして参照します。

# 社内ホストで実行。インバウンドのポートは一切開けない
export CLAUDE_TUNNEL_TOKEN="YOUR_TUNNEL_TOKEN"
claude-tunnel connect \
  --target stdio:./inventory_mcp.py \
  --name internal-inventory
# → 割り当てられた tunnel id (例: tnl_internal_inventory) が表示される

エージェント側では、この識別子を MCP サーバー定義として渡します。重要なのは、エージェントの設定に DB 接続情報や社内 IP を一切書かないことです。エージェントが知ってよいのは「internal-inventory というツール群がある」ことだけにとどめます。

# agent_config.py — エージェント側。社内の詳細は何も持たない
agent = client.beta.agents.create(
    model="claude-opus-4-8",
    name="inventory-assistant",
    instructions=(
        "在庫の問い合わせには check_stock ツールを使う。"
        "ツールが found=false を返した場合は、推測で在庫数を答えない。"
    ),
    mcp_servers=[
        {
            "type": "tunnel",
            "tunnel_id": "tnl_internal_inventory",
            "tool_allowlist": ["check_stock"],  # 使えるツールを明示的に絞る
        }
    ],
    sandbox={"type": "self_hosted", "id": "sbx_team_default"},
)

tool_allowlist を必ず指定することをお勧めします。MCP サーバーが将来別のツールを生やしたときに、エージェントが意図せずそれを使ってしまう事故を防げます。許可リストは「足し算」で管理し、デフォルトでは何も使えない状態から始めるのが安全です。

認可は二段で考える

実運用でいちばん詰まるのは認可です。ここを「トンネルが繋がっているから大丈夫」と一段で済ませると、後で必ず破綻します。私の経験では、次の二段に分けて考えると整理しやすいです。

一段目は「誰がこのエージェントを呼べるか」。これはエージェント呼び出しの入口で、API キーやユーザーセッションで制御します。二段目は「そのツール呼び出しが、どのデータ範囲に触れてよいか」。これは MCP サーバー側で、呼び出しコンテキストに紐づくスコープを見て判断します。

# inventory_mcp.py に認可を足す
from mcp.server.fastmcp import Context
 
@mcp.tool()
async def check_stock(sku: str, ctx: Context) -> dict:
    # トンネル経由で渡るメタデータからテナントを取り出す
    tenant = ctx.request_context.meta.get("tenant")
    if tenant is None:
        raise PermissionError("テナント情報のない呼び出しは拒否します")
 
    conn = await asyncpg.connect(DB_DSN)
    try:
        row = await conn.fetchrow(
            "SELECT quantity, restock_date FROM stock "
            "WHERE sku = $1 AND tenant_id = $2",  # テナント境界を SQL で強制
            sku, tenant,
        )
    finally:
        await conn.close()
    # 以降は前述と同じ

ポイントは、テナント境界を「エージェントへの指示」ではなく「SQL の WHERE 句」で強制することです。プロンプトでの注意書きは、守られることもあれば守られないこともある程度に考えています。データの境界は、プロンプトより下の層で物理的に閉じるのが堅実です。

トンネルが切れたときに何が起きるか

リサーチプレビュー段階のトンネルは、ネットワークの揺らぎで切れることがあります。ここで縮退の挙動を決めておかないと、エージェントが「在庫を確認できなかった」のか「在庫がゼロだった」のかを取り違え、誤った回答を返します。

対策は二つです。一つは、ツール失敗を「不明」として明示的に扱い、推測を禁じること。もう一つは、トンネルクライアント側で自動再接続と健全性チェックを持つことです。

# トンネルクライアントの薄いラッパ。指数バックオフで再接続する
import asyncio
 
async def run_tunnel_with_retry():
    backoff = 1
    while True:
        try:
            await start_tunnel(target="stdio:./inventory_mcp.py",
                               name="internal-inventory")
            backoff = 1  # 接続成功でリセット
        except TunnelDisconnected as e:
            # 切断は異常ではなく想定内のイベントとして扱う
            wait = min(backoff, 30)
            log.warning("tunnel disconnected: %s%ds 後に再接続", e, wait)
            await asyncio.sleep(wait)
            backoff *= 2

エージェント側の指示で「ツールが失敗したら在庫数を推測しない」と明記しているのは、まさにこの切断を想定しています。再接続中の数十秒間に、エージェントが空気を読んで適当な数字を出すのが最悪のパターンです。沈黙して「いま確認できません」と言える状態を、設計で用意しておく必要があります。

自前サンドボックスと組み合わせる意味

最後に、自己ホスト型サンドボックスを併用する意味に触れておきます。トンネルが「社内サーバーへの経路」を閉じるのに対し、自前サンドボックスは「エージェントが実行するコードの隔離先」を自社管理下に置きます。両者は守る対象が違います。

たとえば在庫データを取得したあと、エージェントがそれを集計するコードを書いて実行する場面を考えます。そのコード実行を共有環境ではなく自社管理のサンドボックスで行えば、取得した社内データが実行環境ごと自社の管理境界に収まります。トンネルでデータの「入口」を閉じ、サンドボックスでデータの「処理場所」を閉じる、という二重の閉じ方になります。

私が個人開発で扱うデータは決して大規模ではありませんが、それでも「どこでコードが動いて、どこへデータが出ていくのか」を自分で説明できる状態を保つことを大切にしています。便利さと引き換えに経路が見えなくなるのは、長く運用するうえでいちばん避けたいことだからです。

接続できることと、安全に接続することの間には、認可・縮退・隔離という設計の層があります。まずは在庫照会のような小さなツール一つで、トンネル・許可リスト・テナント境界・再接続のひと通りを自分の手で組んでみることをお勧めします。動く最小構成を一度持っておくと、対象サービスが増えても同じ型で広げていけます。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API & SDK2026-05-30
Claude の品質回帰を eval ハーネスで自動検出する設計
プロンプトを少し直したら、別の入力で出力が静かに悪化していた。本番投入した Claude の品質をプロンプト変更やモデル更新のたびに守るための eval ハーネスを、実装コードと運用での実測値とともに整理しました。
API & SDK2026-04-26
Claude API の stop_reason を読み解く — 応答の途切れを「終了」と誤認しないための設計
Claude API のレスポンスに含まれる stop_reason を正確に分岐するだけで、未完了出力の取りこぼしや無駄なリトライが大きく減ります。end_turn・max_tokens・pause_turn・refusal の見分け方と実装パターンをまとめました。
API & SDK2026-04-23
Claude API プロンプトインジェクション防御の設計パターン — 検出・サニタイゼーション・多層防御
ユーザー入力や外部データが混入する本番LLMアプリで必ず必要になる、プロンプトインジェクション防御の設計パターンを、実装コードと運用面の注意点を交えて体系的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →