Skip to content
This repository was archived by the owner on Sep 23, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/en/customization/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ kimi mcp add --transport http context7 https://mcp.context7.com/mcp \

# Using OAuth authentication
kimi mcp add --transport http --auth oauth linear https://mcp.linear.app/mcp

# Request provider-specific OAuth scopes
kimi mcp add --transport http --auth oauth supabase https://mcp.supabase.com/mcp \
--scope "organizations:read" --scope "projects:read"
```

Add a stdio server (local process):
Expand Down Expand Up @@ -62,6 +66,9 @@ kimi mcp auth linear

This will open a browser to complete the OAuth flow. After successful authorization, Kimi Code CLI will save the token for future use.

Use `--scope` more than once when the provider requires specific OAuth scopes. Scopes are
stored in `~/.kimi/mcp.json` and used by both `kimi mcp auth` and runtime MCP connections.

MCP OAuth tokens are stored in `~/.kimi/mcp-oauth/`. After upgrading from older versions that used FastMCP 2.x, the old token cache is not migrated automatically; if `kimi mcp list` shows that an OAuth server needs authorization, run `kimi mcp auth <name>` again.

**Test a server**
Expand Down
2 changes: 2 additions & 0 deletions docs/en/reference/kimi-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ kimi mcp add [OPTIONS] NAME [TARGET_OR_COMMAND...]
| `--env KEY=VALUE` | `-e` | Environment variable (`stdio` only), can be specified multiple times |
| `--header KEY:VALUE` | `-H` | HTTP header (`http` only), can be specified multiple times |
| `--auth TYPE` | `-a` | Authentication type (e.g., `oauth`, `http` only) |
| `--scope SCOPE` | `-s` | OAuth scope (`oauth` only), can be specified multiple times |

## `list`

Expand All @@ -42,6 +43,7 @@ Output includes:
- Configuration file path
- Name, transport type, and target for each server
- Authorization status for OAuth servers
- Configured OAuth scopes

## `remove`

Expand Down
7 changes: 7 additions & 0 deletions docs/zh/customization/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ kimi mcp add --transport http context7 https://mcp.context7.com/mcp \

# 使用 OAuth 认证
kimi mcp add --transport http --auth oauth linear https://mcp.linear.app/mcp

# 请求服务商指定的 OAuth scope
kimi mcp add --transport http --auth oauth supabase https://mcp.supabase.com/mcp \
--scope "organizations:read" --scope "projects:read"
```

添加 stdio 服务器(本地进程):
Expand Down Expand Up @@ -62,6 +66,9 @@ kimi mcp auth linear

这会打开浏览器完成 OAuth 流程。授权成功后,Kimi Code CLI 会保存 token 供后续使用。

如果服务商要求指定 OAuth scope,可以重复使用 `--scope`。scope 会保存到
`~/.kimi/mcp.json`,并同时用于 `kimi mcp auth` 和运行时 MCP 连接。

MCP OAuth token 存储在 `~/.kimi/mcp-oauth/`。从使用 FastMCP 2.x 的旧版本升级后,旧的 token 缓存不会自动迁移;如果 `kimi mcp list` 显示某个 OAuth 服务器需要授权,重新运行 `kimi mcp auth <name>` 即可。

**测试服务器**
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/reference/kimi-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ kimi mcp add [OPTIONS] NAME [TARGET_OR_COMMAND...]
| `--env KEY=VALUE` | `-e` | 环境变量(仅 `stdio`),可多次指定 |
| `--header KEY:VALUE` | `-H` | HTTP Header(仅 `http`),可多次指定 |
| `--auth TYPE` | `-a` | 认证类型(如 `oauth`,仅 `http`) |
| `--scope SCOPE` | `-s` | OAuth scope(仅 `oauth`),可多次指定 |

## `list`

Expand All @@ -42,6 +43,7 @@ kimi mcp list
- 配置文件路径
- 每个服务器的名称、传输类型和目标
- OAuth 服务器的授权状态
- 已配置的 OAuth scope

## `remove`

Expand Down
35 changes: 35 additions & 0 deletions src/kimi_cli/cli/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ def _parse_key_value_pairs(
# Add streamable HTTP server with OAuth authorization:\n
kimi mcp add --transport http --auth oauth linear https://mcp.linear.app/mcp\n
\n
# Add OAuth server with specific scopes (e.g., Supabase):\n
kimi mcp add --transport http --auth oauth supabase https://mcp.supabase.com/mcp \\\n
--scope "organizations:read" --scope "projects:read" --scope "database:read"\n
\n
# Add stdio server:\n
kimi mcp add --transport stdio chrome-devtools -- npx chrome-devtools-mcp@latest
""".strip(), # noqa: E501
Expand Down Expand Up @@ -139,6 +143,14 @@ def mcp_add(
help="Authorization type (e.g., 'oauth').",
),
] = None,
scope: Annotated[
list[str] | None,
typer.Option(
"--scope",
"-s",
help="OAuth scope to request. Can be specified multiple times.",
),
] = None,
):
"""Add an MCP server."""
config = _load_mcp_config()
Expand All @@ -161,6 +173,9 @@ def mcp_add(
if auth:
typer.echo("--auth is only valid for http transport.", err=True)
raise typer.Exit(code=1)
if scope:
typer.echo("--scope is only valid for http transport.", err=True)
raise typer.Exit(code=1)
command, *command_args = server_args
server_config: dict[str, Any] = {"command": command, "args": command_args}
if env:
Expand All @@ -185,6 +200,11 @@ def mcp_add(
)
if auth:
server_config["auth"] = auth
if scope:
if auth != "oauth":
typer.echo("--scope is only valid with --auth oauth.", err=True)
raise typer.Exit(code=1)
server_config["scopes"] = scope

if "mcpServers" not in config:
config["mcpServers"] = {}
Expand Down Expand Up @@ -242,6 +262,21 @@ def mcp_list():
if transport == "streamable-http":
transport = "http"
line = f"{name} ({transport}): {server['url']}"
if server.get("scopes") is not None and server.get("auth") == "oauth":
from kimi_cli.mcp_oauth import validate_mcp_scopes

try:
scopes = validate_mcp_scopes(server["scopes"])
except ValueError:
typer.echo(
f"Invalid OAuth scopes for MCP server '{name}': "
"expected a list of strings.",
err=True,
)
line += " [invalid scopes]"
else:
if scopes:
line += f" [scopes: {', '.join(scopes)}]"
if server.get("auth") == "oauth" and not _has_oauth_tokens(server["url"]):
line += " [authorization required - run: kimi mcp auth " + name + "]"
else:
Expand Down
53 changes: 49 additions & 4 deletions src/kimi_cli/mcp_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast

from kimi_cli.share import get_share_dir

if TYPE_CHECKING:
import httpx
from fastmcp.client.auth.oauth import OAuth, TokenStorageAdapter
from key_value.aio.stores.filetree import FileTreeStore

Expand Down Expand Up @@ -55,10 +56,53 @@ async def has_mcp_oauth_tokens(server_url: str) -> bool:
return False


def create_mcp_oauth(server_url: str) -> OAuth:
def validate_mcp_scopes(scopes: object | None) -> list[str] | None:
if scopes is None:
return None
if not isinstance(scopes, list):
raise ValueError("OAuth MCP server scopes must be a list of strings.")
validated: list[str] = []
for scope in cast(list[object], scopes):
if not isinstance(scope, str):
raise ValueError("OAuth MCP server scopes must be a list of strings.")
validated.append(scope)
return validated


def create_mcp_oauth(server_url: str, scopes: object | None = None) -> OAuth:
from fastmcp.client.auth.oauth import OAuth

return OAuth(mcp_url=server_url, token_storage=create_mcp_oauth_store())
validated_scopes = validate_mcp_scopes(scopes)

class _PatchedOAuth(OAuth):
"""Apply compatibility workarounds for MCP OAuth providers.

FastMCP 3.2.4 still performs a pre-flight authorization request that
treats HTTP 400 as an invalid client, and only accepts HTTP 200 from
the token endpoint. Some MCP providers use HTTP 400 for the normal
login page and HTTP 201 for a successful token exchange.
"""

async def redirect_handler(self, authorization_url: str) -> None:
import webbrowser

webbrowser.open(authorization_url)

async def _handle_token_response(self, response: httpx.Response) -> None:
if response.status_code == 201:
response.status_code = 200
await super()._handle_token_response(response)

async def _handle_refresh_response(self, response: httpx.Response) -> bool:
if response.status_code == 201:
response.status_code = 200
return await super()._handle_refresh_response(response)

return _PatchedOAuth(
mcp_url=server_url,
scopes=validated_scopes,
token_storage=create_mcp_oauth_store(),
)


def prepare_mcp_server_config(server_config: dict[str, Any]) -> dict[str, Any]:
Expand All @@ -69,4 +113,5 @@ def prepare_mcp_server_config(server_config: dict[str, Any]) -> dict[str, Any]:
if not isinstance(server_url, str) or not server_url:
raise ValueError("OAuth MCP server config must include a non-empty URL.")

return {**server_config, "auth": create_mcp_oauth(server_url)}
scopes = server_config.get("scopes")
return {**server_config, "auth": create_mcp_oauth(server_url, scopes=scopes)}
4 changes: 3 additions & 1 deletion src/kimi_cli/soul/toolset.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,8 @@ async def _connect():
_mark_oauth_unauthorized(server_name)
continue
try:
auth = create_mcp_oauth(server_config.url)
scopes = (server_config.model_extra or {}).get("scopes")
auth = create_mcp_oauth(server_config.url, scopes=scopes)
except Exception as e:
logger.debug(
"Failed to create MCP OAuth storage for {server_name}: {error}",
Expand All @@ -858,6 +859,7 @@ async def _connect():
server_config = server_config.model_copy(update={"auth": auth})

client = fastmcp.Client(MCPConfig(mcpServers={server_name: server_config}))

self._mcp_servers[server_name] = MCPServerInfo(
status="pending", client=client, tools=[]
)
Expand Down
Loading