Files
Stéphane Tailland aaa98b5ee9 fix(mcp): translate OpenAPI 3.0 nullable into JSON Schema
The response schemas added yesterday made every paginated list tool fail. The
plugin's fields are documented as OpenAPI format and validated with a JSON
Schema validator, and `nullable: true` is not a JSON Schema keyword — so it is
ignored, `type: string` is enforced, and `previous: null` on the first page is
rejected as *null is not of type "string"*.

The failure lands after the backend has already done the work, surfaces to the
caller as a bare tool retry exhaustion, and appears nowhere in the gateway's own
logs. Found only because an orchestrating agent asked a question that paged.

The generator now rewrites `nullable: true` into `type: [T, "null"]` wherever it
inlines a schema, request bodies included. A 3.1 spec needs none of this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 12:19:37 +02:00

863 lines
25 KiB
YAML

# GENERATED — do not edit. Edit openapi.yaml or tools-policy.yaml, then:
# k8s-platform/tools/generate-mcp-tools.py <this domain>
#
# Produced by `deck file openapi2mcp`, then post-processed to add the ACLs,
# the MCP behavioural annotations, the response schemas, the real upstream
# host and the JSON-only bodies that Kong's generator does not emit. Each of
# those is a documented gap — see k8s-platform/MCP-TOOL-GENERATION.md.
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: ${NAME}-workforce-mcp-ai-mcp-proxy
namespace: ${NAMESPACE}
plugin: ai-mcp-proxy
config:
mode: conversion-listener
include_consumer_groups: true
tools:
- annotations:
title: List all offices
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve a paginated list of all company office locations worldwide.
method: GET
name: list-offices
parameters:
- description: A page number within the paginated result set.
in: query
name: page
required: false
schema:
type: integer
- description: Number of results to return per page.
in: query
name: page_size
required: false
schema:
type: integer
path: /workforce/v1/offices/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
properties:
count:
type: integer
example: 123
next:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
type: object
description: Company office locations
properties:
officecode:
type: string
maxLength: 10
city:
type: string
maxLength: 50
phone:
type: string
maxLength: 50
addressline1:
type: string
maxLength: 50
addressline2:
type:
- string
- 'null'
maxLength: 50
state:
type:
- string
- 'null'
maxLength: 50
country:
type: string
maxLength: 50
postalcode:
type: string
maxLength: 15
territory:
type: string
maxLength: 10
required:
- addressline1
- city
- country
- officecode
- phone
- postalcode
- territory
- annotations:
title: Get a specific office
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve detailed information about a specific office by its code.
method: GET
name: get-office
parameters:
- description: A unique value identifying this office.
in: path
name: officecode
required: true
schema:
type: string
path: /workforce/v1/offices/{officecode}/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
description: Company office locations
properties:
officecode:
type: string
maxLength: 10
city:
type: string
maxLength: 50
phone:
type: string
maxLength: 50
addressline1:
type: string
maxLength: 50
addressline2:
type:
- string
- 'null'
maxLength: 50
state:
type:
- string
- 'null'
maxLength: 50
country:
type: string
maxLength: 50
postalcode:
type: string
maxLength: 15
territory:
type: string
maxLength: 10
required:
- addressline1
- city
- country
- officecode
- phone
- postalcode
- territory
- annotations:
title: Get employees by office
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve all employees working in a specific office. This endpoint supports office management
and organizational charts.
method: GET
name: get-office-employees
parameters:
- description: The office code
in: path
name: officecode
required: true
schema:
type: string
- description: A page number within the paginated result set.
in: query
name: page
required: false
schema:
type: integer
- description: Number of results to return per page.
in: query
name: page_size
required: false
schema:
type: integer
path: /workforce/v1/offices/{officecode}/employees/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
properties:
count:
type: integer
example: 123
next:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
type: object
description: Employee information and hierarchy
properties:
employeenumber:
type: integer
maximum: 2147483647
minimum: -2147483648
lastname:
type: string
maxLength: 50
firstname:
type: string
maxLength: 50
extension:
type: string
maxLength: 10
email:
type: string
maxLength: 100
jobtitle:
type: string
maxLength: 50
officecode:
type: string
reportsto:
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
required:
- email
- employeenumber
- extension
- firstname
- jobtitle
- lastname
- officecode
- annotations:
title: Create a new office
read_only_hint: false
idempotent_hint: false
destructive_hint: false
open_world_hint: false
description: Add a new office location to the company's network.
method: POST
name: create-office
path: /workforce/v1/offices/
request_body:
content:
application/json:
schema:
properties:
addressline1:
type: string
addressline2:
type: string
city:
type: string
country:
type: string
officecode:
type: string
phone:
type: string
postalcode:
type: string
state:
type: string
territory:
type: string
required:
- addressline1
- city
- country
- officecode
- phone
- postalcode
- territory
type: object
required: true
acl:
allow:
- admin
scheme: http
host: ${REST_HOSTNAME}
responses:
'201':
content:
application/json:
schema:
type: object
description: Company office locations
properties:
officecode:
type: string
maxLength: 10
city:
type: string
maxLength: 50
phone:
type: string
maxLength: 50
addressline1:
type: string
maxLength: 50
addressline2:
type:
- string
- 'null'
maxLength: 50
state:
type:
- string
- 'null'
maxLength: 50
country:
type: string
maxLength: 50
postalcode:
type: string
maxLength: 15
territory:
type: string
maxLength: 10
required:
- addressline1
- city
- country
- officecode
- phone
- postalcode
- territory
- annotations:
title: List all employees
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve a paginated list of all employees in the organization with their details.
method: GET
name: list-employees
parameters:
- description: A page number within the paginated result set.
in: query
name: page
required: false
schema:
type: integer
- description: Number of results to return per page.
in: query
name: page_size
required: false
schema:
type: integer
path: /workforce/v1/employees/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
properties:
count:
type: integer
example: 123
next:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
type: object
description: Employee information and hierarchy
properties:
employeenumber:
type: integer
maximum: 2147483647
minimum: -2147483648
lastname:
type: string
maxLength: 50
firstname:
type: string
maxLength: 50
extension:
type: string
maxLength: 10
email:
type: string
maxLength: 100
jobtitle:
type: string
maxLength: 50
officecode:
type: string
reportsto:
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
required:
- email
- employeenumber
- extension
- firstname
- jobtitle
- lastname
- officecode
- annotations:
title: Get a specific employee
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve detailed information about a specific employee by their employee number.
method: GET
name: get-employee
parameters:
- description: A unique value identifying this employee.
in: path
name: employeenumber
required: true
schema:
type: integer
path: /workforce/v1/employees/{employeenumber}/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
description: Employee information and hierarchy
properties:
employeenumber:
type: integer
maximum: 2147483647
minimum: -2147483648
lastname:
type: string
maxLength: 50
firstname:
type: string
maxLength: 50
extension:
type: string
maxLength: 10
email:
type: string
maxLength: 100
jobtitle:
type: string
maxLength: 50
officecode:
type: string
reportsto:
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
required:
- email
- employeenumber
- extension
- firstname
- jobtitle
- lastname
- officecode
- annotations:
title: Get employees by manager
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve all employees reporting to a specific manager. This endpoint supports organizational
hierarchy and team management.
method: GET
name: get-employee-reports
parameters:
- description: The employee number of the manager
in: path
name: employeenumber
required: true
schema:
type: integer
- description: A page number within the paginated result set.
in: query
name: page
required: false
schema:
type: integer
- description: Number of results to return per page.
in: query
name: page_size
required: false
schema:
type: integer
path: /workforce/v1/employees/{employeenumber}/reports/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
properties:
count:
type: integer
example: 123
next:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
type: object
description: Employee information and hierarchy
properties:
employeenumber:
type: integer
maximum: 2147483647
minimum: -2147483648
lastname:
type: string
maxLength: 50
firstname:
type: string
maxLength: 50
extension:
type: string
maxLength: 10
email:
type: string
maxLength: 100
jobtitle:
type: string
maxLength: 50
officecode:
type: string
reportsto:
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
required:
- email
- employeenumber
- extension
- firstname
- jobtitle
- lastname
- officecode
- annotations:
title: Get customers by sales rep
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
description: Retrieve all customers assigned to a specific sales representative. This endpoint supports
sales rep performance tracking and customer management.
method: GET
name: get-employee-customers
parameters:
- description: The employee number of the sales representative
in: path
name: employeenumber
required: true
schema:
type: integer
- description: A page number within the paginated result set.
in: query
name: page
required: false
schema:
type: integer
- description: Number of results to return per page.
in: query
name: page_size
required: false
schema:
type: integer
path: /workforce/v1/employees/{employeenumber}/customers/
acl:
allow:
- admin
- read_only
- product_manager
- support
scheme: http
host: ${REST_HOSTNAME}
responses:
'200':
content:
application/json:
schema:
type: object
properties:
count:
type: integer
example: 123
next:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
type: object
description: Customer information and contact details
properties:
customernumber:
type: integer
maximum: 2147483647
minimum: -2147483648
customername:
type: string
maxLength: 50
contactlastname:
type: string
maxLength: 50
contactfirstname:
type: string
maxLength: 50
phone:
type: string
maxLength: 50
addressline1:
type: string
maxLength: 50
addressline2:
type:
- string
- 'null'
maxLength: 50
city:
type: string
maxLength: 50
state:
type:
- string
- 'null'
maxLength: 50
postalcode:
type:
- string
- 'null'
maxLength: 15
country:
type: string
maxLength: 50
creditlimit:
type:
- string
- 'null'
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
salesrepemployeenumber:
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
required:
- addressline1
- city
- contactfirstname
- contactlastname
- country
- customername
- customernumber
- phone
- annotations:
title: Create a new employee
read_only_hint: false
idempotent_hint: false
destructive_hint: false
open_world_hint: false
description: Add a new employee to the organization with their job details and office assignment.
method: POST
name: create-employee
path: /workforce/v1/employees/
request_body:
content:
application/json:
schema:
properties:
email:
type: string
employeenumber:
type: integer
extension:
type: string
firstname:
type: string
jobtitle:
type: string
lastname:
type: string
officecode:
type: string
reportsto:
type: integer
required:
- email
- employeenumber
- extension
- firstname
- jobtitle
- lastname
- officecode
type: object
required: true
acl:
allow:
- admin
scheme: http
host: ${REST_HOSTNAME}
responses:
'201':
content:
application/json:
schema:
type: object
description: Employee information and hierarchy
properties:
employeenumber:
type: integer
maximum: 2147483647
minimum: -2147483648
lastname:
type: string
maxLength: 50
firstname:
type: string
maxLength: 50
extension:
type: string
maxLength: 10
email:
type: string
maxLength: 100
jobtitle:
type: string
maxLength: 50
officecode:
type: string
reportsto:
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
required:
- email
- employeenumber
- extension
- firstname
- jobtitle
- lastname
- officecode