Skip to main content
Glama
Iqram09

kong-ai-gateway-mcp

by Iqram09
README.md
# Kong AI Gateway Diagnostics

An MCP server that lets an LLM agent investigate a Kong Gateway through fourteen
read-only, bounded, schema-validated tools, with a deterministic diagnostic engine
producing the facts, a bounded agent loop producing the explanation, and an evaluation
harness measuring whether it works.

```
User:   Why is authentication failing on /payments?

Agent:  find_route_by_path({"path":"/payments"})        ->  route:payments-prod
Agent:  diagnose_route({"route":"route:payments-prod"})   ->  NO_AUTH_PLUGIN (HIGH), CONFIRMED

Agent:  Authentication is not failing on /payments because no authentication plugin
        is attached or enabled for that route at all. Kong is passing every request
        through without requiring credentials. [evidence, next steps, limitations]

        3 steps · 2 tool calls · 12,977 tokens · $0.0046 · 5.0 s
```

Kong is the source of truth. The model selects tools, sequences the investigation and
writes the explanation; it never produces a fact.

**Status:** a working reference implementation, verified against a local Kong 3.9.3 and
the live Gemini API. It is not deployed anywhere and has not been used in production.

---

## Problem

Debugging an API gateway is a sequence of narrow questions against a large, structured
configuration: which route serves this path, which service does it point to, which
plugins actually apply once you account for scope precedence, is the upstream healthy,
does this consumer hold the credential the route requires. Each question is a specific
Admin API call. The failure modes are not exotic -- a disabled plugin that still shows in
the UI, a route attached to the wrong service, a `503` that means "wrong address" rather
than "backend down" -- but they are easy to miss when eyeballing JSON, and the obvious
first hypothesis is often wrong.

An LLM is good at sequencing an investigation from a vague description. It is bad at
being trusted with infrastructure state. This project is about getting the first
property without paying for the second.

## Why MCP?

The Model Context Protocol gives the model a fixed vocabulary of operations, each with a
schema, a description of when to use it, and a bounded result -- instead of an HTTP
client and a URL. That makes the entire attack and blast surface enumerable (see
[`src/mcp/registry.ts`](src/mcp/registry.ts)), lets the same tools serve any MCP client
and this repository's own agent from one definition, and puts a protocol-enforced
validation boundary between the model and the gateway. There is deliberately no
`execute_kong_api_call` tool and never will be
([decisions.md](docs/decisions.md#2-why-not-direct-api-calls-from-the-agent)).

## Architecture

```mermaid
flowchart LR
    Client["AI client<br/>(Claude Desktop, IDE, or npm run agent)"]
    subgraph S["kong-ai-gateway-mcp"]
        direction TB
        MCP["MCP tools (14, read-only)"]
        Ctx["Context management<br/>references · summaries · limits"]
        Diag["Diagnostic engine<br/>evidence collector · pure rules"]
        Kong["Kong client<br/>typed · GET only · bounded"]
        Safety["Safety: permissions · audit"]
        Tel["Telemetry: logs · metrics · cost"]
        MCP --> Ctx --> Diag --> Kong
        MCP -.-> Safety
        MCP -.-> Tel
    end
    subgraph A["Agent + evaluation (needs an LLM key)"]
        Loop["Bounded agent loop"] --> Prov["LLM provider interface"] --> Gem["Gemini provider"]
        Eval["Evaluation harness<br/>28 cases · deterministic scorer"] --> Loop
    end
    Client -- MCP / stdio --> MCP
    Loop -- in-process --> MCP
    Kong -- Admin API --> KG[(Kong 3.9<br/>+ Postgres)]
    Gem --> API[(Gemini API)]
```

The tool layer runs without any LLM. Full component map, data flow and responsibilities
in [docs/architecture.md](docs/architecture.md).

## Features

- **MCP tooling** -- 14 tools on the official TypeScript SDK v2 (`@modelcontextprotocol/server`):
  `find_route_by_path`, `get_route`, `get_service`, `list_services`,
  `list_routes_for_service`, `list_plugins_for_route`, `list_global_plugins`,
  `get_consumer`, `list_consumers`, `list_upstreams`, `check_upstream_health`,
  `diff_config`, `diagnose_route`, `explain_auth_failure`. Every description states what
  the tool does, when to use it, what it returns, and its limits.
- **Kong integration** -- a typed client with explicit methods only, Zod-validated
  responses, a stable error taxonomy, response and page-count ceilings, and credential
  values stripped at the boundary. Kong is treated as authoritative.
- **Deterministic diagnostics** -- eleven pure rules over an evidence bundle that can
  only be filled from Kong. Absence is asserted only when every relevant scope was read.
  Runs in ~64 ms with no model. Detects all ten seeded defects and reports the healthy
  control as clean.
- **Agentic investigation** -- a loop this project owns (not the SDK's), with a step
  limit, identical-call detection, a cumulative context budget, and a halt path that
  produces an explicit "diagnosis incomplete" rather than a guess.
- **Context management** -- references instead of embedded entities, allowlisted plugin
  config projection, announced truncation, cursor pagination written for a model,
  progressive and conditional collection. Measured: a single-route diagnosis is 11% of
  the raw configuration dump.
- **Safety controls** -- no write method exists in `src/`; write tools are gated at
  registration; the server refuses to start with `ENABLE_WRITE_TOOLS=true` because none
  are implemented; secrets are stopped at each boundary they cross, with a test for each.
- **Evaluation** -- 28 cases, three difficulties, varied phrasings, false-positive
  controls, a deterministic scorer with no LLM judge, quota-aware and resumable runs,
  JSON and Markdown reports.
- **Observability** -- structured JSON logs (stderr only; stdout is the protocol), per-call
  telemetry with three correlation ids, a separate audit log, nearest-rank percentiles,
  provider-reported tokens, and cost that says "unavailable" rather than guessing.
- **Provider abstraction** -- one-method `LlmProvider` interface; `@google/genai` imported
  in exactly one file; two Gemini-specific protocol requirements discovered during
  development were absorbed without touching the agent, tools or evaluation.

## Demo

Actual output of `npm run agent -- --trace "Why is authentication failing on /payments?"`
against the seeded gateway and `gemini-3.5-flash-lite` (run captured on 2026-09-12):

```
Investigation trace:

  Step 1  (llm 1602ms, finish=tool_calls)
    -> find_route_by_path({"path":"/payments"})  ok 30ms 209B

  Step 2  (llm 1317ms, finish=tool_calls)
    -> diagnose_route({"route":"route:payments-prod"})  ok 64ms 3162B

  Step 3  (llm 1971ms, finish=stop)

DIAGNOSIS
**Diagnosis:**
Authentication is not failing on `/payments` because **no authentication plugin is
attached or enabled** for that route at all. Kong is currently passing all requests to
`/payments` through without requiring any credentials.

**Evidence:**
- The route `payments-prod` (serving `/payments`) has only a `cors` plugin attached at
  the route scope.
- There are no service-level plugins on `payments-api`.
- The global plugins active on the gateway are `request-size-limiting` and
  `correlation-id`.
- None of these are authentication plugins (`key-auth`, `jwt`, `basic-auth`, etc.).

**What to do next:**
1. Confirm with your team whether the `/payments` endpoint is intentionally public.
2. If authentication *should* be required, configure and enable an appropriate
   authentication plugin (such as `key-auth` or `jwt`) on the route or its backing
   service.

**Limitations:**
This diagnosis reflects gateway configuration only. If clients are receiving 401 or 403
responses when calling `/payments`, those responses are coming from the upstream backend
service (`http://payments-upstream:8080/v1`), not from Kong itself.

RUN METRICS
  steps                3 / 10
  tool calls           2 (0 error(s))
  repeated calls       0
  tools used           find_route_by_path, diagnose_route
  tool latency avg/p95 46.5ms / 64ms
  llm calls            3 (avg 1630ms)
  tokens               12677 in / 300 out / 12977 total
  estimated cost       $0.004553
  context used         3371 / 50000 chars
  wall clock           4993ms
```

The question's premise is wrong -- nothing is _rejecting_ requests -- and the agent
corrects it from evidence rather than accepting it. The `diagnose_route` result it
reasoned over is the same structured object an MCP client would receive:

```json
{
  "confidence": "CONFIRMED",
  "summary": "No enabled authentication plugin applies to route route:payments-prod. ...",
  "findingCount": 2,
  "findings": [
    {
      "code": "NO_AUTH_PLUGIN",
      "category": "AUTHENTICATION",
      "severity": "HIGH",
      "entityType": "route",
      "entityRef": "route:payments-prod",
      "observedState": "No enabled authentication plugin applies to route route:payments-prod. No authentication plugin is attached at route, service or global scope.",
      "expectedState": "A route serving protected resources should have an enabled authentication plugin (key-auth, jwt, basic-auth, oauth2, ...) at route, service or global scope.",
      "evidence": [
        "Route-scoped plugins: cors.",
        "Service-scoped plugins: none.",
        "Global plugins: request-size-limiting, correlation-id."
      ],
      "recommendedNextStep": "Kong forwards every request to /payments without authenticating it, ..."
    }
  ],
  "observed": { "route": { "ref": "route:payments-prod", ... }, "effectivePlugins": [ ... ] },
  "gaps": [],
  "suggestedNextSteps": [ ... ]
}
```

## Installation

Requirements: Node.js >= 20, npm, Docker with Compose v2.

```
git clone <this repository>
cd kong-ai-gateway-mcp
npm install
cp .env.example .env        # defaults work for the local Kong; add GEMINI_API_KEY for the agent
```

Verify the toolchain without Docker or an API key:

```
npm run check               # lint + typecheck + 140 unit tests
```

## Running Kong

```
npm run kong:up             # docker compose up -d  (Postgres, migrations, Kong 3.9)
npm run seed                # 12 services, 12 routes, 4 upstreams, 7 consumers, 20 plugins,
                            # 10 intentional defects; waits for health-check verdicts
npm run health              # PASS/FAIL for Admin API, proxy, seed state
```

Ports on the host: `8000` proxy, `8443` proxy TLS, `127.0.0.1:8001` Admin API,
`127.0.0.1:8002` Kong Manager. The Admin API is an unauthenticated control plane and is
bound to loopback only. Postgres is not published.

Other lifecycle commands: `npm run kong:down`, `npm run kong:reset` (destroys volumes),
`npm run kong:logs`, `npm run seed:reset` (deletes every seeded entity first; the seed is
idempotent either way).

What is seeded and why each defect exists: [docs/scenarios.md](docs/scenarios.md).

## Running the MCP server

```
npm run dev                 # tsx, stdio transport
# or
npm run build && npm start  # node dist/server.js
```

Register with an MCP client by pointing it at the built server. For Claude Desktop, in
`claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "kong-diagnostics": {
      "command": "node",
      "args": ["/absolute/path/to/kong-ai-gateway-mcp/dist/server.js"],
      "env": { "KONG_ADMIN_URL": "http://localhost:8001" }
    }
  }
}
```

The server needs no LLM key. It probes Kong at startup and logs a warning (not an error)
if the gateway is not yet up; tools return structured `UNREACHABLE` errors until it is.

Point clients at `node dist/server.js` (or `npx tsx src/server.ts`) directly, not at
`npm run ...`: npm prints a script banner to stdout, which is the protocol channel.
`npm run dev` is for driving the server by hand; it deliberately does not use watch mode
(see MCP-STDOUT-001 in the failure modes).

## Running the agent

Requires `GEMINI_API_KEY` in `.env`.

```
npm run agent -- "Why is authentication failing on /payments?"
npm run agent -- --trace "Clients hitting /orders get 429 immediately. What's going on?"
npm run agent -- --json "Is /shipping protected?"     # full run record as JSON
```

Exit code is 0 for a complete diagnosis and 1 when the run was cut off by a limit or a
provider error -- in which case the answer says so explicitly.

## Running evaluation

```
npm run eval                        # all 28 cases -> evals/results/run-NNN.{json,md}
npm run eval -- --case auth-001     # one case (repeatable flag)
npm run eval -- --difficulty hard
npm run eval -- --scenario UPSTREAM-UNHEALTHY-001
npm run eval -- --dry-run           # validate the dataset, run nothing
npm run eval -- --resume run-003    # finish a run the provider's daily quota cut short
```

## Configuration

All variables are validated at startup by [`src/config/env.ts`](src/config/env.ts).
Blank values fall through to defaults.

| Variable                     | Default                 | Purpose                                                                                                   |
| ---------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `KONG_ADMIN_URL`             | `http://localhost:8001` | Admin API base URL                                                                                        |
| `KONG_PROXY_URL`             | `http://localhost:8000` | used only by `npm run health`                                                                             |
| `KONG_TIMEOUT_MS`            | `5000`                  | per-request timeout to Kong                                                                               |
| `LLM_PROVIDER`               | `gemini`                | provider for the agent and evaluation                                                                     |
| `GEMINI_API_KEY`             | _(unset)_               | required for `agent` and `eval` only                                                                      |
| `GEMINI_MODEL`               | `gemini-3.5-flash-lite` | any model the key can access; pricing must be declared in `src/telemetry/cost.ts` for cost to be reported |
| `LOG_LEVEL`                  | `info`                  | pino level; audit events always emit                                                                      |
| `LOG_FILE`                   | _(unset)_               | additional JSON log destination; stderr always                                                            |
| `ENABLE_WRITE_TOOLS`         | `false`                 | must stay false: no write tool is implemented and the server refuses to start otherwise                   |
| `MAX_TOOL_RESULT_ITEMS`      | `50`                    | cap on any list in a tool result                                                                          |
| `MAX_CONTEXT_CHARS`          | `12000`                 | cap on a single serialized tool result                                                                    |
| `MAX_AGENT_STEPS`            | `10`                    | LLM calls per run                                                                                         |
| `MAX_REPEATED_TOOL_CALLS`    | `2`                     | identical (tool, arguments) calls per run                                                                 |
| `MAX_TOTAL_TOOL_RESULT_SIZE` | `50000`                 | cumulative tool-result characters per run                                                                 |

`.env` is gitignored. Nothing in the project prints a key; `redactConfig` reduces it to
`[set]` / `[unset]`.

## Evaluation

**Methodology.** One case per user question; 28 cases across 11 scenarios and three
difficulties. Scoring is deterministic -- no LLM judge. A case passes when the
deterministic engine produced every expected finding code during the run, the answer
contains every expected fact, contains no named wrong conclusion, and the run finished
within its limits. Cases the provider's daily quota prevents from running are marked
SKIPPED and excluded from every rate rather than counted as failures.

**Results of the current run** (`run-003`, dataset 1.2.0, `gemini-3.5-flash-lite`,
Kong 3.9.3, 2026-09-12):

|                                          |                                                                                                                 |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Passed                                   | **28 / 28** (easy 6/6, medium 14/14, hard 8/8)                                                                  |
| Finding recall / fact match / completion | 100% / 100% / 100%                                                                                              |
| Steps per case                           | mean 3.5 (min 3, max 9)                                                                                         |
| Tool calls per case                      | mean 2.57; repeated calls: 0                                                                                    |
| Duration per case                        | median 5.4 s; mean 20.5 s (nine cases include 30-60 s of free-tier rate-limit backoff); retry-free median 4.5 s |
| Tokens                                   | 425,710 total, 15,204 per case                                                                                  |
| Estimated cost                           | $0.1496 total, $0.0053 per case                                                                                 |

Two earlier runs are kept: run-001 scored 22/28 and run-002 26/28, and in both every
miss was either a provider rate-limit loss or a correct answer the substring scorer
marked wrong. No case in any run failed because the agent gave a wrong diagnosis. How the
scorer was fixed, and what these numbers do and do not show, is in
[docs/evaluation.md](docs/evaluation.md). Per-case results and every verbatim answer:
[evals/results/run-003.md](evals/results/run-003.md).

## Observability

Structured JSON to stderr (and `LOG_FILE`), never stdout. Every tool call records
`requestId`, `traceId`, `taskId`, tool, duration, status, error category, result size and
whether it was a repeat; every LLM call records provider, model, duration,
provider-reported tokens, finish reason and estimated cost. A separate audit log records
each invocation with sanitized arguments at `warn`. Metrics (call counts, success rate,
mean / p50 / p95 latency, repeat rate, tokens, cost) are computed only from recorded
calls. Details: [docs/observability.md](docs/observability.md).

## Failure Modes

Twelve failures were observed while building this and are documented with symptom, root
cause, impact, mitigation and regression test -- including a halt path that would have
returned an empty answer, a cost calculation off by 10x, a per-day quota mistaken for a
rate limit, a Gemini 3 protocol requirement that broke every case after its first tool
call, and a scorer that marked correct answers wrong. Twenty-three more were deliberately
provoked and verified. [docs/failure-modes.md](docs/failure-modes.md).

## Security

[docs/security.md](docs/security.md) -- threat model, the five boundaries at which
secrets are stopped (each with its test), execution limits, the read-only guarantee and
how it is structural, what prompt injection is and is not mitigated, and what is not
handled.

## Architecture Decisions

[docs/decisions.md](docs/decisions.md) -- fourteen decisions in context / decision /
alternatives / trade-offs / consequences form, each tied to something in this repository.

## Context Management

[docs/context-management.md](docs/context-management.md) -- the mechanisms and the
measurements behind them.

## Testing

```
npm test                    # 140 unit tests; no Docker, no network
npm run test:integration    # 27 tests against live seeded Kong, incl. MCP over real stdio
npm run test:e2e            # 3 tests: question -> agent -> tool -> Kong -> diagnosis (needs a key)
npm run test:all
```

Integration and e2e suites skip themselves with a message when their dependency is
absent, so `npm test` passes on a fresh clone.

## Limitations

- **Read-only.** The agent can find a problem but not fix it. The write flow
  (confirmation token, preview diff, audit, rollback) is designed and documented, not
  built.
- **Rules must be written.** The engine detects the defect classes it has rules for
  (eleven). A novel class is invisible until someone adds a rule; the model cannot
  discover it from a summary that omitted the relevant field.
- **`find_route_by_path` matches whole paths.** It does not simulate Kong's prefix or
  regex routing.
- **One provider.** Gemini only. Anthropic and OpenAI are one file each away, but that
  file does not exist yet.
- **Free-tier constraints shaped the harness.** Per-minute rate limits add 30-60 s to some
  cases; some models have a 20-request daily quota. The harness handles both honestly but
  a paid tier would give cleaner latency numbers.
- **Scoring is substring-based.** It is a regression net for named facts and named wrong
  conclusions, not a judge of prose quality.
- **Prompt injection through gateway data is not detected.** It is defanged (no write
  path, typed arguments, facts from rules) but not detected or filtered.
- **Kong 3.9.x only** has been tested.
- **Sample size.** 28 cases, one model, one full run per dataset version. Enough to
  demonstrate and to catch regressions; not enough to state a stable percentage.

## Roadmap

Possible, not promised:

- Confirmation-gated write tools (enable a plugin, fix a route path) with preview and
  audit, behind `ENABLE_WRITE_TOOLS`.
- Anthropic and OpenAI providers.
- Rules for more defect classes: certificate/SNI mismatches, plugin ordering conflicts,
  consumer-scoped plugin overrides.
- A second full evaluation run per model to establish variance, and evaluation across
  models.
- OpenTelemetry export for tool and LLM spans.
- Human feedback on answers, feeding the case set.
- Other gateways behind the same tool contract.

## License

MIT.

TDQS

A4.5/5.0

Scored across 14 tools

Disambiguation4/5

Tools target distinct Kong entities and actions, and descriptions explicitly steer between similar options such as find_route_by_path vs get_route and diagnose_route vs explain_auth_failure. However, the composite diagnostic tools overlap with the lower-level get/list tools, so an agent could still be unsure whether to assemble evidence manually or use the high-level diagnostic.

Naming Consistency5/5

All 14 tools use consistent snake_case and a verb-first pattern (find_, list_, get_, check_, diff_, diagnose_, explain_). There are no mixed conventions or ambiguous suffixes.

Tool Count5/5

14 tools is well within the expected 3–15 range for a Kong diagnostic surface. Each tool maps to a meaningful entity or check, with no obvious bloat or thin coverage.

Completeness4/5

The set covers core diagnostic paths: route resolution, service/consumer/upstream lookup, plugin scoping, health checks, config diff, and high-level diagnosis. Minor gaps remain, such as no general list_routes or get_upstream, but core route/auth troubleshooting workflows are largely complete.

Maintenance

ActivityMaintained
ResponsivenessNo issues