Map a RESTful API to MCP tools

Incompatible with
on-prem
Tags
Related Resources
Minimum Version
AI Gateway - 2.0
TL;DR

Create an AI MCP Server entity in AI Gateway, and it automatically converts your REST API endpoints into MCP tools that any MCP-compatible AI assistant can call, no custom server code required.

This tutorial shows you how to create an AI MCP Server entity using kongctl to expose a REST API as MCP tools, and how to call those tools from an MCP client.

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 AI Gateway quickstart script to automatically provision a control plane and data plane in Kong Konnect, and configure your environment:

    curl -Ls https://get.konghq.com/ai | bash -s -- -k $KONNECT_TOKEN \
       -e KONG_TRACING_INSTRUMENTATIONS \
       -e KONG_TRACING_SAMPLING_RATE

This sets up a AI Gateway control plane named ai-quickstart, provisions a local data plane, and prints out the following environment variables export:

export AI_GATEWAY_ID=your-gateway-id
export KONNECT_TOKEN=$KONNECT_TOKEN
export KONNECT_CONTROL_PLANE_NAME=ai-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 uses kongctl to manage Konnect resources programmatically. We recommend keeping kongctl up to date with the latest version (1.13.0).

  1. Install kongctl from developer.konghq.com/kongctl.
  2. Verify the installation:

    kongctl version

Run a sample API to expose

Before creating an MCP Server entity, you’ll need an upstream HTTP API to expose. For this tutorial, use the Swagger Petstore sample API running in Docker. This allows you to test the entity without relying on an external service. The Petstore API comes pre-loaded with 10 pets across the available, pending, and sold statuses, which the API exposes through the /pet/findByStatus and /pet/{petId} endpoints.

docker run -d \
  --name swagger-petstore \
  -p 8080:8080 \
  swaggerapi/petstore3:latest

The MCP Server entity will convert this API’s endpoints into MCP tool definitions.

Create an MCP Server entity

With the Petstore API running, create an MCP Server entity configured as a conversion-listener to expose the endpoints /pet/findByStatus and /pet/{petId} as MCP tools. The following example maps the Petstore API operations to MCP tool definitions that the client can invoke.

kongctl apply -f - --auto-approve --pat "$KONNECT_TOKEN" << 'EOF'
ai_gateway_mcp_servers:
  - ref: petstore-mcp
    ai_gateway: !lookup {id: !env AI_GATEWAY_ID}
    name: petstore-mcp
    display_name: "Petstore API"
    type: conversion-listener
    enabled: true
    policies: []
    access:
      acl_attribute_type: consumer
      acls:
        allow: []
      default_tool_acls:
        deny: []
    config:
      url: http://host.docker.internal:8080/api/v3
      route:
        paths:
          - /petstore
      logging:
        payloads: false
      server:
        timeout: 60000
    tools:
      - name: get-pets-by-status
        description: Find pets by status
        method: GET
        path: /petstore/pet/findByStatus
        parameters:
          - name: status
            in: query
            required: true
            schema:
              type: string
              enum:
                - available
                - pending
                - sold
            description: Status value to filter pets by
      - name: get-pet-by-id
        description: Get a pet by ID
        method: GET
        path: /petstore/pet/{petId}
        parameters:
          - description: ID of the pet to retrieve
            in: path
            name: petId
            required: true
            schema:
              type: integer
EOF

Two details of this configuration are worth calling out:

  • Route stripping: route.paths (/petstore) is the prefix clients must hit for the AI Gateway to match this entity, so every tool’s path includes it. Before forwarding upstream, the gateway strips that prefix and appends what’s left (for example, /pet/findByStatus) to config.url.
  • Parameters: AI Gateway builds each tool’s input schema from its parameters list, prefixing every parameter name with its in location: path_<name> for path parameters, query_<name> for query parameters.

Verify that the endpoints are available as tools

Use MCP Inspector CLI to verify that the MCP server exposes get-pet-by-id and get-pets-by-status as tools:

npx -y @modelcontextprotocol/inspector@0.22.0 --cli \
  http://localhost:8000/petstore \
  --transport http --method tools/list |  jq -r '.tools[].name'

You should see the following output:

get-pet-by-id
get-pets-by-status

Validate the configuration

Let’s call the get-pets-by-status tool to see which pets are available:

npx -y @modelcontextprotocol/inspector@0.22.0 --cli \
  http://localhost:8000/petstore \
  --transport http --method tools/call \
  --tool-name get-pets-by-status \
  --tool-arg query_status=available | jq -r '.content[0].text' | jq -c '.[]'

You should see the following output:

{"id":1,"category":{"id":2,"name":"Cats"},"name":"Cat 1","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}],"status":"available"}
{"id":2,"category":{"id":2,"name":"Cats"},"name":"Cat 2","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag2"},{"id":2,"name":"tag3"}],"status":"available"}
{"id":4,"category":{"id":1,"name":"Dogs"},"name":"Dog 1","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}],"status":"available"}
{"id":7,"category":{"id":4,"name":"Lions"},"name":"Lion 1","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}],"status":"available"}
{"id":8,"category":{"id":4,"name":"Lions"},"name":"Lion 2","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag2"},{"id":2,"name":"tag3"}],"status":"available"}
{"id":9,"category":{"id":4,"name":"Lions"},"name":"Lion 3","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag3"},{"id":2,"name":"tag4"}],"status":"available"}
{"id":10,"category":{"id":3,"name":"Rabbits"},"name":"Rabbit 1","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag3"},{"id":2,"name":"tag4"}],"status":"available"}

Now, we can check the details of Lion 1 - id:7 - by calling the get-pet-by-id tool:

npx -y @modelcontextprotocol/inspector@0.22.0 --cli \
  http://localhost:8000/petstore \
  --transport http --method tools/call \
  --tool-name get-pet-by-id \
  --tool-arg path_petId=7 | jq -r '.content[0].text' | jq -c '.'

You should see the following response:

{"id":7,"category":{"id":4,"name":"Lions"},"name":"Lion 1","photoUrls":["url1","url2"],"tags":[{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}],"status":"available"}

You can validate this result against the Swagger Petstore API source.

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!