Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

- **Drop-in OAuth 2.1/OIDC gateway for MCP servers — put it in front, no code changes.**
- **Your IdP, your choice**: Google, GitHub, or any OIDC provider — e.g. Okta, Auth0, Azure AD, Keycloak — plus optional password.
- **Publish local MCP servers safely**: Supports all stdio, SSE, and HTTP transports. For stdio, traffic is converted to `/mcp`. For SSE/HTTP, it’s proxied as-is. Of course, with authentication.
- **Flexible user matching**: Support exact matching and glob patterns for user authorization (e.g., `*@company.com`)
- **Publish local MCP servers safely**: Supports all stdio, SSE, and HTTP transports. For stdio, traffic is converted to `/mcp`. For SSE/HTTP, it's proxied as-is. Of course, with authentication.
- **Verified across major MCP clients**: Claude, Claude Code, ChatGPT, GitHub Copilot, Cursor, etc. — the proxy smooths client-specific quirks for consistent auth.

---
Expand Down
40 changes: 31 additions & 9 deletions docs/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,37 @@ Complete reference for all MCP Auth Proxy configuration options.

#### Generic OIDC

| Option | Environment Variable | Default | Description |
| -------------------------- | ------------------------ | ---------------------- | ----------------------------------------------------------- |
| `--oidc-configuration-url` | `OIDC_CONFIGURATION_URL` | - | OIDC configuration URL |
| `--oidc-client-id` | `OIDC_CLIENT_ID` | - | OIDC client ID |
| `--oidc-client-secret` | `OIDC_CLIENT_SECRET` | - | OIDC client secret |
| `--oidc-allowed-users` | `OIDC_ALLOWED_USERS` | - | Comma-separated list of allowed OIDC users |
| `--oidc-provider-name` | `OIDC_PROVIDER_NAME` | `OIDC` | Display name for OIDC provider |
| `--oidc-scopes` | `OIDC_SCOPES` | `openid,profile,email` | Comma-separated list of OIDC scopes |
| `--oidc-user-id-field` | `OIDC_USER_ID_FIELD` | `/email` | JSON pointer to user ID field in userinfo endpoint response |
| Option | Environment Variable | Default | Description |
| --------------------------- | ------------------------- | ---------------------- | ------------------------------------------------------------ |
| `--oidc-configuration-url` | `OIDC_CONFIGURATION_URL` | - | OIDC configuration URL |
| `--oidc-client-id` | `OIDC_CLIENT_ID` | - | OIDC client ID |
| `--oidc-client-secret` | `OIDC_CLIENT_SECRET` | - | OIDC client secret |
| `--oidc-allowed-users` | `OIDC_ALLOWED_USERS` | - | Comma-separated list of allowed OIDC users (exact match) |
| `--oidc-allowed-users-glob` | `OIDC_ALLOWED_USERS_GLOB` | - | Comma-separated list of glob patterns for allowed OIDC users |
| `--oidc-provider-name` | `OIDC_PROVIDER_NAME` | `OIDC` | Display name for OIDC provider |
| `--oidc-scopes` | `OIDC_SCOPES` | `openid,profile,email` | Comma-separated list of OIDC scopes |
| `--oidc-user-id-field` | `OIDC_USER_ID_FIELD` | `/email` | JSON pointer to user ID field in userinfo endpoint response |

##### OIDC User Matching

You can use both exact matching and glob patterns for OIDC user authorization:

- **Exact matching** (`--oidc-allowed-users`): Users must match exactly
- **Glob patterns** (`--oidc-allowed-users-glob`): Users are matched against [glob patterns](https://github.com/gobwas/glob)

**Examples:**

```bash
# Exact matching
--oidc-allowed-users "user1@example.com,admin@company.org"

# Glob patterns - allow all users from example.com
--oidc-allowed-users-glob "*@example.com"

# Combined exact and glob matching
--oidc-allowed-users "specific@user.com" \
--oidc-allowed-users-glob "*@example.com"
```

### Server Options

Expand Down
5 changes: 5 additions & 0 deletions docs/docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ services:
- GITHUB_CLIENT_SECRET=your-github-client-secret
- GITHUB_ALLOWED_USERS=username1,username2
- GITHUB_ALLOWED_ORGS=org1,org2:team1
- OIDC_CONFIGURATION_URL=https://your-oidc-provider/.well-known/openid_configuration
- OIDC_CLIENT_ID=your-oidc-client-id
- OIDC_CLIENT_SECRET=your-oidc-client-secret
- OIDC_ALLOWED_USERS=specific@user.com
- OIDC_ALLOWED_USERS_GLOB=*@example.com
- TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
volumes:
- ./data:/data
Expand Down
16 changes: 16 additions & 0 deletions docs/docs/oauth-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ Configure OAuth providers to enable secure authentication for your MCP server.

### 2. Configure MCP Auth Proxy

#### Exact user matching:

```bash
./mcp-auth-proxy \
--external-url https://{your-domain} \
Expand All @@ -110,6 +112,19 @@ Configure OAuth providers to enable secure authentication for your MCP server.
-- your-mcp-command
```

#### Glob pattern matching:

```bash
./mcp-auth-proxy \
--external-url https://{your-domain} \
--tls-accept-tos \
--oidc-configuration-url "https://your-provider.com/.well-known/openid-configuration" \
--oidc-client-id "your-oidc-client-id" \
--oidc-client-secret "your-oidc-client-secret" \
--oidc-allowed-users-glob "*@example.com" \
-- your-mcp-command
```

### Provider-Specific Examples

#### Okta
Expand Down Expand Up @@ -167,6 +182,7 @@ export OIDC_CONFIGURATION_URL="https://provider.com/.well-known/openid-configura
export OIDC_CLIENT_ID="your-oidc-client-id"
export OIDC_CLIENT_SECRET="your-oidc-client-secret"
export OIDC_ALLOWED_USERS="user1@example.com,user2@example.com"
export OIDC_ALLOWED_USERS_GLOB="*@example.com"

./mcp-auth-proxy --external-url https://{your-domain} --tls-accept-tos -- your-mcp-command
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/gin-contrib/sessions v1.0.4
github.com/gin-contrib/zap v1.1.5
github.com/gin-gonic/gin v1.10.1
github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/mark3labs/mcp-go v0.37.0
github.com/mattn/go-jsonpointer v0.0.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ github.com/gobuffalo/pop/v6 v6.1.1 h1:eUDBaZcb0gYrmFnKwpuTEUA7t5ZHqNfvS4POqJYXDZ
github.com/gobuffalo/pop/v6 v6.1.1/go.mod h1:1n7jAmI1i7fxuXPZjZb0VBPQDbksRtCoFnrDV5IsvaI=
github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0=
github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
Expand Down
47 changes: 47 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,45 @@ func getEnvBoolWithDefault(key string, defaultValue bool) bool {
return defaultValue
}

// splitWithEscapes splits a string by delimiter, respecting escape sequences
// e.g., "a,b\,c,d" with delimiter "," returns ["a", "b,c", "d"]
func splitWithEscapes(s, delimiter string) []string {
if s == "" {
return []string{}
}

var result []string
var current strings.Builder
escaped := false

for i := 0; i < len(s); i++ {
if escaped {
current.WriteByte(s[i])
escaped = false
} else if s[i] == '\\' && i+1 < len(s) {
// Check if next character is the delimiter
if strings.HasPrefix(s[i+1:], delimiter) {
// Skip the backslash and add the delimiter character
escaped = true
} else {
// Not escaping delimiter, keep the backslash
current.WriteByte(s[i])
}
} else if strings.HasPrefix(s[i:], delimiter) {
// Found unescaped delimiter
result = append(result, strings.TrimSpace(current.String()))
current.Reset()
i += len(delimiter) - 1 // -1 because loop will increment
} else {
current.WriteByte(s[i])
}
}

// Add the last part
result = append(result, strings.TrimSpace(current.String()))
return result
}

func main() {
var listen string
var tlsListen string
Expand All @@ -49,6 +88,7 @@ func main() {
var oidcUserIDField string
var oidcProviderName string
var oidcAllowedUsers string
var oidcAllowedUsersGlob string
var password string
var passwordHash string
var proxyBearerToken string
Expand Down Expand Up @@ -98,6 +138,11 @@ func main() {
}
}

var oidcAllowedUsersGlobList []string
if oidcAllowedUsersGlob != "" {
oidcAllowedUsersGlobList = splitWithEscapes(oidcAllowedUsersGlob, ",")
}

var oidcScopesList []string
if oidcScopes != "" {
oidcScopesList = strings.Split(oidcScopes, ",")
Expand Down Expand Up @@ -149,6 +194,7 @@ func main() {
oidcUserIDField,
oidcProviderName,
oidcAllowedUsersList,
oidcAllowedUsersGlobList,
password,
passwordHash,
trustedProxiesList,
Expand Down Expand Up @@ -190,6 +236,7 @@ func main() {
rootCmd.Flags().StringVar(&oidcUserIDField, "oidc-user-id-field", getEnvWithDefault("OIDC_USER_ID_FIELD", "/email"), "JSON pointer to user ID field in userinfo endpoint response")
rootCmd.Flags().StringVar(&oidcProviderName, "oidc-provider-name", getEnvWithDefault("OIDC_PROVIDER_NAME", "OIDC"), "Display name for OIDC provider")
rootCmd.Flags().StringVar(&oidcAllowedUsers, "oidc-allowed-users", getEnvWithDefault("OIDC_ALLOWED_USERS", ""), "Comma-separated list of allowed OIDC users")
rootCmd.Flags().StringVar(&oidcAllowedUsersGlob, "oidc-allowed-users-glob", getEnvWithDefault("OIDC_ALLOWED_USERS_GLOB", ""), "Comma-separated list of glob patterns for allowed OIDC users")

// Password authentication
rootCmd.Flags().StringVar(&password, "password", getEnvWithDefault("PASSWORD", ""), "Plain text password for authentication (will be hashed with bcrypt)")
Expand Down
Loading