Skip to content

feat(mcp): add --scope option for OAuth and fix upstream auth flow issues#1625

Open
4riel wants to merge 2 commits intoMoonshotAI:mainfrom
4riel:4riel/mcp-oauth-scopes
Open

feat(mcp): add --scope option for OAuth and fix upstream auth flow issues#1625
4riel wants to merge 2 commits intoMoonshotAI:mainfrom
4riel:4riel/mcp-oauth-scopes

Conversation

@4riel
Copy link
Copy Markdown

@4riel 4riel commented Mar 28, 2026

Related Issue

N/A — discovered when connecting to OAuth MCP servers that require specific scopes
(e.g. Supabase requires organizations:read, projects:read, etc.). The existing
kimi mcp add / kimi mcp auth commands had no way to specify or forward scopes.

Description

Problem

  1. kimi mcp add has no --scope option — OAuth servers requiring specific scopes
    cannot be configured correctly
  2. kimi mcp auth uses the dict-config path (fastmcp.Client({"mcpServers": ...}))
    which silently ignores any scopes field in the config
  3. Upstream fastmcp/MCP SDK has three bugs that break OAuth for some providers:
    • OAuth.__init__ strips the URL path, breaking RFC 8707 resource matching
    • redirect_handler pre-flight GET misinterprets 400 responses as "client not found"
    • _handle_token_response rejects HTTP 201 on token exchange

Fix

CLI (src/kimi_cli/cli/mcp.py):

  • Add --scope / -s repeatable option to mcp_add(), validated to require --auth oauth
    and http transport (following the existing --header / --auth guard pattern)
  • Add create_oauth() helper with _PatchedOAuthClient subclass that works around
    the three upstream issues
  • Refactor mcp_auth() to use create_oauth() with manual transport construction,
    so scopes are properly forwarded to the OAuth flow
  • Show configured scopes in mcp_list() output

Runtime (src/kimi_cli/soul/toolset.py):

  • Use create_oauth() when loading MCP tools at runtime for OAuth servers with scopes

Provider compatibility

The implementation is fully provider-agnostic. Verified against:

Provider Scopes server_url fix impact Result
Supabase Required (e.g. organizations:read) Fixes resource matching 29 tools
Sentry Optional (org:read, project:write) Fixes resource matching 21 tools
Vercel Optional (openid) No-op (no URL path) Compatible
Linear None declared Neutral Compatible

Before / After

# Before: no way to specify scopes
kimi mcp add -t http --auth oauth myserver https://mcp.example.com/mcp
kimi mcp auth myserver
# → fails on servers that require scopes

# After: scopes supported
kimi mcp add -t http --auth oauth myserver https://mcp.example.com/mcp \
  --scope "read" --scope "write"
kimi mcp auth myserver
# → Successfully authorized with 'myserver'. Available tools: N

Validation

  • Added 13 new test cases in tests/core/test_mcp_cli.py:
    • Validation guards (4): --scope rejected for stdio, rejected without --auth,
      rejected with non-oauth auth, guard ordering preserved
    • Happy path (2): scope accepted with --auth oauth, no-scope still works
    • Config persistence (2): scopes stored as list, no stale keys when omitted
    • OAuth construction (5): scopes forwarded to OAuth, no-scope path, SSE transport
      selection, headers forwarded, RFC 8707 server_url fix verified
  • End-to-end verified with real OAuth flows (Supabase, Sentry)
  • ruff check and ruff format pass

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog.
  • I have run make gen-docs to update the user documentation.

Open with Devin

…sues

Add --scope/-s repeatable option to `kimi mcp add` for OAuth servers that
require specific scopes. Validated to require --auth oauth and http transport,
following the existing --header/--auth guard pattern.

Refactor `kimi mcp auth` to use manual transport+OAuth construction so scopes
are forwarded. Add create_oauth() helper with _PatchedOAuthClient that works
around three upstream fastmcp/MCP SDK issues: URL path stripping breaking
RFC 8707 resource matching, redirect_handler pre-flight GET misinterpreting
400 responses, and token exchange rejecting HTTP 201.

Show configured scopes in `kimi mcp list` output and pass scopes through to
OAuth during runtime MCP tool loading in toolset.py.
Copilot AI review requested due to automatic review settings March 28, 2026 16:08
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OAuth scope support to MCP server configuration and authorization, and applies upstream OAuth compatibility workarounds so OAuth flows work with providers that require scopes and/or have non-standard behaviors.

Changes:

  • Add repeatable --scope / -s to kimi mcp add, persist scopes in config, and display them in mcp list.
  • Refactor kimi mcp auth to construct transports manually so configured scopes are forwarded into OAuth.
  • Update runtime MCP loading to use the same OAuth construction path for scoped OAuth servers; add CLI-focused tests covering validation, persistence, and OAuth construction.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/kimi_cli/cli/mcp.py Adds --scope, persists/displays scopes, and introduces create_oauth() with upstream OAuth workarounds; updates mcp_auth() to use manual transport construction.
src/kimi_cli/soul/toolset.py Uses create_oauth() + manual transport when loading scoped OAuth MCP servers at runtime.
tests/core/test_mcp_cli.py New tests for --scope validation/persistence and for OAuth/transport construction behavior in mcp_auth().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +385 to +388
from fastmcp.client.transports import SSETransport, StreamableHttpTransport

from kimi_cli.cli.mcp import create_oauth

Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolset now imports create_oauth from kimi_cli.cli.mcp. This introduces a runtime dependency on the CLI module (and its Typer app construction) from the core soul runtime. Consider moving create_oauth into a non-CLI/shared module (e.g. kimi_cli/mcp/oauth.py) and importing it from both CLI and runtime to keep layering clean and avoid accidental CLI-side imports/side effects in non-CLI contexts.

Copilot uses AI. Check for mistakes.
cli = typer.Typer(help="Manage MCP server configurations.")


def create_oauth(mcp_url: str, scopes: list[str] | None = None) -> Any:
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_oauth() is annotated as returning Any, but it always returns an OAuth instance (a _PatchedOAuthClient subclass). Returning a concrete type (e.g. OAuth) would improve type-checking and make call sites (like transport construction) safer/clearer.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f8fb87b0f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +340 to +342
transport_type = server.get("transport", "http")
transport_cls = SSETransport if transport_type == "sse" else StreamableHttpTransport
auth = create_oauth(url, scopes=scopes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve transport inference when transport is unset

This new auth path hardcodes http when the config omits transport, but FastMCP's canonical config supports leaving transport empty and inferring it from the URL (notably /sse endpoints). For OAuth servers loaded from external/canonical MCP configs that rely on URL-based inference, kimi mcp auth will now choose StreamableHttpTransport instead of SSETransport, causing authorization/connect failures that did not happen in the previous Client({"mcpServers": ...}) flow.

Useful? React with 👍 / 👎.

Comment on lines +389 to +392
transport_cls = (
SSETransport
if server_config.transport == "sse"
else StreamableHttpTransport
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep URL-based transport inference in scoped OAuth loading

In the scoped OAuth branch, transport selection is reduced to transport == "sse" else HTTP, which skips FastMCP's normal inference for configs where transport is omitted. That means OAuth servers with scopes and URLs that imply SSE can be loaded with the wrong transport at runtime, while the old MCPConfig(...)->Client(...) path would infer correctly.

Useful? React with 👍 / 👎.

…tAI#1625)

- Extract create_oauth() and _PatchedOAuthClient to kimi_cli/oauth.py
- Fix layering violation: soul/toolset.py imports from kimi_cli.oauth instead of cli.mcp
- Remove scopes gate: OAuth workarounds now apply to ALL OAuth servers, not just those with explicit scopes
- Fix return type: create_oauth() now returns OAuth instead of Any
- Add E2E tests for OAuth scopes support
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af9cf48b19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +308 to +311
auth = create_oauth(url, scopes=scopes)
transport = transport_cls(url, headers=headers, auth=auth)

client = fastmcp.Client(transport)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route mcp test through patched OAuth client

mcp_auth() now builds OAuth with create_oauth() (full-path RFC8707/resource behavior), but mcp_test() still uses the legacy fastmcp.Client({"mcpServers": ...}) path. When a server has no cached token yet (or needs refresh), mcp test can drop back to the unpatched OAuth flow and fail on providers that need path-aware resource/scope handling, even though mcp auth succeeded for the same server. Reusing the same patched transport/auth construction for mcp_test would keep these commands consistent.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants