Skip to content
Merged
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
30 changes: 15 additions & 15 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "^.secrets.baseline|package-lock.json|Cargo.lock|scripts/sign_image.sh|scripts/zap|sonar-project.properties|uv.lock|^.secrets.baseline$",
"lines": null
},
"generated_at": "2026-04-13T06:27:05Z",
"generated_at": "2026-04-13T06:40:07Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -5086,31 +5086,31 @@
"hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3",
"is_secret": false,
"is_verified": false,
"line_number": 4239,
"line_number": 4240,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "559b05f1b2863e725b76e216ac3dadecbf92e244",
"is_secret": false,
"is_verified": false,
"line_number": 4840,
"line_number": 4841,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "a8af4759392d4f7496d613174f33afe2074a4b8d",
"is_secret": false,
"is_verified": false,
"line_number": 4842,
"line_number": 4843,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "85b60d811d16ff56b3654587d4487f713bfa33b7",
"is_secret": false,
"is_verified": false,
"line_number": 15050,
"line_number": 15169,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -5962,47 +5962,47 @@
"hashed_secret": "c377074d6473f35a91001981355da793dc808ffd",
"is_secret": false,
"is_verified": false,
"line_number": 4197,
"line_number": 4220,
"type": "Hex High Entropy String",
"verified_result": null
},
{
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"is_secret": false,
"is_verified": false,
"line_number": 5310,
"line_number": 5333,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "f2b14f68eb995facb3a1c35287b778d5bd785511",
"is_secret": false,
"is_verified": false,
"line_number": 5474,
"line_number": 5497,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "f42a3fabe1e9bed059d727f47eb752e3aa61b977",
"is_secret": false,
"is_verified": false,
"line_number": 5531,
"line_number": 5554,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "b85788b459aa4d67e1070930dae6d0827756aadb",
"is_secret": false,
"is_verified": false,
"line_number": 5569,
"line_number": 5592,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "52dcc83ec1e54426ad58a64854d1eb8d5f5d9685",
"is_secret": false,
"is_verified": false,
"line_number": 5570,
"line_number": 5593,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -9958,31 +9958,31 @@
"hashed_secret": "c00dbbc9dadfbe1e232e93a729dd4752fade0abf",
"is_secret": false,
"is_verified": false,
"line_number": 14402,
"line_number": 14411,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "f2b14f68eb995facb3a1c35287b778d5bd785511",
"is_secret": false,
"is_verified": false,
"line_number": 17159,
"line_number": 17168,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "a4b48a81cdab1e1a5dd37907d6c85ca1c61ddc7c",
"is_secret": false,
"is_verified": false,
"line_number": 17178,
"line_number": 17187,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "dc8002865f92070749b264e76045b04fa3b8de71",
"is_secret": false,
"is_verified": false,
"line_number": 20836,
"line_number": 20845,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
119 changes: 119 additions & 0 deletions mcpgateway/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
from mcpgateway.services.logging_service import LoggingService
from mcpgateway.services.mcp_session_pool import get_mcp_session_pool
from mcpgateway.services.oauth_manager import OAuthManager
from mcpgateway.services.openapi_service import fetch_and_extract_schemas
from mcpgateway.services.performance_service import get_performance_service
from mcpgateway.services.permission_service import PermissionService
from mcpgateway.services.plugin_service import get_plugin_service
Expand Down Expand Up @@ -11772,6 +11773,124 @@ async def admin_edit_tool(
return ORJSONResponse(content={"message": str(ex), "success": False}, status_code=500)


@admin_router.post("/tools/generate-schemas-from-openapi")
# tools.create — this endpoint makes outbound HTTP requests to user-supplied
# URLs to fetch OpenAPI specs. tools.read would let viewers probe internal
# services; tools.create scopes it to users who can already register tools.
@require_permission("tools.create", allow_admin_bypass=False)
async def generate_schemas_from_openapi(
request: Request,
_user=Depends(get_current_user_with_permissions),
) -> JSONResponse:
"""
Generate input_schema and output_schema from OpenAPI specification URL.

Expects JSON body with:
- url: The tool URL (e.g., http://localhost:8100/calculate)
- request_type: HTTP method (GET, POST, etc.)
- openapi_url: (optional) Direct OpenAPI spec URL

Args:
request: FastAPI Request object containing JSON body

Returns:
JSONResponse with generated schemas or error message.
"""
try:
body = await _read_request_json(request)
except Exception:
return ORJSONResponse(
content={"message": "Invalid JSON in request body", "success": False},
status_code=400,
)

if not isinstance(body, dict):
return ORJSONResponse(
content={"message": "Request body must be a JSON object", "success": False},
status_code=400,
)

tool_url = body.get("url", "")
request_type = body.get("request_type", "GET")
openapi_url = body.get("openapi_url", "")

if not isinstance(tool_url, str) or not isinstance(request_type, str) or not isinstance(openapi_url, str):
return ORJSONResponse(
content={"message": "'url', 'request_type', and 'openapi_url' must be strings", "success": False},
status_code=400,
)

tool_url = tool_url.strip()
request_type = request_type.strip()
openapi_url = openapi_url.strip()

if not tool_url:
return ORJSONResponse(
content={"message": "'url' is required to identify the API path and base URL", "success": False},
status_code=400,
)

try:
SecurityValidator.validate_url(tool_url, "Tool URL")
except ValueError as e:
return ORJSONResponse(
content={"message": str(e), "success": False},
status_code=400,
)

parsed = urllib.parse.urlparse(tool_url)
base_url = f"{parsed.scheme}://{parsed.netloc}"
tool_path = parsed.path

try:
input_schema, output_schema, spec_url = await fetch_and_extract_schemas(
base_url=base_url,
path=tool_path,
method=request_type,
openapi_url=openapi_url,
timeout=10.0,
)
except ValueError as e:
return ORJSONResponse(
content={"message": f"Security validation failed: {str(e)}", "success": False},
status_code=400,
)
except KeyError as e:
return ORJSONResponse(
content={"message": str(e), "success": False},
status_code=404,
)
except httpx.HTTPStatusError as e:
LOGGER.warning("OpenAPI spec server returned HTTP %s", e.response.status_code, exc_info=True)
return ORJSONResponse(
content={"message": f"OpenAPI spec server returned HTTP {e.response.status_code}", "success": False},
status_code=502,
)
except httpx.HTTPError:
LOGGER.warning("Failed to fetch OpenAPI spec", exc_info=True)
return ORJSONResponse(
content={"message": "Failed to fetch OpenAPI spec from the provided URL", "success": False},
status_code=502,
)
except Exception:
LOGGER.error("Error fetching OpenAPI spec", exc_info=True)
return ORJSONResponse(
content={"message": "An unexpected error occurred while processing the OpenAPI spec", "success": False},
status_code=500,
)

return ORJSONResponse(
content={
"message": "Schemas generated successfully from OpenAPI spec",
"success": True,
"input_schema": input_schema,
"output_schema": output_schema,
"spec_url": spec_url,
},
status_code=200,
)


@admin_router.post("/tools/{tool_id}/delete")
@require_permission("tools.delete", allow_admin_bypass=False)
async def admin_delete_tool(tool_id: str, request: Request, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions)) -> RedirectResponse:
Expand Down
Loading
Loading