Files
2026-07-13 12:36:28 +08:00

617 lines
22 KiB
Python

from datetime import datetime
from typing import List
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import JSONResponse
from chatchat.server.api_server.api_schemas import (
MCPConnectionCreate,
MCPConnectionUpdate,
MCPConnectionResponse,
MCPConnectionListResponse,
MCPConnectionSearchRequest,
MCPConnectionStatusResponse,
MCPProfileCreate,
MCPProfileResponse,
MCPProfileStatusResponse,
)
from chatchat.server.db.repository.mcp_connection_repository import (
add_mcp_connection,
update_mcp_connection,
get_mcp_connection_by_id,
get_mcp_connections_by_server_name,
get_all_mcp_connections,
get_enabled_mcp_connections,
delete_mcp_connection,
enable_mcp_connection,
disable_mcp_connection,
search_mcp_connections,
get_mcp_profile,
create_mcp_profile,
update_mcp_profile,
reset_mcp_profile,
delete_mcp_profile,
)
from chatchat.utils import build_logger
logger = build_logger()
mcp_router = APIRouter(prefix="/api/v1/mcp_connections", tags=["MCP Connections"])
# MCP Profile 相关路由 - 放在前面避免与 {connection_id} 冲突
@mcp_router.get("/profile", response_model=MCPProfileResponse, summary="获取 MCP 通用配置")
async def get_mcp_profile_endpoint():
"""
获取 MCP 通用配置
"""
logger.info("获取 MCP 通用配置")
try:
profile = get_mcp_profile()
if profile:
logger.info("成功获取 MCP 通用配置")
return MCPProfileResponse(
timeout=profile["timeout"],
working_dir=profile["working_dir"],
env_vars=profile["env_vars"],
update_time=profile["update_time"]
)
else:
logger.info("MCP 通用配置不存在,返回默认配置")
# 如果不存在配置,返回默认配置
return MCPProfileResponse(
timeout=30,
working_dir="/tmp",
env_vars={
"PATH": "/usr/local/bin:/usr/bin:/bin",
"PYTHONPATH": "/app",
"HOME": "/tmp"
},
update_time=datetime.now().isoformat()
)
except Exception as e:
logger.error(f"获取 MCP 通用配置失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.post("/profile", response_model=MCPProfileResponse, summary="创建/更新 MCP 通用配置")
async def create_or_update_mcp_profile(profile_data: MCPProfileCreate):
"""
创建或更新 MCP 通用配置
"""
logger.info(f"创建/更新 MCP 通用配置: timeout={profile_data.timeout}, working_dir={profile_data.working_dir}")
try:
profile_id = create_mcp_profile(
timeout=profile_data.timeout,
working_dir=profile_data.working_dir,
env_vars=profile_data.env_vars,
)
profile = get_mcp_profile()
logger.info(f"成功创建/更新 MCP 通用配置,ID: {profile_id}")
return MCPProfileResponse(
timeout=profile["timeout"],
working_dir=profile["working_dir"],
env_vars=profile["env_vars"],
update_time=profile["update_time"]
)
except Exception as e:
logger.error(f"创建/更新 MCP 通用配置失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.put("/profile", response_model=MCPProfileResponse, summary="更新 MCP 通用配置")
async def update_mcp_profile_endpoint(profile_data: MCPProfileCreate):
"""
更新 MCP 通用配置
"""
logger.info(f"更新 MCP 通用配置: timeout={profile_data.timeout}, working_dir={profile_data.working_dir}")
try:
profile_id = update_mcp_profile(
timeout=profile_data.timeout,
working_dir=profile_data.working_dir,
env_vars=profile_data.env_vars,
)
profile = get_mcp_profile()
logger.info(f"成功更新 MCP 通用配置,ID: {profile_id}")
return MCPProfileResponse(
timeout=profile["timeout"],
working_dir=profile["working_dir"],
env_vars=profile["env_vars"],
update_time=profile["update_time"]
)
except Exception as e:
logger.error(f"更新 MCP 通用配置失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.post("/profile/reset", response_model=MCPProfileStatusResponse, summary="重置 MCP 通用配置")
async def reset_mcp_profile_endpoint():
"""
重置 MCP 通用配置为默认值
"""
logger.info("重置 MCP 通用配置为默认值")
try:
success = reset_mcp_profile()
if success:
logger.info("成功重置 MCP 通用配置")
return MCPProfileStatusResponse(
success=True,
message="MCP 通用配置已重置为默认值"
)
else:
logger.error("重置 MCP 通用配置失败")
return MCPProfileStatusResponse(
success=False,
message="重置 MCP 通用配置失败"
)
except Exception as e:
logger.error(f"重置 MCP 通用配置失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.delete("/profile", response_model=MCPProfileStatusResponse, summary="删除 MCP 通用配置")
async def delete_mcp_profile_endpoint():
"""
删除 MCP 通用配置
"""
logger.info("删除 MCP 通用配置")
try:
success = delete_mcp_profile()
if success:
logger.info("成功删除 MCP 通用配置")
return MCPProfileStatusResponse(
success=True,
message="MCP 通用配置已删除"
)
else:
logger.error("删除 MCP 通用配置失败")
return MCPProfileStatusResponse(
success=False,
message="删除 MCP 通用配置失败"
)
except Exception as e:
logger.error(f"删除 MCP 通用配置失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
def model_to_response(model) -> MCPConnectionResponse:
"""将数据库模型转换为响应对象"""
return MCPConnectionResponse(
id=model.id,
server_name=model.server_name,
args=model.args,
env=model.env,
cwd=model.cwd,
transport=model.transport,
timeout=model.timeout,
enabled=model.enabled,
description=model.description,
config=model.config,
create_time=model.create_time.isoformat() if model.create_time else None,
update_time=model.update_time.isoformat() if model.update_time else None,
)
@mcp_router.post("/", response_model=MCPConnectionResponse, summary="创建 MCP 连接")
async def create_mcp_connection(connection_data: MCPConnectionCreate):
"""
创建新的 MCP 连接配置
"""
logger.info(f"创建 MCP 连接: {connection_data.server_name}")
try:
# 检查服务器名称是否已存在
existing = get_mcp_connections_by_server_name(server_name=connection_data.server_name)
if existing:
logger.error(f"服务器名称 '{connection_data.server_name}' 已存在")
raise HTTPException(
status_code=400,
detail=f"服务器名称 '{connection_data.server_name}' 已存在"
)
connection_id = add_mcp_connection(
server_name=connection_data.server_name,
args=connection_data.args,
env=connection_data.env,
cwd=connection_data.cwd,
transport=connection_data.transport,
timeout=connection_data.timeout,
enabled=connection_data.enabled,
description=connection_data.description,
config=connection_data.config,
)
connection = get_mcp_connection_by_id(connection_id)
logger.info(f"成功创建 MCP 连接: {connection_data.server_name}, ID: {connection_id}")
return MCPConnectionResponse(
id=connection["id"],
server_name=connection["server_name"],
args=connection["args"],
env=connection["env"],
cwd=connection["cwd"],
transport=connection["transport"],
timeout=connection["timeout"],
enabled=connection["enabled"],
description=connection["description"],
config=connection["config"],
create_time=connection["create_time"],
update_time=connection["update_time"],
)
except Exception as e:
logger.error(f"创建 MCP 连接失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.get("/", response_model=MCPConnectionListResponse, summary="获取 MCP 连接列表")
async def list_mcp_connections(
enabled_only: bool = Query(False, description="仅返回启用的连接")
):
"""
获取所有 MCP 连接配置列表
"""
logger.info(f"获取 MCP 连接列表, enabled_only={enabled_only}")
try:
if enabled_only:
connections = get_enabled_mcp_connections()
else:
connections = get_all_mcp_connections()
response_connections = [MCPConnectionResponse(
id=conn["id"],
server_name=conn["server_name"],
args=conn["args"],
env=conn["env"],
cwd=conn["cwd"],
transport=conn["transport"],
timeout=conn["timeout"],
enabled=conn["enabled"],
description=conn["description"],
config=conn["config"],
create_time=conn["create_time"],
update_time=conn["update_time"],
) for conn in connections]
logger.info(f"成功获取 MCP 连接列表,共 {len(response_connections)} 个连接")
return MCPConnectionListResponse(
connections=response_connections,
total=len(response_connections)
)
except Exception as e:
logger.error(f"获取 MCP 连接列表失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.get("/{connection_id}", response_model=MCPConnectionResponse, summary="获取 MCP 连接详情")
async def get_mcp_connection(connection_id: str):
"""
根据 ID 获取 MCP 连接配置详情
"""
logger.info(f"获取 MCP 连接详情: {connection_id}")
try:
connection = get_mcp_connection_by_id(connection_id)
if not connection:
logger.error(f"连接 ID '{connection_id}' 不存在")
raise HTTPException(
status_code=404,
detail=f"连接 ID '{connection_id}' 不存在"
)
logger.info(f"成功获取 MCP 连接详情: {connection_id}")
return model_to_response(connection)
except HTTPException:
raise
except Exception as e:
logger.error(f"获取 MCP 连接详情失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.put("/{connection_id}", response_model=MCPConnectionStatusResponse, summary="更新 MCP 连接")
async def update_mcp_connection_by_id(
connection_id: str,
update_data: MCPConnectionUpdate
):
"""
更新 MCP 连接配置
"""
logger.info(f"更新 MCP 连接: {connection_id}")
try:
# 检查连接是否存在
existing = get_mcp_connection_by_id(connection_id)
if not existing:
logger.error(f"连接 ID '{connection_id}' 不存在")
return MCPConnectionStatusResponse(
connection_id=connection_id,
success=False,
message=f"连接 ID '{connection_id}' 不存在"
)
# 如果更新名称,检查是否与其他连接冲突
if update_data.server_name and update_data.server_name != existing.server_name:
name_existing = get_connections_by_server_name(server_name=update_data.server_name)
if name_existing:
logger.error(f"服务器名称 '{update_data.server_name}' 已存在")
return MCPConnectionStatusResponse(
connection_id=connection_id,
success=False,
message=f"服务器名称 '{update_data.server_name}' 已存在"
)
updated_id = update_mcp_connection(
connection_id=connection_id,
server_name=update_data.server_name,
args=update_data.args,
env=update_data.env,
cwd=update_data.cwd,
transport=update_data.transport,
timeout=update_data.timeout,
enabled=update_data.enabled,
description=update_data.description,
config=update_data.config,
)
if updated_id:
connection = get_mcp_connection_by_id(connection_id)
logger.info(f"成功更新 MCP 连接: {connection_id}")
return MCPConnectionStatusResponse(
connection_id=connection["id"],
success=True,
message="成功更新",
)
else:
logger.error("更新 MCP 连接失败")
return MCPConnectionStatusResponse(
connection_id=connection_id,
success=False,
message=f"更新 MCP 连接失败",
)
except HTTPException:
raise
except Exception as e:
logger.error(f"更新 MCP 连接失败: {str(e)}")
return MCPConnectionStatusResponse(
connection_id=connection_id,
success=False,
message=f"更新 MCP 连接失败: {str(e)}",
)
@mcp_router.delete("/{connection_id}", response_model=MCPConnectionStatusResponse, summary="删除 MCP 连接")
async def delete_mcp_connection_by_id(connection_id: str):
"""
删除 MCP 连接配置
"""
logger.info(f"删除 MCP 连接: {connection_id}")
try:
# 检查连接是否存在
existing = get_mcp_connection_by_id(connection_id)
if not existing:
logger.error(f"连接 ID '{connection_id}' 不存在")
raise HTTPException(
status_code=404,
detail=f"连接 ID '{connection_id}' 不存在"
)
success = delete_mcp_connection(connection_id)
if success:
logger.info(f"成功删除 MCP 连接: {connection_id}")
return MCPConnectionStatusResponse(
success=True,
message="连接删除成功",
connection_id=connection_id
)
else:
logger.error(f"删除 MCP 连接失败: {connection_id}")
return MCPConnectionStatusResponse(
success=False,
message="连接删除失败",
connection_id=connection_id
)
except HTTPException:
raise
except Exception as e:
logger.error(f"删除 MCP 连接失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.post("/{connection_id}/enable", response_model=MCPConnectionStatusResponse, summary="启用 MCP 连接")
async def enable_mcp_connection_endpoint(connection_id: str):
"""
启用指定的 MCP 连接
"""
logger.info(f"启用 MCP 连接: {connection_id}")
try:
# 检查连接是否存在
existing = get_mcp_connection_by_id(connection_id)
if not existing:
logger.error(f"连接 ID '{connection_id}' 不存在")
raise HTTPException(
status_code=404,
detail=f"连接 ID '{connection_id}' 不存在"
)
success = enable_mcp_connection(connection_id)
if success:
logger.info(f"成功启用 MCP 连接: {connection_id}")
return MCPConnectionStatusResponse(
success=True,
message="连接启用成功",
connection_id=connection_id
)
else:
logger.error(f"启用 MCP 连接失败: {connection_id}")
return MCPConnectionStatusResponse(
success=False,
message="连接启用失败",
connection_id=connection_id
)
except HTTPException:
raise
except Exception as e:
logger.error(f"启用 MCP 连接失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.post("/{connection_id}/disable", response_model=MCPConnectionStatusResponse, summary="禁用 MCP 连接")
async def disable_mcp_connection_endpoint(connection_id: str):
"""
禁用指定的 MCP 连接
"""
logger.info(f"禁用 MCP 连接: {connection_id}")
try:
# 检查连接是否存在
existing = get_mcp_connection_by_id(connection_id)
if not existing:
logger.error(f"连接 ID '{connection_id}' 不存在")
raise HTTPException(
status_code=404,
detail=f"连接 ID '{connection_id}' 不存在"
)
success = disable_mcp_connection(connection_id)
if success:
logger.info(f"成功禁用 MCP 连接: {connection_id}")
return MCPConnectionStatusResponse(
success=True,
message="连接禁用成功",
connection_id=connection_id
)
else:
logger.error(f"禁用 MCP 连接失败: {connection_id}")
return MCPConnectionStatusResponse(
success=False,
message="连接禁用失败",
connection_id=connection_id
)
except HTTPException:
raise
except Exception as e:
logger.error(f"禁用 MCP 连接失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.post("/search", response_model=MCPConnectionListResponse, summary="搜索 MCP 连接")
async def search_mcp_connections_endpoint(search_request: MCPConnectionSearchRequest):
"""
根据条件搜索 MCP 连接配置
"""
logger.info(f"搜索 MCP 连接: keyword={search_request.keyword}, transport={search_request.transport}, enabled={search_request.enabled}, limit={search_request.limit}")
try:
connections = search_mcp_connections(
keyword=search_request.keyword,
transport=search_request.transport,
enabled=search_request.enabled,
limit=search_request.limit,
)
response_connections = [MCPConnectionResponse(
id=conn["id"],
server_name=conn["server_name"],
args=conn["args"],
env=conn["env"],
cwd=conn["cwd"],
transport=conn["transport"],
timeout=conn["timeout"],
enabled=conn["enabled"],
description=conn["description"],
config=conn["config"],
create_time=conn["create_time"],
update_time=conn["update_time"],
) for conn in connections]
logger.info(f"成功搜索 MCP 连接,找到 {len(response_connections)} 个连接")
return MCPConnectionListResponse(
connections=response_connections,
total=len(response_connections)
)
except Exception as e:
logger.error(f"搜索 MCP 连接失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.get("/server/{server_name}", response_model=MCPConnectionListResponse, summary="根据服务器名称获取连接")
async def get_connections_by_server_name(server_name: str):
"""
根据服务器名称获取 MCP 连接配置列表
"""
logger.info(f"根据服务器名称获取 MCP 连接: {server_name}")
try:
connections = get_mcp_connections_by_server_name(server_name)
response_connections = [MCPConnectionResponse(
id=conn["id"],
server_name=conn["server_name"],
args=conn["args"],
env=conn["env"],
cwd=conn["cwd"],
transport=conn["transport"],
timeout=conn["timeout"],
enabled=conn["enabled"],
description=conn["description"],
config=conn["config"],
create_time=conn["create_time"],
update_time=conn["update_time"],
) for conn in connections]
logger.info(f"成功根据服务器名称获取 MCP 连接,找到 {len(response_connections)} 个连接")
return MCPConnectionListResponse(
connections=response_connections,
total=len(response_connections)
)
except Exception as e:
logger.error(f"根据服务器名称获取 MCP 连接失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@mcp_router.get("/enabled/list", response_model=MCPConnectionListResponse, summary="获取启用的 MCP 连接")
async def list_enabled_mcp_connections():
"""
获取所有启用的 MCP 连接配置
"""
logger.info("获取启用的 MCP 连接列表")
try:
connections = get_enabled_mcp_connections()
response_connections = [MCPConnectionResponse(
id=conn["id"],
server_name=conn["server_name"],
args=conn["args"],
env=conn["env"],
cwd=conn["cwd"],
transport=conn["transport"],
timeout=conn["timeout"],
enabled=conn["enabled"],
description=conn["description"],
config=conn["config"],
create_time=conn["create_time"],
update_time=conn["update_time"],
) for conn in connections]
logger.info(f"成功获取启用的 MCP 连接列表,共 {len(response_connections)} 个连接")
return MCPConnectionListResponse(
connections=response_connections,
total=len(response_connections)
)
except Exception as e:
logger.error(f"获取启用的 MCP 连接列表失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# MCP Profile 相关路由已移至文件开头以避免路由冲突