You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement end-to-end OAuth 2.0 authentication for the MCP server using FastMCP v3 (>=3.0.0) auth primitives, where MCP clients authenticate via an external Identity Provider (IdP) and the server forwards the JWT as a Bearer token directly to Splunk 10.2's REST API -- eliminating the need for Splunk username/password credentials.
Problem Statement
Currently, the MCP server authenticates to Splunk using username/password credentials passed via HTTP headers, environment variables, or tool parameters (src/client/splunk_client.py). This has security limitations:
Credentials are transmitted and cached in memory
No token expiration or rotation
No integration with enterprise identity providers
No fine-grained authorization based on user identity
Three issues were identified and corrected during planning:
No token exchange endpoint exists. Splunk 10.2 does NOT expose POST /services/auth/oauth2/token. Instead, Splunk accepts IdP JWTs directly as Bearer tokens on standard REST API endpoints. Splunk validates the JWT against its OIDC configuration (JWKS, issuer, audience) and maps groups to roles internally.
FastMCP version is v3, not v2. The project uses fastmcp>=3.0.0,<4. All API references use correct v3 class names, import paths, and constructor signatures (OIDCProxy, OAuthProxy with upstream_* params, JWTVerifier, get_access_token()).
Two Splunk client files exist. Both src/client/splunk_client.py (config-based, no token support) and src/splunk_client.py (legacy, already supports token=) exist. These must be consolidated.
Architecture
End-to-End Flow
sequenceDiagram
participant Client as MCP Client
participant IdP as Identity Provider
participant MCP as MCP Server
participant Splunk as Splunk 10.2+
Client->>MCP: GET /.well-known/oauth-protected-resource
MCP-->>Client: Authorization server metadata (IdP URL)
Client->>IdP: Authenticate (OAuth 2.0 flow)
IdP-->>Client: JWT access token (with scopes, groups, client_id)
Client->>MCP: MCP request + Bearer JWT
MCP->>MCP: FastMCP validates JWT (JWTVerifier via JWKS)
MCP->>MCP: Authorization checks (scopes, tags)
MCP->>MCP: Extract JWT from AccessToken via get_access_token()
MCP->>Splunk: REST API call with Authorization: Bearer JWT
Splunk->>Splunk: Validate JWT via OIDC config, map groups to Splunk roles
Splunk-->>MCP: Splunk data response
MCP-->>Client: MCP response
Loading
The key insight: the MCP server is a pass-through for the JWT. It validates the token for its own authorization purposes (via FastMCP), then forwards the same JWT to Splunk's REST API. Splunk independently validates the JWT against its own OIDC configuration.
Two Authentication Boundaries
flowchart LR
subgraph boundary1 [Boundary 1: MCP Client to MCP Server]
MCPClient[MCP Client] -->|"OIDCProxy / OAuthProxy"| IdP[Identity Provider]
IdP -->|"JWT access token"| MCPClient
MCPClient -->|"Bearer JWT"| MCPServer[MCP Server]
MCPServer -->|"JWTVerifier validates"| MCPServer
end
subgraph boundary2 [Boundary 2: MCP Server to Splunk]
MCPServer -->|"Bearer JWT passthrough"| SplunkAPI[Splunk REST API]
SplunkAPI -->|"OIDC validation + role mapping"| SplunkAPI
end
Loading
FastMCP v3 Auth Primitives
Boundary 1: OIDCProxy (recommended) or OAuthProxy
OIDCProxy (from fastmcp.server.auth.oidc_proxy) is the recommended choice for IdP-agnostic design. It auto-discovers endpoints from the IdP's /.well-known/openid-configuration, reducing env var count from 11 to 5.
Phase 0: Spike -- Validate JWT Bearer Passthrough to Splunk
Before building anything, validate the core assumption:
Test 1: Does splunklib.client.Service(token=<idp_jwt>) set Authorization: Bearer <jwt> or Authorization: Splunk <jwt>? The legacy Splunk token format uses Splunk prefix, not Bearer.
Test 2: If splunklib uses the wrong header format, test raw httpx calls with Authorization: Bearer <jwt> against a Splunk 10.2 instance with OIDC configured.
Outcome: Determines whether Phase 1 uses splunklib or httpx for the OAuth path.
Summary
Implement end-to-end OAuth 2.0 authentication for the MCP server using FastMCP v3 (
>=3.0.0) auth primitives, where MCP clients authenticate via an external Identity Provider (IdP) and the server forwards the JWT as a Bearer token directly to Splunk 10.2's REST API -- eliminating the need for Splunk username/password credentials.Problem Statement
Currently, the MCP server authenticates to Splunk using username/password credentials passed via HTTP headers, environment variables, or tool parameters (
src/client/splunk_client.py). This has security limitations:Splunk 10.2+ introduces OAuth 2.0 external authorization via OIDC, and FastMCP v3 provides OIDCProxy, OAuthProxy, and Authorization primitives. We can connect these to build a secure end-to-end flow.
Critical Design Decisions (from review)
Three issues were identified and corrected during planning:
POST /services/auth/oauth2/token. Instead, Splunk accepts IdP JWTs directly as Bearer tokens on standard REST API endpoints. Splunk validates the JWT against its OIDC configuration (JWKS, issuer, audience) and maps groups to roles internally.fastmcp>=3.0.0,<4. All API references use correct v3 class names, import paths, and constructor signatures (OIDCProxy,OAuthProxywithupstream_*params,JWTVerifier,get_access_token()).src/client/splunk_client.py(config-based, no token support) andsrc/splunk_client.py(legacy, already supportstoken=) exist. These must be consolidated.Architecture
End-to-End Flow
sequenceDiagram participant Client as MCP Client participant IdP as Identity Provider participant MCP as MCP Server participant Splunk as Splunk 10.2+ Client->>MCP: GET /.well-known/oauth-protected-resource MCP-->>Client: Authorization server metadata (IdP URL) Client->>IdP: Authenticate (OAuth 2.0 flow) IdP-->>Client: JWT access token (with scopes, groups, client_id) Client->>MCP: MCP request + Bearer JWT MCP->>MCP: FastMCP validates JWT (JWTVerifier via JWKS) MCP->>MCP: Authorization checks (scopes, tags) MCP->>MCP: Extract JWT from AccessToken via get_access_token() MCP->>Splunk: REST API call with Authorization: Bearer JWT Splunk->>Splunk: Validate JWT via OIDC config, map groups to Splunk roles Splunk-->>MCP: Splunk data response MCP-->>Client: MCP responseThe key insight: the MCP server is a pass-through for the JWT. It validates the token for its own authorization purposes (via FastMCP), then forwards the same JWT to Splunk's REST API. Splunk independently validates the JWT against its own OIDC configuration.
Two Authentication Boundaries
flowchart LR subgraph boundary1 [Boundary 1: MCP Client to MCP Server] MCPClient[MCP Client] -->|"OIDCProxy / OAuthProxy"| IdP[Identity Provider] IdP -->|"JWT access token"| MCPClient MCPClient -->|"Bearer JWT"| MCPServer[MCP Server] MCPServer -->|"JWTVerifier validates"| MCPServer end subgraph boundary2 [Boundary 2: MCP Server to Splunk] MCPServer -->|"Bearer JWT passthrough"| SplunkAPI[Splunk REST API] SplunkAPI -->|"OIDC validation + role mapping"| SplunkAPI endFastMCP v3 Auth Primitives
Boundary 1: OIDCProxy (recommended) or OAuthProxy
OIDCProxy (from
fastmcp.server.auth.oidc_proxy) is the recommended choice for IdP-agnostic design. It auto-discovers endpoints from the IdP's/.well-known/openid-configuration, reducing env var count from 11 to 5.OAuthProxy fallback (from
fastmcp.server.auth import OAuthProxy) for IdPs without OIDC discovery:Authorization: FastMCP v3 callable system
require_scopes("splunk:read")-- scope-based per-toolrestrict_tag("admin", scopes=["splunk:admin"])-- tag-based via middlewareget_access_token()-- retrieveAccessTokeninside tools (contains.tokenraw string,.claims,.scopes)Implementation Phases
Phase 0: Spike -- Validate JWT Bearer Passthrough to Splunk
Before building anything, validate the core assumption:
splunklib.client.Service(token=<idp_jwt>)setAuthorization: Bearer <jwt>orAuthorization: Splunk <jwt>? The legacy Splunk token format usesSplunkprefix, notBearer.splunklibuses the wrong header format, test rawhttpxcalls withAuthorization: Bearer <jwt>against a Splunk 10.2 instance with OIDC configured.splunkliborhttpxfor the OAuth path.Phase 1: Splunk JWT Bearer Adapter
src/client/splunk_bearer_auth.py-- JWT Bearer passthrough adapter (NOT a token exchange)SplunkBearerAuthclass that creates Splunk connections using JWT Bearer tokenshttpx-based wrapper ifsplunklibdoes not support Bearer formatPhase 2: Consolidate Splunk Client Files
src/splunk_client.pyintosrc/client/splunk_client.pybearer_tokenparameter toget_splunk_service()src/splunk_client.pyPhase 3: FastMCP Auth Provider Setup
src/auth/__init__.pypackagesrc/auth/splunk_oidc_auth.pywithcreate_splunk_auth_provider()factoryOIDCProxy(preferred) orOAuthProxybased on env varsPhase 4: Authorization Middleware
src/auth/splunk_authorization.pyrequire_splunk_group(group)-- checks JWT groups claimAuthContextpatternPhase 5: Wire Into Server Startup
src/server.py(lines 486-591) to supportSPLUNK_AUTH_MODE=oauthcreate_splunk_auth_provider()to get the auth providerFastMCP(auth=auth_provider)AuthMiddlewareinstances for tag-based authorizationPhase 6: Token Forwarding in Request Pipeline
src/core/base.pyBaseTool.get_splunk_service()to useget_access_token().token(raw JWT string) fromAccessTokenget_splunk_service(bearer_token=raw_jwt)flowchart TD Request[Incoming MCP Request with Bearer JWT] --> FastMCPAuth[FastMCP Auth Layer validates JWT] FastMCPAuth --> AuthMiddleware[AuthMiddleware checks scopes/tags] AuthMiddleware --> Tool[Tool executes] Tool --> GetToken["get_access_token() extracts JWT"] GetToken --> GetService["get_splunk_service(bearer_token=jwt)"] GetService --> SplunkBearerAuth[SplunkBearerAuth forwards JWT] SplunkBearerAuth --> SplunkAPI[Splunk REST API validates JWT via OIDC]Phase 7: Tests
SplunkBearerAuth-- mocksplunklib.client.Serviceand verify token is passed correctlycreate_splunk_auth_provider()-- verify correct provider is created based on env varsStaticTokenVerifierfor dev testing (no real IdP needed)Phase 8: Documentation
docs/auth-oauth.mdsetup guideEnvironment Variables (Reduced)
With
OIDCProxy, only 5-6 env vars needed:SPLUNK_AUTH_MODEoauthorlegacy(default:legacy)IDP_OIDC_CONFIG_URL/.well-known/openid-configurationURLMCP_OAUTH_CLIENT_IDMCP_OAUTH_CLIENT_SECRETMCP_BASE_URLMCP_OAUTH_AUDIENCEFor
OAuthProxyfallback (non-OIDC IdPs), add:IDP_AUTHORIZE_URL,IDP_TOKEN_URL,IDP_JWKS_URI,IDP_ISSUER_URLFile Changes Summary
src/auth/__init__.pysrc/auth/splunk_oidc_auth.pysrc/auth/splunk_authorization.pysrc/client/splunk_bearer_auth.pysrc/client/splunk_client.pysrc/splunk_client.pysrc/client/splunk_client.pysrc/core/base.pyget_access_token()to forward JWTsrc/server.pycreate_splunk_auth_provider()docs/auth-oauth.mdtests/test_splunk_bearer_auth.pytests/test_splunk_oidc_auth.pytests/test_splunk_authorization.pyBackward Compatibility
SPLUNK_AUTH_MODE=legacy)MCP_AUTH_PROVIDERdynamic loading preserved (OAuth mode is an alternative, not a replacement)SPLUNK_HOST,SPLUNK_USERNAME, etc.) continue to workMCP_AUTH_DISABLED=truestill bypasses all authDependencies
fastmcp>=3.0.0,<4(already in pyproject.toml)httpx>=0.27.0(already in pyproject.toml -- needed if splunklib does not support Bearer format)splunk-sdk>=2.1.0(already in pyproject.toml)References