From aaa98b5ee9dac6364d38f1fec480060bc09e3fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tailland?= Date: Sat, 29 Aug 2026 12:19:37 +0200 Subject: [PATCH] fix(mcp): translate OpenAPI 3.0 nullable into JSON Schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- kong/mcp/ai-mcp-proxy.yaml | 130 ++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 52 deletions(-) diff --git a/kong/mcp/ai-mcp-proxy.yaml b/kong/mcp/ai-mcp-proxy.yaml index 53b44fd..7652afe 100644 --- a/kong/mcp/ai-mcp-proxy.yaml +++ b/kong/mcp/ai-mcp-proxy.yaml @@ -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