Skip to content

MCP管理Runbook

/admin/mcp は、MCPのサービスアカウント対応付け、クライアントToken、policy、接続診断、監査証跡を扱う管理者専用control planeです。PR #150PR #152 で実装された運用境界を説明します。今回の実runtimeとVisual QAの詳細は Issue #151の証跡index に固定しています。

セキュリティ境界

  • ブラウザは/api/admin/mcp/*を呼びます。frontend BFFはリクエストごとにForgejo管理者権限を確認してから、制限されたリクエストだけを内部のmcp-adminへ転送します。
  • /api/admin/mcp/*のadmin BFF API requestは、未ログインなら401、認証済みでもForgejo管理者でなければ403です。/admin/mcp page自体は未認証ならloginへredirectし、非管理者または安全でないtransportなら404を返します。mcp-adminはhost portを公開せず、Compose networkからだけ到達できます。
  • 再認証formは現在のForgejo passwordをForgejoへ直接照合します。passwordは保存、admin serviceへの転送、log出力をせず、formを消去した後も保持しません。発行される5分間のHttpOnly proofはbrowser sessionと管理者subjectに束縛されます。
  • BFFからadminへの内部credentialはmcp-adminだけがDocker secretとして読みます。mcp-adminはmode 0440でprivateなopenface-mcp-admin-bridge volumeへcopyし、frontendは/run/mcp-admin-bridge/tokenをread-onlyで読みます。raw Docker secretをfrontendへmountせず、環境変数やbrowser valueにも置きません。
  • サービスアカウントのcredential参照名はOPENFACE_MCP_FORGEJO_TOKEN_ALLOWLISTにあるものだけを許可します。参照先はOPENFACE_MCP_FORGEJO_TOKEN_ROOT直下のreadableなregular fileかつnon-symlinkでなければなりません。内部admin credentialをallowlistへ追加しないでください。
  • client Tokenの平文は、認証済みBFFへのissue/rotate成功responseだけで返され、一度だけ表示されます。その後のstate、list、revoke、audit、connection-test responseは平文を返しません。dialogを閉じるか破棄した後、UIは平文を保持せず、registry、log、auditにも復元可能な平文を残しません。

MCP profileを起動する

secretとMCP stateはrepositoryの外で作成します。Composeの./secrets/... defaultはlocal development用で、repository外のsecret境界にはなりません。Compose起動前に次の3つのpathをabsolute pathへ設定します。Windows PowerShellの例は内部credentialだけを生成するため、Forgejo token fileはdeploymentのsecret管理手順で投入してください。

powershell
$secretRoot = Join-Path $env:ProgramData 'OpenFace\secrets'
$stateRoot = Join-Path $env:ProgramData 'OpenFace\mcp-state'
New-Item -ItemType Directory -Force $secretRoot, $stateRoot | Out-Null
$hostIdentities = @([Security.Principal.WindowsIdentity]::GetCurrent().Name)
# Docker Desktopが別のhost identityを使う場合だけ、そのidentityを明示的に追加します。
# $hostIdentities += 'CONTOSO\openface-docker'
$aclGrants = @($hostIdentities | ForEach-Object { '{0}:(OI)(CI)(F)' -f $_ })
foreach ($path in @($secretRoot, $stateRoot)) {
  icacls.exe $path /reset /T /C | Out-Null
  if ($LASTEXITCODE -ne 0) { throw "Failed to reset existing ACLs under $path" }
  icacls.exe $path /inheritance:r /T /C | Out-Null
  if ($LASTEXITCODE -ne 0) { throw "Failed to remove inherited ACLs from $path" }
  icacls.exe $path /grant:r $aclGrants /T /C | Out-Null
  if ($LASTEXITCODE -ne 0) { throw "Failed to protect ACLs on $path" }
}
$internalTokenPath = Join-Path $secretRoot 'openface-mcp-admin-internal-token'
$forgejoTokenPath = Join-Path $secretRoot 'openface-mcp-forgejo-user-token'
$bytes = New-Object byte[] 48
$rng = [Security.Cryptography.RandomNumberGenerator]::Create()
try { $rng.GetBytes($bytes) } finally { $rng.Dispose() }
[Convert]::ToBase64String($bytes) | Set-Content -NoNewline -LiteralPath $internalTokenPath
$env:OPENFACE_MCP_ADMIN_INTERNAL_TOKEN_FILE = $internalTokenPath
$env:OPENFACE_MCP_FORGEJO_USER_TOKEN_FILE = $forgejoTokenPath
$env:OPENFACE_MCP_STATE_DIR = $stateRoot

bounded serviceを起動またはrebuildし、secretの中身を表示せずにhealthを確認します。

bash
docker compose --profile mcp up -d --build frontend gateway openface-mcp mcp-admin
docker compose --profile mcp ps

8001を公開portにせず、BFFを迂回せず、docker-compose.yml.env、screenshot、Issue comment、Gitへcommitするclient設定にsecret値を置かないでください。

操作手順

  1. Forgejo管理者で/admin/mcpを開き、再認証を完了します。proofがない、期限切れ、改変、別session、別subjectに束縛されている場合はfail closedで拒否されなければなりません。
  2. サービスアカウント対応付けを追加または選択します。Forgejo user、allowlist済みsecret参照名、必要最小限のscope、明示したrepository権限を選びます。
  3. 対応付けのscopeとrepositoryのsubsetだけを持つclient Tokenを発行します。TTLは実運用で必要な最短値にします。
  4. 一度きりのdialogを開いたまま接続を確認を実行し、initializetools/listresources/listを確認します。到達性、HTTP/認証失敗、JSON-RPC失敗、利用可能なtool/resource数を区別し、Tokenや上流error本文をechoしません。
  5. Tokenは保護されたclient secret storeへコピーしたらdialogを閉じるか破棄します。平文をticket、shell history、screenshot、browser bookmark、source fileへ置かないでください。
  6. 対応付けを無効化または再マッピングしたときは、以前のmapping versionに紐付くTokenが失効したことを確認します。Tokenのrotateでは前のTokenが失効します。

安全なclient snippet

以下はplaceholderだけを含む例です。<OPENFACE_HOST><TOKEN_FILE>はローカルで置換し、実Tokenをcommitしないでください。

Codex CLI

powershell
$env:OPENFACE_MCP_TOKEN_FILE = '<TOKEN_FILE>'
$env:OPENFACE_MCP_TOKEN = (Get-Content -LiteralPath $env:OPENFACE_MCP_TOKEN_FILE -Raw).Trim()
codex mcp add openface --url https://<OPENFACE_HOST>/mcp --bearer-token-env-var OPENFACE_MCP_TOKEN

Claude Desktop

json
{
  "mcpServers": {
    "openface": {
      "command": "openface-mcp-stdio",
      "env": {
        "OPENFACE_MCP_REMOTE_URL": "https://<OPENFACE_HOST>/mcp",
        "OPENFACE_MCP_CLIENT_TOKEN_FILE": "<TOKEN_FILE>"
      }
    }
  }
}

VS Code

json
{
  "servers": {
    "openface": {
      "type": "http",
      "url": "https://<OPENFACE_HOST>/mcp",
      "headers": { "Authorization": "Bearer ${input:openface-token}" }
    }
  },
  "inputs": [
    {
      "id": "openface-token",
      "type": "promptString",
      "description": "OpenFace MCP token",
      "password": true
    }
  ]
}

Policyと監査

Policy更新には画面に表示されたrevisionを含めます。同時更新があればconflictになるため、再読み込みして新しいrevisionを確認してから再送します。既定はdenyで、明示的なallowだけが適用され、read-only ruleは一致するwriteを拒否します。

監査は実際のoutcome(alloweddeniedfailedreplayedchanged)、subject、client、tool、期間、bounded cursorで絞り込めます。summaryは現在ページだけでなくfilterに一致する全recordを数えます。展開recordは承認済みfieldだけを返し、Token平文、Token digest、Forgejo PAT path、idempotency fingerprint、audit-chain hashは返しません。

障害対応Runbook

  • Token紛失: 直ちに失効し、最小scopeで再発行して、clientの保護されたsecret storeを更新します。
  • サービスアカウント侵害の疑い: 先に対応付けを無効化し、Forgejo credential secretをrotateし、再対応付けしてから新しいclient Tokenを発行します。
  • Policy conflict: 再読み込みしてから再実行します。未確認のrevisionを上書きしません。
  • Admin backend停止: mcp-adminのhealth、Docker secret mount、lifecycle/policy/audit volumeの権限を確認します。BFFを迂回する公開portは追加しません。
  • Backupとrestore: 設定したOPENFACE_MCP_STATE_DIRのToken registryとlifecycle auditに加え、openface-mcp-state volume全体を一貫したSQLite snapshotとしてbackupします。snapshotにはpolicy/audit database、/data/write-safety.sqlite3、隣接する.hmac-keyを必ず含めます。これらはidempotencyとoperation-reconciliation historyを保持するため、一部だけrestoreすると、以前に上流で成功したか不明なwriteを再実行する可能性があります。restore後は管理者/非管理者のaccess、短命で制限されたTokenの発行、接続確認、失効、audit outcomeを確認します。

Release QAの境界

base、solarpunk、cyberpunk themeのdesktop/mobile幅を実runtimeで手動確認します。tabのwrap、一度きりのsecret処理、contrast、control、footer位置、horizontal overflowを確認します。screenshot生成は意図的にCI gateにしません。今回のmerged mainに対するマスク済み証跡は Issue #151 にあります。

Released under the MIT License. Third-party components retain their own licenses.