mirror of
https://github.com/wso2/open-mcp-auth-proxy.git
synced 2025-06-28 01:23:30 +00:00
Refactor configurations
This commit is contained in:
parent
61d3c7e7e1
commit
5c1cc13ff3
6 changed files with 167 additions and 247 deletions
114
README.md
114
README.md
|
@ -31,7 +31,7 @@ go build -o openmcpauthproxy ./cmd/proxy
|
||||||
The Open MCP Auth Proxy supports two transport modes:
|
The Open MCP Auth Proxy supports two transport modes:
|
||||||
|
|
||||||
1. **SSE Mode (Default)**: For MCP servers that use Server-Sent Events transport
|
1. **SSE Mode (Default)**: For MCP servers that use Server-Sent Events transport
|
||||||
2. **stdio Mode**: For MCP servers that use stdio transport, which requires starting a MCP Server as a subprocess
|
2. **stdio Mode**: For MCP servers that use stdio transport, which requires starting a subprocess
|
||||||
|
|
||||||
You can specify the transport mode in the `config.yaml` file:
|
You can specify the transport mode in the `config.yaml` file:
|
||||||
|
|
||||||
|
@ -45,24 +45,38 @@ Or use the `--stdio` flag to override the configuration:
|
||||||
./openmcpauthproxy --stdio
|
./openmcpauthproxy --stdio
|
||||||
```
|
```
|
||||||
|
|
||||||
**Configuration Requirements by Transport Mode:**
|
### Configuration
|
||||||
|
|
||||||
**SSE Mode:**
|
The configuration uses a unified structure with common settings and transport-specific options:
|
||||||
- `mcp_server_base_url` is required (points to an external MCP server)
|
|
||||||
- The `command` section is optional and will be ignored
|
|
||||||
- No subprocess will be started
|
|
||||||
- The proxy expects an external MCP server to be running at the specified URL
|
|
||||||
|
|
||||||
**stdio Mode:**
|
```yaml
|
||||||
- The `command` section in `config.yaml` is mandatory
|
# Common configuration
|
||||||
- `mcp_server_base_url` is optional (if not specified, it will use `command.base_url`)
|
listen_port: 8080
|
||||||
- The proxy will start a subprocess as specified in the command configuration
|
base_url: "http://localhost:8000" # Base URL for the MCP server
|
||||||
- The subprocess will be terminated when the proxy shuts down
|
port: 8000 # Port for the MCP server
|
||||||
|
|
||||||
|
# Path configuration
|
||||||
|
paths:
|
||||||
|
sse: "/sse" # SSE endpoint path
|
||||||
|
messages: "/messages" # Messages endpoint path
|
||||||
|
|
||||||
|
# Transport mode configuration
|
||||||
|
transport_mode: "sse" # Options: "sse" or "stdio"
|
||||||
|
|
||||||
|
# stdio-specific configuration (used only when transport_mode is "stdio")
|
||||||
|
stdio:
|
||||||
|
enabled: true
|
||||||
|
user_command: "npx -y @modelcontextprotocol/server-github"
|
||||||
|
work_dir: "" # Working directory (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- In SSE mode, the proxy connects to an external MCP server at the specified `base_url`
|
||||||
|
- In stdio mode, the proxy starts a subprocess using the `stdio.user_command` configuration
|
||||||
|
- Common settings like `base_url`, `port`, and `paths` are used for both transport modes
|
||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
Allows you to just enable authentication and authorization for your MCP server with the preconfigured auth provider powered by Asgardeo.
|
|
||||||
|
|
||||||
If you don't have an MCP server, follow the instructions given here to start your own MCP server for testing purposes.
|
If you don't have an MCP server, follow the instructions given here to start your own MCP server for testing purposes.
|
||||||
|
|
||||||
1. Navigate to `resources` directory.
|
1. Navigate to `resources` directory.
|
||||||
|
@ -91,36 +105,28 @@ python3 echo_server.py
|
||||||
|
|
||||||
#### Configure the Auth Proxy
|
#### Configure the Auth Proxy
|
||||||
|
|
||||||
Update the following parameters in `config.yaml`.
|
Update the necessary parameters in `config.yaml` as shown in the examples above.
|
||||||
|
|
||||||
### Configuration examples:
|
|
||||||
|
|
||||||
**SSE mode (using external MCP server):**
|
|
||||||
```yaml
|
|
||||||
transport_mode: "sse" # Transport mode: "sse" or "stdio"
|
|
||||||
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server (required in SSE mode)
|
|
||||||
listen_port: 8080 # Address where the proxy will listen
|
|
||||||
```
|
|
||||||
|
|
||||||
**stdio mode (using subprocess):**
|
|
||||||
```yaml
|
|
||||||
transport_mode: "stdio" # Transport mode: "sse" or "stdio"
|
|
||||||
|
|
||||||
command:
|
|
||||||
enabled: true # Must be true in stdio mode
|
|
||||||
user_command: "npx -y @modelcontextprotocol/server-github" # Required in stdio mode
|
|
||||||
base_url: "http://localhost:8000" # Used as MCP server base URL if not specified above
|
|
||||||
port: 8000
|
|
||||||
sse_path: "/sse" # SSE endpoint path
|
|
||||||
message_path: "/messages" # Messages endpoint path
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Start the Auth Proxy
|
#### Start the Auth Proxy
|
||||||
|
|
||||||
|
For the demo mode with pre-configured authentication:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./openmcpauthproxy --demo
|
./openmcpauthproxy --demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For standard mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./openmcpauthproxy
|
||||||
|
```
|
||||||
|
|
||||||
|
For stdio mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./openmcpauthproxy --stdio
|
||||||
|
```
|
||||||
|
|
||||||
The `--demo` flag enables a demonstration mode with pre-configured authentication and authorization with a sandbox powered by [Asgardeo](https://asgardeo.io/).
|
The `--demo` flag enables a demonstration mode with pre-configured authentication and authorization with a sandbox powered by [Asgardeo](https://asgardeo.io/).
|
||||||
|
|
||||||
#### Connect Using an MCP Client
|
#### Connect Using an MCP Client
|
||||||
|
@ -143,9 +149,17 @@ Enable authorization for the MCP server through your own Asgardeo organization
|
||||||
Create a configuration file config.yaml with the following parameters:
|
Create a configuration file config.yaml with the following parameters:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
|
# Common configuration
|
||||||
listen_port: 8080 # Address where the proxy will listen
|
listen_port: 8080
|
||||||
transport_mode: "sse" # Transport mode: "sse" or "stdio"
|
base_url: "http://localhost:8000" # Base URL for the MCP server
|
||||||
|
|
||||||
|
# Path configuration
|
||||||
|
paths:
|
||||||
|
sse: "/sse"
|
||||||
|
messages: "/messages"
|
||||||
|
|
||||||
|
# Transport mode
|
||||||
|
transport_mode: "sse" # or "stdio"
|
||||||
|
|
||||||
asgardeo:
|
asgardeo:
|
||||||
org_name: "<org_name>" # Your Asgardeo org name
|
org_name: "<org_name>" # Your Asgardeo org name
|
||||||
|
@ -159,26 +173,6 @@ asgardeo:
|
||||||
./openmcpauthproxy --asgardeo
|
./openmcpauthproxy --asgardeo
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use with any standard OAuth Server
|
|
||||||
|
|
||||||
Enable authorization for the MCP server with a compliant OAuth server
|
|
||||||
|
|
||||||
#### Configuration
|
|
||||||
|
|
||||||
Create a configuration file config.yaml with the following parameters:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
|
|
||||||
listen_port: 8080 # Address where the proxy will listen
|
|
||||||
transport_mode: "sse" # Transport mode: "sse" or "stdio"
|
|
||||||
```
|
|
||||||
**TODO**: Update the configs for a standard OAuth Server.
|
|
||||||
|
|
||||||
#### Start the Auth Proxy
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./openmcpauthproxy
|
|
||||||
```
|
|
||||||
#### Integrating with existing OAuth Providers
|
#### Integrating with existing OAuth Providers
|
||||||
|
|
||||||
- [Auth0](docs/Auth0.md) - Enable authorization for the MCP server through your Auth0 organization.
|
- [Auth0](docs/Auth0.md) - Enable authorization for the MCP server through your Auth0 organization.
|
|
@ -37,43 +37,37 @@ func main() {
|
||||||
// Override transport mode if stdio flag is set
|
// Override transport mode if stdio flag is set
|
||||||
if *stdioMode {
|
if *stdioMode {
|
||||||
cfg.TransportMode = config.StdioTransport
|
cfg.TransportMode = config.StdioTransport
|
||||||
// Validate command config for stdio mode
|
// Ensure stdio is enabled
|
||||||
if err := cfg.Command.Validate(cfg.TransportMode); err != nil {
|
cfg.Stdio.Enabled = true
|
||||||
|
// Re-validate config
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
logger.Error("Configuration error: %v", err)
|
logger.Error("Configuration error: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Ensure MCPPaths are properly configured
|
logger.Info("Using transport mode: %s", cfg.TransportMode)
|
||||||
if cfg.TransportMode == config.StdioTransport && cfg.Command.Enabled {
|
logger.Info("Using MCP server base URL: %s", cfg.BaseURL)
|
||||||
// Use command.base_url for MCPServerBaseURL in stdio mode
|
logger.Info("Using MCP paths: SSE=%s, Messages=%s", cfg.Paths.SSE, cfg.Paths.Messages)
|
||||||
cfg.MCPServerBaseURL = cfg.Command.GetBaseURL()
|
|
||||||
|
|
||||||
// Use command paths for MCPPaths in stdio mode
|
// 2. Start subprocess if configured and in stdio mode
|
||||||
cfg.MCPPaths = cfg.Command.GetPaths()
|
|
||||||
|
|
||||||
logger.Info("Using MCP server baseUrl: %s", cfg.MCPServerBaseURL)
|
|
||||||
logger.Info("Using MCP paths: %v", cfg.MCPPaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Start subprocess if configured and in stdio mode
|
|
||||||
var procManager *subprocess.Manager
|
var procManager *subprocess.Manager
|
||||||
if cfg.TransportMode == config.StdioTransport && cfg.Command.Enabled && cfg.Command.UserCommand != "" {
|
if cfg.TransportMode == config.StdioTransport && cfg.Stdio.Enabled {
|
||||||
// Ensure all required dependencies are available
|
// Ensure all required dependencies are available
|
||||||
if err := subprocess.EnsureDependenciesAvailable(cfg.Command.UserCommand); err != nil {
|
if err := subprocess.EnsureDependenciesAvailable(cfg.Stdio.UserCommand); err != nil {
|
||||||
logger.Warn("%v", err)
|
logger.Warn("%v", err)
|
||||||
logger.Warn("Subprocess may fail to start due to missing dependencies")
|
logger.Warn("Subprocess may fail to start due to missing dependencies")
|
||||||
}
|
}
|
||||||
|
|
||||||
procManager = subprocess.NewManager()
|
procManager = subprocess.NewManager()
|
||||||
if err := procManager.Start(&cfg.Command); err != nil {
|
if err := procManager.Start(cfg); err != nil {
|
||||||
logger.Warn("Failed to start subprocess: %v", err)
|
logger.Warn("Failed to start subprocess: %v", err)
|
||||||
}
|
}
|
||||||
} else if cfg.TransportMode == config.SSETransport {
|
} else if cfg.TransportMode == config.SSETransport {
|
||||||
logger.Info("Using SSE transport mode, not starting subprocess")
|
logger.Info("Using SSE transport mode, not starting subprocess")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Create the chosen provider
|
// 3. Create the chosen provider
|
||||||
var provider authz.Provider
|
var provider authz.Provider
|
||||||
if *demoMode {
|
if *demoMode {
|
||||||
cfg.Mode = "demo"
|
cfg.Mode = "demo"
|
||||||
|
@ -92,18 +86,18 @@ func main() {
|
||||||
provider = authz.NewDefaultProvider(cfg)
|
provider = authz.NewDefaultProvider(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. (Optional) Fetch JWKS if you want local JWT validation
|
// 4. (Optional) Fetch JWKS if you want local JWT validation
|
||||||
if err := util.FetchJWKS(cfg.JWKSURL); err != nil {
|
if err := util.FetchJWKS(cfg.JWKSURL); err != nil {
|
||||||
logger.Error("Failed to fetch JWKS: %v", err)
|
logger.Error("Failed to fetch JWKS: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Build the main router
|
// 5. Build the main router
|
||||||
mux := proxy.NewRouter(cfg, provider)
|
mux := proxy.NewRouter(cfg, provider)
|
||||||
|
|
||||||
listen_address := fmt.Sprintf(":%d", cfg.ListenPort)
|
listen_address := fmt.Sprintf(":%d", cfg.ListenPort)
|
||||||
|
|
||||||
// 7. Start the server
|
// 6. Start the server
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: listen_address,
|
Addr: listen_address,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
|
@ -117,18 +111,18 @@ func main() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 8. Wait for shutdown signal
|
// 7. Wait for shutdown signal
|
||||||
stop := make(chan os.Signal, 1)
|
stop := make(chan os.Signal, 1)
|
||||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
<-stop
|
<-stop
|
||||||
logger.Info("Shutting down...")
|
logger.Info("Shutting down...")
|
||||||
|
|
||||||
// 9. First terminate subprocess if running
|
// 8. First terminate subprocess if running
|
||||||
if procManager != nil && procManager.IsRunning() {
|
if procManager != nil && procManager.IsRunning() {
|
||||||
procManager.Shutdown()
|
procManager.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. Then shutdown the server
|
// 9. Then shutdown the server
|
||||||
logger.Info("Shutting down HTTP server...")
|
logger.Info("Shutting down HTTP server...")
|
||||||
shutdownCtx, cancel := proxy.NewShutdownContext(5 * time.Second)
|
shutdownCtx, cancel := proxy.NewShutdownContext(5 * time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -138,29 +132,3 @@ func main() {
|
||||||
}
|
}
|
||||||
logger.Info("Stopped.")
|
logger.Info("Stopped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to ensure a path is in a list
|
|
||||||
func ensurePathInList(paths *[]string, path string) {
|
|
||||||
// Check if path exists in the list
|
|
||||||
for _, p := range *paths {
|
|
||||||
if p == path {
|
|
||||||
return // Path already exists
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Path doesn't exist, add it
|
|
||||||
*paths = append(*paths, path)
|
|
||||||
logger.Info("Added path %s to MCPPaths", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to ensure an origin is in a list
|
|
||||||
func ensureOriginInList(origins *[]string, origin string) {
|
|
||||||
// Check if origin exists in the list
|
|
||||||
for _, o := range *origins {
|
|
||||||
if o == origin {
|
|
||||||
return // Origin already exists
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Origin doesn't exist, add it
|
|
||||||
*origins = append(*origins, origin)
|
|
||||||
logger.Info("Added %s to allowed CORS origins", origin)
|
|
||||||
}
|
|
36
config.yaml
36
config.yaml
|
@ -1,30 +1,31 @@
|
||||||
# config.yaml
|
# config.yaml
|
||||||
|
|
||||||
transport_mode: "stdio" # Options: "sse" or "stdio"
|
# Common configuration for all transport modes
|
||||||
|
|
||||||
# For SSE mode, mcp_server_base_url and mcp_paths are required
|
|
||||||
# For stdio mode, both are optional and will be derived from command configuration if not specified
|
|
||||||
mcp_server_base_url: "http://localhost:8000"
|
|
||||||
listen_port: 8080
|
listen_port: 8080
|
||||||
timeout_seconds: 10
|
base_url: "http://localhost:8000" # Base URL for the MCP server
|
||||||
mcp_paths: # Required in SSE mode, ignored in stdio mode (derived from command)
|
|
||||||
- /messages/
|
|
||||||
- /sse
|
|
||||||
|
|
||||||
# Subprocess configuration
|
|
||||||
command:
|
|
||||||
enabled: true
|
|
||||||
user_command: "npx -y @modelcontextprotocol/server-github" # User only needs to provide this part
|
|
||||||
base_url: "http://localhost:8000" # Will be used for CORS and in the full command
|
|
||||||
port: 8000 # Port for the MCP server
|
port: 8000 # Port for the MCP server
|
||||||
sse_path: "/sse" # SSE endpoint path
|
timeout_seconds: 10
|
||||||
message_path: "/messages" # Messages endpoint path
|
|
||||||
|
# Path configuration
|
||||||
|
paths:
|
||||||
|
sse: "/sse" # SSE endpoint path
|
||||||
|
messages: "/messages" # Messages endpoint path
|
||||||
|
|
||||||
|
# Transport mode configuration
|
||||||
|
transport_mode: "sse" # Options: "sse" or "stdio"
|
||||||
|
|
||||||
|
# stdio-specific configuration (used only when transport_mode is "stdio")
|
||||||
|
stdio:
|
||||||
|
enabled: true
|
||||||
|
user_command: "npx -y @modelcontextprotocol/server-github"
|
||||||
work_dir: "" # Working directory (optional)
|
work_dir: "" # Working directory (optional)
|
||||||
# env: # Environment variables (optional)
|
# env: # Environment variables (optional)
|
||||||
# - "NODE_ENV=development"
|
# - "NODE_ENV=development"
|
||||||
|
|
||||||
|
# Path mapping (optional)
|
||||||
path_mapping:
|
path_mapping:
|
||||||
|
|
||||||
|
# CORS configuration
|
||||||
cors:
|
cors:
|
||||||
allowed_origins:
|
allowed_origins:
|
||||||
- "http://localhost:5173"
|
- "http://localhost:5173"
|
||||||
|
@ -38,6 +39,7 @@ cors:
|
||||||
- "Content-Type"
|
- "Content-Type"
|
||||||
allow_credentials: true
|
allow_credentials: true
|
||||||
|
|
||||||
|
# Demo configuration for Asgardeo
|
||||||
demo:
|
demo:
|
||||||
org_name: "openmcpauthdemo"
|
org_name: "openmcpauthdemo"
|
||||||
client_id: "N0U9e_NNGr9mP_0fPnPfPI0a6twa"
|
client_id: "N0U9e_NNGr9mP_0fPnPfPI0a6twa"
|
||||||
|
|
|
@ -15,7 +15,21 @@ const (
|
||||||
StdioTransport TransportMode = "stdio"
|
StdioTransport TransportMode = "stdio"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AsgardeoConfig groups all Asgardeo-specific fields
|
// Common path configuration for all transport modes
|
||||||
|
type PathsConfig struct {
|
||||||
|
SSE string `yaml:"sse"`
|
||||||
|
Messages string `yaml:"messages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdioConfig contains stdio-specific configuration
|
||||||
|
type StdioConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
UserCommand string `yaml:"user_command"` // The command provided by the user
|
||||||
|
WorkDir string `yaml:"work_dir"` // Working directory (optional)
|
||||||
|
Args []string `yaml:"args,omitempty"` // Additional arguments
|
||||||
|
Env []string `yaml:"env,omitempty"` // Environment variables
|
||||||
|
}
|
||||||
|
|
||||||
type DemoConfig struct {
|
type DemoConfig struct {
|
||||||
ClientID string `yaml:"client_id"`
|
ClientID string `yaml:"client_id"`
|
||||||
ClientSecret string `yaml:"client_secret"`
|
ClientSecret string `yaml:"client_secret"`
|
||||||
|
@ -70,123 +84,74 @@ type DefaultConfig struct {
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AuthServerBaseURL string
|
AuthServerBaseURL string
|
||||||
MCPServerBaseURL string `yaml:"mcp_server_base_url"`
|
|
||||||
ListenPort int `yaml:"listen_port"`
|
ListenPort int `yaml:"listen_port"`
|
||||||
|
BaseURL string `yaml:"base_url"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
JWKSURL string
|
JWKSURL string
|
||||||
TimeoutSeconds int `yaml:"timeout_seconds"`
|
TimeoutSeconds int `yaml:"timeout_seconds"`
|
||||||
MCPPaths []string `yaml:"mcp_paths"`
|
|
||||||
PathMapping map[string]string `yaml:"path_mapping"`
|
PathMapping map[string]string `yaml:"path_mapping"`
|
||||||
Mode string `yaml:"mode"`
|
Mode string `yaml:"mode"`
|
||||||
CORSConfig CORSConfig `yaml:"cors"`
|
CORSConfig CORSConfig `yaml:"cors"`
|
||||||
TransportMode TransportMode `yaml:"transport_mode"`
|
TransportMode TransportMode `yaml:"transport_mode"`
|
||||||
|
Paths PathsConfig `yaml:"paths"`
|
||||||
|
Stdio StdioConfig `yaml:"stdio"`
|
||||||
|
|
||||||
// Nested config for Asgardeo
|
// Nested config for Asgardeo
|
||||||
Demo DemoConfig `yaml:"demo"`
|
Demo DemoConfig `yaml:"demo"`
|
||||||
Asgardeo AsgardeoConfig `yaml:"asgardeo"`
|
Asgardeo AsgardeoConfig `yaml:"asgardeo"`
|
||||||
Default DefaultConfig `yaml:"default"`
|
Default DefaultConfig `yaml:"default"`
|
||||||
Command Command `yaml:"command"` // Command to run
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command struct with explicit configuration for all relevant paths
|
// Validate checks if the config is valid based on transport mode
|
||||||
type Command struct {
|
func (c *Config) Validate() error {
|
||||||
Enabled bool `yaml:"enabled"`
|
// Validate based on transport mode
|
||||||
UserCommand string `yaml:"user_command"` // Only the part provided by the user
|
if c.TransportMode == StdioTransport {
|
||||||
BaseUrl string `yaml:"base_url"` // Base URL for the MCP server
|
if !c.Stdio.Enabled {
|
||||||
Port int `yaml:"port"` // Port for the MCP server
|
return fmt.Errorf("stdio.enabled must be true in stdio transport mode")
|
||||||
SsePath string `yaml:"sse_path"` // SSE endpoint path
|
}
|
||||||
MessagePath string `yaml:"message_path"` // Messages endpoint path
|
if c.Stdio.UserCommand == "" {
|
||||||
WorkDir string `yaml:"work_dir"` // Working directory
|
return fmt.Errorf("stdio.user_command is required in stdio transport mode")
|
||||||
Args []string `yaml:"args,omitempty"` // Additional arguments
|
}
|
||||||
Env []string `yaml:"env,omitempty"` // Environment variables
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks if the command config is valid based on transport mode
|
// Validate paths
|
||||||
func (c *Command) Validate(transportMode TransportMode) error {
|
if c.Paths.SSE == "" {
|
||||||
if transportMode == StdioTransport {
|
c.Paths.SSE = "/sse" // Default value
|
||||||
if !c.Enabled {
|
|
||||||
return fmt.Errorf("command must be enabled in stdio transport mode")
|
|
||||||
}
|
}
|
||||||
if c.UserCommand == "" {
|
if c.Paths.Messages == "" {
|
||||||
return fmt.Errorf("user_command is required in stdio transport mode")
|
c.Paths.Messages = "/messages" // Default value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate base URL
|
||||||
|
if c.BaseURL == "" {
|
||||||
|
if c.Port > 0 {
|
||||||
|
c.BaseURL = fmt.Sprintf("http://localhost:%d", c.Port)
|
||||||
|
} else {
|
||||||
|
c.BaseURL = "http://localhost:8000" // Default value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBaseURL returns the base URL for the MCP server
|
// GetMCPPaths returns the list of paths that should be proxied to the MCP server
|
||||||
func (c *Command) GetBaseURL() string {
|
func (c *Config) GetMCPPaths() []string {
|
||||||
if c.BaseUrl != "" {
|
return []string{c.Paths.SSE, c.Paths.Messages}
|
||||||
return c.BaseUrl
|
|
||||||
}
|
|
||||||
if c.Port > 0 {
|
|
||||||
return fmt.Sprintf("http://localhost:%d", c.Port)
|
|
||||||
}
|
|
||||||
return "http://localhost:8000" // default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPaths returns the SSE and message paths
|
// BuildExecCommand constructs the full command string for execution in stdio mode
|
||||||
func (c *Command) GetPaths() []string {
|
func (c *Config) BuildExecCommand() string {
|
||||||
var paths []string
|
if c.Stdio.UserCommand == "" {
|
||||||
|
|
||||||
// Add SSE path
|
|
||||||
ssePath := c.SsePath
|
|
||||||
if ssePath == "" {
|
|
||||||
ssePath = "/sse" // default
|
|
||||||
}
|
|
||||||
paths = append(paths, ssePath)
|
|
||||||
|
|
||||||
// Add message path
|
|
||||||
messagePath := c.MessagePath
|
|
||||||
if messagePath == "" {
|
|
||||||
messagePath = "/messages" // default
|
|
||||||
}
|
|
||||||
paths = append(paths, messagePath)
|
|
||||||
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildExecCommand constructs the full command string for execution
|
|
||||||
func (c *Command) BuildExecCommand() string {
|
|
||||||
if c.UserCommand == "" {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply defaults if not specified
|
|
||||||
port := c.Port
|
|
||||||
if port == 0 {
|
|
||||||
port = 8000
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUrl := c.BaseUrl
|
|
||||||
if baseUrl == "" {
|
|
||||||
baseUrl = fmt.Sprintf("http://localhost:%d", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
ssePath := c.SsePath
|
|
||||||
if ssePath == "" {
|
|
||||||
ssePath = "/sse"
|
|
||||||
}
|
|
||||||
|
|
||||||
messagePath := c.MessagePath
|
|
||||||
if messagePath == "" {
|
|
||||||
messagePath = "/messages"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the full command
|
// Construct the full command
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
`npx -y supergateway --stdio "%s" --port %d --baseUrl %s --ssePath %s --messagePath %s`,
|
`npx -y supergateway --stdio "%s" --port %d --baseUrl %s --ssePath %s --messagePath %s`,
|
||||||
c.UserCommand, port, baseUrl, ssePath, messagePath,
|
c.Stdio.UserCommand, c.Port, c.BaseURL, c.Paths.SSE, c.Paths.Messages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExec returns the complete command string for execution
|
|
||||||
func (c *Command) GetExec() string {
|
|
||||||
if c.UserCommand == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.BuildExecCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConfig reads a YAML config file into Config struct.
|
// LoadConfig reads a YAML config file into Config struct.
|
||||||
func LoadConfig(path string) (*Config, error) {
|
func LoadConfig(path string) (*Config, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
@ -200,6 +165,8 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
if err := decoder.Decode(&cfg); err != nil {
|
if err := decoder.Decode(&cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set default values
|
||||||
if cfg.TimeoutSeconds == 0 {
|
if cfg.TimeoutSeconds == 0 {
|
||||||
cfg.TimeoutSeconds = 15 // default
|
cfg.TimeoutSeconds = 15 // default
|
||||||
}
|
}
|
||||||
|
@ -209,25 +176,15 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
cfg.TransportMode = SSETransport // Default to SSE
|
cfg.TransportMode = SSETransport // Default to SSE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate command config based on transport mode
|
// Set default port if not specified
|
||||||
if err := cfg.Command.Validate(cfg.TransportMode); err != nil {
|
if cfg.Port == 0 {
|
||||||
|
cfg.Port = 8000 // default
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the configuration
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// In stdio mode, use command.base_url for MCPServerBaseURL if it's not explicitly set
|
|
||||||
if cfg.TransportMode == StdioTransport && cfg.MCPServerBaseURL == "" {
|
|
||||||
cfg.MCPServerBaseURL = cfg.Command.GetBaseURL()
|
|
||||||
} else if cfg.TransportMode == SSETransport && cfg.MCPServerBaseURL == "" {
|
|
||||||
return nil, fmt.Errorf("mcp_server_base_url is required in SSE transport mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
// In stdio mode, set the MCPPaths from the command configuration
|
|
||||||
if cfg.TransportMode == StdioTransport && cfg.Command.Enabled {
|
|
||||||
// Override MCPPaths with paths from command configuration
|
|
||||||
cfg.MCPPaths = cfg.Command.GetPaths()
|
|
||||||
} else if cfg.TransportMode == SSETransport && len(cfg.MCPPaths) == 0 {
|
|
||||||
return nil, fmt.Errorf("mcp_paths are required in SSE transport mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
|
@ -82,7 +82,8 @@ func NewRouter(cfg *config.Config, provider authz.Provider) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MCP paths
|
// MCP paths
|
||||||
for _, path := range cfg.MCPPaths {
|
mcpPaths := cfg.GetMCPPaths()
|
||||||
|
for _, path := range mcpPaths {
|
||||||
mux.HandleFunc(path, buildProxyHandler(cfg, modifiers))
|
mux.HandleFunc(path, buildProxyHandler(cfg, modifiers))
|
||||||
registeredPaths[path] = true
|
registeredPaths[path] = true
|
||||||
}
|
}
|
||||||
|
@ -105,7 +106,8 @@ func buildProxyHandler(cfg *config.Config, modifiers map[string]RequestModifier)
|
||||||
logger.Error("Invalid auth server URL: %v", err)
|
logger.Error("Invalid auth server URL: %v", err)
|
||||||
panic(err) // Fatal error that prevents startup
|
panic(err) // Fatal error that prevents startup
|
||||||
}
|
}
|
||||||
mcpBase, err := url.Parse(cfg.MCPServerBaseURL)
|
|
||||||
|
mcpBase, err := url.Parse(cfg.BaseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Invalid MCP server URL: %v", err)
|
logger.Error("Invalid MCP server URL: %v", err)
|
||||||
panic(err) // Fatal error that prevents startup
|
panic(err) // Fatal error that prevents startup
|
||||||
|
@ -113,11 +115,7 @@ func buildProxyHandler(cfg *config.Config, modifiers map[string]RequestModifier)
|
||||||
|
|
||||||
// Detect SSE paths from config
|
// Detect SSE paths from config
|
||||||
ssePaths := make(map[string]bool)
|
ssePaths := make(map[string]bool)
|
||||||
for _, p := range cfg.MCPPaths {
|
ssePaths[cfg.Paths.SSE] = true
|
||||||
if p == "/sse" {
|
|
||||||
ssePaths[p] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
origin := r.Header.Get("Origin")
|
origin := r.Header.Get("Origin")
|
||||||
|
@ -294,7 +292,8 @@ func isAuthPath(path string) bool {
|
||||||
|
|
||||||
// isMCPPath checks if the path is an MCP path
|
// isMCPPath checks if the path is an MCP path
|
||||||
func isMCPPath(path string, cfg *config.Config) bool {
|
func isMCPPath(path string, cfg *config.Config) bool {
|
||||||
for _, p := range cfg.MCPPaths {
|
mcpPaths := cfg.GetMCPPaths()
|
||||||
|
for _, p := range mcpPaths {
|
||||||
if strings.HasPrefix(path, p) {
|
if strings.HasPrefix(path, p) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,8 @@ func (m *Manager) SetShutdownDelay(duration time.Duration) {
|
||||||
m.shutdownDelay = duration
|
m.shutdownDelay = duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start launches a subprocess based on the command configuration
|
// Start launches a subprocess based on the configuration
|
||||||
func (m *Manager) Start(cmdConfig *config.Command) error {
|
func (m *Manager) Start(cfg *config.Config) error {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
@ -76,12 +76,12 @@ func (m *Manager) Start(cmdConfig *config.Command) error {
|
||||||
return os.ErrExist
|
return os.ErrExist
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cmdConfig.Enabled || cmdConfig.UserCommand == "" {
|
if !cfg.Stdio.Enabled || cfg.Stdio.UserCommand == "" {
|
||||||
return nil // Nothing to start
|
return nil // Nothing to start
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the full command string
|
// Get the full command string
|
||||||
execCommand := cmdConfig.GetExec()
|
execCommand := cfg.BuildExecCommand()
|
||||||
if execCommand == "" {
|
if execCommand == "" {
|
||||||
return nil // No command to execute
|
return nil // No command to execute
|
||||||
}
|
}
|
||||||
|
@ -92,13 +92,13 @@ func (m *Manager) Start(cmdConfig *config.Command) error {
|
||||||
cmd := exec.Command("sh", "-c", execCommand)
|
cmd := exec.Command("sh", "-c", execCommand)
|
||||||
|
|
||||||
// Set working directory if specified
|
// Set working directory if specified
|
||||||
if cmdConfig.WorkDir != "" {
|
if cfg.Stdio.WorkDir != "" {
|
||||||
cmd.Dir = cmdConfig.WorkDir
|
cmd.Dir = cfg.Stdio.WorkDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set environment variables if specified
|
// Set environment variables if specified
|
||||||
if len(cmdConfig.Env) > 0 {
|
if len(cfg.Stdio.Env) > 0 {
|
||||||
cmd.Env = append(os.Environ(), cmdConfig.Env...)
|
cmd.Env = append(os.Environ(), cfg.Stdio.Env...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture stdout/stderr
|
// Capture stdout/stderr
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue