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:35 +02:00
parent 5ffd0837e1
commit ffa2b1a72d
+36 -24
View File
@@ -58,13 +58,15 @@ config:
type: integer type: integer
example: 123 example: 123
next: next:
type: string type:
nullable: true - string
- 'null'
format: uri format: uri
example: http://api.example.org/accounts/?page=4 example: http://api.example.org/accounts/?page=4
previous: previous:
type: string type:
nullable: true - string
- 'null'
format: uri format: uri
example: http://api.example.org/accounts/?page=2 example: http://api.example.org/accounts/?page=2
results: results:
@@ -84,15 +86,17 @@ config:
type: string type: string
format: date format: date
shippeddate: shippeddate:
type: string type:
- string
- 'null'
format: date format: date
nullable: true
status: status:
type: string type: string
maxLength: 15 maxLength: 15
comments: comments:
type: string type:
nullable: true - string
- 'null'
customernumber: customernumber:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
@@ -148,15 +152,17 @@ config:
type: string type: string
format: date format: date
shippeddate: shippeddate:
type: string type:
- string
- 'null'
format: date format: date
nullable: true
status: status:
type: string type: string
maxLength: 15 maxLength: 15
comments: comments:
type: string type:
nullable: true - string
- 'null'
customernumber: customernumber:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
@@ -217,13 +223,15 @@ config:
type: integer type: integer
example: 123 example: 123
next: next:
type: string type:
nullable: true - string
- 'null'
format: uri format: uri
example: http://api.example.org/accounts/?page=4 example: http://api.example.org/accounts/?page=4
previous: previous:
type: string type:
nullable: true - string
- 'null'
format: uri format: uri
example: http://api.example.org/accounts/?page=2 example: http://api.example.org/accounts/?page=2
results: results:
@@ -323,15 +331,17 @@ config:
type: string type: string
format: date format: date
shippeddate: shippeddate:
type: string type:
- string
- 'null'
format: date format: date
nullable: true
status: status:
type: string type: string
maxLength: 15 maxLength: 15
comments: comments:
type: string type:
nullable: true - string
- 'null'
customernumber: customernumber:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
@@ -385,13 +395,15 @@ config:
type: integer type: integer
example: 123 example: 123
next: next:
type: string type:
nullable: true - string
- 'null'
format: uri format: uri
example: http://api.example.org/accounts/?page=4 example: http://api.example.org/accounts/?page=4
previous: previous:
type: string type:
nullable: true - string
- 'null'
format: uri format: uri
example: http://api.example.org/accounts/?page=2 example: http://api.example.org/accounts/?page=2
results: results: