Skip to content

Implement Go MCP server to verify OAuth flow#3

Open
bettercallsaulj wants to merge 37 commits into
dev_go_sdkfrom
dev_go_auth
Open

Implement Go MCP server to verify OAuth flow#3
bettercallsaulj wants to merge 37 commits into
dev_go_sdkfrom
dev_go_auth

Conversation

@bettercallsaulj

@bettercallsaulj bettercallsaulj commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

This PR must be merge after #1

RahulHere added 25 commits March 13, 2026 20:19
Add CGO bindings for gopher-auth library initialization, shutdown, and
version functions in a new auth.go file.

Functions added:
- IsAuthAvailable(): Check if auth functions are available
- AuthInit(): Initialize the auth library
- AuthShutdown(): Shutdown the auth library and release resources
- AuthVersion(): Get the auth library version string

The implementation follows the same CGO pattern as the existing library.go
and uses the same CFLAGS and LDFLAGS for linking with libgopher-orch.
Extend auth.go with CGO bindings for auth client management functions.
These functions enable creating and configuring auth clients for JWT
token validation.

New types:
- AuthClientHandle: Opaque handle to a native auth client

New functions:
- AuthClientCreate(jwksURI, issuer string): Create auth client with
  JWKS endpoint and expected issuer for token validation
- AuthClientDestroy(client AuthClientHandle): Destroy client and
  release resources, safe to call with nil
- AuthClientSetOption(client AuthClientHandle, key, value string):
  Configure client options like cache_duration, auto_refresh, and
  request_timeout

All functions properly handle C string allocation and deallocation
using defer for memory safety.
Extend auth.go with CGO bindings for JWT token validation.

New types:
- ValidationResult: Go struct containing validation status with
  Valid (bool), ErrorCode (int32), and ErrorMessage (string) fields
- gopher_auth_validation_result_t: C struct for FFI communication

New functions:
- AuthValidateToken(client AuthClientHandle, token string): Validates
  a JWT token using the auth client and returns validation result

The function handles nil client gracefully by returning an error result.
Error messages from the native library are properly converted to Go strings
with nil pointer checking.
Extend auth.go with CGO bindings for JWT payload decoding and claim
extraction without requiring token validation.

New types:
- AuthPayloadHandle: Opaque handle to a decoded JWT payload

New functions:
- AuthDecodeToken(token string): Decode JWT token without validation,
  returns payload handle for claim extraction
- AuthPayloadDestroy(payload AuthPayloadHandle): Release payload resources,
  safe to call with nil
- AuthPayloadGetSubject(payload AuthPayloadHandle): Get subject (sub) claim
- AuthPayloadGetIssuer(payload AuthPayloadHandle): Get issuer (iss) claim
- AuthPayloadGetAudience(payload AuthPayloadHandle): Get audience (aud) claim
- AuthPayloadGetScope(payload AuthPayloadHandle): Get scope claim as
  space-separated string
- AuthPayloadGetExpiry(payload AuthPayloadHandle): Get expiration time (exp)
  as Unix timestamp
- AuthPayloadGetIssuedAt(payload AuthPayloadHandle): Get issued at (iat)
  as Unix timestamp

All functions handle nil payload gracefully by returning empty values.
Add comprehensive unit tests for all auth FFI functions in auth_test.go.

Tests include:
- IsAuthAvailable: Verifies availability check works
- AuthInit/AuthShutdown: Tests init/shutdown cycles don't panic
- AuthVersion: Verifies non-empty version string is returned
- AuthClientCreate: Tests client creation with valid parameters
- AuthClientDestroy: Tests destroy with valid and nil handles
- AuthClientSetOption: Tests setting various client options
- AuthValidateToken: Tests validation with invalid/empty tokens and nil client
- AuthDecodeToken: Tests decoding invalid token
- AuthPayloadDestroy: Tests destroy with nil handle
- AuthPayloadGetters: Tests all getters return empty values for nil payload

Both auth.go and auth_test.go now use the 'auth' build tag since the
native library does not yet export the gopher_auth_* symbols. Build with
'-tags auth' once the native library is updated with auth support.
Add the basic directory structure and configuration files for the Go auth
example MCP server. This follows the same project layout as the JavaScript
and Python auth examples.

Directory structure:
- examples/auth/config/ - Configuration loading code
- examples/auth/middleware/ - OAuth middleware implementation
- examples/auth/routes/ - HTTP route handlers
- examples/auth/tools/ - MCP tool definitions

Files:
- go.mod with module definition and replace directive for parent module
- main.go placeholder with empty main function
- server.config with all documented OAuth/OIDC options matching JS format

The server.config includes settings for:
- Server binding (host, port, server_url)
- OAuth/OIDC provider configuration
- JWKS caching options
- Auth bypass mode for development
Add the configuration loading functionality for the Go auth MCP server.
This provides INI-style config file parsing matching the format used by
the JavaScript and Python auth examples.

Implementation:
- AuthServerConfig struct with all server, OAuth, scope, and cache settings
- parseConfigFile() for INI-style file parsing with comment support
- LoadConfigFromFile() to load and populate config from file
- CreateDefaultConfig() for sensible default values

The config loader handles:
- Server: host, port, server_url bindings
- OAuth: auth_server_url, jwks_uri, issuer, client credentials
- OAuth endpoints: authorize and token URLs
- Exchange IDPs as comma-separated list
- Allowed scopes as space-separated string
- JWKS cache settings: duration, auto-refresh, request timeout
- Auth bypass mode for development

Default values match typical development configuration with
3600 second JWKS cache and 30 second request timeout.
Extend the configuration loader with automatic endpoint derivation for
OAuth/OIDC endpoints. This allows users to specify just the auth_server_url
and have all required endpoints derived automatically (Keycloak pattern).

New features:
- DeriveEndpoints() method on AuthServerConfig
- parseBool() helper for flexible boolean parsing

Endpoint derivation logic (when auth_server_url is set):
- jwks_uri = {auth_server_url}/protocol/openid-connect/certs
- issuer = {auth_server_url}
- oauth_authorize_url = {auth_server_url}/protocol/openid-connect/auth
- oauth_token_url = {auth_server_url}/protocol/openid-connect/token

Endpoints are only derived if not explicitly set in config, allowing
users to override individual endpoints while still using derivation
for others.
Add comprehensive unit tests for the configuration loader module. Tests
cover all aspects of config parsing, endpoint derivation, and default
value creation.

Tests included:
- parseConfigFile() with valid INI content
- parseConfigFile() handles comments and empty lines
- parseConfigFile() returns error for nonexistent file
- LoadConfigFromFile() with full config
- Endpoint derivation from auth_server_url
- Endpoint derivation with trailing slash handling
- Explicit endpoint values override derivation
- Server URL derivation from host and port
- Server URL not derived when explicitly set
- parseBool() with true/TRUE/1/yes variants
- parseBool() with false/FALSE/0/no/empty variants
- Integer parsing for port and durations
- CreateDefaultConfig() returns expected defaults
- AllowedScopes parsing from space-separated string
- ExchangeIDPs parsing from comma-separated string

Added testify dependency for assertions.
Add health check endpoint for the auth MCP server. This provides a
standard health monitoring endpoint that returns server status and uptime.

Implementation:
- HealthResponse struct with status, timestamp, version, uptime fields
- HealthHandler(version string) returns http.HandlerFunc
- Tracks server start time in package variable
- Calculates uptime in seconds since server start
- Uses RFC3339 format for timestamp
- Returns Content-Type: application/json header

Response format:
{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00Z",
  "version": "1.0.0",
  "uptime": 123
}
Add OAuth/OIDC discovery endpoints for the auth MCP server following
RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server Metadata).

Endpoints implemented:
- /.well-known/oauth-protected-resource (RFC 9728)
- /.well-known/oauth-authorization-server (RFC 8414)

ProtectedResourceResponse includes:
- resource: Server MCP endpoint URL
- authorization_servers: List with auth server URL
- scopes_supported: Allowed scopes from config
- bearer_methods_supported: header and query
- resource_documentation: Server docs URL

AuthorizationServerResponse includes:
- issuer, authorization_endpoint, token_endpoint, jwks_uri
- registration_endpoint pointing to /oauth/register
- Standard response_types, grant_types, auth_methods
- Code challenge methods: S256 and plain

Helper functions:
- RegisterOAuthRoutes() to register all OAuth endpoints
- writeJSONResponse() for JSON with CORS headers
- handleCORS() for OPTIONS preflight requests
Extend OAuth routes with additional endpoints for OIDC discovery,
authorization redirect, and dynamic client registration.

New endpoints:
- /.well-known/openid-configuration (OIDC Discovery)
- /oauth/authorize (redirects to IDP with query params forwarded)
- /oauth/register (RFC 7591 stateless client registration)

OpenIDConfigurationResponse extends RFC 8414 with:
- userinfo_endpoint derived from auth_server_url
- subject_types_supported: ["public"]
- id_token_signing_alg_values_supported: ["RS256"]

Authorize endpoint:
- HTTP 302 redirect to oauth_authorize_url
- All query parameters forwarded to IDP
- CORS headers included

Register endpoint (RFC 7591 stateless mode):
- POST with JSON body containing redirect_uris
- Returns client credentials from server config
- client_id_issued_at: current Unix timestamp
- client_secret_expires_at: 0 (never expires)
- Handles OPTIONS preflight for CORS
Add JSON-RPC 2.0 types and helper functions for the MCP handler.
This provides the foundation for MCP protocol communication.

Types defined:
- JSONRPCRequest with jsonrpc, id, method, params fields
- JSONRPCResponse with jsonrpc, id, result, error fields
- RPCError with code, message, data fields

Error code constants:
- ParseError (-32700): Invalid JSON
- InvalidRequest (-32600): Invalid request object
- MethodNotFound (-32601): Method not found
- InvalidParams (-32602): Invalid method parameters
- InternalError (-32603): Internal server error

Helper functions:
- sendError(): Send error response with code and message
- sendErrorWithData(): Send error with additional data
- sendResult(): Send success response with result

All responses include:
- Content-Type: application/json header
- Access-Control-Allow-Origin: * header for CORS
Add the MCP handler with core JSON-RPC method implementations for
the auth example server.

Tool interface (tools/tool.go):
- Tool interface with Name, Description, InputSchema, RequiredScope, Execute
- ToolInfo struct for tools/list response metadata

MCPHandler struct:
- tools map for registered tools
- NewMCPHandler() constructor
- RegisterTool() to add tools to registry
- ServeHTTP() implementing http.Handler interface

Request handling:
- JSON-RPC 2.0 version validation
- Method routing to appropriate handler
- ParseError response for invalid JSON
- MethodNotFound response for unknown methods
- CORS preflight handling via OPTIONS

Placeholder for tools/call handler to be implemented in next prompt.
Add the tools/call method handler to the MCP server for executing
registered tools with scope-based authorization.

Types added:
- ToolsCallParams: name and arguments from JSON-RPC params
- ToolCallResult: MCP content array with isError flag
- ContentItem: type and text fields for content items
- AuthContext: user auth info with scopes (used by middleware)
- AuthContextKey: context key for storing auth context

Handler flow:
1. Parse params to get tool name and arguments
2. Look up tool in registry, return error if not found
3. Get auth context from request context
4. Check if user has required scope for the tool
5. If scope missing, return access denied response
6. Execute tool handler with arguments
7. Return tool result wrapped in content array
Add OAuth authentication middleware with token extraction logic. Uses
build tags to conditionally compile with or without native FFI support.

Files:
- oauth_auth.go (//go:build auth): Full implementation with FFI
- oauth_auth_stub.go (//go:build !auth): Stub for building without auth

OAuthAuthMiddleware struct:
- authClient: FFI handle for token validation
- config: Server configuration

Token extraction (extractToken):
- Checks Authorization header for "Bearer {token}" format
- Falls back to access_token query parameter
- Returns empty string if no token found

Updated go.mod to require gopher-orch-go for FFI package.
Extend OAuth middleware with token validation logic using FFI bindings.
Both auth and stub versions now have complete Middleware implementation.

Middleware function (auth version):
1. Skip auth for public paths (isPublicPath)
2. Skip auth if config.AuthDisabled is true
3. Extract token from request
4. Return 401 if token missing
5. Validate token using ffi.AuthValidateToken()
6. Return 401 with error if validation fails
7. Decode payload using ffi.AuthDecodeToken()
8. Create AuthContext from payload claims
9. Store AuthContext in request context
10. Call next handler with updated context
11. Cleanup payload handle with defer
Add example MCP tools demonstrating scope-based authorization with
deterministic weather simulation using FNV-1a hashing.

Tools implemented:

1. get-weather (no scope required):
   - Input: city (string, required)
   - Returns: city, temperature, condition, humidity, windSpeed
   - Public access for basic weather queries

2. get-forecast (requires mcp:read):
   - Input: city (string, required), days (int, default 5)
   - Returns: city, unit, forecast array
   - Forecast includes: day, highTemperature, lowTemperature,
     condition, precipitationChance
   - Limited to 14 days maximum

3. get-weather-alerts (requires mcp:admin):
   - Input: region (string, required)
   - Returns: region, alerts array, count
   - Alerts include: id, type, severity, message, issued, expires
   - 0-3 alerts generated per region
Add server entry point with command line parsing, configuration loading,
route setup, and graceful shutdown handling. Two versions for different
build modes.
Startup sequence:
1. Parse command line flags
2. Load configuration from file
3. Apply command line overrides
4. Derive OAuth endpoints if needed
5. Initialize auth library (auth version only)
6. Create auth client with options (auth version only)
7. Create OAuth middleware
8. Create MCP handler
9. Register weather tools
10. Setup HTTP routes
11. Print startup banner
12. Start HTTP server
13. Handle graceful shutdown on SIGINT/SIGTERM
Add convenience script for building and running the auth example server,
plus comprehensive documentation covering all features and usage.

run_example.sh:
- Checks Go version (1.21+ required)
- Checks for native library availability
- Supports --no-auth flag for development mode
- Supports --auth flag for native build
- Supports --host, --port, --config flags
- Supports --help for usage information
- Color-coded output for status messages
- Automatic fallback to stub build if native library missing

README.md covers:
- Overview of the auth example
- Prerequisites (Go version, native library)
- Quick start instructions (with and without auth)
- Configuration file format and all options
- Available endpoints with curl examples
- Available tools and their scope requirements
- Testing with and without authentication
- Troubleshooting common issues
- Project structure overview
Update build.sh to build the auth example as part of the standard build
process.

New Step 5 - Build auth example:
- Builds stub mode version (auth-server) without native library
- Attempts native auth build (auth-server-native) if library exists
- Gracefully handles missing auth symbols in native library

Updated final output:
- Shows auth example binary location
- Adds instructions for running auth example

Build produces:
- examples/auth/auth-server (stub mode, always works)
- examples/auth/auth-server-native (native auth, optional)
Remove build tag system and stub files. The auth example now always
requires the native library to build and run.

Changes:
- Remove //go:build auth tags from all files
- Delete stub files (main.go stub, oauth_auth_stub.go)
- Rename main_auth.go to main.go
- Simplify build.sh to always build with CGO flags
- Update run_example.sh to require native library

Removed files:
- examples/auth/main_auth.go (merged into main.go)
- examples/auth/middleware/oauth_auth_stub.go

Updated files:
- src/ffi/auth.go - removed build tag
- src/ffi/auth_test.go - removed build tag
- examples/auth/main.go - removed build tag
- examples/auth/middleware/oauth_auth.go - removed build tag
- examples/auth/run_example.sh - requires native library
- build.sh - simplified auth example build step
…lity (#2)

Update OAuth endpoints and middleware to work correctly with MCP Inspector's
OAuth flow, matching the JS implementation behavior.

Changes:
- Update submodule to br_release branch (v0.1.2)
- Add /.well-known/oauth-protected-resource/mcp endpoint
- Fix authorization_servers to point to local server URL
- Add comprehensive CORS headers with MCP-specific headers
- Handle OPTIONS preflight requests in auth middleware
- Add 'none' to token_endpoint_auth_methods_supported for PKCE
- Use localhost instead of 0.0.0.0 for client-facing URLs
- Add -tags auth to run_example.sh build command
- Update build.sh to always pull latest from branch in .gitmodules
Add release infrastructure for Go SDK:
- dump-version.sh: Prepares releases by updating CHANGELOG.md and creating git tags
- install-native.sh: Helper script for users to download native libraries
- CHANGELOG.md: Initial changelog with Keep a Changelog format
- .github/workflows/release.yml: CI workflow to create GitHub Release with native binaries

Release flow: ./dump-version.sh -> git push origin br_release vX.Y.Z
Make the auth example self-contained so it can be used by third-party
developers without needing the full gopher-mcp-go repository locally.

Changes:
- Remove replace directive from go.mod, use SDK via go get
- Update imports from gopher-orch-go to gopher-mcp-go
- Rewrite run_example.sh to download native libs from GitHub releases
- Add SDK_VERSION and NATIVE_LIB_DIR environment variable support
- Update README with installation and troubleshooting instructions
- Add .gitignore for native/ directory and build artifacts
RahulHere added 4 commits March 22, 2026 01:21
Module rename:
- Change module path from gopher-orch-go to gopher-mcp-go
- Update all imports to use new module path
- Add replace directive for local development

Linker fixes:
- Remove invalid -lfmt from LDFLAGS (library doesn't exist)
- Fix in build.sh, run_example.sh, and FFI files
- Remove replace directive (use published SDK from GitHub)
- Download native libraries from gopher-mcp-go releases
- Default to latest release
Align release.yml with publish.md documentation:
- Download native binaries from gopher-orch using same version tag
- Simplify download and prepare steps
- Remove verbose logging

The Go SDK version must match a gopher-orch release version.
Use dump-version.sh to ensure version alignment.
Extract base version (X.Y.Z) from extended version (X.Y.Z.E) when
downloading native binaries from gopher-orch.

Example: Go SDK v0.1.2.1 downloads from gopher-orch v0.1.2
RahulHere added 8 commits March 22, 2026 01:36
- Fetch latest SDK version from GitHub releases automatically
- Use go get instead of go mod download to trigger proxy.golang.org
- Update go.mod with the latest version
- Add documentation about pkg.go.dev indexing
Go's semver does not support 4-part versions (X.Y.Z.E).
Convert to prerelease format (X.Y.Z-E) which is valid Go semver.

- dump-version.sh: Convert 0.1.2.3 -> 0.1.2-3
- release.yml: Extract base version 0.1.2 from 0.1.2-3
X.Y.Z-N (numeric extension like 0.1.2-4) should NOT be marked as
prerelease. Only actual prereleases like -rc1, -alpha, -beta should
be marked as prerelease.
Drop old SDK dependency before go get to avoid version conflict
with cached modules that have incorrect module path.
After fetching the SDK via go get, explicitly trigger pkg.go.dev
indexing by fetching module info through proxy.golang.org.
- Rename title from gopher-orch to gopher-mcp-go
- Update all import paths from gopher-orch-go to gopher-mcp-go
- Fix license from MIT to Apache License 2.0 (matches LICENSE file)
- Add OAuth 2.0 Authentication to features list
- Add Auth Example section with usage instructions
- Document server endpoints and configuration options
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant