mirror of
https://github.com/wso2/open-mcp-auth-proxy.git
synced 2025-06-28 01:23:30 +00:00
Add support for SSE and stdio transport modes
This commit is contained in:
parent
2548eb569a
commit
61d3c7e7e1
4 changed files with 177 additions and 48 deletions
58
README.md
58
README.md
|
@ -26,11 +26,44 @@ go build -o openmcpauthproxy ./cmd/proxy
|
||||||
|
|
||||||
## Using Open MCP Auth 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
|
### Quick Start
|
||||||
|
|
||||||
Allows you to just enable authentication and authorization for your MCP server with the preconfigured auth provider powered by Asgardeo.
|
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.
|
||||||
2. Initialize a virtual environment.
|
2. Initialize a virtual environment.
|
||||||
|
@ -60,13 +93,28 @@ python3 echo_server.py
|
||||||
|
|
||||||
Update the following parameters in `config.yaml`.
|
Update the following parameters in `config.yaml`.
|
||||||
|
|
||||||
### demo mode configuration:
|
### Configuration examples:
|
||||||
|
|
||||||
|
**SSE mode (using external MCP server):**
|
||||||
```yaml
|
```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
|
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
|
||||||
|
|
||||||
```bash
|
```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
|
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,
|
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. 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.
|
||||||

|

|
||||||
2. Note the **Client ID** and **Client secret** of this application. This is required by the auth proxy
|
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
|
```yaml
|
||||||
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
|
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
|
||||||
listen_port: 8080 # Address where the proxy will listen
|
listen_port: 8080 # Address where the proxy will listen
|
||||||
|
transport_mode: "sse" # Transport mode: "sse" or "stdio"
|
||||||
|
|
||||||
asgardeo:
|
asgardeo:
|
||||||
org_name: "<org_name>" # Your Asgardeo org name
|
org_name: "<org_name>" # Your Asgardeo org name
|
||||||
|
@ -121,6 +170,7 @@ Create a configuration file config.yaml with the following parameters:
|
||||||
```yaml
|
```yaml
|
||||||
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
|
mcp_server_base_url: "http://localhost:8000" # URL of your MCP server
|
||||||
listen_port: 8080 # Address where the proxy will listen
|
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.
|
**TODO**: Update the configs for a standard OAuth Server.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ func main() {
|
||||||
demoMode := flag.Bool("demo", false, "Use Asgardeo-based provider (demo).")
|
demoMode := flag.Bool("demo", false, "Use Asgardeo-based provider (demo).")
|
||||||
asgardeoMode := flag.Bool("asgardeo", false, "Use Asgardeo-based provider (asgardeo).")
|
asgardeoMode := flag.Bool("asgardeo", false, "Use Asgardeo-based provider (asgardeo).")
|
||||||
debugMode := flag.Bool("debug", false, "Enable debug logging")
|
debugMode := flag.Bool("debug", false, "Enable debug logging")
|
||||||
|
stdioMode := flag.Bool("stdio", false, "Use stdio transport mode instead of SSE")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
logger.SetDebug(*debugMode)
|
logger.SetDebug(*debugMode)
|
||||||
|
@ -33,39 +34,31 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Ensure MCPPaths includes the configured paths from the command
|
// Override transport mode if stdio flag is set
|
||||||
if cfg.Command.Enabled {
|
if *stdioMode {
|
||||||
// Add SSE path to MCPPaths if not already present
|
cfg.TransportMode = config.StdioTransport
|
||||||
ssePath := cfg.Command.SsePath
|
// Validate command config for stdio mode
|
||||||
if ssePath == "" {
|
if err := cfg.Command.Validate(cfg.TransportMode); err != nil {
|
||||||
ssePath = "/sse" // default
|
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
|
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
|
// Ensure all required dependencies are available
|
||||||
if err := subprocess.EnsureDependenciesAvailable(cfg.Command.UserCommand); err != nil {
|
if err := subprocess.EnsureDependenciesAvailable(cfg.Command.UserCommand); err != nil {
|
||||||
logger.Warn("%v", err)
|
logger.Warn("%v", err)
|
||||||
|
@ -76,6 +69,8 @@ func main() {
|
||||||
if err := procManager.Start(&cfg.Command); err != nil {
|
if err := procManager.Start(&cfg.Command); err != nil {
|
||||||
logger.Warn("Failed to start subprocess: %v", err)
|
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
|
// 4. Create the chosen provider
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
# config.yaml
|
# 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"
|
mcp_server_base_url: "http://localhost:8000"
|
||||||
listen_port: 8080
|
listen_port: 8080
|
||||||
timeout_seconds: 10
|
timeout_seconds: 10
|
||||||
|
mcp_paths: # Required in SSE mode, ignored in stdio mode (derived from command)
|
||||||
mcp_paths:
|
|
||||||
- /messages/
|
- /messages/
|
||||||
- /sse
|
- /sse
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"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
|
// AsgardeoConfig groups all Asgardeo-specific fields
|
||||||
type DemoConfig struct {
|
type DemoConfig struct {
|
||||||
ClientID string `yaml:"client_id"`
|
ClientID string `yaml:"client_id"`
|
||||||
|
@ -60,21 +69,22 @@ type DefaultConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AuthServerBaseURL string
|
AuthServerBaseURL string
|
||||||
MCPServerBaseURL string `yaml:"mcp_server_base_url"`
|
MCPServerBaseURL string `yaml:"mcp_server_base_url"`
|
||||||
ListenPort int `yaml:"listen_port"`
|
ListenPort int `yaml:"listen_port"`
|
||||||
JWKSURL string
|
JWKSURL string
|
||||||
TimeoutSeconds int `yaml:"timeout_seconds"`
|
TimeoutSeconds int `yaml:"timeout_seconds"`
|
||||||
MCPPaths []string `yaml:"mcp_paths"`
|
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"`
|
||||||
|
|
||||||
// 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 Command `yaml:"command"` // Command to run
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command struct with explicit configuration for all relevant paths
|
// Command struct with explicit configuration for all relevant paths
|
||||||
|
@ -90,6 +100,51 @@ type Command struct {
|
||||||
Env []string `yaml:"env,omitempty"` // Environment variables
|
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
|
// BuildExecCommand constructs the full command string for execution
|
||||||
func (c *Command) BuildExecCommand() string {
|
func (c *Command) BuildExecCommand() string {
|
||||||
if c.UserCommand == "" {
|
if c.UserCommand == "" {
|
||||||
|
@ -148,5 +203,31 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
if cfg.TimeoutSeconds == 0 {
|
if cfg.TimeoutSeconds == 0 {
|
||||||
cfg.TimeoutSeconds = 15 // default
|
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
|
return &cfg, nil
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue