Bug Description
When connecting to remote MCP servers that require OAuth but do not support dynamic client registration (e.g. GitHub's https://api.githubcopilot.com/mcp/), the OAuth flow hard-fails because there is no mechanism to provide a pre-registered ClientID through Kit's configuration.
The mcp-go library's transport.OAuthConfig already supports a ClientID field, but Kit's connection pool never populates it. And Kit's OAuthFlowRunner.RunAuthFlow() treats dynamic registration failure as a fatal error with no fallback.
Root Cause
There are two related sub-issues:
1. Connection pool never passes ClientID to the transport
In internal/tools/connection_pool.go, both createStreamableClient() (line ~423) and createSSEClient() (line ~376) construct the OAuthConfig with only RedirectURI, PKCEEnabled, and TokenStore — but never ClientID:
options = append(options, transport.WithHTTPOAuth(transport.OAuthConfig{
RedirectURI: p.oauthFlow.handler.RedirectURI(),
PKCEEnabled: true,
TokenStore: tokenStore,
// ClientID is never set!
}))
The mcp-go OAuthConfig struct already has a ClientID field that would work if populated:
// In mcp-go/client/transport/oauth.go
type OAuthConfig struct {
ClientID string // ← Already exists, never populated by Kit
ClientURI string
ClientSecret string
RedirectURI string
Scopes []string
TokenStore TokenStore
PKCEEnabled bool
// ...
}
2. OAuthFlowRunner hard-fails when dynamic registration isn't supported
In internal/tools/oauth_flow.go, RunAuthFlow() (line ~48) calls RegisterClient() when GetClientID() == "" and treats failure as fatal:
if oauthHandler.GetClientID() == "" {
if err := oauthHandler.RegisterClient(ctx, "kit"); err != nil {
return fmt.Errorf("oauth flow: dynamic client registration failed: %w", err)
// ← Hard error, no fallback
}
}
For servers like GitHub, the OAuth metadata (discovered via RFC 9728) has no registration_endpoint:
// GET https://github.com/.well-known/oauth-authorization-server/login/oauth
{
"issuer": "https://github.com/login/oauth",
"authorization_endpoint": "https://github.com/login/oauth/authorize",
"token_endpoint": "https://github.com/login/oauth/access_token",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"]
// No registration_endpoint!
}
This causes mcp-go's RegisterClient() to correctly return "server does not support dynamic client registration", which Kit then treats as a fatal error.
Note: mcp-go is behaving correctly here — it returns a clear error when the server doesn't support dynamic registration. The issue is that Kit has no way to provide a ClientID upfront and no graceful fallback when registration fails.
Impact
Any remote MCP server that requires OAuth but doesn't support dynamic client registration is completely unusable with Kit. This includes:
- GitHub MCP Server (
https://api.githubcopilot.com/mcp/) — the official GitHub MCP server
- Potentially any OAuth server using standard GitHub OAuth, Google OAuth, etc. that requires pre-registered apps
This affects both the Kit CLI/TUI and SDK users.
Reproduction
# .kit.yml
mcpServers:
github:
type: remote
url: https://api.githubcopilot.com/mcp/
$ kit
Warning: Failed to load MCP server 'github': failed to get connection from pool:
failed to create connection for github: OAuth authorization failed: oauth flow:
dynamic client registration failed: server does not support dynamic client registration
Suggested Fix
Option A: Add OAuth fields to MCPServerConfig (recommended)
Add optional OAuth configuration to MCPServerConfig in internal/config/config.go:
type MCPServerConfig struct {
Type string `json:"type"`
Command []string `json:"command,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
URL string `json:"url,omitempty"`
Headers []string `json:"headers,omitempty"`
AllowedTools []string `json:"allowedTools,omitempty"`
ExcludedTools []string `json:"excludedTools,omitempty"`
// OAuth configuration for remote servers
OAuthClientID string `json:"oauthClientId,omitempty" yaml:"oauthClientId,omitempty"`
OAuthClientSecret string `json:"oauthClientSecret,omitempty" yaml:"oauthClientSecret,omitempty"`
OAuthScopes []string `json:"oauthScopes,omitempty" yaml:"oauthScopes,omitempty"`
// Legacy fields...
}
Then in connection_pool.go, pass it through:
oauthCfg := transport.OAuthConfig{
RedirectURI: p.oauthFlow.handler.RedirectURI(),
PKCEEnabled: true,
TokenStore: tokenStore,
}
if serverConfig.OAuthClientID != "" {
oauthCfg.ClientID = serverConfig.OAuthClientID
}
if serverConfig.OAuthClientSecret != "" {
oauthCfg.ClientSecret = serverConfig.OAuthClientSecret
}
if len(serverConfig.OAuthScopes) > 0 {
oauthCfg.Scopes = serverConfig.OAuthScopes
}
options = append(options, transport.WithHTTPOAuth(oauthCfg))
This would enable configuration like:
mcpServers:
github:
type: remote
url: https://api.githubcopilot.com/mcp/
oauthClientId: "Ov23liXXXXXXXXXXXXXX" # From a registered GitHub OAuth App
And for SDK users:
host.AddMCPServer(ctx, "github", kit.MCPServerConfig{
Type: "remote",
URL: "https://api.githubcopilot.com/mcp/",
OAuthClientID: os.Getenv("GITHUB_OAUTH_CLIENT_ID"),
})
Option B: Add a client registration store
Add a persistent store for client registrations (separate from token store) so that once dynamic registration succeeds, the ClientID is remembered for future connections. This wouldn't help for servers that never support dynamic registration (like GitHub), but would prevent repeated registration for servers that do.
Option C: Graceful fallback in OAuthFlowRunner
When RegisterClient() fails, instead of hard-failing, the flow could check if the server's metadata provides enough information to proceed without a client ID (some OAuth servers allow public clients without registration). This is more of a stretch but worth considering.
Options A and B are complementary — A solves the immediate problem for servers like GitHub, and B improves the experience for servers that do support dynamic registration.
Related
Environment
- Kit: v0.48.0
- mcp-go: v0.47.1
- Go: 1.26.1
Bug Description
When connecting to remote MCP servers that require OAuth but do not support dynamic client registration (e.g. GitHub's
https://api.githubcopilot.com/mcp/), the OAuth flow hard-fails because there is no mechanism to provide a pre-registeredClientIDthrough Kit's configuration.The
mcp-golibrary'stransport.OAuthConfigalready supports aClientIDfield, but Kit's connection pool never populates it. And Kit'sOAuthFlowRunner.RunAuthFlow()treats dynamic registration failure as a fatal error with no fallback.Root Cause
There are two related sub-issues:
1. Connection pool never passes ClientID to the transport
In
internal/tools/connection_pool.go, bothcreateStreamableClient()(line ~423) andcreateSSEClient()(line ~376) construct theOAuthConfigwith onlyRedirectURI,PKCEEnabled, andTokenStore— but neverClientID:The
mcp-goOAuthConfigstruct already has aClientIDfield that would work if populated:2. OAuthFlowRunner hard-fails when dynamic registration isn't supported
In
internal/tools/oauth_flow.go,RunAuthFlow()(line ~48) callsRegisterClient()whenGetClientID() == ""and treats failure as fatal:For servers like GitHub, the OAuth metadata (discovered via RFC 9728) has no
registration_endpoint:This causes
mcp-go'sRegisterClient()to correctly return"server does not support dynamic client registration", which Kit then treats as a fatal error.Note:
mcp-gois behaving correctly here — it returns a clear error when the server doesn't support dynamic registration. The issue is that Kit has no way to provide aClientIDupfront and no graceful fallback when registration fails.Impact
Any remote MCP server that requires OAuth but doesn't support dynamic client registration is completely unusable with Kit. This includes:
https://api.githubcopilot.com/mcp/) — the official GitHub MCP serverThis affects both the Kit CLI/TUI and SDK users.
Reproduction
Suggested Fix
Option A: Add OAuth fields to MCPServerConfig (recommended)
Add optional OAuth configuration to
MCPServerConfigininternal/config/config.go:Then in
connection_pool.go, pass it through:This would enable configuration like:
And for SDK users:
Option B: Add a client registration store
Add a persistent store for client registrations (separate from token store) so that once dynamic registration succeeds, the
ClientIDis remembered for future connections. This wouldn't help for servers that never support dynamic registration (like GitHub), but would prevent repeated registration for servers that do.Option C: Graceful fallback in OAuthFlowRunner
When
RegisterClient()fails, instead of hard-failing, the flow could check if the server's metadata provides enough information to proceed without a client ID (some OAuth servers allow public clients without registration). This is more of a stretch but worth considering.Options A and B are complementary — A solves the immediate problem for servers like GitHub, and B improves the experience for servers that do support dynamic registration.
Related
AddMCPServer()also doesn't inheritAuthHandler/TokenStoreFactorywhen creating a new tool manager, which is a prerequisite for OAuth to work at all when adding servers at runtime.Environment