$ claude mcp list
claude.ai Notion: https://mcp.notion.com/mcp - ✔ Connected
claude.ai Slack: https://mcp.slack.com/mcp - ! Needs authentication
plugin:figma:figma: https://mcp.figma.com/mcp (HTTP) - ! Needs authentication
! Needs authentication が付いたサーバーは、OAuth を通すまでツールが1つも見えません。Claude Code v2.1.186 以降は claude mcp login <name> で、セッションを開かずにシェルからサインインを終わらせられます。以下は v2.1.263 で確認した挙動です。
claude mcp login はシェルで完結する
公式ドキュメントの “Authenticate from the command line” セクションが説明しているのがこのコマンドです。/mcp パネルを開くために対話セッションを起動する必要がなくなりました。
サーバー名は claude mcp list の表記と一致させる
$ claude mcp login sentry
名前を間違えると、設定済みサーバーの一覧を添えて弾かれます。
$ claude mcp login nosuchserver
No MCP server named "nosuchserver". Configured servers: claude.ai Atlassian Rovo,
claude.ai Gmail, claude.ai Google Calendar, context7, drawio
(and 4 more — run `claude mcp list` to see all)
プラグインが提供するサーバーは plugin:figma:figma のようにコロン区切りの名前になります。ここも claude mcp list の表記をそのまま渡します。
ブラウザのないマシンでは –no-browser
v2.1.191 以降、Claude Code は SSH 接続中やディスプレイサーバーのない Linux を検出し、ブラウザを開く代わりに認可 URL を標準出力へ書きます。手元のマシンでその URL を開き、ブラウザのアドレスバーに出たリダイレクト URL をプロンプトへ貼り戻す流れです。
$ claude mcp login sentry --no-browser
ローカルブラウザが見つかる環境でも、--no-browser を明示すれば URL 表示に固定できます。
非対話シェルでは完走しない
貼り戻しの入力を受け取る都合上、このコマンドは端末を要求します。バックグラウンド実行や標準入力を潰した状態だと、認可 URL を出す前に止まります。
$ claude mcp login demo --no-browser < /dev/null
Starting authentication for "demo"…
Couldn't complete authentication for "demo": stdin isn't a terminal, so
authentication can't be completed here. Re-run in an interactive terminal
— e.g. `ssh -t` — and paste the redirect URL when prompted.
終了コードは 0 で返ります。CI のジョブに claude mcp login を並べても、失敗として検出できません。SSH 越しに叩くなら ssh -t で疑似端末を割り当ててください。
コールバックポートを固定して事前登録URIに合わせる
Claude Code は既定で OAuth コールバック用に空きポートをランダムに選びます。リダイレクト URI を事前登録させるサーバーだと、この方式では登録済みの値と一致しません。
–callback-port はポートだけを決める
$ claude mcp add --transport http \
--callback-port 8080 \
my-server https://mcp.example.com/mcp
リダイレクト URI は http://localhost:PORT/callback の形になります。サーバー側の開発者ポータルには http://localhost:8080/callback を登録します。ポート番号以外は Claude Code が決めるため、パスを /oauth/callback などに変えることはできません。
設定できたかは claude mcp get で確認する
$ claude mcp get demo
demo:
Scope: Local config (private to you in this project)
Status: ✘ Failed to connect
Issue: ENOTFOUND: getaddrinfo ENOTFOUND mcp.example.com
Type: http
URL: https://mcp.example.com/mcp
OAuth: client_id configured, callback_port 8080
最終行の OAuth: が設定の確認箇所です。接続に失敗していても、この行は設定値をそのまま返します。手元の Figma プラグインは http://localhost:3118/callback を使っていました。プラグインが callbackPort を持つと、同梱の設定がそのまま採用されます。
動的クライアント登録に非対応のサーバーへ繋ぐ
claude mcp add でサーバーを足して claude mcp login を叩いたのに、ブラウザが開かず次のエラーで止まることがあります。
Incompatible auth server: does not support dynamic client registration
Dynamic Client Registration(動的クライアント登録)は、クライアント ID を認可サーバー側が自動発行する RFC 7591 の仕組みです。Claude Code はこれと Client ID Metadata Document を自動で試し、どちらも駄目なら上のエラーを返します。では手動で何を用意するか。サーバーの開発者ポータルで OAuth アプリを登録し、クライアント ID とクライアントシークレットを受け取ります。
先にリダイレクトURIを登録してからポートを合わせる
ポートを先に決めて --callback-port に渡すと、開発者ポータルへ登録済みの URI と食い違います。登録するリダイレクト URI のポートを控えて、それを --callback-port に渡します。ポート番号自体は空いていれば何番でも構いません。
–client-secret は値を引数に書かない
$ claude mcp add --transport http \
--client-id your-client-id --client-secret --callback-port 8080 \
my-server https://mcp.example.com/mcp
--client-secret は値を取りません。フラグだけ渡すとマスク付きの入力を求められ、シェル履歴にシークレットが残らない作りです。サーバーがシークレット不要のパブリッククライアントなら、--client-id だけで足ります。
Claude Code は入力したシークレットを設定ファイルへ書きません。~/.claude.json に残るのは clientId と callbackPort だけです。
{
"demo": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"oauth": {
"clientId": "demo-client-id",
"callbackPort": 8080
}
}
}
CI では MCP_CLIENT_SECRET で対話を飛ばす
$ MCP_CLIENT_SECRET=your-secret claude mcp add --transport http \
--client-id your-client-id --client-secret --callback-port 8080 \
my-server https://mcp.example.com/mcp
環境変数を置くとマスク入力を飛ばせます。ただしサーバーの登録が終わるだけで、サインインは別です。前述のとおり claude mcp login は端末を要求するため、CI の中で OAuth を完走させることはできません。これらのフラグは HTTP と SSE にだけ効き、stdio サーバーには影響しません。
チームで同じ設定を配るなら、oauth オブジェクトごと .mcp.json に書いてコミットする形になります。スコープの切り分けはClaude Code MCPのスコープ管理—.mcp.jsonでチーム共有する設計で整理しました。
oauth.scopes は claude mcp get に出てこない
公式の “Restrict OAuth scopes” セクションにある oauth.scopes は、認可フローで要求するスコープを固定する設定です。値は RFC 6749 §3.3 のスペース区切り文字列。
$ claude mcp add-json demo2 \
'{"type":"http","url":"https://mcp.example.com/mcp","oauth":{"callbackPort":8123,"scopes":"channels:read chat:write"}}'
Added http MCP server demo2 to local config
$ claude mcp get demo2
demo2:
Scope: Local config (private to you in this project)
Type: http
URL: https://mcp.example.com/mcp
OAuth: callback_port 8123
scopes の行がありません。Claude Code は値を保存していて、~/.claude.json を開けば "scopes": "channels:read chat:write" が残っています。CLI からピン留めしたスコープを検算できないので、確認は設定ファイル側で行います。
v2.1.196 以降、oauth.scopes を置かない場合は WWW-Authenticate ヘッダーか保護リソースメタデータが示すスコープだけを要求します。以前は認可サーバーの scopes_supported をまるごと要求していて、管理者専用スコープを広告する IdP が invalid_scope で認可を拒否していました。なお scopes_supported に offline_access があると、Claude Code はそれをピン留めしたスコープへ足します。ブラウザで再サインインせずにアクセストークンを更新するためです。
トークンはキーチェーンの mcpOAuth に入る
公式ドキュメントは「システムキーチェーン(macOS)または認証情報ファイルに安全に保存され、設定には保存されません」としか書きません。実体を見ると、Anthropic アカウントのトークンと MCP のトークンが同じキーチェーン項目に同居していました。
秘密値を出さずにキーだけ数える
$ security find-generic-password -s "Claude Code-credentials" -w \
| python3 -c "import json,sys; [print(k) for k in json.load(sys.stdin).get('mcpOAuth', {})]"
plugin:figma:figma|d39d3b6252bc1ac5
plugin:figma:figma|3cd030064c4b9f62
plugin:figma:figma|2e8d0be4381965aa
service 名は Claude Code-credentials。JSON のトップレベルに claudeAiOauth と mcpOAuth が並びます。mcpOAuth のキーは <サーバー名>|<16桁の16進> という形式で、値は serverName / serverUrl / accessToken / refreshToken / clientId / clientSecret / redirectUri / expiresAt と、検出結果を保持する discoveryState を持ちます。
使われないエントリが積み上がる
上の3つは serverUrl も redirectUri も同一です。中身を比べると refreshToken と expiresAt を持つのは1つだけで、残りの2つは accessToken しかありません。サインインをやり直した分が消えずに残っています。トークン自体は使われませんが、キーチェーンには居座り続けます。
Claude Code は要認証のサーバーを ~/.claude/mcp-needs-auth-cache.json にも書き出します。
{"plugin:figma:figma":{"timestamp":1788664907652}}
公式ドキュメントにこのファイルの記述はありません。v2.1.193 で入った「認証が必要なサーバーがあると起動時に通知する」挙動と対応する位置にあります。
claude mcp logout は保存が無くても成功する
$ claude mcp logout demo
Signed out of "demo". Run `claude mcp login demo` to authenticate again.
一度もサインインしていないサーバーに対しても同じメッセージを返します。終了コードも 0。トークンが実際に消えたかどうかの判定には使えないため、確認するならキーチェーン側を直接見ます。
OAuth 以外の認証は headersHelper で渡す
Kerberos や社内 SSO のように OAuth を使わないサーバー向けに、公式の “Use dynamic headers for custom authentication” セクションが headersHelper を用意しています。
静的な Authorization ヘッダーは OAuth に落ちない
headers.Authorization に固定トークンを書いた状態でサーバーがそれを拒否すると、Claude Code は OAuth へフォールバックせず接続失敗として報告します。
{
"mcpServers": {
"internal-api": {
"type": "http",
"url": "https://mcp.internal.example.com",
"headers": { "Authorization": "Bearer 期限切れトークン" }
}
}
}
OAuth を使いたいならヘッダーを消します。トークンを都度生成したいなら headersHelper に切り替えます。
{
"mcpServers": {
"internal-api": {
"type": "http",
"url": "https://mcp.internal.example.com",
"headersHelper": "/opt/bin/get-mcp-auth-headers.sh"
}
}
}
スクリプトは文字列のキーと値だけの JSON オブジェクトを標準出力へ書きます。実行はシェル経由、タイムアウトは10秒。接続のたびに走り、キャッシュはありません。トークンを使い回すかどうかはスクリプト側の責任です。
Claude Code がヘルパーへ渡す環境変数
| 変数 | 値 |
|---|---|
CLAUDE_CODE_MCP_SERVER_NAME | MCP サーバーの名前 |
CLAUDE_CODE_MCP_SERVER_URL | MCP サーバーの URL |
CLAUDE_PLUGIN_ROOT | プラグインのルート。プラグイン提供のサーバーでのみ設定される |
v2.1.193 以降、ツール呼び出しが 401 か 403 を返すと Claude Code はヘルパーを再実行して繋ぎ直し、呼び出しを1回だけ再試行します。再試行も失敗したときにはじめて、/mcp に要認証の印が付きます。1本のスクリプトで複数サーバーを捌けるので、サーバーごとにヘルパーを分ける必要はありません。
claude -p では OAuth フローを開けない
非対話モードには /mcp パネルがないため、Claude Code は OAuth フローを走らせられません。v2.1.196 以降、claude -p や Agent SDK の実行中に認証が必要なサーバーがあると、Claude はそのサーバー名を挙げて「認可されるまでツールが使えない」と応答します。サーバーが設定されていないかのように黙る挙動ではなくなりました。
この判定は tool search が有効なときに効きます。既定で有効なので、意識して切っていなければそのまま当てはまります。トークン消費との兼ね合いはClaude Code MCP tool search—実測7万トークン削減の設定判断に書きました。バッチを回す前に、対話セッションで claude mcp login を済ませておくのが確実です。
まとめ
claude mcp login <name>は v2.1.186 以降。端末を要求するので CI からは完走できず、失敗しても終了コードは 0- 事前登録のリダイレクト URI を使うサーバーは
--callback-portでポートを揃える。パスは/callback固定 - 動的クライアント登録に非対応なら
--client-idと--client-secret。シークレットは設定ファイルではなくキーチェーンへ入る oauth.scopesはclaude mcp getに表示されない。検算は~/.claude.jsonで行う
トークンの実体は Claude Code-credentials の mcpOAuth 配下です。claude mcp get は clientId と callbackPort しか返さないので、スコープは ~/.claude.json、トークンの有無はキーチェーンと、見る場所を分けます。