11import logging
22from typing import Optional
33
4- from fastapi import APIRouter , Header , HTTPException
4+ from fastapi import APIRouter , Header , HTTPException , UploadFile , File , Form
55from fastapi .responses import JSONResponse
66from http import HTTPStatus
77
8- from consts .const import NEXENT_MCP_DOCKER_IMAGE
8+ from consts .const import NEXENT_MCP_DOCKER_IMAGE , ENABLE_UPLOAD_IMAGE
99from consts .exceptions import MCPConnectionError , MCPNameIllegal , MCPContainerError
1010from consts .model import MCPConfigRequest
1111from services .remote_mcp_service import (
1414 get_remote_mcp_server_list ,
1515 check_mcp_health_and_update_db ,
1616 delete_mcp_by_container_id ,
17+ upload_and_start_mcp_image ,
1718)
19+ from database .remote_mcp_db import check_mcp_name_exists
1820from services .tool_configuration_service import get_tool_from_remote_mcp_server
1921from services .mcp_container_service import MCPContainerManager
2022from utils .auth_utils import get_current_user_id
@@ -116,6 +118,7 @@ async def get_remote_proxies(
116118 return JSONResponse (
117119 status_code = HTTPStatus .OK ,
118120 content = {"remote_mcp_server_list" : remote_mcp_server_list ,
121+ "enable_upload_image" : ENABLE_UPLOAD_IMAGE ,
119122 "status" : "success" }
120123 )
121124 except Exception as e :
@@ -196,6 +199,11 @@ async def add_mcp_from_config(
196199 errors .append (f"{ service_name } : port is required" )
197200 continue
198201
202+ # Check if MCP service name already exists before starting container
203+ if check_mcp_name_exists (mcp_name = service_name , tenant_id = tenant_id ):
204+ errors .append (f"{ service_name } : MCP name already exists" )
205+ continue
206+
199207 # Build full command to run inside nexent/nexent-mcp image
200208 full_command = [
201209 "python" ,
@@ -224,22 +232,13 @@ async def add_mcp_from_config(
224232 )
225233
226234 # Register to remote MCP server list
227- try :
228- await add_remote_mcp_server_list (
229- tenant_id = tenant_id ,
230- user_id = user_id ,
231- remote_mcp_server = container_info ["mcp_url" ],
232- remote_mcp_server_name = service_name ,
233- container_id = container_info ["container_id" ],
234- )
235- except MCPNameIllegal :
236- # If name already exists, try to stop the container we just created
237- try :
238- await container_manager .stop_mcp_container (container_info ["container_id" ])
239- except Exception :
240- pass
241- errors .append (f"{ service_name } : MCP name already exists" )
242- continue
235+ await add_remote_mcp_server_list (
236+ tenant_id = tenant_id ,
237+ user_id = user_id ,
238+ remote_mcp_server = container_info ["mcp_url" ],
239+ remote_mcp_server_name = service_name ,
240+ container_id = container_info ["container_id" ],
241+ )
243242
244243 results .append ({
245244 "service_name" : service_name ,
@@ -251,10 +250,12 @@ async def add_mcp_from_config(
251250 })
252251
253252 except MCPContainerError as e :
254- logger .error (f"Failed to start MCP container { service_name } : { e } " )
253+ logger .error (
254+ f"Failed to start MCP container { service_name } : { e } " )
255255 errors .append (f"{ service_name } : { str (e )} " )
256256 except Exception as e :
257- logger .error (f"Unexpected error adding MCP { service_name } : { e } " )
257+ logger .error (
258+ f"Unexpected error adding MCP { service_name } : { e } " )
258259 errors .append (f"{ service_name } : { str (e )} " )
259260
260261 if errors and not results :
@@ -404,3 +405,62 @@ async def get_container_logs(
404405 status_code = HTTPStatus .INTERNAL_SERVER_ERROR ,
405406 detail = f"Failed to get container logs: { str (e )} "
406407 )
408+
409+
410+ # Conditionally add upload-image route based on ENABLE_UPLOAD_IMAGE setting
411+ if ENABLE_UPLOAD_IMAGE :
412+ @router .post ("/upload-image" )
413+ async def upload_mcp_image (
414+ file : UploadFile = File (..., description = "Docker image tar file" ),
415+ port : int = Form (..., ge = 1 , le = 65535 ,
416+ description = "Host port to expose the MCP server on (1-65535)" ),
417+ service_name : Optional [str ] = Form (
418+ None , description = "Name for the MCP service (auto-generated if not provided)" ),
419+ env_vars : Optional [str ] = Form (
420+ None , description = "Environment variables as JSON string" ),
421+ authorization : Optional [str ] = Header (None )
422+ ):
423+ """
424+ Upload Docker image tar file and start MCP container.
425+
426+ Container naming: {filename-without-extension}-{tenant-id[:8]}-{user-id[:8]}
427+ """
428+ try :
429+ user_id , tenant_id = get_current_user_id (authorization )
430+
431+ # Read file content
432+ content = await file .read ()
433+
434+ # Call service layer to handle the business logic
435+ result = await upload_and_start_mcp_image (
436+ tenant_id = tenant_id ,
437+ user_id = user_id ,
438+ file_content = content ,
439+ filename = file .filename ,
440+ port = port ,
441+ service_name = service_name ,
442+ env_vars = env_vars ,
443+ )
444+
445+ return JSONResponse (status_code = HTTPStatus .OK , content = result )
446+
447+ except ValueError as e :
448+ logger .error (f"Validation error: { e } " )
449+ raise HTTPException (
450+ status_code = HTTPStatus .BAD_REQUEST , detail = str (e ))
451+ except MCPNameIllegal as e :
452+ logger .error (f"MCP name conflict: { e } " )
453+ raise HTTPException (status_code = HTTPStatus .CONFLICT , detail = str (e ))
454+ except MCPContainerError as e :
455+ logger .error (f"Container error: { e } " )
456+ raise HTTPException (
457+ status_code = HTTPStatus .SERVICE_UNAVAILABLE , detail = str (e ))
458+ except Exception as e :
459+ logger .error (f"Failed to upload and start MCP container: { e } " )
460+ raise HTTPException (
461+ status_code = HTTPStatus .INTERNAL_SERVER_ERROR ,
462+ detail = f"Failed to upload and start MCP container: { str (e )} "
463+ )
464+ else :
465+ logger .info (
466+ "MCP image upload feature is disabled (ENABLE_UPLOAD_IMAGE=false)" )
0 commit comments