SMCP Proxy
STDIOA secure reverse proxy for MCP services with OIDC authentication.
A secure reverse proxy for MCP services with OIDC authentication.
A secure reverse proxy for Model Context Protocol (MCP) services with OIDC authentication.
The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to Large Language Models (LLMs). It works like a "USB-C port for AI applications" - creating a consistent interface between applications and AI models.
MCP follows a client-server architecture where host applications connect to multiple servers, enabling flexibility in switching between different AI providers while maintaining consistent data handling practices and security.
For more information, visit modelcontextprotocol.io.
SMCP Proxy provides a secure layer in front of Model Context Protocol (MCP) services, enabling enterprise-grade authentication and authorization using OIDC. MCP is a protocol designed for interacting with Large Language Models (LLMs) in a standardized way.
The proxy consists of two main components:
The main goals of this project are:
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Application │ │ Proxy Client │ │ Proxy Server │ ┌───────────┐
│ (no auth) │──────│ (local) │──────│ (with auth) │──────│ MCP Server│
└───────────────┘ └───────────────┘ └───────────────┘ └───────────┘
│ ▲
│ │
│ │
▼ │
┌────────────┐ │
│ OIDC │───────────────┘
│ Provider │
└────────────┘
# Build the single executable that includes both server and client functionality go build -o smcp-proxy ./cmd/smcp
SMCP Proxy is also available as a Docker image that can be pulled from the GitHub Container Registry:
# Pull the latest image docker pull ghcr.io/ksysoev/smcp-proxy:main # Pull a specific version docker pull ghcr.io/ksysoev/smcp-proxy:v1.0.0
Run the server:
docker run -p 8080:8080 -v $(pwd)/configs:/app/configs ghcr.io/ksysoev/smcp-proxy:main server --config=/app/configs/proxy-server.yml
Run the client:
docker run -p 8081:8081 ghcr.io/ksysoev/smcp-proxy:main client --server-url=http://your-server:8080
The Docker image is built for both AMD64 and ARM64 architectures.
Configuration is provided via YAML files. Sample configurations are available in the configs
directory.
Server configuration is specified in configs/proxy-server.yml
:
server: host: "0.0.0.0" port: 8080 read_timeout: "30s" write_timeout: "30s" shutdown_timeout: "10s" # Authentication configuration (default: none) auth: # Mode can be "none" or "oidc" mode: "none" mcp: # Global timeout for all backends (can be overridden per backend) timeout: "60s" # Configure multiple MCP backends backends: # Sequential thinking MCP server - id: "sequentialthinking" name: "Sequential Thinking" transport: "stdio" path: "/v1/sequentialthinking" strip_path: true stdio: command: "docker" args: ["run", "--rm", "-i", "mcp/sequentialthinking"] stdio_timeout: "60s" # Memory MCP server - id: "memory" name: "Memory" transport: "stdio" path: "/v1/memory" strip_path: true stdio: command: "docker" args: ["run", "-i", "-v", "claude-memory:/app/dist", "--rm", "mcp/memory"] stdio_timeout: "60s" # OIDC settings (only used when auth.mode is "oidc") oidc: issuers: - "https://your-identity-provider.com" # Replace with your actual OIDC issuer URL audience: "your-api-audience" # Replace with your API audience required_claims: # Define required claims that must be present in the token # For example: # roles: "admin" optional_claims: # Define optional claims that if present must match specific values # For example: # scope: "read:data" tls: enabled: false # cert_file: "/path/to/cert.pem" # key_file: "/path/to/key.pem" metrics: enabled: true path: "/metrics"
The proxy client has been simplified to use command-line arguments and environment variables instead of a configuration file.
Client flags:
--host string Host to bind the client to (default "127.0.0.1")
--port int Port to bind the client to (default 8081)
--read-timeout duration HTTP read timeout (default 30s)
--write-timeout duration HTTP write timeout (default 30s)
--shutdown-timeout duration Graceful shutdown timeout (default 10s)
Server flags:
--server-url string URL of the proxy server (required)
--server-timeout duration Timeout for requests to the server (default 60s)
Auth flags:
--auth-mode string Authentication mode (none, oidc) (default "none")
OIDC flags (only used when auth-mode is "oidc"):
--oidc-issuer string OIDC issuer URL
--oidc-client-id string OIDC client ID
--oidc-client-secret string OIDC client secret
--oidc-audience string OIDC audience
--oidc-scopes string OIDC scopes (comma-separated) (default "openid")
--oidc-cache-ttl duration OIDC token cache TTL (default 5m0s)
--oidc-token-ttl-delta duration OIDC token TTL delta (default 30s)
TLS flags:
--tls Enable TLS (default false)
--tls-cert string Path to TLS certificate file
--tls-key string Path to TLS key file
Metrics flags:
--metrics Enable metrics endpoint (default true)
--metrics-path string Metrics endpoint path (default "/metrics")
Logger flags:
-l, --log-level string Log level (debug, info, warn, error) (default "info")
-f, --log-format string Log format (text, json) (default "text")
All command-line options can also be set using environment variables with the prefix SMCP_CLIENT_
. For example:
# Required configuration export SMCP_SERVER_URL="http://localhost:8080" # Authentication mode (default is "none") export SMCP_AUTH_MODE="none" # or "oidc" # OIDC settings (only needed if SMCP_AUTH_MODE="oidc") export SMCP_OIDC_ISSUER="https://your-identity-provider.com" export SMCP_OIDC_CLIENT_ID="your-client-id" export SMCP_OIDC_CLIENT_SECRET="your-client-secret" # Optional configuration export SMCP_CLIENT_HOST="127.0.0.1" export SMCP_CLIENT_PORT=8081 export SMCP_OIDC_AUDIENCE="your-api-audience" export SMCP_OIDC_SCOPES="openid,profile,email"
The server configuration still uses YAML files and can be overridden using environment variables with the prefix SMCP_PROXY_
.
# Run the server without authentication ./smcp-proxy server --config=configs/proxy-server.yml --log-level=debug
# Run the server with OIDC authentication ./smcp-proxy server --config=configs/proxy-server.yml --auth-mode=oidc --log-level=debug
# Run the client without authentication ./smcp-proxy client --server-url="http://localhost:8080" --log-level=debug # Or using environment variables export SMCP_SERVER_URL="http://localhost:8080" ./smcp-proxy client --log-level=debug
# Run the client with OIDC authentication ./smcp-proxy client \ --auth-mode=oidc \ --server-url="http://localhost:8080" \ --oidc-issuer="https://your-identity-provider.com" \ --oidc-client-id="your-client-id" \ --oidc-client-secret="your-client-secret" \ --log-level=debug # Or using environment variables export SMCP_SERVER_URL="http://localhost:8080" export SMCP_AUTH_MODE="oidc" export SMCP_OIDC_ISSUER="https://your-identity-provider.com" export SMCP_OIDC_CLIENT_ID="your-client-id" export SMCP_OIDC_CLIENT_SECRET="your-client-secret" ./smcp-proxy client --log-level=debug
Once both components are running:
The proxy server supports multiple MCP backends with different transport types:
┌─────────────┐ ┌───────────────────────────────┐
│ Request to │ │ Proxy Server │
│ /v1/seq../ ├─────────────────►│ │──► Stdio Sequential Thinking Backend
└─────────────┘ │ │
│ │
┌─────────────┐ │ │
│ Request to │ │ OIDC Authentication + │
│ /v1/memory/ ├─────────────────►│ Path-Based Routing │──► Stdio Memory Backend
└─────────────┘ │ │
│ │
┌─────────────┐ │ │
│ Request to │ │ │
│ /other/path ├─────────────────►│ │──► 404 Not Found
└─────────────┘ └───────────────────────────────┘
With the strip_path
option enabled, the proxy will remove the path prefix before forwarding the request to the backend:
/v1/sequentialthinking/completions
→ Forwarded to Sequential Thinking backend as /completions
/v1/memory/messages
→ Forwarded to Memory backend as /messages
/some/other/path
→ Returns 404 Not Found (no matching backend)The proxy supports two types of backends:
HTTP Backends (transport: "http"
):
url: "http://mcp-server.example.com"
Stdio Backends (transport: "stdio"
):
# Sequential thinking MCP server stdio: command: "docker" args: ["run", "--rm", "-i", "mcp/sequentialthinking"] # Memory MCP server stdio: command: "docker" args: ["run", "-i", "-v", "claude-memory:/app/dist", "--rm", "mcp/memory"]
The proxy provides a /api/models
endpoint that returns information about all configured backends, following the Anthropic API models format. This allows clients to discover available models and their capabilities.
.
├── cmd/ # Application entry points
│ └── smcp/ # Single executable directory
│ └── main.go # Main entry point
├── configs/ # Configuration files
│ ├── proxy-server.yml # Server configuration
│ └── proxy-client.yml # Client configuration example (not required)
├── internal/ # Private application code
│ ├── middleware/ # HTTP middleware
│ │ ├── logging.go # Request logging middleware
│ │ └── recovery.go # Panic recovery middleware
│ └── metrics/ # Metrics implementation (placeholder)
├── pkg/ # Public API
│ ├── auth/ # Authentication components
│ │ ├── validator.go # OIDC token validation
│ │ └── client.go # OIDC client credentials flow
│ ├── cmd/ # Command line interface
│ │ ├── root.go # Root command
│ │ ├── server.go # Server command
│ │ └── client.go # Client command
│ ├── config/ # Configuration handling
│ │ ├── server_config.go # Server configuration
│ │ └── client_config.go # Client configuration
│ └── proxy/ # Proxy implementation
│ ├── server.go # Server-side proxy
│ └── client.go # Client-side proxy
├── go.mod # Go module definition
└── README.md # This file
This project uses GitHub Actions for continuous integration:
golangci-lint
and fieldalignment
to ensure code quality and performanceTo run tests and linting locally:
# Run all tests go test ./... # Run tests with race detection and coverage go test -race -coverprofile=coverage.txt -covermode=atomic ./... # Run linting golangci-lint run # Check struct field alignment fieldalignment -test ./...