From 4a51c6d711f5db68fa5061fd5c0d15a065583c6a Mon Sep 17 00:00:00 2001 From: Allen <0xfatdog@gmail.com> Date: Fri, 14 Aug 2026 00:11:23 +0800 Subject: [PATCH] fix(claude): restore native MCP OAuth login --- .claude-plugin/plugin.json | 10 +----- .claude/CLAUDE.md | 11 ++++--- .github/workflows/scripts-test.yml | 4 +++ .mcp.json | 5 +-- AGENTS.md | 11 ++++--- README.md | 6 ++-- docs/README_zh.md | 6 ++-- tests/claude-code-plugin.bats | 53 ++++++++++++++++++++++++++++++ 8 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 tests/claude-code-plugin.bats diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4386da5..12f67cf 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -24,13 +24,5 @@ "real-time-data" ], "skills": "./skills/", - "mcpServers": "./.mcp.json", - "userConfig": { - "AGENTKEY_API_KEY": { - "title": "AgentKey API Key", - "description": "Your AgentKey API key. Get one free at https://console.agentkey.app/", - "type": "string", - "sensitive": true - } - } + "mcpServers": "./.mcp.json" } diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index eac673d..e83638c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -13,7 +13,7 @@ AgentKey has **two pieces** and a full end-user install is two commands: The skill is useless without the MCP server; the MCP server works without the skill but the agent won't know to prefer it over built-in web search. Keep this mental model when editing docs — do not let either command drift into claiming it does both. -The same repo also works as a Claude Code plugin (via `.claude-plugin/plugin.json` + `.mcp.json`) for users on the plugin marketplace path; in that mode the plugin's `userConfig` + `.mcp.json` substitute for step 2. +The same repo also works as a Claude Code plugin (via `.claude-plugin/plugin.json` + `.mcp.json`) for users on the plugin marketplace path; the remote HTTP entry has no static authentication header, so Claude Code follows the server's 401/RFC 9728 metadata into its native MCP OAuth flow. That OAuth sign-in substitutes for step 2. It additionally works as a **Codex plugin** (via `.codex-plugin/plugin.json` + `.codex-plugin/mcp.json`, distributed through `.agents/plugins/marketplace.json` — the repo is its own marketplace, added with `codex plugin marketplace add chainbase-labs/agentkey`). Codex plugins have no `userConfig`/header-interpolation mechanism, so auth uses MCP OAuth instead: the server's `/v1/mcp` endpoint advertises `WWW-Authenticate: Bearer resource_metadata=…` (RFC 9728) and supports dynamic client registration, so `.codex-plugin/mcp.json` needs only `type` + `url` — discovery does the rest. In that mode the OAuth sign-in substitutes for step 2. @@ -38,7 +38,7 @@ agentkey/ ├── .kimi-plugin/ │ └── plugin.json # Kimi Code manifest with inline HTTP MCP entry (OAuth) ├── .agents/plugins/marketplace.json # Codex marketplace listing this repo as a local-source plugin -├── .mcp.json # Auto-registers AgentKey MCP when installed as a Claude Code plugin +├── .mcp.json # Claude MCP entry — HTTP + OAuth discovery, no static headers ├── gemini-extension.json # Gemini CLI extension — Streamable HTTP + OAuth discovery ├── plugin.json # Antigravity desktop/CLI plugin marker ├── mcp_config.json # Antigravity remote MCP entry — serverUrl + OAuth discovery @@ -86,8 +86,9 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - release-please automatically bumps all four manifest versions + `CHANGELOG.md` from merged conventional-commit PRs; maintainers review + merge the generated Release PR rather than editing these files directly **Changes to `.mcp.json`:** -- The MCP server is `type: http` (remote endpoint, no subprocess), so inject the API key by interpolating the userConfig value as `${user_config.AGENTKEY_API_KEY}` in the `Authorization` header — the key name MUST match the `plugin.json` `userConfig` key. Do NOT use `${CLAUDE_PLUGIN_OPTION_}`: those env vars are only exported to stdio/subprocess servers and hook/monitor commands, and are not interpolated into an http server's headers. -- Only matters for the Claude Code plugin path; the Skills-CLI path writes MCP config through `npx @agentkey/cli --auth-login` +- Keep the remote server as the minimal `type: http` + `url` entry. Do not add `headers`, `headersHelper`, `userConfig`, a static token, or an API-key placeholder. +- Claude Code treats any configured `Authorization` header as explicit header authentication and does not fall back to OAuth when that header receives a 401. With no header, the server's 401 and RFC 9728 protected-resource metadata expose the native **Authenticate** action and `claude mcp login plugin:agentkey:agentkey` flow. +- This applies only to the Claude Code plugin path; the Skills-CLI path still writes API-key MCP config through `npx @agentkey/cli --auth-login`. **Changes to `.codex-plugin/mcp.json`:** - Codex plugin MCP config does NOT support `${user_config.*}` interpolation — a literal `${…}` would be sent as the Authorization header. Auth is MCP OAuth via RFC 9728 discovery: the server's 401 advertises `resource_metadata`, and the rmcp client automatically appends `resource=` to the authorization request. @@ -136,7 +137,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Setup mode in SKILL.md runs `! npx -y @agentkey/cli --auth-login` to authenticate via browser — same command as step 2 of the public install - `@agentkey/cli --auth-login` auto-writes MCP configs for 16 agents (canonical list lives in `AGENT_REGISTRY` in `../AgentKey-Server/cli/src/lib/mcp-clients.ts`): Claude Code, Claude Desktop, Cursor, Codex, Gemini CLI, OpenCode, Qwen Code, iFlow CLI, Kimi CLI, Kiro CLI, Windsurf, Warp, Amp, Crush, droid, openclaw. The `--only ` flag (used by install.sh's `MCP_TARGETS` and install.ps1's `$McpTargets`) filters this list — its id values MUST match `npx skills add -a` ids, with `claude-desktop` as the one documented MCP-only exception. Goose / kode / kilo still need a manual JSON paste (see SKILL.md's "Fallback" section); when adding more agents server-side, keep `MCP_AUTO_AGENTS` in both install scripts and the cleanup list in both uninstall scripts in sync. -- `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode; the API key flows from plugin userConfig into the `Authorization: Bearer ${user_config.AGENTKEY_API_KEY}` header (no stdio binary is launched) +- `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode with no static header or `userConfig`; Claude Code performs native MCP OAuth discovery after the server's 401 response. - `.cursor-plugin/plugin.json` registers the same endpoint inline in Cursor plugin mode, authenticated through Cursor's native MCP OAuth flow - `.kimi-plugin/plugin.json` registers the same endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. - `gemini-extension.json` registers the same endpoint through `httpUrl` in Gemini CLI extension mode. Gemini discovers the existing `skills/agentkey/` tree; `oauth.enabled` starts native OAuth automatically and `/mcp auth agentkey` retries it manually. diff --git a/.github/workflows/scripts-test.yml b/.github/workflows/scripts-test.yml index 306b6eb..32a0d37 100644 --- a/.github/workflows/scripts-test.yml +++ b/.github/workflows/scripts-test.yml @@ -9,6 +9,8 @@ on: - 'gemini-extension.json' - 'plugin.json' - 'mcp_config.json' + - '.mcp.json' + - '.claude-plugin/plugin.json' - 'README.md' - 'docs/README_zh.md' - 'tests/**' @@ -23,6 +25,8 @@ on: - 'gemini-extension.json' - 'plugin.json' - 'mcp_config.json' + - '.mcp.json' + - '.claude-plugin/plugin.json' - 'README.md' - 'docs/README_zh.md' - 'tests/**' diff --git a/.mcp.json b/.mcp.json index f1bf32c..2a958b8 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,10 +2,7 @@ "mcpServers": { "agentkey": { "type": "http", - "url": "https://api.agentkey.app/v1/mcp", - "headers": { - "Authorization": "Bearer ${user_config.AGENTKEY_API_KEY}" - } + "url": "https://api.agentkey.app/v1/mcp" } } } diff --git a/AGENTS.md b/AGENTS.md index 1c1fe06..bbdcff0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ The skill is useless without the MCP server; the MCP server works without the sk The same repo also works as: -- a **Claude Code plugin** (`.claude-plugin/plugin.json` + root `.mcp.json`) — the plugin's `userConfig` injects the API key via `${user_config.AGENTKEY_API_KEY}`, substituting for step 2. +- a **Claude Code plugin** (`.claude-plugin/plugin.json` + root `.mcp.json`) — the remote HTTP entry has no static authentication header, so Claude Code follows the server's 401/RFC 9728 metadata into its native MCP OAuth flow, substituting for step 2. - a **Codex plugin** (`.codex-plugin/plugin.json` + `.codex-plugin/mcp.json`, distributed through `.agents/plugins/marketplace.json`; the repo is its own marketplace: `codex plugin marketplace add chainbase-labs/agentkey`). Codex plugins have no `userConfig`/header-interpolation mechanism, so auth uses MCP OAuth via the server's RFC 9728 metadata discovery (`type` + `url` only in mcp.json), substituting for step 2. - a **Cursor plugin** (`.cursor-plugin/plugin.json`). The Cursor-native manifest bundles `skills/` and an inline remote-HTTP MCP entry. Cursor authenticates through the server's MCP OAuth discovery, substituting for step 2. - a **Kimi Code plugin** (`.kimi-plugin/plugin.json`). Kimi requires `mcpServers` to be an inline object in the manifest. The remote AgentKey endpoint uses Kimi's native MCP OAuth flow; after install Kimi shows the standard `/reload` hint, then the user signs in with `/mcp-config login plugin-agentkey:agentkey` when Kimi reports that OAuth is required. @@ -35,7 +35,7 @@ agentkey/ ├── .kimi-plugin/ │ └── plugin.json # Kimi Code manifest with inline HTTP MCP entry (OAuth) ├── .agents/plugins/marketplace.json # Codex marketplace listing this repo as a local-source plugin -├── .mcp.json # Auto-registers AgentKey MCP when installed as a Claude Code plugin +├── .mcp.json # Claude MCP entry — HTTP + OAuth discovery, no static headers ├── gemini-extension.json # Gemini CLI extension — Streamable HTTP + OAuth discovery ├── plugin.json # Antigravity desktop/CLI plugin marker ├── mcp_config.json # Antigravity remote MCP entry — serverUrl + OAuth discovery @@ -83,8 +83,9 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - release-please automatically bumps all four manifest versions + `CHANGELOG.md` from merged conventional-commit PRs; maintainers review + merge the generated Release PR rather than editing these files directly **Changes to the root `.mcp.json` (Claude Code plugin path):** -- The MCP server is `type: http` (remote endpoint, no subprocess), so inject the API key by interpolating the userConfig value as `${user_config.AGENTKEY_API_KEY}` in the `Authorization` header — the key name MUST match the `.claude-plugin/plugin.json` `userConfig` key. Do NOT use `${CLAUDE_PLUGIN_OPTION_}`: those env vars are only exported to stdio/subprocess servers and hook/monitor commands, and are not interpolated into an http server's headers. -- Only matters for the Claude Code plugin path; the Skills-CLI path writes MCP config through `npx @agentkey/cli --auth-login` +- Keep the remote server as the minimal `type: http` + `url` entry. Do not add `headers`, `headersHelper`, `userConfig`, a static token, or an API-key placeholder. +- Claude Code treats any configured `Authorization` header as explicit header authentication and does not fall back to OAuth when that header receives a 401. With no header, the server's 401 and RFC 9728 protected-resource metadata expose the native **Authenticate** action and `claude mcp login plugin:agentkey:agentkey` flow. +- This applies only to the Claude Code plugin path; the Skills-CLI path still writes API-key MCP config through `npx @agentkey/cli --auth-login`. **Changes to `.codex-plugin/mcp.json` (Codex plugin path):** - Codex plugin MCP config does NOT support `${user_config.*}` interpolation — a literal `${…}` would be sent as the Authorization header. Auth is MCP OAuth via RFC 9728 discovery: the server's 401 advertises `resource_metadata`, and the rmcp client automatically appends `resource=` to the authorization request. @@ -133,7 +134,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Setup mode in SKILL.md runs `! npx -y @agentkey/cli --auth-login` to authenticate via browser — same command as step 2 of the public install - `@agentkey/cli --auth-login` auto-writes MCP configs for 16 agents (canonical list lives in `AGENT_REGISTRY` in `../AgentKey-Server/cli/src/lib/mcp-clients.ts`): Claude Code, Claude Desktop, Cursor, Codex, Gemini CLI, OpenCode, Qwen Code, iFlow CLI, Kimi CLI, Kiro CLI, Windsurf, Warp, Amp, Crush, droid, openclaw. The `--only ` flag (used by install.sh's `MCP_TARGETS` and install.ps1's `$McpTargets`) filters this list — its id values MUST match `npx skills add -a` ids, with `claude-desktop` as the one documented MCP-only exception. Goose / kode / kilo still need a manual JSON paste (see SKILL.md's "Fallback" section); when adding more agents server-side, keep `MCP_AUTO_AGENTS` in both install scripts and the cleanup list in both uninstall scripts in sync. -- Root `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode; the API key flows from plugin userConfig into the `Authorization: Bearer ${user_config.AGENTKEY_API_KEY}` header (no stdio binary is launched) +- Root `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode with no static header or `userConfig`; Claude Code performs native MCP OAuth discovery after the server's 401 response. - `.codex-plugin/mcp.json` registers the same endpoint in Codex plugin mode, authenticated via MCP OAuth (RFC 9728 discovery; no `oauth_resource` — see checklist above) - `.cursor-plugin/plugin.json` registers the same endpoint inline in Cursor plugin mode, authenticated through Cursor's native MCP OAuth flow - `.kimi-plugin/plugin.json` registers the same endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. diff --git a/README.md b/README.md index 5ed7c70..bf40f83 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ npx -y @agentkey/cli --auth-login **Iterating on the MCP server itself?** The server lives at `AgentKey-Server/` (Go) and exposes the MCP endpoint at `/v1/mcp`. Run a local server (`make run`) and point your MCP config at `http://localhost:8081/v1/mcp` to test changes end-to-end. -**Claude Code plugin mode** — install straight from the marketplace. The plugin prompts you for your AgentKey API key on enable and wires the MCP server for you, so there's **no second `@agentkey/cli` step**: +**Claude Code plugin mode** — install straight from the marketplace. The plugin bundles a header-free remote HTTP entry and uses Claude Code's native MCP OAuth flow, so there is **no API key to paste and no second `@agentkey/cli` step**: ```bash # Public install @@ -351,7 +351,7 @@ claude plugin marketplace add /absolute/path/to/agentkey claude plugin install agentkey@agentkey ``` -On enable, Claude Code prompts for `AGENTKEY_API_KEY` (stored in your OS keychain) and injects it into the plugin's `.mcp.json` via `${user_config.AGENTKEY_API_KEY}`. Reload a local checkout with `claude plugin update agentkey` after edits. Day-to-day skill iteration is still fastest via the skills-CLI path; the plugin path is the one-step option for Claude Code users. +After installation, run `/reload-plugins`, open `/mcp`, select the AgentKey entry, and choose **Authenticate** to complete browser sign-in. From a terminal, `claude mcp login plugin:agentkey:agentkey` starts the same OAuth flow. The plugin's `.mcp.json` intentionally has no `Authorization` header: adding one makes Claude Code treat the server as header-authenticated and suppresses OAuth fallback after a 401. Reload a local checkout with `claude plugin update agentkey` after edits. Day-to-day Skill iteration is still fastest via the Skills CLI path; the plugin path is the OAuth-based option for Claude Code users. **Codex plugin mode** — install from the marketplace bundled in this repo. Auth is OAuth (browser sign-in on install), so there's **no API key to paste and no second `@agentkey/cli` step**: @@ -437,7 +437,7 @@ agentkey/ ├── .kimi-plugin/ │ └── plugin.json # Kimi manifest with inline MCP OAuth entry ├── .agents/plugins/marketplace.json # Codex marketplace (this repo is its own marketplace) -├── .mcp.json # Used when installed as a Claude Code plugin +├── .mcp.json # Claude plugin MCP entry (OAuth discovery, no headers) ├── gemini-extension.json # Gemini CLI extension manifest (MCP OAuth + skills) ├── plugin.json # Antigravity desktop/CLI plugin manifest ├── mcp_config.json # Antigravity remote MCP entry (serverUrl + OAuth) diff --git a/docs/README_zh.md b/docs/README_zh.md index 3df3775..396484b 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -339,7 +339,7 @@ npx -y @agentkey/cli --auth-login **想改 MCP Server 本身?** MCP server 在 `AgentKey-Server/`(Go),端点是 `/v1/mcp`。本地起服务(`make run`),把 MCP 配置指向 `http://localhost:8081/v1/mcp` 就能端到端验证。 -**Claude Code 插件模式** —— 直接从 marketplace 安装。插件启用时会提示你填 AgentKey API Key 并自动接好 MCP server,**不需要再单独跑 `@agentkey/cli`**: +**Claude Code 插件模式** —— 直接从 marketplace 安装。插件内置不带 header 的远程 HTTP 配置,并使用 Claude Code 原生 MCP OAuth,**不用粘贴 API Key,也不需要再单独跑 `@agentkey/cli`**: ```bash # 公开安装 @@ -351,7 +351,7 @@ claude plugin marketplace add /absolute/path/to/agentkey claude plugin install agentkey@agentkey ``` -启用时 Claude Code 会提示填 `AGENTKEY_API_KEY`(存进系统钥匙串),并通过 `${user_config.AGENTKEY_API_KEY}` 注入插件的 `.mcp.json`。改了本地 checkout 后用 `claude plugin update agentkey` 重新加载。日常 Skill 迭代仍是 skills CLI 最快;插件路径是给 Claude Code 用户的一步到位选项。 +安装后运行 `/reload-plugins`,打开 `/mcp`,选择 AgentKey 并点击 **Authenticate**,然后在浏览器完成登录。也可以在终端运行 `claude mcp login plugin:agentkey:agentkey` 启动同一个 OAuth 流程。插件的 `.mcp.json` 会刻意保持无 `Authorization` header:一旦配置这个 header,Claude Code 就会把 server 判定为 header 认证,并在 401 后屏蔽 OAuth fallback。改了本地 checkout 后用 `claude plugin update agentkey` 重新加载。日常 Skill 迭代仍是 Skills CLI 最快;插件路径是 Claude Code 用户的 OAuth 安装方式。 **Codex 插件模式** —— 从本仓库自带的 marketplace 安装。认证走 OAuth(安装时浏览器登录),**不用粘贴 API Key,也不需要再单独跑 `@agentkey/cli`**: @@ -437,7 +437,7 @@ agentkey/ ├── .kimi-plugin/ │ └── plugin.json # Kimi 清单,内联 MCP OAuth 配置 ├── .agents/plugins/marketplace.json # Codex marketplace(本仓库即自己的 marketplace) -├── .mcp.json # 作为 Claude Code 插件安装时使用 +├── .mcp.json # Claude 插件 MCP 配置(OAuth 自动发现,无 headers) ├── gemini-extension.json # Gemini CLI 扩展清单(MCP OAuth + skills) ├── plugin.json # Antigravity 桌面端/CLI 插件清单 ├── mcp_config.json # Antigravity 远程 MCP 配置(serverUrl + OAuth) diff --git a/tests/claude-code-plugin.bats b/tests/claude-code-plugin.bats new file mode 100644 index 0000000..e0c4a70 --- /dev/null +++ b/tests/claude-code-plugin.bats @@ -0,0 +1,53 @@ +#!/usr/bin/env bats + +setup() { + REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)" + MANIFEST="$REPO_ROOT/.claude-plugin/plugin.json" + MCP_CONFIG="$REPO_ROOT/.mcp.json" +} + +@test "Claude Code plugin leaves authentication to native MCP OAuth" { + python3 - "$MANIFEST" "$MCP_CONFIG" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as handle: + manifest = json.load(handle) +with open(sys.argv[2], encoding="utf-8") as handle: + config = json.load(handle) + +assert manifest["mcpServers"] == "./.mcp.json" +assert "userConfig" not in manifest +assert config == { + "mcpServers": { + "agentkey": { + "type": "http", + "url": "https://api.agentkey.app/v1/mcp", + } + } +} +server = config["mcpServers"]["agentkey"] +assert "headers" not in server +assert "headersHelper" not in server +PY +} + +@test "Claude Code plugin docs expose both native OAuth entry points" { + python3 - "$REPO_ROOT/README.md" "$REPO_ROOT/docs/README_zh.md" <<'PY' +import sys + +for path in sys.argv[1:]: + with open(path, encoding="utf-8") as handle: + readme = " ".join(handle.read().split()) + + expected_phrases = ( + "/reload-plugins", + "/mcp", + "**Authenticate**", + "claude mcp login plugin:agentkey:agentkey", + ) + for expected in expected_phrases: + assert expected in readme, f"{path}: {expected}" + assert "`Authorization` header" in readme, path +PY +}