Add support for SSE and stdio transport modes

This commit is contained in:
Chiran Fernando 2025-04-05 09:37:43 +05:30
parent 2548eb569a
commit 61d3c7e7e1
4 changed files with 177 additions and 48 deletions

View file

@ -26,11 +26,44 @@ go build -o openmcpauthproxy ./cmd/proxy
## Using Open MCP Auth Proxy
### Transport Modes
The Open MCP Auth Proxy supports two transport modes:
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
You can specify the transport mode in the `config.yaml` file:
```yaml
transport_mode: "sse" # Options: "sse" or "stdio"
```
Or use the `--stdio` flag to override the configuration:
```bash
./openmcpauthproxy --stdio
```
**Configuration Requirements by Transport Mode:**
**SSE Mode:**
- `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:**
- The `command` section in `config.yaml` is mandatory
- `mcp_server_base_url` is optional (if not specified, it will use `command.base_url`)
- The proxy will start a subprocess as specified in the command configuration
- The subprocess will be terminated when the proxy shuts down
### Quick Start
Allows you to just enable authentication and authorization for your MCP server with the preconfigured auth provider powered by Asgardeo.
If you dont 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.
2. Initialize a virtual environment.
@ -60,13 +93,28 @@ python3 echo_server.py
Update the following parameters in `config.yaml`.
### demo mode configuration:
### Configuration examples:
**SSE mode (using external MCP server):**
```yaml
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
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
```bash
@ -86,7 +134,7 @@ Enable authorization for the MCP server through your own Asgardeo organization
1. [Register]([url](https://asgardeo.io/signup)) and create an organization in Asgardeo
2. Now, you need to authorize the OpenMCPAuthProxy to allow dynamically registering MCP Clients as applications in your organization. To do that,
1. Create an [M2M application](https://wso2.com/asgardeo/docs/guides/applications/register-machine-to-machine-app/)
1. [Authorize this application](https://wso2.com/asgardeo/docs/guides/applications/register-machine-to-machine-app/#authorize-the-api-resources-for-the-app) to invoke “Application Management API” with the `internal_application_mgt_create` scope.
1. [Authorize this application](https://wso2.com/asgardeo/docs/guides/applications/register-machine-to-machine-app/#authorize-the-api-resources-for-the-app) to invoke "Application Management API" with the `internal_application_mgt_create` scope.
![image](https://github.com/user-attachments/assets/0bd57cac-1904-48cc-b7aa-0530224bc41a)
2. Note the **Client ID** and **Client secret** of this application. This is required by the auth proxy
@ -97,6 +145,7 @@ 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"
asgardeo:
org_name: "<org_name>" # Your Asgardeo org name
@ -121,6 +170,7 @@ 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.
@ -131,4 +181,4 @@ listen_port: 8080 # Address where the proxy will lis
```
#### 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.

View file

@ -22,6 +22,7 @@ func main() {
demoMode := flag.Bool("demo", false, "Use Asgardeo-based provider (demo).")
asgardeoMode := flag.Bool("asgardeo", false, "Use Asgardeo-based provider (asgardeo).")
debugMode := flag.Bool("debug", false, "Enable debug logging")
stdioMode := flag.Bool("stdio", false, "Use stdio transport mode instead of SSE")
flag.Parse()
logger.SetDebug(*debugMode)
@ -33,39 +34,31 @@ func main() {
os.Exit(1)
}
// 2. Ensure MCPPaths includes the configured paths from the command
if cfg.Command.Enabled {
// Add SSE path to MCPPaths if not already present
ssePath := cfg.Command.SsePath
if ssePath == "" {
ssePath = "/sse" // default
// Override transport mode if stdio flag is set
if *stdioMode {
cfg.TransportMode = config.StdioTransport
// Validate command config for stdio mode
if err := cfg.Command.Validate(cfg.TransportMode); err != nil {
logger.Error("Configuration error: %v", err)
os.Exit(1)
}
messagePath := cfg.Command.MessagePath
if messagePath == "" {
messagePath = "/messages" // default
}
// Make sure paths are in MCPPaths
ensurePathInList(&cfg.MCPPaths, ssePath)
ensurePathInList(&cfg.MCPPaths, messagePath)
// Configure baseUrl
baseUrl := cfg.Command.BaseUrl
if baseUrl == "" {
if cfg.Command.Port > 0 {
baseUrl = fmt.Sprintf("http://localhost:%d", cfg.Command.Port)
} else {
baseUrl = "http://localhost:8000" // default
}
}
logger.Info("Using MCP server baseUrl: %s", baseUrl)
}
// 3. Start subprocess if configured
// 2. Ensure MCPPaths are properly configured
if cfg.TransportMode == config.StdioTransport && cfg.Command.Enabled {
// Use command.base_url for MCPServerBaseURL in stdio mode
cfg.MCPServerBaseURL = cfg.Command.GetBaseURL()
// Use command paths for MCPPaths 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
if cfg.Command.Enabled && cfg.Command.UserCommand != "" {
if cfg.TransportMode == config.StdioTransport && cfg.Command.Enabled && cfg.Command.UserCommand != "" {
// Ensure all required dependencies are available
if err := subprocess.EnsureDependenciesAvailable(cfg.Command.UserCommand); err != nil {
logger.Warn("%v", err)
@ -76,6 +69,8 @@ func main() {
if err := procManager.Start(&cfg.Command); err != nil {
logger.Warn("Failed to start subprocess: %v", err)
}
} else if cfg.TransportMode == config.SSETransport {
logger.Info("Using SSE transport mode, not starting subprocess")
}
// 4. Create the chosen provider

View file

@ -1,10 +1,13 @@
# config.yaml
transport_mode: "stdio" # Options: "sse" or "stdio"
# 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
timeout_seconds: 10
mcp_paths:
mcp_paths: # Required in SSE mode, ignored in stdio mode (derived from command)
- /messages/
- /sse

View file

@ -1,11 +1,20 @@
package config
import (
"os"
"fmt"
"os"
"gopkg.in/yaml.v2"
)
// Transport mode for MCP server
type TransportMode string
const (
SSETransport TransportMode = "sse"
StdioTransport TransportMode = "stdio"
)
// AsgardeoConfig groups all Asgardeo-specific fields
type DemoConfig struct {
ClientID string `yaml:"client_id"`
@ -60,21 +69,22 @@ type DefaultConfig struct {
}
type Config struct {
AuthServerBaseURL string
MCPServerBaseURL string `yaml:"mcp_server_base_url"`
ListenPort int `yaml:"listen_port"`
JWKSURL string
TimeoutSeconds int `yaml:"timeout_seconds"`
MCPPaths []string `yaml:"mcp_paths"`
PathMapping map[string]string `yaml:"path_mapping"`
Mode string `yaml:"mode"`
CORSConfig CORSConfig `yaml:"cors"`
AuthServerBaseURL string
MCPServerBaseURL string `yaml:"mcp_server_base_url"`
ListenPort int `yaml:"listen_port"`
JWKSURL string
TimeoutSeconds int `yaml:"timeout_seconds"`
MCPPaths []string `yaml:"mcp_paths"`
PathMapping map[string]string `yaml:"path_mapping"`
Mode string `yaml:"mode"`
CORSConfig CORSConfig `yaml:"cors"`
TransportMode TransportMode `yaml:"transport_mode"`
// Nested config for Asgardeo
Demo DemoConfig `yaml:"demo"`
Asgardeo AsgardeoConfig `yaml:"asgardeo"`
Default DefaultConfig `yaml:"default"`
Command Command `yaml:"command"` // Command to run
Command Command `yaml:"command"` // Command to run
}
// Command struct with explicit configuration for all relevant paths
@ -90,6 +100,51 @@ type Command struct {
Env []string `yaml:"env,omitempty"` // Environment variables
}
// Validate checks if the command config is valid based on transport mode
func (c *Command) Validate(transportMode TransportMode) error {
if transportMode == StdioTransport {
if !c.Enabled {
return fmt.Errorf("command must be enabled in stdio transport mode")
}
if c.UserCommand == "" {
return fmt.Errorf("user_command is required in stdio transport mode")
}
}
return nil
}
// GetBaseURL returns the base URL for the MCP server
func (c *Command) GetBaseURL() string {
if c.BaseUrl != "" {
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
func (c *Command) GetPaths() []string {
var paths []string
// 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 == "" {
@ -148,5 +203,31 @@ func LoadConfig(path string) (*Config, error) {
if cfg.TimeoutSeconds == 0 {
cfg.TimeoutSeconds = 15 // default
}
// Set default transport mode if not specified
if cfg.TransportMode == "" {
cfg.TransportMode = SSETransport // Default to SSE
}
// Validate command config based on transport mode
if err := cfg.Command.Validate(cfg.TransportMode); err != nil {
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
}
}