This example demonstrates how to set up an OAuth2-protected MCP server using the NVIDIA NeMo Agent Toolkit FastMCP server runtime. This complements the unprotected Simple Calculator FastMCP example to demonstrate both authenticated and unauthenticated MCP servers using the FastMCP server runtime.
This example uses per-user mode, enabling complete per-user isolation while accessing the same protected calculator tools.
This example consists of three main components:
graph TB
subgraph Client["MCP Client (Calculator Client)"]
direction TB
CalculatorClient["Calculator Client Workflow"]
MCPClientPlugin["MCP Client Plugin<br/>• Discovers tools<br/>• Handles OAuth2 flow<br/>• Manages JWT tokens"]
CalculatorClient --> MCPClientPlugin
end
subgraph Server["MCP Server Using FastMCP Runtime (Calculator)"]
direction TB
Calculator["Calculator Workflow"]
OAuthMiddleware["FastMCP OAuth2 Resource Server Middleware<br/>• Introspects tokens<br/>• Checks scopes<br/>• Validates activity"]
Calculator --> OAuthMiddleware
end
subgraph Auth["Authorization Server (Keycloak)"]
direction LR
AuthCore["Keycloak OAuth2 Server<br/>• Authenticates users<br/>• Issues tokens<br/>• Provides introspection endpoint"]
end
MCPClientPlugin -->|"① MCP initialize<br/>(triggers authentication flow)"| Calculator
MCPClientPlugin -->|"② OAuth2 Authorization Flow<br/>(Browser-based)"| AuthCore
MCPClientPlugin -->|"③ MCP tool calls<br/>(Authorization: Bearer token)"| Calculator
OAuthMiddleware -.->|"Introspect token<br/>(RFC 7662)"| AuthCore
style Client fill:#e1f5ff
style Server fill:#ffe1e1
style Auth fill:#e1ffe1
- NVIDIA NeMo Agent Toolkit installed (see Installation Guide)
- Keycloak server running locally (see setup instructions below)
# Start Keycloak
docker run -d --name keycloak \
-p 127.0.0.1:8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest start-devWait for Keycloak to start (about 30-60 seconds). Check logs:
docker logs -f keycloakLook for: Listening on: http://0.0.0.0:8080
Access Keycloak: Open http://localhost:8080 in your browser
-
Log in to Keycloak Admin Console:
- Username:
admin - Password:
admin
- Username:
-
Verify you are in the
masterrealm (top-left dropdown) -
Create the
calculator_mcp_executescope (for the calculator):- Go to Client scopes (left sidebar)
- Click Create client scope
- Fill in:
- Name:
calculator_mcp_execute - Description:
Permission to execute calculator operations - Type:
Optional - Protocol:
openid-connect - Include in token scope:
On
- Name:
- Click Save
-
Verify OpenID discovery endpoint:
curl http://localhost:8080/realms/master/.well-known/openid-configuration | python3 -m json.toolYou should see the OAuth2 endpoints, including:
authorization_endpoint:http://localhost:8080/realms/master/protocol/openid-connect/authtoken_endpoint:http://localhost:8080/realms/master/protocol/openid-connect/tokenintrospection_endpoint:http://localhost:8080/realms/master/protocol/openid-connect/token/introspect
You can register the client manually or use the dynamic client registration feature. For testing, manual registration is used.
-
In Keycloak Admin Console, go to Clients (left sidebar)
-
Click Create client
-
General settings:
- Client ID:
nat-mcp-client - Client type:
OpenID Connect - Click Next
- Client ID:
-
Capability config:
- Client authentication:
On(confidential client) - Authorization:
Off - Authentication flow:
- Standard flow (authorization code)
- Direct access grants
- Click Next
- Client authentication:
-
Login settings:
- Valid redirect URIs:
http://localhost:8000/auth/redirect - Web origins:
http://localhost:8000 - Click Save
- Valid redirect URIs:
-
Add client scope if not already added:
- Go to Client scopes tab
- Click Add client scope
- Select
calculator_mcp_execute - Choose Optional
- Click Add
-
Set Consent required:
- Go to Settings tab
- Toggle Consent required to
On(scroll down to the bottom of the page to see the setting) - Click Save
-
Get client credentials:
- Go to Credentials tab
- Copy the Client secret
- Note the Client ID:
nat-mcp-client
The FastMCP server runtime uses OAuth2 token introspection for this example. Register a resource server client so the MCP server can authenticate to Keycloak when introspecting tokens.
-
In Keycloak Admin Console, go to Clients
-
Click Create client
-
General settings:
- Client ID:
nat-mcp-resource-server - Client type:
OpenID Connect - Click Next
- Client ID:
-
Capability config:
- Client authentication:
On(confidential client) - Authorization:
Off - Authentication flow:
- Direct access grants
- Click Next
- Click Save
- Client authentication:
-
Get resource server credentials:
- Go to Credentials tab
- Copy the Client secret
- Note the Client ID:
nat-mcp-resource-server
# Terminal 1
export NAT_CALCULATOR_RESOURCE_CLIENT_ID="nat-mcp-resource-server" # Resource server client ID
export NAT_CALCULATOR_RESOURCE_CLIENT_SECRET="<your-resource-client-secret>" # Resource server client secret from Step 4.5
nat fastmcp server run --config_file examples/MCP/simple_calculator_fastmcp_protected/configs/config-server.ymlSet the client ID and client secret from Step 3 in the environment variables:
# Terminal 2
export NAT_CALCULATOR_CLIENT_ID="nat-mcp-client" # OAuth client ID for the MCP client (auth code flow)
export NAT_CALCULATOR_CLIENT_SECRET="<your-client-secret>" # OAuth client secret for the MCP client from Step 3.8
nat run --config_file examples/MCP/simple_calculator_fastmcp_protected/configs/config-client.yml \
--input "Is the product of 2 and 3 greater than the current hour of the day?"What should happen:
- Browser opens with Keycloak login page
- Log in with any user (or create one)
- Consent page appears requesting
calculator_mcp_executescope - Browser redirects back to
localhost:8000/auth/redirect - Workflow continues and calls the calculator
- Response returned successfully
To stop and remove Keycloak:
docker stop keycloak
docker rm keycloakTo restart with clean state:
docker rm -f keycloak
# Then run the start command againThis configures the protected MCP server frontend with OAuth2 resource server authentication using the FastMCP server runtime:
general:
front_end:
_type: fastmcp
name: "Protected Calculator FastMCP"
port: 9902
host: localhost
server_auth:
issuer_url: http://localhost:8080/realms/master
introspection_endpoint: http://localhost:8080/realms/master/protocol/openid-connect/token/introspect
client_id: ${NAT_CALCULATOR_RESOURCE_CLIENT_ID:-"nat-mcp-resource-server"}
client_secret: ${NAT_CALCULATOR_RESOURCE_CLIENT_SECRET}
scopes: [calculator_mcp_execute]This configures an MCP client to connect to the protected MCP server in per-user mode:
function_groups:
mcp_calculator_protected:
_type: per_user_mcp_client
server:
transport: streamable-http
url: http://localhost:9902/mcp
auth_provider: mcp_oauth2_calculator
authentication:
mcp_oauth2_calculator:
_type: mcp_oauth2
server_url: http://localhost:9902/mcp
redirect_uri: http://localhost:8000/auth/redirect
client_id: ${NAT_CALCULATOR_CLIENT_ID:-"nat-mcp-client"}
client_secret: ${NAT_CALCULATOR_CLIENT_SECRET}
scopes: [calculator_mcp_execute]
workflow:
_type: per_user_react_agent
tool_names: [mcp_calculator_protected]examples/MCP/simple_calculator_fastmcp/: FastMCP calculator example without authentication
- FastMCP Server - Learn about running the FastMCP server runtime
- MCP Client - Learn about using the MCP client to interact with the MCP server