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>
This commit is contained in:
Stéphane Tailland
2026-08-29 12:19:37 +02:00
parent 8db2bb6e39
commit aaa98b5ee9
+78 -52
View File
@@ -57,13 +57,15 @@ config:
type: integer
example: 123
next:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
@@ -85,12 +87,14 @@ config:
type: string
maxLength: 50
addressline2:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
state:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
country:
type: string
@@ -155,12 +159,14 @@ config:
type: string
maxLength: 50
addressline2:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
state:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
country:
type: string
@@ -228,13 +234,15 @@ config:
type: integer
example: 123
next:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
@@ -265,10 +273,11 @@ config:
officecode:
type: string
reportsto:
type: integer
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
nullable: true
required:
- email
- employeenumber
@@ -346,12 +355,14 @@ config:
type: string
maxLength: 50
addressline2:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
state:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
country:
type: string
@@ -412,13 +423,15 @@ config:
type: integer
example: 123
next:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
@@ -449,10 +462,11 @@ config:
officecode:
type: string
reportsto:
type: integer
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
nullable: true
required:
- email
- employeenumber
@@ -516,10 +530,11 @@ config:
officecode:
type: string
reportsto:
type: integer
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
nullable: true
required:
- email
- employeenumber
@@ -577,13 +592,15 @@ config:
type: integer
example: 123
next:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
@@ -614,10 +631,11 @@ config:
officecode:
type: string
reportsto:
type: integer
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
nullable: true
required:
- email
- employeenumber
@@ -675,13 +693,15 @@ config:
type: integer
example: 123
next:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
type:
- string
- 'null'
format: uri
example: http://api.example.org/accounts/?page=2
results:
@@ -710,33 +730,38 @@ config:
type: string
maxLength: 50
addressline2:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
city:
type: string
maxLength: 50
state:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 50
postalcode:
type: string
nullable: true
type:
- string
- 'null'
maxLength: 15
country:
type: string
maxLength: 50
creditlimit:
type: string
type:
- string
- 'null'
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
nullable: true
salesrepemployeenumber:
type: integer
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
nullable: true
required:
- addressline1
- city
@@ -822,10 +847,11 @@ config:
officecode:
type: string
reportsto:
type: integer
type:
- integer
- 'null'
maximum: 2147483647
minimum: -2147483648
nullable: true
required:
- email
- employeenumber