CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
oauth-mcp-proxy is an OAuth 2.1 authentication library for Go MCP servers. It provides server-side OAuth integration with minimal code (3-line integration via WithOAuth()), supporting multiple providers (HMAC, Okta, Google, Azure AD).
Version: v1.0.0 (Supports both mark3labs/mcp-go and official modelcontextprotocol/go-sdk)
Build Commands
# Run tests
make test
# Run tests with verbose output
make test-verbose
# Run tests with coverage report (generates coverage.html)
make test-coverage
# Run linters (same as CI - checks go.mod tidy + golangci-lint)
make lint
# Format code
make fmt
# Clean build artifacts and caches
make clean
# Install/download dependencies
make install
# Check for security vulnerabilities
make vulnArchitecture
Package Structure (v1.0.0)
oauth-mcp-proxy/
├── [core package - SDK-agnostic]
│ ├── oauth.go - Server type, NewServer, ValidateTokenCached
│ ├── config.go - Configuration validation and provider setup
│ ├── cache.go - Token cache with 5-minute TTL
│ ├── context.go - Context utilities (WithOAuthToken, GetUserFromContext, etc.)
│ ├── handlers.go - OAuth HTTP endpoints (/.well-known/*, /oauth/*)
│ ├── middleware.go - CreateHTTPContextFunc for token extraction
│ ├── logger.go - Logger interface
│ ├── metadata.go - OAuth metadata structures
│ └── provider/ - Token validators (HMAC, OIDC)
│
├── mark3labs/ - Adapter for mark3labs/mcp-go SDK
│ ├── oauth.go - WithOAuth → ServerOption
│ └── middleware.go - Middleware for mark3labs types
│
└── mcp/ - Adapter for official modelcontextprotocol/go-sdk
└── oauth.go - WithOAuth → http.HandlerCore Components
Core Package (SDK-agnostic):
oauth.go -
Servertype,NewServer(),ValidateTokenCached()(used by adapters)config.go - Configuration validation and provider setup
cache.go - Token caching logic (
TokenCache,CachedToken)context.go - Context utilities (
WithOAuthToken,GetOAuthToken,WithUser,GetUserFromContext)handlers.go - OAuth HTTP endpoints
provider/provider.go - Token validators (HMACValidator, OIDCValidator)
Adapters (SDK-specific):
mark3labs/ - Middleware adapter for
mark3labs/mcp-gomcp/ - HTTP handler wrapper for official SDK
Key Design Patterns
OpenTelemetry Pattern: Core logic is SDK-agnostic; adapters provide SDK-specific integration
Instance-scoped: Each
Serverinstance has its own token cache and validator (no globals)Provider abstraction:
TokenValidatorinterface supports multiple OAuth providersCaching strategy: Tokens cached for 5 minutes using SHA-256 hash as key
Context propagation: OAuth token extracted from HTTP header → stored in context → validated → user added to context
Integration Flow
mark3labs SDK:
1. HTTP request with "Authorization: Bearer <token>" header
2. CreateHTTPContextFunc() extracts token → adds to context via WithOAuthToken()
3. mark3labs middleware validates token:
- Calls Server.ValidateTokenCached() (checks cache first)
- If not cached, validates via provider (HMAC or OIDC)
- Caches result (5-minute TTL)
4. Adds authenticated User to context via WithUser()
5. Tool handler accesses user via GetUserFromContext(ctx)Official SDK:
1. HTTP request with "Authorization: Bearer <token>" header
2. mcp adapter's HTTP handler intercepts request
3. Validates token via Server.ValidateTokenCached():
- Checks cache first (5-minute TTL)
- If not cached, validates via provider
- Caches result
4. Adds token and user to context (WithOAuthToken, WithUser)
5. Passes request to official SDK's StreamableHTTPHandler
6. Tool handler accesses user via GetUserFromContext(ctx)Provider System
HMAC: Validates JWT tokens with shared secret (testing/dev)
OIDC: Validates tokens via JWKS/OIDC discovery (Okta/Google/Azure)
All validation happens in
provider/provider.goValidators implement
TokenValidatorinterface
Testing
The codebase has extensive test coverage across multiple scenarios:
api_test.go - Core API functionality tests
integration_test.go - End-to-end integration tests
security_test.go - Security validation tests
attack_scenarios_test.go - Security attack scenario tests
middleware_compatibility_test.go - Middleware compatibility tests
provider/provider_test.go - Token validator tests
Run single test:
go test -v -run TestName ./...Test Patterns
Tests use table-driven subtests with t.Run():
tests := []struct {
name string
// test fields
}{...}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test body
})
}Mock validators implement TokenValidator interface. Use httptest.NewRecorder() for HTTP handler tests.
Configuration
ConfigBuilder Pattern (Recommended)
Use ConfigBuilder for production code instead of direct Config structs:
cfg, _ := oauth.NewConfigBuilder().
WithProvider("okta").
WithIssuer("https://company.okta.com").
WithAudience("api://my-server").
WithHost(host).WithPort(port).
Build()Build() validates config and auto-constructs ServerURL if not set.
Context Timeouts
OIDC validation: 10 seconds
Provider initialization: 30 seconds
Security Requirements
Redirect URI validation: All URIs must be in explicit allowlist
State parameter HMAC: OAuth states are HMAC-signed to prevent CSRF
Audience validation: Both HMAC and OIDC validators explicitly check
audclaimNo raw token logging: Only log
fmt.Sprintf("%x", sha256.Sum256([]byte(token)))[:16]TLS in production: Always warn if
useTLS=falseinLogStartup()
Important Notes
User Context: Always use
GetUserFromContext(ctx)in tool handlers to access authenticated userToken Caching: Tokens cached for 5 minutes - design for this TTL in testing. Cache uses
sync.RWMutexwith background cleanup viadeleteExpiredToken()goroutineLogging: Config.Logger is optional. If nil, uses default logger (log.Printf with level prefixes)
Modes: Library supports "native" (token validation only) and "proxy" (OAuth flow proxy) modes. Auto-detected based on
ClientIDpresenceAdapter Pattern:
WithOAuth()is in adapter packages (mark3labs.WithOAuth()ormcp.WithOAuth()) for SDK-specific integration
Common Gotchas
SDK Imports: Adapter code (
mark3labs/,mcp/) can import SDKs. Core package cannot - keep it SDK-agnosticContext Propagation: Always extract user via
GetUserFromContext(ctx)in tool handlersCache Expiry: Background cleanup runs in goroutine to avoid lock contention
Mode Detection: Config auto-detects "native" vs "proxy" based on
ClientIDpresenceLogger Fallback: If
cfg.Logger == nil, usesdefaultLogger{}withlog.Printf
File Naming Conventions
Core logic:
oauth.go,config.go,cache.go,context.go,handlers.go,middleware.goTests:
*_test.go(e.g.,security_test.go,integration_test.go)Adapters:
mark3labs/oauth.go,mcp/oauth.go(not*_adapter.go)Provider:
provider/provider.go(single file, multiple validators)
Using the Library
With mark3labs/mcp-go
import (
oauth "github.com/tuannvm/oauth-mcp-proxy"
"github.com/tuannvm/oauth-mcp-proxy/mark3labs"
)
oauthServer, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{...})
mcpServer := server.NewMCPServer("name", "1.0.0", oauthOption)
streamableServer := server.NewStreamableHTTPServer(mcpServer, ...)
mux.HandleFunc("/mcp", oauthServer.WrapMCPEndpoint(streamableServer))Note: WrapMCPEndpoint() provides automatic 401 handling with proper WWW-Authenticate headers when Bearer token is missing. It also passes through OPTIONS requests (CORS) and non-Bearer auth schemes.
With Official SDK
import (
oauth "github.com/tuannvm/oauth-mcp-proxy"
mcpoauth "github.com/tuannvm/oauth-mcp-proxy/mcp"
)
mcpServer := mcp.NewServer(&mcp.Implementation{...}, nil)
_, handler, _ := mcpoauth.WithOAuth(mux, &oauth.Config{...}, mcpServer)
http.ListenAndServe(":8080", handler) // 401 handling automaticNote: Official SDK adapter includes automatic 401 handling in the returned handler.
Extending the Library
Adding a New OAuth Provider
Add validator to
provider/provider.goimplementingTokenValidatorinterfaceUpdate
createValidator()switch inconfig.goAdd provider documentation in
docs/providers/
Adding a New SDK Adapter
Create
<sdk>/oauth.gowithWithOAuth()functionFollow pattern: create
oauth.Server, register handlers, return SDK-specific middleware/optionNever import MCP SDKs in core package
Adding New Endpoints
Add handler method to
OAuth2Handlerinhandlers.goRegister in
RegisterHandlers()inoauth.go
Documentation References
examples/README.md- Complete setup guide with Okta configurationexamples/mark3labs/andexamples/official/- Working examples (simple + advanced)docs/providers/*.md- Provider-specific setup (OKTA.md, GOOGLE.md, AZURE.md, HMAC.md)docs/CONFIGURATION.md- All configuration optionsdocs/SECURITY.md- Production best practicesdocs/TROUBLESHOOTING.md- Common issues and solutions
Last updated
Was this helpful?