Skip to content

Commit 5b0b400

Browse files
authored
script to generate compatible spec added (#348)
1 parent 781cab8 commit 5b0b400

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

services/chatbot/src/mcpserver/server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import httpx
77
from fastmcp import FastMCP
88

9-
from .tool_helpers import fix_array_responses_in_spec
9+
from .tool_helpers import fix_array_responses_in_spec, OpenAPIRefResolver
1010
from .config import Config
1111

1212
# Configure logging
@@ -78,7 +78,8 @@ def get_http_client():
7878
# Load your OpenAPI spec
7979
with open(Config.OPENAPI_SPEC, "r") as f:
8080
openapi_spec = json.load(f)
81-
openapi_spec = fix_array_responses_in_spec(openapi_spec)
81+
OpenAPIRefResolver(openapi_spec).format_openapi_spec()
82+
fix_array_responses_in_spec(openapi_spec)
8283

8384
# Create the MCP server
8485
mcp = FastMCP.from_openapi(

services/chatbot/src/mcpserver/tool_helpers.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
32
from chatbot.extensions import db
43

54

@@ -25,5 +24,71 @@ def fix_array_responses_in_spec(spec):
2524

2625
if schema.get("type") == "array":
2726
del media["schema"]
27+
28+
class OpenAPIRefResolver:
29+
def __init__(self, spec):
30+
self.spec = spec
31+
self.components = spec.get("components", {}).get("schemas", {})
32+
33+
def resolve_ref(self, ref):
34+
if not ref.startswith("#/components/schemas/"):
35+
return None
36+
37+
schema_name = ref.split("/")[-1]
38+
if schema_name not in self.components:
39+
return None
40+
41+
return self.components[schema_name]
42+
43+
def inline_all_refs(self, schema, visited=None):
44+
if visited is None:
45+
visited = set()
46+
47+
if isinstance(schema, dict):
48+
if "$ref" in schema:
49+
ref = schema["$ref"]
50+
if ref.startswith("#/components/schemas/"):
51+
schema_name = ref.split("/")[-1]
52+
53+
if schema_name in visited:
54+
return {"type": "object", "description": f"Circular reference to {schema_name}"}
55+
56+
visited.add(schema_name)
57+
resolved = self.resolve_ref(ref)
58+
if resolved:
59+
inlined = self.inline_all_refs(resolved, visited.copy())
60+
visited.discard(schema_name)
61+
return inlined
62+
else:
63+
return schema
64+
else:
65+
return schema
66+
else:
67+
return {key: self.inline_all_refs(value, visited) for key, value in schema.items()}
68+
elif isinstance(schema, list):
69+
return [self.inline_all_refs(item, visited) for item in schema]
70+
else:
71+
return schema
72+
73+
def process_schema_recursively(self, schema):
74+
return self.inline_all_refs(schema)
2875

29-
return spec
76+
def format_openapi_spec(self):
77+
for path_item in self.spec.get("paths", {}).values():
78+
for method, operation in path_item.items():
79+
if method in ["get", "post", "put", "patch", "delete", "options", "head", "trace"]:
80+
if "requestBody" in operation:
81+
content = operation["requestBody"].get("content", {})
82+
for media_obj in content.values():
83+
if "schema" in media_obj:
84+
media_obj["schema"] = self.process_schema_recursively(media_obj["schema"])
85+
86+
for response in operation.get("responses", {}).values():
87+
content = response.get("content", {})
88+
for media_obj in content.values():
89+
if "schema" in media_obj:
90+
media_obj["schema"] = self.process_schema_recursively(media_obj["schema"])
91+
92+
if "components" in self.spec and "schemas" in self.spec["components"]:
93+
for schema_name, schema_def in self.spec["components"]["schemas"].items():
94+
self.spec["components"]["schemas"][schema_name] = self.process_schema_recursively(schema_def)

0 commit comments

Comments
 (0)