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
2 changes: 1 addition & 1 deletion backend/agents/create_agent_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ async def create_agent_run_info(
remote_mcp_list = await get_remote_mcp_server_list(tenant_id=tenant_id, is_need_auth=True)
default_mcp_url = urljoin(LOCAL_MCP_SERVER, "sse")
remote_mcp_list.append({
"remote_mcp_server_name": "nexent",
"remote_mcp_server_name": "outer-apis",
"remote_mcp_server": default_mcp_url,
"status": True,
"authorization_token": None
Expand Down
137 changes: 135 additions & 2 deletions backend/apps/tool_config_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging
from http import HTTPStatus
from typing import Optional
from typing import Optional, Dict, Any

from fastapi import APIRouter, Header, HTTPException
from fastapi import APIRouter, Header, HTTPException, Body
from fastapi.responses import JSONResponse

from consts.exceptions import MCPConnectionError, NotFoundException
Expand All @@ -14,6 +14,11 @@
list_all_tools,
load_last_tool_config_impl,
validate_tool_impl,
import_openapi_json,
list_outer_api_tools,
get_outer_api_tool,
delete_outer_api_tool,
_refresh_outer_api_tools_in_mcp,
)
from utils.auth_utils import get_current_user_id

Expand Down Expand Up @@ -134,3 +139,131 @@
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=str(e)
)


# --------------------------------------------------
# Outer API Tools (OpenAPI to MCP Conversion)
# --------------------------------------------------

@router.post("/import_openapi")
async def import_openapi_api(
openapi_json: Dict[str, Any] = Body(...),

Check failure on line 150 in backend/apps/tool_config_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ1sj8idtvGp9fbwMOMD&open=AZ1sj8idtvGp9fbwMOMD&pullRequest=2765
authorization: Optional[str] = Header(None)

Check failure on line 151 in backend/apps/tool_config_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ1sj8idtvGp9fbwMOME&open=AZ1sj8idtvGp9fbwMOME&pullRequest=2765
):
"""
Import OpenAPI JSON and convert tools to MCP format.
This will sync tools with the database (update existing, create new, delete removed).
After import, refreshes the MCP server to register new tools.
"""
try:
user_id, tenant_id = get_current_user_id(authorization)
result = import_openapi_json(openapi_json, tenant_id, user_id)

mcp_result = _refresh_outer_api_tools_in_mcp(tenant_id)
result["mcp_refresh"] = mcp_result

return JSONResponse(
status_code=HTTPStatus.OK,
content={
"message": "OpenAPI import successful",
"status": "success",
"data": result
}
)
except Exception as e:
logger.error(f"Failed to import OpenAPI: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to import OpenAPI: {str(e)}"
)


@router.get("/outer_api_tools")
async def list_outer_api_tools_api(
authorization: Optional[str] = Header(None)

Check failure on line 183 in backend/apps/tool_config_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ1sj8idtvGp9fbwMOMF&open=AZ1sj8idtvGp9fbwMOMF&pullRequest=2765
):
"""
List all outer API tools for the current tenant.
"""
try:
_, tenant_id = get_current_user_id(authorization)
tools = list_outer_api_tools(tenant_id)
return JSONResponse(
status_code=HTTPStatus.OK,
content={
"message": "success",
"data": tools
}
)
except Exception as e:
logger.error(f"Failed to list outer API tools: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to list outer API tools: {str(e)}"
)


@router.get("/outer_api_tools/{tool_id}")
async def get_outer_api_tool_api(
tool_id: int,
authorization: Optional[str] = Header(None)

Check failure on line 209 in backend/apps/tool_config_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ1sj8idtvGp9fbwMOMG&open=AZ1sj8idtvGp9fbwMOMG&pullRequest=2765
):
"""
Get a specific outer API tool by ID.
"""
try:
_, tenant_id = get_current_user_id(authorization)
tool = get_outer_api_tool(tool_id, tenant_id)
if tool is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Tool not found"
)
return JSONResponse(
status_code=HTTPStatus.OK,
content={
"message": "success",
"data": tool
}
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get outer API tool: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to get outer API tool: {str(e)}"
)


@router.delete("/outer_api_tools/{tool_id}")
async def delete_outer_api_tool_api(
tool_id: int,
authorization: Optional[str] = Header(None)

Check failure on line 242 in backend/apps/tool_config_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ1sj8idtvGp9fbwMOMH&open=AZ1sj8idtvGp9fbwMOMH&pullRequest=2765
):
"""
Delete an outer API tool.
"""
try:
user_id, tenant_id = get_current_user_id(authorization)
deleted = delete_outer_api_tool(tool_id, tenant_id, user_id)
if not deleted:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Tool not found"
)
return JSONResponse(
status_code=HTTPStatus.OK,
content={
"message": "Tool deleted successfully",
"status": "success"
}
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to delete outer API tool: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to delete outer API tool: {str(e)}"
)
1 change: 1 addition & 0 deletions backend/consts/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ class VectorDatabaseType(str, Enum):

# MCP Server
LOCAL_MCP_SERVER = os.getenv("NEXENT_MCP_SERVER")
MCP_MANAGEMENT_API = os.getenv("MCP_MANAGEMENT_API", "http://localhost:5015")


# Invite code
Expand Down
21 changes: 21 additions & 0 deletions backend/database/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,24 @@ class SkillInstance(TableBase):
tenant_id = Column(String(100), doc="Tenant ID")
enabled = Column(Boolean, default=True, doc="Whether this skill is enabled for the agent")
version_no = Column(Integer, default=0, primary_key=True, nullable=False, doc="Version number. 0 = draft/editing state, >=1 = published snapshot")


class OuterApiTool(TableBase):
"""
Outer API tools table - stores converted OpenAPI tools as MCP tools.
"""
__tablename__ = "ag_outer_api_tools"
__table_args__ = {"schema": SCHEMA}

id = Column(BigInteger, Sequence("ag_outer_api_tools_id_seq", schema=SCHEMA),
primary_key=True, nullable=False, doc="Tool ID, unique primary key")
name = Column(String(100), nullable=False, doc="Tool name (unique identifier)")
description = Column(Text, doc="Tool description")
method = Column(String(10), doc="HTTP method: GET/POST/PUT/DELETE")
url = Column(Text, nullable=False, doc="API endpoint URL")
headers_template = Column(JSONB, doc="Headers template as JSON")
query_template = Column(JSONB, doc="Query parameters template as JSON")
body_template = Column(JSONB, doc="Request body template as JSON")
input_schema = Column(JSONB, doc="MCP input schema as JSON")
tenant_id = Column(String(100), doc="Tenant ID for multi-tenancy")
is_available = Column(Boolean, default=True, doc="Whether the tool is available")
Loading
Loading