Skip to content

Implement Java MCP server with auth feature#3

Open
bettercallsaulj wants to merge 31 commits intodev_java_sdkfrom
dev_java_sdk_auth
Open

Implement Java MCP server with auth feature#3
bettercallsaulj wants to merge 31 commits intodev_java_sdkfrom
dev_java_sdk_auth

Conversation

@bettercallsaulj
Copy link
Collaborator

This PR must be merged after #1

RahulHere added 30 commits March 17, 2026 17:06
Creates the initial project skeleton for the OAuth-protected MCP server
example using Javalin web framework.

Changes:
- Add pom.xml with Javalin 6.1.3, Jackson 2.16.1, SLF4J, JUnit 5
- Configure maven-compiler-plugin for Java 17
- Configure maven-jar-plugin with main class
- Configure maven-shade-plugin for fat JAR packaging
- Add minimal Application.java entry point
- Create directory structure for main and test sources
Creates an INI-style configuration file with all settings required for
the OAuth-protected MCP server.

Changes:
- Add server.config with documented configuration options
- Server settings: host, port, server_url
- OAuth settings: client credentials, auth_server_url, endpoint URLs
- Automatic endpoint derivation from auth_server_url
- Scope configuration for access control
- Cache settings: JWKS duration, auto-refresh, timeout
- Development mode: auth_disabled flag for testing
Implements the configuration class with all server settings and
INI-style configuration file parsing.

Changes:
- Add AuthServerConfig with all configuration fields
- Default values: host 0.0.0.0, port 3001, cache 3600s, timeout 5000ms
- Implement parseConfigFile() for INI format parsing
- Handle comments (#), empty lines, whitespace trimming
- Split only on first '=' to support URLs with query params
- Add unit tests for parsing and default values
Implements configuration building from parsed map and validation logic
for required OAuth fields.

Changes:
- Add buildFromMap() with default value handling
- Derive OAuth endpoints from auth_server_url automatically
- Use localhost in server_url when host is 0.0.0.0
- Add validate() for required fields when auth enabled
- Add fromFile() convenience method
- Add defaultDisabled() factory method
- Support boolean parsing for "true", "1" values
- Add unit tests for derivation, validation, and edge cases
Implements the authentication context model that holds user information
extracted from validated JWT tokens.

Changes:
- Add AuthContext with userId, scopes, audience, tokenExpiry, authenticated
- Implement hasScope() for efficient scope checking
- Add empty() factory for unauthenticated contexts
- Add anonymous() factory for development mode
- Pre-compute scope set for O(1) lookups
- Handle null and empty scopes gracefully
- Add unit tests for scope checking and factory methods
Implement core JSON-RPC 2.0 protocol objects for MCP communication.

Changes:
- Add JsonRpcRequest with jsonrpc, id, method, and params fields
- Add JsonRpcError with standard error codes (-32700 to -32603)
- Add factory methods for parse, invalid request, method not found, invalid params, and internal errors
- Add JsonRpcResponse with success() and error() static builders
- Use @JsonInclude(NON_NULL) to omit null fields in serialization
- Add comprehensive unit tests for serialization and deserialization
Implement tool-related model classes for MCP tool definitions and results.

Changes:
- Add ToolSpec with name, description, inputSchema, and toMap() method
- Add ToolContent with text() and image() factory methods
- Add ToolResult with text() and error() factory methods
- Use @JsonInclude(NON_NULL) to omit null fields in ToolContent
- Add comprehensive unit tests for all factory methods and serialization
Implement CORS handling for MCP server with all required headers.

Changes:
- Add CorsFilter with setCorsHeaders() and handlePreflight() methods
- Include MCP-specific headers (Mcp-Session-Id, Mcp-Protocol-Version)
- Set Access-Control-Allow-Origin to allow all origins
- Set 204 No Content status for preflight requests
- Add Mockito test dependencies for Context mocking
- Add unit tests verifying all CORS headers are set correctly
Point submodule to the latest release version.

Changes:
- Add branch = br_release to .gitmodules
- Update submodule to commit c8e7c406 (Release version 0.1.2)
Implement HealthHandler for server health monitoring.

Changes:
- Add HealthHandler with handle(Context) method
- Return status "ok" and ISO8601 timestamp
- Optionally include version when provided via constructor
- Set content type to application/json
- Add unit tests for all response fields
Implement RFC 9728 protected resource metadata endpoint.

Changes:
- Add OAuthEndpoints class with AuthServerConfig dependency
- Add registerRoutes() to wire up Javalin routes
- Add protectedResourceMetadata() handler returning RFC 9728 JSON
- Register both /.well-known/oauth-protected-resource paths
- Add OPTIONS handlers for CORS preflight
- Split space-separated scopes into list for JSON response
- Add unit tests for response structure and scope splitting
Implement RFC 8414 authorization server metadata discovery.

Changes:
- Add authorizationServerMetadata() handler
- Return issuer, authorization_endpoint, token_endpoint, jwks_uri
- Include scopes_supported, response_types_supported, grant_types_supported
- Include token_endpoint_auth_methods_supported
- Register GET /.well-known/oauth-authorization-server route
- Register OPTIONS handler for CORS preflight
- Add unit tests for response structure and required fields
Implement OIDC discovery and OAuth authorization/registration endpoints.

Changes:
- Add openidConfiguration() handler extending auth server metadata
- Include userinfo_endpoint and id_token_signing_alg_values_supported
- Add authorize() handler with 302 redirect to OAuth provider
- URL-encode all query parameters (response_type, client_id, redirect_uri, etc.)
- Support PKCE with code_challenge and code_challenge_method
- Add register() handler for dynamic client registration
- Generate client_id with "client_" prefix and 16 random chars
- Generate client_secret as UUID
- Return 201 status with registration response
- Add comprehensive unit tests for all endpoints
Implement OAuth middleware foundation with JWT token extraction.

Changes:
- Add GopherAuthClient interface for token validation
- Add ValidationResult and TokenPayload model classes
- Add OAuthAuthMiddleware with extractToken() method
- Support Bearer token from Authorization header (case-insensitive)
- Support access_token query parameter as fallback
- Header takes priority over query parameter
- Add ThreadLocal for current AuthContext
- Add getAuthContext() and isAuthDisabled() accessors
- Add comprehensive unit tests for token extraction
Implement public path detection and auth requirement logic.

Changes:
- Add PUBLIC_PATHS list (/health, /.well-known/, /oauth/, /favicon.ico)
- Add PROTECTED_PREFIXES list (/mcp, /rpc, /events, /sse)
- Add isPublicPath() method to check if path is public
- Add requiresAuth() method with layered checks:
  - Returns false if auth disabled
  - Returns false if auth client is null
  - Returns false if path is public
  - Returns true if path matches protected prefix
  - Returns false for unknown paths (default open)
- Add comprehensive unit tests for all path patterns
Implement 401 Unauthorized response for auth failures.

Changes:
- Add sendUnauthorized() method with error and description params
- Build WWW-Authenticate header with realm, resource_metadata, scope
- Include error and error_description in header
- Set CORS headers on unauthorized response
- Return JSON body with error details
- Escape quotes and backslashes in header values
- Add unit tests for response status, headers, and body
Implement Javalin Handler interface for authentication middleware.

Changes:
- Implement Handler interface for Javalin middleware integration
- Add handle() method with complete auth flow:
  - Public paths set empty context
  - Auth disabled sets anonymous context
  - Null auth client sets anonymous context
  - Missing token returns 401
  - Invalid token returns 401
  - Valid token extracts payload and sets auth context
- Add hasScope() helper delegating to auth context
- Add integration tests for all handle scenarios
- Test valid token flow with mocked GopherAuthClient
Implement core MCP handler for JSON-RPC request processing.

Changes:
- Add McpHandler implementing Javalin Handler
- Define JSON-RPC error code constants
- Add handle() with request parsing and method routing
- Route initialize, tools/list, tools/call, ping methods
- Add handleInitialize() returning protocol version and capabilities
- Add handleToolsList() and handleToolsCall() methods
- Add registerTool() for tool registration
- Add sendSuccess() and sendError() response helpers
- Add comprehensive unit tests for all methods
Implement weather-related MCP tools with authentication.

Changes:
- Add WeatherTools with register() static method
- Add get-weather tool (no scope required)
- Add get-forecast tool (requires mcp:read scope)
- Add get-weather-alerts tool (requires mcp:admin scope)
- Use deterministic data based on city/region hash
- Add accessDenied() helper for scope errors
- Add getCondition() helper for weather conditions
- Check scope only when auth is enabled
- Add comprehensive unit tests for all tools
Implement complete server entry point with configuration and routing.

Changes:
- Add full Application.main() with server initialization
- Print ASCII banner on startup
- Load config from file or use defaults
- Create all components (middleware, endpoints, handlers)
- Register weather tools with MCP handler
- Configure Javalin with CORS and route handlers
- Register auth middleware for /mcp and /rpc paths
- Print available endpoints and auth status
- Add shutdown hook for graceful termination
- Add run_example.sh with --no-auth and --config options
- Add README.md with usage instructions and curl examples
Refactor GopherAuthClient, ValidationResult, and TokenPayload from the
example code to the main library at com.gophersecurity.orch.auth.

This allows the auth interfaces to be reused across multiple examples
and applications that depend on the gopher-orch library.

Changes:
- Create src/main/java/com/gophersecurity/orch/auth/ package
- Move GopherAuthClient, ValidationResult, TokenPayload to library
- Update example imports to use library package
- Add gopher-orch dependency to example pom.xml
- Update root pom.xml to exclude examples/auth from compilation
- Bump library version to 0.1.2
Move AuthContext class from example to the main library at
com.gophersecurity.orch.auth to complete the auth module.

AuthContext represents the authentication state from JWT validation
and pairs naturally with GopherAuthClient, TokenPayload, and
ValidationResult.

Changes:
- Add AuthContext.java to library auth package
- Add AuthContextTest.java to library test directory
- Update OAuthAuthMiddleware import to use library package
- Remove old AuthContext files from example
Update build.sh to include building and testing the auth example
after the main library is built and installed.

Changes:
- Step 6: Install library to local Maven repo (enables example dependency)
- Step 7: Build, test, and package auth example
- Add run auth example command to build output
The MCP inspector was getting CORS errors because authorization_servers
pointed directly to Keycloak instead of the MCP server.

Changes:
- Change authorization_servers to point to MCP server URL (with CORS)
- Change resource field to include /mcp path suffix
- Add registration_endpoint to authorization server metadata
- Add code_challenge_methods_supported (S256)
- Add subject_types_supported to OpenID configuration
- Merge base OIDC scopes (openid, profile, email) with configured scopes
- Use server URL as fallback issuer when issuer is empty
- Remove client_credentials grant type (not used in MCP)
- Remove token response type (only code flow supported)
- Add none to token_endpoint_auth_methods_supported
- Update tests to match new behavior
Match the JS/Rust implementations for proper MCP OAuth flow:

/oauth/authorize:
- Forward all query parameters as-is to the auth endpoint
- Don't add/override client_id - just pass through what client sends

/oauth/register:
- Return pre-configured server credentials (stateless mode)
- MCP clients "register" and receive the server's OAuth credentials
- This allows clients to use credentials that work with the real IDP

This fixes the "client_id is not right" error in MCP inspector.
Changed run_example.sh to always run mvn package instead of only
building when the JAR doesn't exist. This ensures code changes are
picked up each time the server is started.
The authorization_endpoint in OAuth metadata must point to the MCP
server's /oauth/authorize endpoint, not directly to the external auth
server. This enables MCP Inspector to redirect through the MCP server
which then proxies to the real auth provider.

Changes:
- authorization_endpoint now returns {server_url}/oauth/authorize
- token_endpoint still points directly to external auth server
- Updated tests to reflect new behavior
When auth_disabled=false, protected paths (/mcp, /rpc) now return 401
even when the native auth client is not initialized. This triggers
the OAuth flow in MCP Inspector instead of bypassing authentication.

Changes:
- Remove authClient null check bypass in middleware
- Middleware now returns 401 for missing tokens regardless of client
- Revert authorization_endpoint to use external OAuth URL
- Update tests to reflect new behavior
The status message now correctly shows auth is enabled for OAuth flow
even when native client is not loaded. Also fixes potential NPE when
a token is provided but authClient is null.

Changes:
- Update status message to show "ENABLED (OAuth flow only)"
- Add null check for authClient before token validation
- Return 401 with "server_error" when client unavailable
When native auth client is not loaded:
- No token: return 401 to trigger OAuth flow
- With token: allow request (trust the OAuth token)

This prevents infinite OAuth loops when MCP Inspector successfully
authenticates but the server can't validate tokens natively.

Changes:
- Allow requests with tokens when authClient is null
- Update status message to clarify behavior
- Add test for token-with-null-client case
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