Claude Media
Claude Code plugin userConfigの書き方 — 機密値の保存先と置換の制限

Claude Code plugin userConfigの書き方 — 機密値の保存先と置換の制限

settings.jsonを手編集させずにプラグインが値を受け取る仕組みと、機密値の保存先、シェル形式hookで使えない制限をまとめます。

userConfigとは何のためのフィールドか

プラグインが外部APIのエンドポイントやトークンのようなユーザー固有の値を必要とするとき、settings.jsonを手で編集させるのは配布の障害になります。plugin.jsonuserConfigフィールドを使うと、プラグインを有効化した時点でClaude Codeが値の入力を求めるダイアログを出し、ユーザーは項目名の意味だけ理解すれば設定を終えられます。

チーム向けに配布する社内プラグインでは、APIのエンドポイントやトークンがユーザーごと・環境ごとに異なるのが普通です。userConfigを使わずに済ませようとすると、インストール手順書に「settings.jsonを開いてこのキーを追記してください」という一文が必要になります。すると、キー名の打ち間違いや設置場所の間違いが問い合わせの原因になります。userConfigを定義しておけば、この手順そのものが不要になります。プラグイン構造の基本はClaude Codeプラグイン(Plugins)完全ガイドで扱っています。plugin.json自体が初めての場合は、まずそちらで全体像をつかむと読み進めやすくなります。

最小構成を書く

userConfigはキーごとにオブジェクトを並べる形式です。キーは有効な識別子である必要があります。

{
  "userConfig": {
    "api_endpoint": {
      "type": "string",
      "title": "API endpoint",
      "description": "Your team's API endpoint"
    },
    "api_token": {
      "type": "string",
      "title": "API token",
      "description": "API authentication token",
      "sensitive": true
    }
  }
}

各オプションが持てるフィールドは次の8つです。

フィールド必須内容
type必須必須内容string / number / boolean / directory / fileのいずれか
title必須必須内容設定ダイアログに表示されるラベル
description必須必須内容項目の下に表示される説明文
sensitive必須任意内容trueなら入力をマスクし、値をsettings.jsonではなく機密ストレージに保存する
required必須任意内容trueなら未入力時にバリデーションが失敗する
default必須任意内容未入力時に使われる値
multiple必須任意内容string型で複数値の配列入力を許可する
min / max必須任意内容number型の範囲制約

型ごとの入力例

stringsensitive以外の型も組み合わせられます。次の例は、接続先ホスト(string)・リトライ回数(number、範囲制約つき)・詳細ログの有効化(boolean)・ローカルの作業ディレクトリ(directory)を1つのプラグインでまとめて定義したものです。

{
  "userConfig": {
    "host": {
      "type": "string",
      "title": "接続先ホスト",
      "description": "監視対象サービスのホスト名",
      "required": true
    },
    "retry_count": {
      "type": "number",
      "title": "リトライ回数",
      "description": "接続失敗時の再試行回数",
      "default": 3,
      "min": 0,
      "max": 10
    },
    "verbose": {
      "type": "boolean",
      "title": "詳細ログ",
      "description": "デバッグ用の詳細ログを出力する",
      "default": false
    },
    "workspace": {
      "type": "directory",
      "title": "作業ディレクトリ",
      "description": "生成物を書き出すローカルディレクトリ"
    }
  }
}

min/maxnumber型にしか効かないので、retry_countのような回数・件数系の項目に付けておくと、有効化時点で範囲外の値をはじけます。directoryfileは、ユーザーのローカル環境に依存するパスをプラグイン側で決め打ちしないための型です。${CLAUDE_PLUGIN_ROOT}のようなプラグイン内パスとは役割が異なります。フォルダごとまとめて指定したいならdirectory、証明書1本のように単一ファイルへの参照が要るならfile、と使い分けます。

値をコンポーネント側で参照する

定義した値は${user_config.KEY}という形式で、MCPサーバーやLSPサーバーの設定、hookのコマンドに埋め込んで参照できます。機密でない値はスキルやエージェントの本文にも埋め込めます。すべての値はCLAUDE_PLUGIN_OPTION_<KEY>という環境変数(キー名は大文字化)としてもhookプロセスへエクスポートされるので、置換構文が使えない箇所ではこちらを読む選択肢があります。

MCPサーバーの設定で使う例

${user_config.KEY}は、MCPサーバーやLSPサーバーの設定、hookのコマンドに埋め込んで参照できる値です。MCPサーバーのhttp/sse/ws接続では、urlheadersの各フィールドにもこの記法をそのまま使えます。トークンをsensitive: trueで定義しておけば、認証ヘッダーを平文で.mcp.jsonに書かずに済みます。

{
  "mcpServers": {
    "internal-api": {
      "url": "${user_config.api_endpoint}",
      "headers": {
        "Authorization": "Bearer ${user_config.api_token}"
      }
    }
  }
}

同じ動的ヘッダーの仕組みをheadersHelperで実現する構成もあります。ただしheadersHelperはスクリプトとして実行されるため、シェル形式のhookコマンドと同じ理由で${user_config.*}の直接埋め込みは拒否されます。ヘルパースクリプト内で値を使いたい場合は、設定ファイル経由で読み出す構成にします。MCPサーバー自体の定義方法はClaude Code MCP設定ガイドにまとめてあります。

機密値はスキル本文には渡らない

${user_config.KEY}をスキルやエージェントの本文に埋め込めるのは、非機密の値に限られます。トークンやパスワードのようなsensitive: trueの値をスキル本文で参照する経路は用意されていません。これはモデルのコンテキストにそのまま流れる場所へ機密値を混ぜないための線引きで、認証情報を扱いたいときはMCPサーバーの設定やhookのコマンド側で完結させ、スキル本文には「認証済みの前提で動く」ことだけを書くのが安全な設計です。

シェル形式のhookでは使えない

シェル経由で実行されるフィールドは${user_config.*}の置換を拒否します。設定値をシェルコマンドへそのまま埋め込むと、値の中身次第でシェルに任意のコマンドを実行させられてしまうためで、該当箇所はエラーになって止まります。v2.1.207より前はこの置換が実際に行われており、脆弱性としてふさがれた経緯があります。

拒否されるフィールドには、それぞれ代わりの値の渡し方が用意されています。

拒否されるフィールド値の渡し方
シェル形式のhookコマンド値の渡し方exec形式に切り替えてargsで渡す、またはhookの環境変数からCLAUDE_PLUGIN_OPTION_<KEY>を読む
モニターのコマンド値の渡し方スクリプト側で設定ファイルから値を読む
MCPのheadersHelper値の渡し方スクリプト側で設定ファイルから値を読む

v2.1.207より前のプラグインでこの置換に依存していたものは、上記のいずれかへの書き換えが必要です。

sensitiveな値の保存先

sensitive: trueを付けた値はsettings.jsonには書かれず、macOSならKeychain、対応するキーチェーンがないプラットフォームでは~/.claude/.credentials.jsonに保存されます。この保存先はOAuthトークンと共用で、合計容量はおよそ2KBに制限されています。複数の機密値を持つプラグインを設計するときは、この上限を踏まえてサイズを小さく保つ必要があります。

機密でない値は、ユーザーのsettings.jsonpluginConfigsキーの下にpluginConfigs[<plugin-id>].optionsとして保存されます。settings.jsonの他のキーとの関係やスコープの全体像はClaude Code settings.json完全ガイドで扱っています。

値の優先順位(どのsettings.jsonが読まれるか)

Claude CodeがpluginConfigsを読みに行くのは、次の3か所だけです。

  • ユーザー設定: ~/.claude/settings.json。有効化時のダイアログが実際に書き込む場所
  • --settings: CLIフラグまたはSDKのインライン設定
  • 管理設定: 組織が管理するポリシー

同じキーが複数の場所で設定されている場合、管理設定が最優先、次に--settings、最後にユーザー設定という順で解決されます。この3つのうち取り除けるのはユーザー設定だけで、--setting-sourcesuserを含めずに渡すとスキップされます。

一方、プロジェクトの.claude/settings.json.claude/settings.local.jsonにあるpluginConfigsのエントリは無視されます。cloneしたリポジトリ側にも値を置けてしまうと、そこから来た値がhookのコマンドやMCP/LSPサーバーの設定へ流れ込む経路になるためです。v2.1.207より前はこれらも読まれていました。この制限はpluginConfigsに限った話で、どのプラグインを有効にするかを決めるenabledPluginsは引き続きプロジェクト設定とローカル設定を尊重します。

優先順位が実際に効く場面

この制限が効いてくるのは、社外や第三者が触れるリポジトリでプラグインを使うときです。cloneしたリポジトリの.claude/settings.jsonpluginConfigsのエントリを仕込まれていても、それだけではプラグインの動作に影響しません。反映されるのはユーザー設定・--settings・管理設定の3か所に実際に値が置かれたときだけなので、リポジトリ側の設定ファイルを経由してAPIエンドポイントやトークンをすり替えるような経路は塞がれています。

Agent SDKを使う場合も同じ制限が適用されます。SDKのsettingSourcesオプションは、CLIの--setting-sourcesと同じ一覧を制御します。プログラムからClaude Codeを起動する構成でも、ユーザー設定を含めるかどうかでpluginConfigsの反映有無が決まります。

channelsのuserConfigとの違い

channelsフィールドを使うプラグインでは、チャンネルごとに独自のuserConfigを持てます。トップレベルのuserConfigと同じスキーマで、serverで指定したMCPサーバーに紐づくチャンネルの設定として個別に定義する点が違います。

{
  "channels": [
    {
      "server": "telegram",
      "userConfig": {
        "bot_token": {
          "type": "string",
          "title": "Bot token",
          "description": "Telegram bot token",
          "sensitive": true
        },
        "owner_id": {
          "type": "string",
          "title": "Owner ID",
          "description": "Your Telegram user ID"
        }
      }
    }
  ]
}

serverフィールドは必須で、プラグインのmcpServersに定義したキーと一致させます。BotトークンのようにチャンネルごとにIDや鍵が変わる構成では、トップレベルのuserConfigではなくこちらを使うと、チャンネルの追加・削除に応じて設定項目もまとまって管理できます。

defaultrequiredは別の役割

この2つは似ているようで、担う役割がまったく違います。defaultは未入力時にどの値で埋めるかを決めるだけで、有効化そのものは常に通ります。requiredは逆に、値が空のままでは有効化のバリデーションを失敗させる制約です。両方を同じフィールドに付けることもできますが、defaultを設定してある項目は未入力でもその値が使われるため、required: trueを併用してもバリデーションで止まる場面はほとんどありません。運用チームの多くが使う値をdefaultに設定しつつrequired: trueは付けない、という組み合わせにしておくと、大半のユーザーは何も変更せずに有効化でき、例外的な環境だけが値を書き換えるという設計にできます。

よくあるつまずき

  • hookのコマンドで${user_config.KEY}を書いたらエラーになった: シェル形式のフィールドでは拒否される仕様。exec形式か環境変数CLAUDE_PLUGIN_OPTION_<KEY>に切り替える
  • 機密値のはずがsettings.jsonに平文で入っている: sensitive: trueを付け忘れている
  • プロジェクトのsettings.jsonに書いた値がプラグインに反映されない: pluginConfigsはプロジェクト設定・ローカル設定からは読まれない仕様。ユーザー設定か--settings、管理設定のいずれかに書く
  • 機密値をいくつも定義したらエラーになった: Keychain/credentials.jsonの共有ストレージはおよそ2KBが上限。値を絞るか、大きなデータは設定ファイル経由に切り替える

よくある質問

userConfigを定義しないとどうなりますか

有効化時に値の入力を求められることはなく、プラグインはsettings.jsonを手編集する前提の従来通りの動作になります。

multiple: trueはどの型で使えますか

string型でのみ有効です。複数値の配列入力を許可するオプションで、numberbooleanには付けられません。

機密値をMCPサーバーの設定にそのまま埋め込めますか

MCPサーバーの設定フィールド自体には${user_config.KEY}を埋め込めます。拒否されるのはシェル形式のhookコマンドと、モニターのコマンド、MCPのheadersHelperの3か所だけです。

pluginConfigsenabledPluginsは同じ制限を受けますか

受けません。pluginConfigsはプロジェクト設定・ローカル設定から読まれませんが、プラグインの有効・無効を決めるenabledPluginsは引き続きプロジェクト設定とローカル設定を尊重します。

まとめ

userConfigは、プラグイン利用者にsettings.jsonを直接編集させずに値を受け取るための仕組みです。機密値はsensitive: trueでKeychain相当のストレージへ逃がし、シェル形式のhookコマンドでは置換が拒否されるためexec形式か環境変数で受け取ります。値の読み込み元はユーザー設定・--settings・管理設定の3つに限られ、プロジェクト側の設定ファイルからは読まれない点が設計上の要になります。

この記事を共有:XはてブLinkedIn