Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions pkg/converter/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,20 @@ func (c *Converter) convertRequestBody(requestBodyRef *openapi3.RequestBodyRef)
arg.Properties = nestedProps["properties"].(map[string]any)
}
}
// Handle allOf
if propRef.Value.Type == "" && len(propRef.Value.AllOf) == 1 {
arg.Type = "object"
arg.Properties = c.allOfHandle(propRef.Value.AllOf[0])
if propRef.Value.Type == "" {
// Handle allOf, anyOf, oneOf
if len(propRef.Value.AllOf) == 1 {
arg.Type = "object"
arg.Properties = c.allOfHandle(propRef.Value.AllOf[0])
} else if c.hasCommonType(propRef.Value.AllOf) {
arg.Type = propRef.Value.AllOf[0].Value.Type
}
if c.hasCommonType(propRef.Value.AnyOf) {
arg.Type = propRef.Value.AnyOf[0].Value.Type
}
if c.hasCommonType(propRef.Value.OneOf) {
arg.Type = propRef.Value.OneOf[0].Value.Type
}
}

args = append(args, arg)
Expand Down Expand Up @@ -459,6 +469,19 @@ func (c *Converter) allOfHandle(schemaRef *openapi3.SchemaRef) map[string]interf
return properties
}

func (c *Converter) hasCommonType(schemaRefs []*openapi3.SchemaRef) bool {
if len(schemaRefs) == 0 {
return false
}
first := schemaRefs[0].Value.Type
for _, schemaRef := range schemaRefs {
if first != schemaRef.Value.Type {
return false
}
}
return true
}
Comment on lines +472 to +483
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的 hasCommonType 函数逻辑不完整,仅比较类型是否一致,未考虑其他属性如格式、枚举等。

🟡 Major | 🐞 Bugs

📋 问题详情

该函数用于判断一组 schema 是否具有相同的类型,但实际在 OpenAPI 规范中,即使类型相同,格式(如 string + email)或枚举值不同也会导致语义不同。当前实现可能误判,导致错误的类型合并。

💡 解决方案

建议增强 hasCommonType 函数,不仅比较 Type,还应考虑 FormatEnum 等关键属性,确保语义一致性。

- func (c *Converter) hasCommonType(schemaRefs []*openapi3.SchemaRef) bool {
-     if len(schemaRefs) == 0 {
-         return false
-     }
-     first := schemaRefs[0].Value.Type
-     for _, schemaRef := range schemaRefs {
-         if first != schemaRef.Value.Type {
-             return false
-         }
-     }
-     return true
- }
+ func (c *Converter) hasCommonType(schemaRefs []*openapi3.SchemaRef) bool {
+     if len(schemaRefs) == 0 {
+         return false
+     }
+     first := schemaRefs[0].Value
+     for _, schemaRef := range schemaRefs {
+         if first.Type != schemaRef.Value.Type ||
+            first.Format != schemaRef.Value.Format ||
+            !reflect.DeepEqual(first.Enum, schemaRef.Value.Enum) {
+             return false
+         }
+     }
+     return true
+ }

您的反馈对我们很重要!(建议右键在新标签页中打开以下链接)

有用意见👍无用意见👎错误意见❌


// createRequestTemplate creates an MCP request template from an OpenAPI operation
func (c *Converter) createRequestTemplate(path, method string, operation *openapi3.Operation) (*models.RequestTemplate, error) {
// Get the server URL from the OpenAPI specification
Expand Down
6 changes: 6 additions & 0 deletions pkg/converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ func TestEndToEndConversion(t *testing.T) {
expectedOutput: "../../test/expected-output-schema-test-mcp.yaml",
serverName: "output-schema-api",
},
{
name: "Handle Subschemas",
inputFile: "../../test/subschemas.json",
expectedOutput: "../../test/expected-subschemas-mcp.yaml",
serverName: "openapi-server",
},
}

for _, tc := range testCases {
Expand Down
25 changes: 25 additions & 0 deletions test/expected-subschemas-mcp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
server:
name: openapi-server
tools:
- name: post_user
description: Test allOf, anyOf, and oneOf in request body
args:
- name: allOfUser
description: Combines both BaseUser and ExtraInfo via allOf
type: object
position: body
- name: anyOfUser
description: Valid if matches any of BaseUser or ExtraInfo
type: object
position: body
- name: oneOfUser
description: Valid if matches exactly one of BaseUser or ExtraInfo
type: object
position: body
requestTemplate:
url: /user
method: POST
headers:
- key: Content-Type
value: application/json
responseTemplate: {}
99 changes: 99 additions & 0 deletions test/subschemas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"openapi": "3.0.3",
"info": {
"title": "Composition Test API",
"version": "1.0.0"
},
"paths": {
"/user": {
"post": {
"summary": "Test allOf, anyOf, and oneOf in request body",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompositionTest"
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"components": {
"schemas": {
"CompositionTest": {
"type": "object",
"properties": {
"allOfUser": {
"description": "Combines both BaseUser and ExtraInfo via allOf",
"allOf": [
{
"$ref": "#/components/schemas/BaseUser"
},
{
"$ref": "#/components/schemas/ExtraInfo"
}
]
},
"anyOfUser": {
"description": "Valid if matches any of BaseUser or ExtraInfo",
"anyOf": [
{
"$ref": "#/components/schemas/BaseUser"
},
{
"$ref": "#/components/schemas/ExtraInfo"
}
]
},
"oneOfUser": {
"description": "Valid if matches exactly one of BaseUser or ExtraInfo",
"oneOf": [
{
"$ref": "#/components/schemas/BaseUser"
},
{
"$ref": "#/components/schemas/ExtraInfo"
}
]
}
}
},
"BaseUser": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
},
"ExtraInfo": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0
}
}
}
}
}
}