接続の準備はほぼできています。エージェントは設定ファイルを組み立て、サービス名を入れて、残り1つのフィールド、API keyのところで止まりました。サービスが発行したのは長い文字列で、それを見ただけではどこに置けばいいのかわかりません。チャットのメッセージに貼りますか?コードの隣のファイルに入れますか?確実に動くように、設定ファイルに直接書きますか?

1つのモデルを使って整理しましょう。値をどこで設定するのか、どのプロセスがそれを受け取るのか、そして秘密そのものを見せずに設定済みだと確かめるにはどうするのかを見ていきます。モデルは、ローカルでの実行でも、MCP接続でも、公開したアプリケーションでも同じです。変わるのは、誰の環境を設定するかだけです。

アクセスキーの役割

キーはリクエストをアカウントまたはプロジェクトと、付与されている権限に結び付けます。サービスはキーを見て、誰がアクセスしてきたのか、その相手に何が許されているのか、これまでにどれだけリクエストしたのかを把握します。キーの正確な意味は、特定のサービスによって定義されます。あるサービスではアカウント全体へのアクセス、別のサービスでは1つのプロジェクトのみへの読み取り専用アクセスの場合もあります。

手順書やガイドでは、"キー"と並んでトークンという言葉もよく出てきます。普段の文章では同じ意味で使われることが多いものの、仕組みの細部は違います。違うのは、取得方法、有効期限、権限の範囲です。完全な同義語とは考えない方がよいでしょう。サービスが実際に何を発行するのかは、そのサービスのドキュメントに書かれています。

そもそもなぜサービスがあなたを区別する必要があるのかは、身近な例で考えるとわかりやすくなります。アカウントを作り、月間リクエスト数に上限のあるプランを選んで、キーを受け取ったとします。その後サービスは、このキーを手がかりにあなたのリクエストを数え、上限と照らし合わせ、有料プランなら請求書を発行します。キーが他人の手に渡るということは、あなたの上限と請求が、別の誰かの利用に回るということです。

リクエストがキーと一緒にサービスに到達し、サービスが所有者を見つけ、権限と上限をチェックしてからリクエストを許可または拒否しますAPIへのリクエストキーが添えられているサービスがキーを読むチェックを始める所有者を特定するアカウントかプロジェクトアクセスを確認する権限と上限の残り判断を下す許可するか拒否するか
サービスは順番にチェックを行ってからリクエストに応答します

キー自体が判断を下すわけではありません。キーはサービスに所有者の設定の在りかを示すだけで、そのリクエストを許可するかどうか、上限から差し引くかどうかを決めるのはサービスです。

中には、サービスがあえて公開用と指定している値もあります。こうした値はページのコードに入れるものです。公開用の値か秘密の値かは、変数名からは見分けられません。用途はドキュメントに書かれているので、そちらで確認してください。以下の例ではどれもfc-EXAMPLE-0000000000のような明らかなダミー値を使っています。本物のキーはこういう見た目ではありません。

アプリケーションが秘密を保存できる場所

こんな場面を想像してください。キーをページのコードに直接書き込み、プロジェクトを起動すると、APIから応答が返ってきました。すべて動いています。公開後もアプリケーションは開き、リクエストも通ります。これで解決したように見えます。

ところが、コードと一緒にキーも、訪問者全員のブラウザに届いています。開発者ツールを開いて、JavaScriptやリクエストの中からキーを見つけ、コピーするだけです。そうすれば、その人はあなたの名前でリクエストを送れるようになります。そのリクエストはあなたの上限を消費し続け、やがてサービスはあなた自身のリクエストを拒否し始めます。

公開されたページのコードにあるキーが訪問者のブラウザに届き、コピーされて他人のリクエストに使われ、所有者の上限を消費します公開されたコード中にキーが書いてある訪問者のブラウザコード全体を受け取るキーがコピーされるもう秘密ではない他人のリクエストあなたの名前で送られるあなたの上限減り続ける
ページのコードにある有効なキーは、訪問者全員のキーになります

問題は特定のビルドツールにあるのではありません。ページがブラウザに渡したものは、HTML、スタイル、JavaScript、リクエストのアドレスやヘッダーまで、すべてブラウザの持ち主が見られます。ビルドの時点でキーをローカルの.envから読み込んだとしても、JavaScriptに埋め込まれた時点で、残りのコードと一緒に訪問者の手元に届きます。

Backendとは、サーバー上で動くコードのことです。訪問者が受け取るのはその応答だけで、ソースコードやサーバープロセスの環境変数は受け取りません。そのため、backendはキーを自分の側に保持し、アプリケーションとして外部サービスにアクセスできます。

リクエストの流れはこう変わります。ブラウザは秘密のキーを含まない普通のリクエストを、あなたのbackendに送ります。Backendは自分の環境からキーを読み取り、それを外部APIへの別のリクエストに付けて送り、ブラウザには結果だけを返します。

ブラウザは普通のリクエストをbackendに送り、backendが自分の環境からキーを付けて外部APIにアクセスしますブラウザキーはないBackendリクエストを送る外部APIキーを確認するBackendの環境ここにキーがあるキーなしキー付き
秘密が登場するのは、backendと外部APIの間だけです

これで訪問者に見えるのは、backendのアドレスと応答データだけで、外部サービスのキーは見えません。秘密は、それを使うプロセスの手元に残ります。

秘密の持ち主になりうるのは、次の2つです。

  • ローカルのプロセス - 連携を接続したClaude Code、スクリプト、dev-サーバーなど。あなたのコンピューターで起動し、キーもそこで受け取ります。
  • 公開したアプリケーションのbackend - 自分で外部APIにアクセスする、サーバー上のコード。その環境はホスティングの管理画面で設定します。

この2つは別々の設定です。サービスも変数名も同じだとしても、あなたのコンピューターの環境にあるキーが、ひとりでにホスティング側に現れることはありません。

キーをbackendに移せばブラウザからは隠せますが、公開されたbackend自体は、やはり他人からのリクエストに対して守る必要があります。

Claude Codeに値の設定を頼まれる前に、2つのことを答えてもらってください。どのプロセスがキーを使うのか、そして値がどういう経路でそのプロセスに届くのかです。答えが出ないなら、まだ設定する段階ではありません。

環境変数の仕組み

環境変数とは、プログラムが起動時に受け取る、名前付きの値のことです。FIRECRAWL_API_KEYのような名前と、値、つまり例の長い文字列を持っています。設定ファイルやコードに書くのは名前の方です。値は別の場所にあり、コードの隣には置きません。

では、プログラムは値をどこから受け取るのでしょうか。ターミナルから起動したプログラムは、そのターミナルの環境を引き継ぎます。あるウィンドウで変数を設定し、そこからClaude Codeを起動すれば、Claude Codeにはその変数が見えます。2つ目のウィンドウを開いてそちらで変数を設定しても、すでに動いているプロセスには影響しません。環境は起動時に渡されたものだからです。

"キーは設定したのに動かない"という声のいちばん多い原因は、ここにあります。値を別のウィンドウで設定したか、起動した後に設定したかのどちらかです。変数があるターミナルからプロセスを起動し直せば直ります。

練習用の例です。値は明らかにダミーです。

# bash: 現在のターミナル用の変数
export FIRECRAWL_API_KEY=fc-EXAMPLE-0000000000
# PowerShell: 同じ内容
$env:FIRECRAWL_API_KEY = "fc-EXAMPLE-0000000000"

練習用のダミー値なら、こう書いてもかまいません。ただし、本物のキーをこの方法で入力するのはやめましょう。文字列がまるごとコマンド履歴に残り、ターミナルを閉じた後もそこに残ります。本物の値には、エコーなしの入力を使います。文字列を貼り付けても画面には何も表示されず、履歴に残るのはコマンドだけです。

# bash: 値を非表示で入力する
read -s -r FIRECRAWL_API_KEY
export FIRECRAWL_API_KEY
# PowerShell: 非表示で入力し、値を取り出して環境変数に入れる
$secure = Read-Host "FIRECRAWL_API_KEY" -AsSecureString
$env:FIRECRAWL_API_KEY = [System.Net.NetworkCredential]::new('', $secure).Password

設定の確認は、値が画面にもログにもスクリーンショットにも出ない方法で行います。"設定されているかどうか"がわかればよいので、2行で足ります。

# bash
[ -n "$FIRECRAWL_API_KEY" ] && echo "設定済み" || echo "未設定"
# PowerShell
if ($env:FIRECRAWL_API_KEY) { "設定済み" } else { "未設定" }

長さも先頭の数文字も表示する必要はありません。確認には何の役にも立たないうえ、キーの一部はもう秘密の一部です。

変数を恒久的に設定する方法はたくさんあり、システムごとに違います。今それを覚える必要はありません。ローカルでの作業ならターミナルで十分ですし、ホスティングでは変数をそのサービスの管理画面で設定します。

環境変数は、秘密をコードに書かないための手段であって、保護の仕組みではありません。値は暗号化されず、同じ環境で起動したプログラムなら自由に読めます。だからこそ、誰のプロセスを設定しているのかが重要なのです。

.envと.env.exampleが必要な理由

新しいターミナルを開くたびに変数を手で設定するのはすぐに面倒になるので、値はテキストファイル.envにまとめておきます。1行に、名前と値のペアを1つずつ書きます。

ここで見落とされがちな大事な点があります。ファイルがあるだけでは、何も設定されません。誰かがそのファイルを読み、値をプロセスの環境に配る必要があります。それをするのは、アプリケーション自体か、ライブラリか、あなたのプログラムを起動するlauncherです。.envを作れば、どんなプログラムでも自動で読み込んでくれる、と考えることはできません。あなたのケースで実際に何がファイルを読み込むのかは、推測せずにはっきり確かめておきましょう。

その隣には、たいていさらに2つのファイルがあります。

# .env - あなたのマシンにだけ置く
FIRECRAWL_API_KEY=fc-EXAMPLE-0000000000
# .env.example - プロジェクトを開く人向けのテンプレート
# 値は空で、名前だけを書く
FIRECRAWL_API_KEY=
# .gitignore
.env
ファイル役割中身
.env起動用のローカルな値本物の名前と値。あなたのコンピューターにだけ置く
.env.exampleプロジェクトを開く人へのヒント同じ名前で値は空。短いコメントが付くこともある
.gitignoreローカルのファイルをリポジトリに入れないためのルールファイル名を書いた行

分ける理由は単純です。.env.exampleは"このプロジェクトにはどんな変数が必要か"という問いに答えるファイルで、値が入っていないので、リポジトリに置いても問題ありません。本物のキーは、一時的にでもここに書き写しません。

まだGitに慣れていないなら、ここでは.gitignoreの役割だけ覚えておけば十分です。リポジトリに追加しないファイルを列挙するためのファイルです。Gitと.gitignoreの使い方は、レベル13で詳しく扱います。

次は、.gitignoreでよくはまる落とし穴です。このルールが効くのは、Gitがまだ追跡していないファイルだけです。.envを一度でもコミットしてしまうと、.gitignoreに1行追加しても追跡は外れません。ファイルはバージョン管理下に残り、その変更はこれからもコミットに含まれ続けます。Gitのドキュメントにも、はっきりそう書かれています。すでにGitが追跡しているファイルには、無視のルールは適用されません。gitignoreのドキュメント。

.gitignoreのルールは追跡されていないファイルを守りますが、すでにGitに入ったファイルの追跡は止めません.gitignoreにある行.envGitはすでに追跡している.env?ルールが効く.envはローカルに残るファイルはGitに残る追跡の解除が別に必要いいえはい
結果は、Gitがそのファイルを以前から追跡していたかどうかで決まります

図の中で大事な条件は真ん中にあります。.gitignoreの同じ1行が役に立つのは、.envが追跡されていない間だけです。すでに追跡されているファイルには、別の手順が必要です。

だからこそ、ファイルに本物の秘密を書き込む前に、Gitがそのファイルを知らないことを確かめておきましょう。git statusを見れば、ファイルが変更として扱われているかどうかがわかります。すでに追跡されている場合は、git rm --cached .envコマンドで追跡を外し、その変更をコミットします。ファイルはディスクに残りますが、バージョン管理からは外れます。

ただし、これが効くのはこれからのコミットだけです。すでに履歴に入った値はそのまま残るので、別の方法で対処する必要があります。これについては後で説明します。

MCP接続にキーを渡す方法

MCPは、外部ツールをClaude Codeに接続する仕組みです。接続の内容は.mcp.jsonファイルに書きます。このファイルはたいていプロジェクトと一緒にリポジトリに置かれ、チーム全員が見られます。つまり、ここに秘密を書いてはいけません。

FirecrawlサービスをHTTPで接続する最小限の設定は、次のとおりです。

{
  "mcpServers": {
    "firecrawl": {
      "type": "http",
      "url": "https://mcp.firecrawl.dev/v2/mcp",
      "headers": {
        "Authorization": "Bearer ${FIRECRAWL_API_KEY}"
      }
    }
  }
}

ファイルにキーはありません。ヘッダーにあるのは${FIRECRAWL_API_KEY}、つまり名前による環境変数の参照です。Claude Codeは、自分が起動したプロセスの環境から値を取り出して、ここに埋め込みます。リポジトリに入るのは名前だけで、値は一人ひとりの手元に残ります。Claude Codeがこの置き換えを行えるのは、url、headers、env、command、argsの各フィールドです。Claude CodeのMCPに関するドキュメント。

値はターミナルからプロセスの環境に入り、リクエストのヘッダーに埋め込まれます。共有の設定ファイルには変数名だけが入っていますターミナルで入力プロセスの環境サービスへリクエスト.mcp.jsonには名前だけ${FIRECRAWL_API_KEY}
値はプロセスの環境にあり、共有ファイルには変数名だけが入っています

図を見ると、リクエストのヘッダーで、別々の場所から来た2つのものが合流しているのがわかります。ヘッダーのひな形は共有の設定ファイルから、値はプロセスの環境から来ます。どの段階でも、リポジトリに入るファイルにキーが書き込まれることはありません。

アドレスとキーの渡し方は、そのサービス自身のドキュメントで確認します。Firecrawlの場合はAuthorizationヘッダーで、ドキュメントでは、キーをMCPのアドレスに入れないようわざわざ注意しています。理由は単純です。アドレスはただのテキストなので、設定ファイルにもログにもコマンド履歴にも残り、一緒にキーも残ってしまいます。

ブラウザでのログインに対応しているサービスもあります。クライアントが自分でログインページを開くので、API keyを手で渡す必要がそもそもありません。Firecrawlには、そのための専用のアドレスが用意されています。FirecrawlのMCPドキュメント。

ツールが応答しないときは、次の順に確認しましょう。

  1. 設定ファイルにある変数名と、あなたが設定した変数名が、最後の1文字まで一致している;
  2. 値が、Claude Codeを起動したプロセスで設定されている;
  3. 変数を設定した後に、プロセスを起動し直している。

変数がない場合でも、Claude Codeは黙って落ちたりはしません。設定ファイルを読み込み、${FIRECRAWL_API_KEY}というテキストを置き換えずに残し、足りない変数の名前を含む警告を出します。値そのものはどこにも表示されないので、この診断は安全です。問題を探すために環境全体を出力する必要は、まったくありません。

この例は、すでにできあがっている接続を理解するためのものです。サーバーの選び方や追加の仕方、サーバーにどんな権限を与えるかは別のテーマです。ここで大事なのは、この構成の中で秘密がどこにあるかだけです。

キーが外に漏れてしまったら

よくあることです。キーがメッセージやコミット、スクリーンショットに入ってしまった。慌てる必要はありませんが、後回しにするのもよくありません。手順は次のとおりです。

  1. サービスの管理画面でキーを無効化するか、新しいものに差し替える。古い文字列で何も開けなくなるのは、この手順の後だけです;
  2. 新しい値を、必要な場所に設定する。ローカル、ホスティング、接続の設定などです;
  3. 漏れた場所に対処する。メッセージを削除する、ファイルを修正する、アクセスを閉じる;
  4. 管理画面で、キーがどう使われたか、サービスが利用額として何を表示しているかを確認する。

順番はこのとおりで、最初の手順は入れ替えられません。メッセージを削除しても、古いコミットの上に新しいコミットを重ねても、以前の秘密が安全になるわけではありません。メッセージはもう読まれたかもしれませんし、リポジトリはクローンされ、履歴は保存されたかもしれません。キーが有効な間は、それを持っている全員が使えます。

リポジトリについては、もう1つ補足があります。秘密がコミットに入ってしまった場合のために、GitHubにはそうしたデータを削除するガイドがあります。リポジトリからの機密データの削除。ただし、履歴を完全に消去すると既存のコミットが書き換えられるため、そのリポジトリで作業している全員に影響します。こうした操作はリポジトリの所有者と相談して行うもので、学習用の記事を読んで一人で実行するものではありません。一方、キーの差し替えはすぐに行い、誰かに相談する必要もありません。

Claude Codeに任せること

この記事の仕組みのほとんどは、エージェントに任せられます。ただ1つを除いて。秘密そのものを入力するのは、あなたです。エージェントに任せてよいのは、次のようなことです。

  • 値ではなく、変数名を使って設定ファイルを用意する;
  • 何が変数をプロセスに読み込むのか、そして変数がどのプロセスに届く必要があるのかを明示する;
  • クライアント向けのビルドにも、Gitが追跡しているファイルにも、秘密が入っていないことを確認する;
  • アクセスの確認を行い、値を表示せずに結果を報告する。

あなたの役目は、本物のキーを、そのために用意された仕組みで入力することです。ターミナルでの非表示入力、ローカルのファイル、ホスティングの管理画面などです。秘密がチャットやスクリーンショット、エージェントのレポートに入ることは決してありません。"確認のために一瞬だけ"であってもです。

依頼は、たとえば次のように書けます。

このサービスをAPIで接続して。キーは自分で設定する。
アドレス、メソッド、キーの渡し方はドキュメントから確認して。
設定ファイルには値ではなく環境変数の名前を入れて。
どのプロセスが変数を受け取るべきか、何がそれを読み込むのかを教えて。
クライアント向けのビルドとGitが追跡しているファイルにキーがないことを確認して。
アクセスの確認は、値もその一部も出力せずに行って。
結果を報告して: 設定済み、未設定、アクセス拒否のどれか。

最後に、レポートの読み方について。"変数が設定されている"と"アクセスできる"は別の主張で、片方をもう片方の代わりにしてはいけません。値がないことと、サービスに拒否されることも違います。前者はリクエストに署名するものがない状態、後者はサービスが応答したもののアクセスを認めなかった状態です。エージェントが"完了"とだけ言ったら、具体的に何を確認したのかを聞いてください。

長いリストの代わりに、ルールは1つ

秘密はチャットに送らない、スクリーンショットに写さない、リポジトリに入るファイルに貼り付けない。この記事のそれ以外の内容はすべて、このルールを守りながら作業の邪魔をしないための方法です。