From 0db333ef08014e4a94a6a3c2876e68e7e385d7f9 Mon Sep 17 00:00:00 2001 From: Vasu Date: Sat, 15 Nov 2025 23:08:20 +0000 Subject: [PATCH] feat: add basic OpenAPI parser and tool generator Signed-off-by: Vasu --- mcpgateway/openapi_generator/cli.py | 33 +++++++++ mcpgateway/openapi_generator/parser.py | 58 +++++++++++++++ .../openapi_generator/tool_generator.py | 70 +++++++++++++++++++ mcpgateway/openapi_generator/writer.py | 17 +++++ 4 files changed, 178 insertions(+) create mode 100644 mcpgateway/openapi_generator/cli.py create mode 100644 mcpgateway/openapi_generator/parser.py create mode 100644 mcpgateway/openapi_generator/tool_generator.py create mode 100644 mcpgateway/openapi_generator/writer.py diff --git a/mcpgateway/openapi_generator/cli.py b/mcpgateway/openapi_generator/cli.py new file mode 100644 index 000000000..45581b935 --- /dev/null +++ b/mcpgateway/openapi_generator/cli.py @@ -0,0 +1,33 @@ +from mcpgateway.openapi_generator.parser import OpenAPIParser +from mcpgateway.openapi_generator.tool_generator import ToolGenerator +from mcpgateway.openapi_generator.writer import ToolWriter + + +def main(): + import sys + + if len(sys.argv) < 2: + print("Usage: python -m mcpgateway.openapi_generator.cli [--base-url ]") + return + + filepath = sys.argv[1] + base_url = None + + if "--base-url" in sys.argv: + idx = sys.argv.index("--base-url") + base_url = sys.argv[idx + 1] + + parser = OpenAPIParser(filepath) + parsed = parser.load() + + generator = ToolGenerator(parsed, base_url=base_url) + tools = generator.generate_tools() + + writer = ToolWriter() + out_dir = writer.write(tools) + + print("Generated tools saved to:", out_dir) + + +if __name__ == "__main__": + main() diff --git a/mcpgateway/openapi_generator/parser.py b/mcpgateway/openapi_generator/parser.py new file mode 100644 index 000000000..f2832cade --- /dev/null +++ b/mcpgateway/openapi_generator/parser.py @@ -0,0 +1,58 @@ +import json +import yaml +from pathlib import Path + +class OpenAPIParser: + def __init__(self, file_path): + self.file_path = Path(file_path) + self.data = {} + self.endpoints = [] + self.schemas = {} + + def load(self): + if self.file_path.suffix.lower() in [".yaml", ".yml"]: + with open(self.file_path, "r") as f: + self.data = yaml.safe_load(f) + else: + with open(self.file_path, "r") as f: + self.data = json.load(f) + + self._read_schemas() + self._read_get_endpoints() + return {"schemas": self.schemas, "endpoints": self.endpoints} + + def _read_schemas(self): + comps = self.data.get("components", {}) + raw = comps.get("schemas", {}) + + for name, body in raw.items(): + props = {} + for p, info in body.get("properties", {}).items(): + props[p] = info.get("type", "string") + self.schemas[name] = props + + def _read_get_endpoints(self): + paths = self.data.get("paths", {}) + + for path, methods in paths.items(): + get_def = methods.get("get") + if not get_def: + continue + + item = { + "path": path, + "name": get_def.get("operationId", ""), + "summary": get_def.get("summary", ""), + "params": [] + } + + for p in get_def.get("parameters", []): + item["params"].append({ + "name": p.get("name", ""), + "type": p.get("schema", {}).get("type", "string"), + "required": p.get("required", False) + }) + + self.endpoints.append(item) + + diff --git a/mcpgateway/openapi_generator/tool_generator.py b/mcpgateway/openapi_generator/tool_generator.py new file mode 100644 index 000000000..82636069a --- /dev/null +++ b/mcpgateway/openapi_generator/tool_generator.py @@ -0,0 +1,70 @@ +from mcpgateway.schemas import ToolCreate, AuthenticationValues + +class ToolGenerator: + def __init__(self, parsed, base_url): + self.schemas = parsed.get("schemas", {}) + self.endpoints = parsed.get("endpoints", []) + self.base_url = base_url.rstrip("/") + + def generate_tools(self): + tools = [] + + for ep in self.endpoints: + name = ep.get("name") or ep.get("operationId") or "autoTool" + + props = {} + required = [] + for p in ep.get("params", []): + props[p["name"]] = {"type": p["type"]} + if p.get("required"): + required.append(p["name"]) + + input_schema = { + "type": "object", + "properties": props + } + if required: + input_schema["required"] = required + + full_url = f"{self.base_url}{ep['path']}" + + auth = AuthenticationValues( + auth_type="bearer", + auth_value=None, + username=None, + password=None, + token="", + auth_header_key=None, + auth_header_value=None + ) + + tool = ToolCreate( + name=name, + displayName=name, + url=full_url, + description=ep.get("summary", ""), + integration_type="REST", + request_type="GET", + headers=None, + input_schema=input_schema, + output_schema=None, + annotations={}, + jsonpath_filter="", + auth=auth, + gateway_id=None, + tags=[], # type: ignore + team_id=None, + owner_email=None, + base_url=self.base_url, + path_template=ep["path"], + query_mapping=None, + header_mapping=None, + expose_passthrough=True, + allowlist=None, + plugin_chain_pre=None, + plugin_chain_post=None + ) + + tools.append(tool) + + return tools diff --git a/mcpgateway/openapi_generator/writer.py b/mcpgateway/openapi_generator/writer.py new file mode 100644 index 000000000..157d4bf53 --- /dev/null +++ b/mcpgateway/openapi_generator/writer.py @@ -0,0 +1,17 @@ +import json +from pathlib import Path + +class ToolWriter: + def __init__(self, output_dir="generated_tools"): + self.output = Path(output_dir) + self.output.mkdir(exist_ok=True) + + def write(self, tools): + path = self.output / "tools.json" + + data = [t.model_dump() for t in tools] + + with open(path, "w") as f: + json.dump(data, f, indent=2) + + return path