Skip to content

Commit c625a66

Browse files
authored
Enhance: Support cookie flow for mcp-ui (#17)
* Enhance: Support cookie flow for mcp-ui Signed-off-by: Daishan Peng <daishan@acorn.io>
1 parent 95dcb80 commit c625a66

File tree

22 files changed

+1512
-442
lines changed

22 files changed

+1512
-442
lines changed

cmd/root.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net/http"
7+
"net/url"
8+
9+
"github.com/gptscript-ai/cmd"
10+
"github.com/obot-platform/mcp-oauth-proxy/pkg/proxy"
11+
"github.com/obot-platform/mcp-oauth-proxy/pkg/types"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
var (
16+
version = "dev"
17+
buildTime = "unknown"
18+
)
19+
20+
// RootCmd represents the base command when called without any subcommands
21+
type RootCmd struct {
22+
// Database configuration
23+
DatabaseDSN string `name:"database-dsn" env:"DATABASE_DSN" usage:"Database connection string (PostgreSQL or SQLite file path). If empty, uses SQLite at data/oauth_proxy.db"`
24+
25+
// OAuth Provider configuration
26+
OAuthClientID string `name:"oauth-client-id" env:"OAUTH_CLIENT_ID" usage:"OAuth client ID from your OAuth provider" required:"true"`
27+
OAuthClientSecret string `name:"oauth-client-secret" env:"OAUTH_CLIENT_SECRET" usage:"OAuth client secret from your OAuth provider" required:"true"`
28+
OAuthAuthorizeURL string `name:"oauth-authorize-url" env:"OAUTH_AUTHORIZE_URL" usage:"Authorization endpoint URL from your OAuth provider (e.g., https://accounts.google.com)" required:"true"`
29+
30+
// Scopes and MCP configuration
31+
ScopesSupported string `name:"scopes-supported" env:"SCOPES_SUPPORTED" usage:"Comma-separated list of supported OAuth scopes (e.g., 'openid,profile,email')" required:"true"`
32+
MCPServerURL string `name:"mcp-server-url" env:"MCP_SERVER_URL" usage:"URL of the MCP server to proxy requests to" required:"true"`
33+
34+
// Security configuration
35+
EncryptionKey string `name:"encryption-key" env:"ENCRYPTION_KEY" usage:"Base64-encoded 32-byte AES-256 key for encrypting sensitive data (optional)"`
36+
37+
// Server configuration
38+
Port string `name:"port" env:"PORT" usage:"Port to run the server on" default:"8080"`
39+
Host string `name:"host" env:"HOST" usage:"Host to bind the server to" default:"localhost"`
40+
RoutePrefix string `name:"route-prefix" env:"ROUTE_PREFIX" usage:"Optional prefix for all routes (e.g., '/oauth2')"`
41+
42+
// Logging
43+
Verbose bool `name:"verbose,v" usage:"Enable verbose logging"`
44+
Version bool `name:"version" usage:"Show version information"`
45+
46+
Mode string `name:"mode" env:"MODE" usage:"Mode to run the server in" default:"proxy"`
47+
}
48+
49+
func (c *RootCmd) Run(cobraCmd *cobra.Command, args []string) error {
50+
if c.Version {
51+
fmt.Printf("MCP OAuth Proxy\n")
52+
fmt.Printf("Version: %s\n", version)
53+
fmt.Printf("Built: %s\n", buildTime)
54+
return nil
55+
}
56+
57+
// Configure logging
58+
if c.Verbose {
59+
log.SetFlags(log.LstdFlags | log.Lshortfile)
60+
log.Println("Verbose logging enabled")
61+
}
62+
63+
// Convert CLI config to internal config format
64+
config := &types.Config{
65+
DatabaseDSN: c.DatabaseDSN,
66+
OAuthClientID: c.OAuthClientID,
67+
OAuthClientSecret: c.OAuthClientSecret,
68+
OAuthAuthorizeURL: c.OAuthAuthorizeURL,
69+
ScopesSupported: c.ScopesSupported,
70+
MCPServerURL: c.MCPServerURL,
71+
EncryptionKey: c.EncryptionKey,
72+
Mode: c.Mode,
73+
RoutePrefix: c.RoutePrefix,
74+
}
75+
76+
// Validate configuration
77+
if err := c.validateConfig(); err != nil {
78+
return fmt.Errorf("configuration validation failed: %w", err)
79+
}
80+
81+
// Create OAuth proxy
82+
oauthProxy, err := proxy.NewOAuthProxy(config)
83+
if err != nil {
84+
return fmt.Errorf("failed to create OAuth proxy: %w", err)
85+
}
86+
defer func() {
87+
if err := oauthProxy.Close(); err != nil {
88+
log.Printf("Error closing database: %v", err)
89+
}
90+
}()
91+
92+
// Get HTTP handler
93+
handler := oauthProxy.GetHandler()
94+
95+
// Start server
96+
address := fmt.Sprintf("%s:%s", c.Host, c.Port)
97+
log.Printf("Starting OAuth proxy server on %s", address)
98+
log.Printf("OAuth Provider: %s", c.OAuthAuthorizeURL)
99+
log.Printf("MCP Server: %s", c.MCPServerURL)
100+
log.Printf("Database: %s", c.getDatabaseType())
101+
102+
return http.ListenAndServe(address, handler)
103+
}
104+
105+
func (c *RootCmd) validateConfig() error {
106+
if c.OAuthClientID == "" {
107+
return fmt.Errorf("oauth-client-id is required")
108+
}
109+
if c.OAuthClientSecret == "" {
110+
return fmt.Errorf("oauth-client-secret is required")
111+
}
112+
if c.OAuthAuthorizeURL == "" {
113+
return fmt.Errorf("oauth-authorize-url is required")
114+
}
115+
if c.ScopesSupported == "" {
116+
return fmt.Errorf("scopes-supported is required")
117+
}
118+
if c.MCPServerURL == "" {
119+
return fmt.Errorf("mcp-server-url is required")
120+
}
121+
if c.Mode == proxy.ModeProxy {
122+
if u, err := url.Parse(c.MCPServerURL); err != nil || u.Scheme != "http" && u.Scheme != "https" {
123+
return fmt.Errorf("invalid MCP server URL: %w", err)
124+
} else if u.Path != "" && u.Path != "/" || u.RawQuery != "" || u.Fragment != "" {
125+
return fmt.Errorf("MCP server URL must not contain a path, query, or fragment")
126+
}
127+
}
128+
return nil
129+
}
130+
131+
func (c *RootCmd) getDatabaseType() string {
132+
if c.DatabaseDSN == "" {
133+
return "SQLite (data/oauth_proxy.db)"
134+
}
135+
if len(c.DatabaseDSN) > 10 && (c.DatabaseDSN[:11] == "postgres://" || c.DatabaseDSN[:14] == "postgresql://") {
136+
return "PostgreSQL"
137+
}
138+
return fmt.Sprintf("SQLite (%s)", c.DatabaseDSN)
139+
}
140+
141+
// Customizer interface implementation for additional command customization
142+
func (c *RootCmd) Customize(cobraCmd *cobra.Command) {
143+
cobraCmd.Use = "mcp-oauth-proxy"
144+
cobraCmd.Short = "OAuth 2.1 proxy server for MCP (Model Context Protocol)"
145+
cobraCmd.Long = `MCP OAuth Proxy is a comprehensive OAuth 2.1 proxy server that provides
146+
OAuth authorization server functionality with PostgreSQL/SQLite storage.
147+
148+
This proxy supports multiple OAuth providers (Google, Microsoft, GitHub) and
149+
proxies requests to MCP servers with user context headers.
150+
151+
Examples:
152+
# Start with environment variables
153+
export OAUTH_CLIENT_ID="your-google-client-id"
154+
export OAUTH_CLIENT_SECRET="your-secret"
155+
export OAUTH_AUTHORIZE_URL="https://accounts.google.com"
156+
export SCOPES_SUPPORTED="openid,profile,email"
157+
export MCP_SERVER_URL="http://localhost:3000"
158+
mcp-oauth-proxy
159+
160+
# Start with CLI flags
161+
mcp-oauth-proxy \
162+
--oauth-client-id="your-google-client-id" \
163+
--oauth-client-secret="your-secret" \
164+
--oauth-authorize-url="https://accounts.google.com" \
165+
--scopes-supported="openid,profile,email" \
166+
--mcp-server-url="http://localhost:3000"
167+
168+
# Use PostgreSQL database
169+
mcp-oauth-proxy \
170+
--database-dsn="postgres://user:pass@localhost:5432/oauth_db?sslmode=disable" \
171+
--oauth-client-id="your-client-id" \
172+
# ... other required flags
173+
174+
Configuration:
175+
Configuration values are loaded in this order (later values override earlier ones):
176+
1. Default values
177+
2. Environment variables
178+
3. Command line flags
179+
180+
Database Support:
181+
- PostgreSQL: Full ACID compliance, recommended for production
182+
- SQLite: Zero configuration, perfect for development and small deployments`
183+
184+
cobraCmd.Version = version
185+
}
186+
187+
// Execute is the main entry point for the CLI
188+
func Execute() error {
189+
rootCmd := &RootCmd{}
190+
cobraCmd := cmd.Command(rootCmd)
191+
return cobraCmd.Execute()
192+
}

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ module github.com/obot-platform/mcp-oauth-proxy
33
go 1.25.0
44

55
require (
6+
github.com/golang-jwt/jwt/v5 v5.3.0
67
github.com/gorilla/handlers v1.5.2
8+
github.com/gptscript-ai/cmd v0.0.0-20250530150401-bc71fddf8070
9+
github.com/spf13/cobra v1.7.0
710
github.com/stretchr/testify v1.10.0
11+
golang.org/x/oauth2 v0.30.0
812
gorm.io/driver/postgres v1.6.0
913
gorm.io/driver/sqlite v1.6.0
1014
gorm.io/gorm v1.30.1
@@ -13,6 +17,7 @@ require (
1317
require (
1418
github.com/davecgh/go-spew v1.1.1 // indirect
1519
github.com/felixge/httpsnoop v1.0.3 // indirect
20+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1621
github.com/jackc/pgpassfile v1.0.0 // indirect
1722
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
1823
github.com/jackc/pgx/v5 v5.7.5 // indirect
@@ -23,8 +28,8 @@ require (
2328
github.com/mattn/go-sqlite3 v1.14.32 // indirect
2429
github.com/pmezard/go-difflib v1.0.0 // indirect
2530
github.com/rogpeppe/go-internal v1.8.0 // indirect
31+
github.com/spf13/pflag v1.0.5 // indirect
2632
golang.org/x/crypto v0.41.0 // indirect
27-
golang.org/x/oauth2 v0.30.0 // indirect
2833
golang.org/x/sync v0.16.0 // indirect
2934
golang.org/x/text v0.28.0 // indirect
3035
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
12
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
34
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
56
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
7+
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
8+
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
69
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
710
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
11+
github.com/gptscript-ai/cmd v0.0.0-20250530150401-bc71fddf8070 h1:xm5ZZFraWFwxyE7TBEncCXArubCDZTwG6s5bpMzqhSY=
12+
github.com/gptscript-ai/cmd v0.0.0-20250530150401-bc71fddf8070/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw=
13+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
14+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
815
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
916
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
1017
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -30,6 +37,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
3037
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3138
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
3239
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
40+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
41+
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
42+
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
43+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
44+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
3345
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3446
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
3547
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

main.go

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,15 @@
11
package main
22

33
import (
4-
"log"
5-
"net/http"
4+
"fmt"
5+
"os"
66

7-
"github.com/obot-platform/mcp-oauth-proxy/pkg/proxy"
7+
"github.com/obot-platform/mcp-oauth-proxy/cmd"
88
)
99

1010
func main() {
11-
// Load configuration from environment variables
12-
config, err := proxy.LoadConfigFromEnv()
13-
if err != nil {
14-
log.Fatalf("Failed to load configuration: %v", err)
11+
if err := cmd.Execute(); err != nil {
12+
fmt.Println(err)
13+
os.Exit(1)
1514
}
16-
17-
proxy, err := proxy.NewOAuthProxy(config)
18-
if err != nil {
19-
log.Fatalf("Failed to create OAuth proxy: %v", err)
20-
}
21-
defer func() {
22-
if err := proxy.Close(); err != nil {
23-
log.Printf("Error closing database: %v", err)
24-
}
25-
}()
26-
27-
// Get HTTP handler
28-
handler := proxy.GetHandler()
29-
30-
// Start server
31-
log.Print("Starting OAuth proxy server on localhost:" + config.Port)
32-
log.Fatal(http.ListenAndServe(":"+config.Port, handler))
3315
}

0 commit comments

Comments
 (0)