Secure MCP tools with OAuth2 and Okta

Deployment Platform
Minimum Version
Kong Gateway - 3.13
TL;DR

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

This is a Konnect tutorial and requires a Konnect personal access token.

  1. Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.

  2. Export your token to an environment variable:

     export KONNECT_TOKEN='YOUR_KONNECT_PAT'
    
  3. 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-output
    

    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'
    

    Copy and paste these into your terminal to configure your session.

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.

  1. Export your license to an environment variable:

     export KONG_LICENSE_DATA='LICENSE-CONTENTS-GO-HERE'
    
  2. Run the quickstart script:

    curl -Ls https://get.konghq.com/quickstart | bash -s -- -e KONG_LICENSE_DATA 
    

    Once Kong Gateway is ready, you will see the following message:

     Kong Gateway Ready
    

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.

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:

  1. 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 -
    

To learn more about entities, you can read our entities documentation.

  1. Go to WeatherAPI.
  2. Sign up for a free account.
  3. Navigate to your dashboard and copy your API key.
  4. Export your API key:

     export DECK_WEATHERAPI_API_KEY='your-weatherapi-api-key'
    

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

  1. Go to Security > API > Authorization Servers.
  2. Click default.
  3. Go to the Scopes tab.
  4. Click Add Scope.
  5. Name: mcp:access
  6. Display phrase: Access MCP tools
  7. Check Set as a default scope.
  8. Click Create.

Add an access policy

  1. In the same default authorization server, go to the Access Policies tab.
  2. Click Add Policy.
  3. Name: MCP Access
  4. Assign to: All clients
  5. Click Create Policy.

Add a rule to the policy

  1. Inside the MCP Access policy, click Add Rule.
  2. Rule Name: Allow MCP
  3. Grant type: check Client Credentials, Authorization Code, and Device Authorization.
  4. User is: Any user assigned the app
  5. Scopes requested: Any scopes
  6. Click Create Rule.

Export your Okta authorization server URL and introspection endpoint. To find these values:

  1. Go to Security > API > Authorization Servers.
  2. Click the default server.
  3. Copy the Issuer URI (for example, https://your-org.okta.com/oauth2/default). This is the authorization server URL.
  4. Append /v1/introspect to 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)

  1. Go to Applications > Applications > Create App Integration.
  2. Sign-in method: OIDC - OpenID Connect
  3. Application type: Web Application
  4. App integration name: Kong MCP Gateway
  5. Grant types: check Client Credentials and Authorization Code.
  6. 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.
  7. Assignments: Skip group assignment for now
  8. Click Save.
  9. Copy the Client ID and Client Secret. These go into the AI Gateway ai-mcp-oauth2 plugin config.
  10. Go to the Assignments tab, click Assign > Assign to People, and assign your user.
  11. 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'
    

Create the native application (used by MCP Inspector)

  1. Go to Applications > Applications > Create App Integration.
  2. Sign-in method: OIDC - OpenID Connect
  3. Application type: Native Application
  4. App integration name: MCP Inspector
  5. Grant types: check Authorization Code.
  6. Sign-in redirect URIs: http://localhost:6274/oauth/callback/debug
  7. Assignments: Skip group assignment for now
  8. Click Save.
  9. Go to the Assignments tab, click Assign > Assign to People, and assign your user.
  10. 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-oauth2 Plugin config for token introspection. The Native Application Client ID is what you enter in MCP Inspector when connecting to the OAuth-protected MCP endpoint.

This guide uses the MCP Inspector to test the OAuth-protected MCP endpoint.

  1. Ensure you have Node.js and npm installed. If needed, download them from https://nodejs.org.

  2. Update npx to the latest version:
     npm install -g npx
    
  3. Install the Inspector:
     npm install -g @modelcontextprotocol/inspector
    

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

  1. Start MCP Inspector:

     npx @modelcontextprotocol/inspector@latest --mcp-url http://localhost:8000/weather/mcp
    
  2. Open the MCP Inspector UI in your browser at the URL shown in the terminal output.

  3. Set Transport Type to Streamable HTTP.

  4. Set the URL to http://localhost:8000/weather/mcp.

  5. Select Authentication in the left sidebar.

  6. Enter the Native Application Client ID from the Okta setup (the MCP Inspector app, not the Kong MCP Gateway app). 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.

  7. Click Guided OAuth Flow.

  8. Metadata Discovery: click Continue.

  9. Client Registration: click Continue.

  10. 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.

  11. Request Authorization and acquire authorization code: paste the authorization code and click Continue.

  12. Token Request: click Continue.

  13. Authentication Complete shows a green checkmark.

  14. Click Connect. MCP Inspector connects to the OAuth-protected MCP endpoint.

Validate

  1. 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
    
  2. Select the Realtime API tool, enter London for the query_q parameter, 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
       }
     }
    
  3. 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"}}}'
    

    The response returns a 401 status, confirming the AI MCP OAuth2 plugin is enforcing authentication on MCP tool calls.

Cleanup

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.

curl -Ls https://get.konghq.com/quickstart | bash -s -- -d

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!