Secure MCP tools with OAuth2 and Okta
Configure the AI MCP Proxy plugin to expose REST API endpoints as MCP tools, then add the AI MCP OAuth2 plugin to validate access tokens from Okta before MCP clients can call those tools. Then, use MCP Inspector to validate the OAuth2 flow.
Prerequisites
Kong Konnect
This is a Konnect tutorial and requires a Konnect personal access token.
-
Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.
-
Export your token to an environment variable:
export KONNECT_TOKEN='YOUR_KONNECT_PAT'Copied! -
Run the quickstart script to automatically provision a Control Plane and Data Plane, and configure your environment:
curl -Ls https://get.konghq.com/quickstart | bash -s -- -k $KONNECT_TOKEN --deck-outputCopied!This sets up a Konnect Control Plane named
quickstart, provisions a local Data Plane, and prints out the following environment variable exports:export DECK_KONNECT_TOKEN=$KONNECT_TOKEN export DECK_KONNECT_CONTROL_PLANE_NAME=quickstart export KONNECT_CONTROL_PLANE_URL=https://us.api.konghq.com export KONNECT_PROXY_URL='http://localhost:8000'Copied!Copy and paste these into your terminal to configure your session.
Kong Gateway running
This tutorial requires Kong Gateway Enterprise. If you don’t have Kong Gateway set up yet, you can use the quickstart script with an enterprise license to get an instance of Kong Gateway running almost instantly.
-
Export your license to an environment variable:
export KONG_LICENSE_DATA='LICENSE-CONTENTS-GO-HERE'Copied! -
Run the quickstart script:
curl -Ls https://get.konghq.com/quickstart | bash -s -- -e KONG_LICENSE_DATACopied!Once Kong Gateway is ready, you will see the following message:
Kong Gateway Ready
decK v1.43+
decK is a CLI tool for managing Kong Gateway declaratively with state files. To complete this tutorial, install decK version 1.43 or later.
This guide uses deck gateway apply, which directly applies entity configuration to your Gateway instance.
We recommend upgrading your decK installation to take advantage of this tool.
You can check your current decK version with deck version.
Required entities
For this tutorial, you’ll need Kong Gateway entities, like Gateway Services and Routes, pre-configured. These entities are essential for Kong Gateway to function but installing them isn’t the focus of this guide. Follow these steps to pre-configure them:
-
Run the following command:
echo ' _format_version: "3.0" services: - name: weather-api-service url: https://api.weatherapi.com/v1 routes: - name: weather-api-route paths: - "/api/weather" service: name: weather-api-service protocols: - http - https - name: weather-mcp paths: - "/weather/mcp" - "/.well-known/oauth-protected-resource/weather/mcp" service: name: weather-api-service protocols: - http - https ' | deck gateway apply -Copied!
To learn more about entities, you can read our entities documentation.
WeatherAPI
- Go to WeatherAPI.
- Sign up for a free account.
- Navigate to your dashboard and copy your API key.
-
Export your API key:
export DECK_WEATHERAPI_API_KEY='your-weatherapi-api-key'Copied!
Okta
You need an Okta admin account with a developer organization.
Complete the following steps to configure Okta for MCP OAuth2 authentication. This setup creates two application registrations: a Web Application (used by AI Gateway for token introspection) and a Native Application (used by MCP Inspector for the authorization code flow).
Add a custom scope
- Go to Security > API > Authorization Servers.
- Click
default. - Go to the Scopes tab.
- Click Add Scope.
- Name:
mcp:access - Display phrase:
Access MCP tools - Check Set as a default scope.
- Click Create.
Add an access policy
- In the same
defaultauthorization server, go to the Access Policies tab. - Click Add Policy.
- Name:
MCP Access - Assign to: All clients
- Click Create Policy.
Add a rule to the policy
- Inside the
MCP Accesspolicy, click Add Rule. - Rule Name:
Allow MCP - Grant type: check Client Credentials, Authorization Code, and Device Authorization.
- User is: Any user assigned the app
- Scopes requested: Any scopes
- Click Create Rule.
Export your Okta authorization server URL and introspection endpoint. To find these values:
- Go to Security > API > Authorization Servers.
- Click the
defaultserver. - Copy the Issuer URI (for example,
https://your-org.okta.com/oauth2/default). This is the authorization server URL. - Append
/v1/introspectto the Issuer URI to get the introspection endpoint.
export DECK_OKTA_AUTH_SERVER='https://your-org.okta.com/oauth2/default'
export DECK_OKTA_INTROSPECTION_ENDPOINT='https://your-org.okta.com/oauth2/default/v1/introspect'
Create the web application (used by AI Gateway for introspection)
- Go to Applications > Applications > Create App Integration.
- Sign-in method: OIDC - OpenID Connect
- Application type: Web Application
- App integration name:
Kong MCP Gateway - Grant types: check Client Credentials and Authorization Code.
- Set Sign-in redirect URIs to any valid URL (for example,
http://localhost/unused). Kong does not use the redirect flow for this app, but Okta requires the field. - Assignments: Skip group assignment for now
- Click Save.
- Copy the Client ID and Client Secret. These go into the AI Gateway
ai-mcp-oauth2plugin config. - Go to the Assignments tab, click Assign > Assign to People, and assign your user.
-
Export the credentials:
export DECK_OKTA_CLIENT_ID='your-kong-web-app-client-id' export DECK_OKTA_CLIENT_SECRET='your-kong-web-app-client-secret'Copied!
Create the native application (used by MCP Inspector)
- Go to Applications > Applications > Create App Integration.
- Sign-in method: OIDC - OpenID Connect
- Application type: Native Application
- App integration name:
MCP Inspector - Grant types: check Authorization Code.
- Sign-in redirect URIs:
http://localhost:6274/oauth/callback/debug - Assignments: Skip group assignment for now
- Click Save.
- Go to the Assignments tab, click Assign > Assign to People, and assign your user.
- Copy the Client ID. This is the Client ID you enter in MCP Inspector. No secret is needed for this public client.
The two applications serve different purposes. The Web Application Client ID and Client Secret go into the AI Gateway
ai-mcp-oauth2Plugin config for token introspection. The Native Application Client ID is what you enter in MCP Inspector when connecting to the OAuth-protected MCP endpoint.
MCP Inspector
This guide uses the MCP Inspector to test the OAuth-protected MCP endpoint.
-
Ensure you have Node.js and npm installed. If needed, download them from https://nodejs.org.
- Update
npxto the latest version:npm install -g npxCopied! - Install the Inspector:
npm install -g @modelcontextprotocol/inspectorCopied!
Configure the AI MCP Proxy tools
Configure the AI MCP Proxy plugin in conversion-only mode on the weather-api-route Route. This instance converts the WeatherAPI REST endpoints into MCP tool definitions. The weather-tools tag lets the listener instance discover and aggregate these tools.
echo '
_format_version: "3.0"
plugins:
- name: ai-mcp-proxy
route: weather-api-route
tags:
- weather-tools
config:
mode: conversion-only
tools:
- annotations:
title: Realtime API
description: Forecast weather API method returns, depending upon your price
plan level, upto next 14 day weather forecast and weather alert as json or
xml. The data is returned as a Forecast Object.<br /><br />Forecast object
contains astronomy data, day weather forecast and hourly interval weather
information for a given city.
method: GET
path: current.json
query:
key:
- "${{ env "DECK_WEATHERAPI_API_KEY" }}"
parameters:
- name: q
in: query
description: Pass US Zipcode, UK Postcode, Canada Postalcode, IP address,
Latitude/Longitude (decimal degree) or city name. Visit [request parameter
section](https://www.weatherapi.com/docs/#intro-request) to learn more.
required: true
type: string
- in: query
name: lang
type: string
required: false
description: Returns 'condition:text' field in API in the desired language.<br
/> Visit [request parameter section](https://www.weatherapi.com/docs/#intro-request)
to check 'lang-code'.
- annotations:
title: Forecast API
description: Forecast weather API method returns, depending upon your price
plan level, upto next 14 day weather forecast and weather alert as json or
xml. The data is returned as a Forecast Object.<br /><br />Forecast object
contains astronomy data, day weather forecast and hourly interval weather
information for a given city.
method: GET
path: forecast.json
query:
key:
- "${{ env "DECK_WEATHERAPI_API_KEY" }}"
parameters:
- in: query
name: q
schema:
type: string
required: true
description: Pass US Zipcode, UK Postcode, Canada Postalcode, IP address,
Latitude/Longitude (decimal degree) or city name. Visit [request parameter
section](https://www.weatherapi.com/docs/#intro-request) to learn more.
- in: query
name: days
schema:
type: integer
enum:
- 1
- 2
- 3
required: true
description: Number of days of weather forecast. Value ranges from 1 to 14
- in: query
name: dt
schema:
type: string
format: date
required: false
description: Date should be between today and next 14 day in yyyy-MM-dd format.
e.g. '2015-01-01'
- in: query
name: unixdt
schema:
type: integer
required: false
description: Please either pass 'dt' or 'unixdt' and not both in same request.
unixdt should be between today and next 14 day in Unix format. e.g. 1490227200
- in: query
name: hour
schema:
type: integer
required: false
description: Must be in 24 hour. For example 5 pm should be hour=17, 6 am
as hour=6
- in: query
name: lang
schema:
type: string
required: false
description: Returns 'condition:text' field in API in the desired language.<br
/> Visit [request parameter section](https://www.weatherapi.com/docs/#intro-request)
to check 'lang-code'.
- in: query
name: alerts
schema:
type: string
required: false
description: Enable/Disable alerts in forecast API output. Example, alerts=yes
or alerts=no.
- in: query
name: aqi
schema:
type: string
required: false
description: Enable/Disable Air Quality data in forecast API output. Example,
aqi=yes or aqi=no.
- in: query
name: tp
schema:
type: integer
required: false
description: Get 15 min interval or 24 hour average data for Forecast and
History API. Available for Enterprise clients only. E.g:- tp=15
- annotations:
title: History API
description: History weather API method returns historical weather for a date
on or after 1st Jan, 2010 as json. The data is returned as a Forecast Object.
method: GET
path: history.json
query:
key:
- "${{ env "DECK_WEATHERAPI_API_KEY" }}"
parameters:
- name: q
in: query
description: Pass US Zipcode, UK Postcode, Canada Postalcode, IP address,
Latitude/Longitude (decimal degree) or city name. Visit [request parameter
section](https://www.weatherapi.com/docs/#intro-request) to learn more.
required: true
type: string
- name: dt
in: query
description: Date on or after 1st Jan, 2015 in yyyy-MM-dd format
required: true
type: string
format: date
- name: unixdt
in: query
description: Please either pass 'dt' or 'unixdt' and not both in same request.<br
/>unixdt should be on or after 1st Jan, 2015 in Unix format
required: false
type: integer
- name: end_dt
in: query
description: Date on or after 1st Jan, 2015 in yyyy-MM-dd format<br />'end_dt'
should be greater than 'dt' parameter and difference should not be more
than 30 days between the two dates.
required: false
type: string
format: date
- name: unixend_dt
in: query
description: Date on or after 1st Jan, 2015 in Unix Timestamp format<br />unixend_dt
has same restriction as 'end_dt' parameter. Please either pass 'end_dt'
or 'unixend_dt' and not both in same request. e.g. unixend_dt=1490227200
required: false
type: integer
- name: hour
in: query
description: Must be in 24 hour. For example 5 pm should be hour=17, 6 am
as hour=6
required: false
type: integer
- name: lang
in: query
description: Returns 'condition:text' field in API in the desired language.<br
/> Visit [request parameter section](https://www.weatherapi.com/docs/#intro-request)
to check 'lang-code'.
required: false
type: string
- annotations:
title: Search API
description: WeatherAPI.com Search or Autocomplete API returns matching cities
and towns as an array of Location object.
method: GET
path: search.json
query:
key:
- "${{ env "DECK_WEATHERAPI_API_KEY" }}"
parameters:
- name: q
in: query
description: Pass US Zipcode, UK Postcode, Canada Postalcode, IP address,
Latitude/Longitude (decimal degree) or city name. Visit [request parameter
section](https://www.weatherapi.com/docs/#intro-request) to learn more.
required: true
type: string
- annotations:
title: IP Lookup API
description: IP Lookup API method allows a user to get up to date information
for an IP address.
method: GET
path: ip.json
query:
key:
- "${{ env "DECK_WEATHERAPI_API_KEY" }}"
parameters:
- name: q
in: query
description: Pass IP address.
required: true
type: string
' | deck gateway apply -
Configure the AI MCP Proxy listener
Configure a second AI MCP Proxy plugin instance in listener mode on the weather-mcp Route. This instance aggregates tools tagged weather-tools and serves them over the MCP protocol to connected clients.
echo '
_format_version: "3.0"
plugins:
- name: ai-mcp-proxy
route: weather-mcp
config:
mode: listener
server:
tag: weather-tools
timeout: 45000
logging:
log_statistics: true
log_payloads: false
max_request_body_size: 32768
' | deck gateway apply -
Configure the CORS plugin
Add the CORS plugin to the weather-mcp Route so that MCP Inspector’s browser-based OAuth callback can reach the MCP endpoint.
echo '
_format_version: "3.0"
plugins:
- name: cors
route: weather-mcp
enabled: true
config:
origins:
- http://localhost:6274
' | deck gateway apply -
Configure the AI MCP OAuth2 plugin
Configure the AI MCP OAuth2 plugin on the weather-mcp Route. This plugin validates OAuth2 access tokens issued by Okta before allowing MCP clients to call the weather tools.
The resource field identifies this MCP server to the authorization server. The metadata_endpoint path must match one of the paths on the weather-mcp Route so the plugin can serve the OAuth Protected Resource Metadata that MCP clients need to discover the authorization server.
insecure_relaxed_audience_validation is set to true because Okta does not yet include the resource URL in the audience (aud) claim as defined in RFC 8707.
echo '
_format_version: "3.0"
plugins:
- name: ai-mcp-oauth2
route: weather-mcp
enabled: true
config:
client_id: "${{ env "DECK_OKTA_CLIENT_ID" }}"
client_secret: "${{ env "DECK_OKTA_CLIENT_SECRET" }}"
insecure_relaxed_audience_validation: true
authorization_servers:
- "${{ env "DECK_OKTA_AUTH_SERVER" }}"
introspection_endpoint: "${{ env "DECK_OKTA_INTROSPECTION_ENDPOINT" }}"
resource: http://localhost:8000/weather/mcp
metadata_endpoint: "/.well-known/oauth-protected-resource/weather/mcp"
' | deck gateway apply -
Connect with MCP Inspector
-
Start MCP Inspector:
npx @modelcontextprotocol/inspector@latest --mcp-url http://localhost:8000/weather/mcpCopied! -
Open the MCP Inspector UI in your browser at the URL shown in the terminal output.
-
Set Transport Type to Streamable HTTP.
-
Set the URL to
http://localhost:8000/weather/mcp. -
Select Authentication in the left sidebar.
-
Enter the Native Application Client ID from the Okta setup (the
MCP Inspectorapp, not theKong MCP Gatewayapp). Leave Client Secret empty.Use the Client ID from the Native Application (
MCP Inspector) you created in Okta. Do not use the Web Application Client ID. The Web Application credentials are used by Kong Gateway for token introspection, not by MCP clients. -
Click Guided OAuth Flow.
-
Metadata Discovery: click Continue.
-
Client Registration: click Continue.
-
Preparing Authorization: click the authorization link. A new browser tab opens with the Okta login page. Sign in with your Okta user credentials. Copy the authorization code from the browser.
-
Request Authorization and acquire authorization code: paste the authorization code and click Continue.
-
Token Request: click Continue.
-
Authentication Complete shows a green checkmark.
-
Click Connect. MCP Inspector connects to the OAuth-protected MCP endpoint.
Validate
-
In MCP Inspector, go to the Tools tab and click List Tools. You should see the weather tools exposed by the AI MCP Proxy plugin:
Forecast API History API IP Lookup API Search API Realtime API -
Select the Realtime API tool, enter
Londonfor thequery_qparameter, and click Run Tool. You should receive a JSON response with current weather data:{ "location": { "name": "London", "region": "City of London, Greater London", "country": "United Kingdom" }, "current": { "temp_c": 15.3, "condition": { "text": "Partly cloudy" }, "wind_kph": 11.2, "humidity": 72 } } -
To confirm that unauthenticated requests are rejected, send an MCP tool call without a token:
curl --no-progress-meter --fail-with-body http://localhost:8000/weather/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"Realtime API","arguments":{"query_q":"London"}}}'Copied!The response returns a
401status, confirming the AI MCP OAuth2 plugin is enforcing authentication on MCP tool calls.
Cleanup
Clean up Konnect environment
If you created a new control plane and want to conserve your free trial credits or avoid unnecessary charges, delete the new control plane used in this tutorial.
Destroy the Kong Gateway container
curl -Ls https://get.konghq.com/quickstart | bash -s -- -d