ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
FROM docker:dind
|
||||
|
||||
# Configure mirror
|
||||
ARG ENABLE_MIRROR=false
|
||||
ENV PIP_INDEX_URL=${ENABLE_MIRROR:+https://mirrors.aliyun.com/pypi/simple}
|
||||
|
||||
RUN apk add --no-cache python3 py3-pip curl tini
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
WORKDIR /app/container_server
|
||||
|
||||
COPY container_server/pyproject.toml /app/container_server/pyproject.toml
|
||||
COPY container_server/src /app/container_server/src
|
||||
|
||||
RUN uv sync --python-preference=only-system
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=5 \
|
||||
CMD curl -f http://localhost:8000/health || exit 1
|
||||
|
||||
ENV DOCKER_MODE=dind
|
||||
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
|
||||
CMD ["sh", "-c", "(dockerd-entrypoint.sh) & (sleep 5 && uv run --no-sync -m container_server.main) & wait -n"]
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
[project]
|
||||
name = "container-server"
|
||||
version = "0.1.0"
|
||||
description = "Container Server"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"docker",
|
||||
"aiohttp",
|
||||
"pydantic",
|
||||
"pydantic-settings",
|
||||
"fastapi[standard]",
|
||||
"python-dotenv",
|
||||
"websockets",
|
||||
"pyyaml",
|
||||
"mcp>=1.12.2",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
Vendored
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
import os
|
||||
|
||||
debug_mode = os.getenv("DEBUG_MODE", "false").lower() == "true"
|
||||
container_server_port = int(os.getenv("CONTAINER_SERVER_PORT", "9000"))
|
||||
|
||||
docker_registry_url = os.getenv("DOCKER_REGISTRY_URL")
|
||||
docker_registry_user_name = os.getenv("DOCKER_REGISTRY_USER_NAME")
|
||||
docker_registry_password = os.getenv("DOCKER_REGISTRY_PASSWORD")
|
||||
|
||||
gateway_server_addr = os.getenv("GATEWAY_SERVER_ADDR", "http://mcp-gateway:8000")
|
||||
|
||||
mcp_server_image_id = os.getenv(
|
||||
"VIRTUALPC_MCP_SERVER_IMAGE_ID",
|
||||
"aworld-registry-registry-vpc.ap-southeast-1.cr.aliyuncs.com/aworld/mcp-server",
|
||||
)
|
||||
|
||||
docker_mode = os.getenv("DOCKER_MODE", "dind")
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
from functools import cache
|
||||
import socket
|
||||
import logging
|
||||
import traceback
|
||||
import asyncio
|
||||
import uuid
|
||||
import httpx
|
||||
import threading
|
||||
|
||||
from .dockers import docker_helper
|
||||
|
||||
from .configs import (
|
||||
container_server_port,
|
||||
docker_registry_url,
|
||||
docker_registry_user_name,
|
||||
docker_registry_password,
|
||||
mcp_server_image_id,
|
||||
gateway_server_addr,
|
||||
docker_mode,
|
||||
debug_mode,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
token = str(uuid.uuid4())
|
||||
|
||||
|
||||
async def wait_docker_ready(timeout: int = 30):
|
||||
for i in range(timeout):
|
||||
try:
|
||||
cmd = ["docker", "ps"]
|
||||
p = await asyncio.subprocess.create_subprocess_exec(
|
||||
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT
|
||||
)
|
||||
stdout, _ = await p.communicate()
|
||||
if p.returncode == 0:
|
||||
logger.info(f"Docker daemon is ready! \n{stdout.decode()}")
|
||||
return
|
||||
else:
|
||||
logger.warning(f"Docker daemon is not ready! {stdout.decode()}")
|
||||
except:
|
||||
logger.error(f"Docker daemon is not ready! {traceback.format_exc()}")
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
logger.error(f"Docker daemon is not ready after {timeout} seconds!")
|
||||
raise Exception(f"Docker daemon is not ready after {timeout} seconds!")
|
||||
|
||||
|
||||
async def start_container_server_register_task():
|
||||
register_url = f"{gateway_server_addr}/api/container_server/register"
|
||||
local_ip_addr = get_local_ip()
|
||||
|
||||
async def register():
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
register_url,
|
||||
json={
|
||||
"token": token,
|
||||
"ip_addr": local_ip_addr,
|
||||
"port": container_server_port,
|
||||
"cpu_load": [],
|
||||
"memory_usage": [],
|
||||
},
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Register container server failed: {register_url}, {e}")
|
||||
raise
|
||||
|
||||
async def init_register():
|
||||
for _ in range(20):
|
||||
try:
|
||||
await register()
|
||||
logger.info(f"Init register success")
|
||||
break
|
||||
except:
|
||||
await asyncio.sleep(3)
|
||||
else:
|
||||
logger.error(f"Init register failed after 20 times!")
|
||||
raise Exception(f"Init register failed after 20 times!")
|
||||
|
||||
await init_register()
|
||||
|
||||
async def update_register():
|
||||
while True:
|
||||
try:
|
||||
await register()
|
||||
except:
|
||||
logger.error(f"Update register failed: {traceback.format_exc()}")
|
||||
await asyncio.sleep(10)
|
||||
|
||||
asyncio.create_task(update_register())
|
||||
|
||||
|
||||
async def load_mcp_server_image():
|
||||
if docker_registry_url and docker_registry_user_name and docker_registry_password:
|
||||
await docker_helper.login_async(
|
||||
registry_url=docker_registry_url,
|
||||
username=docker_registry_user_name,
|
||||
password=docker_registry_password,
|
||||
)
|
||||
if not debug_mode:
|
||||
await docker_helper.pull_async(mcp_server_image_id)
|
||||
|
||||
|
||||
async def start_mcp_server_life_cycle_manager():
|
||||
pass
|
||||
|
||||
|
||||
async def clean_mcp_server_container():
|
||||
pass
|
||||
|
||||
|
||||
async def create_mcp_server_container():
|
||||
mcp_port = docker_helper.get_available_port()
|
||||
novnc_port = docker_helper.get_available_port()
|
||||
ip_addr = get_local_ip()
|
||||
|
||||
container_name = f"mcp_server_{str(uuid.uuid4()).replace('-', '')}"
|
||||
logger.info(
|
||||
f"Create mcp server container: {container_name}, image_id: {mcp_server_image_id}, mcp_port: {mcp_port}, novnc_port: {novnc_port}"
|
||||
)
|
||||
try:
|
||||
ports = {4242: f"{mcp_port}", 5901: f"{novnc_port}"}
|
||||
|
||||
network = ""
|
||||
if docker_mode == "host":
|
||||
network = "visualvirtualpc_virtualpc-network"
|
||||
|
||||
container = await docker_helper.run_async(
|
||||
image_id=mcp_server_image_id,
|
||||
container_name=container_name,
|
||||
ports=ports,
|
||||
network=network,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Create mcp server container success, waiting for Ready: {container.id}, ip_addr: {ip_addr}, mcp_port: {mcp_port}, novnc_port: {novnc_port}"
|
||||
)
|
||||
|
||||
def tail_logs():
|
||||
logs = container.logs(stream=True, tail=100, follow=True)
|
||||
try:
|
||||
buffer = []
|
||||
for line in logs:
|
||||
buffer.append(line.decode())
|
||||
if len(buffer) >= 20:
|
||||
logger.info(f"VPC[{container.name}] >>> \n{'> '.join(buffer)}\n")
|
||||
buffer.clear()
|
||||
if buffer:
|
||||
logger.info(f"VPC[{container.name}] >>> \n{'> '.join(buffer)}\n")
|
||||
logger.info(f"VPC [{container.name}] logs end!")
|
||||
except Exception as e:
|
||||
logger.error(f"Error in tail_logs: {e}")
|
||||
|
||||
# Start log tailing in background thread
|
||||
log_thread = threading.Thread(
|
||||
target=tail_logs, name=f"VPC_{container.name}_logs", daemon=True
|
||||
)
|
||||
log_thread.start()
|
||||
|
||||
async def health_check(timeout: float = 3.0):
|
||||
try:
|
||||
# async with httpx.AsyncClient() as client:
|
||||
# response = await client.get(
|
||||
# f"http://{ip_addr}:{mcp_port}/health",
|
||||
# timeout=httpx.Timeout(timeout),
|
||||
# )
|
||||
# response.raise_for_status()
|
||||
# return True
|
||||
return await docker_helper.check_health(container.id)
|
||||
except Exception as e:
|
||||
logger.error(f"Check mcp server health error! {e}")
|
||||
return False
|
||||
|
||||
max_check = 30
|
||||
for i in range(max_check):
|
||||
if await health_check():
|
||||
logger.info(
|
||||
f"MCP server {ip_addr}:{mcp_port} is ready: {i+1}/{max_check}"
|
||||
)
|
||||
break
|
||||
else:
|
||||
logger.warning(
|
||||
f"MCP server {ip_addr}:{mcp_port} is not ready: {i+1}/{max_check}"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
else:
|
||||
logger.error(
|
||||
f"MCP server {ip_addr}:{mcp_port} is not ready after {max_check} times!"
|
||||
)
|
||||
raise Exception(
|
||||
f"MCP server {ip_addr}:{mcp_port} is not ready after {max_check} times!"
|
||||
)
|
||||
|
||||
if docker_mode == "host":
|
||||
ip_addr = await docker_helper.get_container_ip(container.id)
|
||||
mcp_port = 4242
|
||||
novnc_port = 5901
|
||||
|
||||
return container.id, ip_addr, mcp_port, novnc_port
|
||||
except:
|
||||
logger.error(f"Create mcp server container failed: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
async def shutdown_mcp_server_container(container_id: str):
|
||||
try:
|
||||
await docker_helper.stop_async(container_id)
|
||||
except:
|
||||
logger.error(f"Shutdown mcp server container failed: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
@cache
|
||||
def get_local_ip() -> str | None:
|
||||
try:
|
||||
host_name = socket.gethostname()
|
||||
_, _, ip_list = socket.gethostbyname_ex(host_name)
|
||||
for ip in ip_list:
|
||||
if not ip.startswith("127."):
|
||||
return ip
|
||||
except Exception as e:
|
||||
logger.error(f"Get local ip failed: {traceback.format_exc()}")
|
||||
|
||||
raise RuntimeError("Get local ip failed")
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from typing import Tuple
|
||||
import docker
|
||||
import logging
|
||||
import socket
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
client = docker.from_env(timeout=600)
|
||||
|
||||
|
||||
async def login_async(registry_url: str, username: str, password: str):
|
||||
try:
|
||||
return await asyncio.to_thread(login, registry_url, username, password)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in login_async: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def pull_async(image_id: str):
|
||||
try:
|
||||
return await asyncio.to_thread(pull, image_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in pull_async: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def run_async(
|
||||
image_id: str,
|
||||
container_name: str,
|
||||
ports: dict[int, str] = {},
|
||||
network: str = "",
|
||||
volumes: dict[str, str] = {},
|
||||
environments: dict[str, str] = {},
|
||||
):
|
||||
try:
|
||||
return await asyncio.to_thread(
|
||||
run, image_id, container_name, ports, network, volumes, environments
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in run_async: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def exec_async(container_id: str, cmd: list[str]) -> Tuple[int, str]:
|
||||
try:
|
||||
return await asyncio.to_thread(exec, container_id, cmd)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in exec_async: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def stop_async(container_id: str):
|
||||
try:
|
||||
return await asyncio.to_thread(stop, container_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in stop_async: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def check_health(container_id: str):
|
||||
try:
|
||||
container = client.containers.get(container_id)
|
||||
container.reload()
|
||||
return container.health == "healthy"
|
||||
except Exception as e:
|
||||
logger.error(f"Error in check_health: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def get_container_ip(container_id: str):
|
||||
try:
|
||||
container = client.containers.get(container_id)
|
||||
container.reload()
|
||||
nets = container.attrs["NetworkSettings"]["Networks"]
|
||||
net = list(nets.values())[0]
|
||||
return net["IPAddress"]
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_container_ip: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def build_image_async(image_id: str, dockerfile: str, context_path: str):
|
||||
async def build():
|
||||
try:
|
||||
cmd = [
|
||||
"docker",
|
||||
"build",
|
||||
"--platform",
|
||||
"linux/amd64",
|
||||
"-t",
|
||||
image_id,
|
||||
"-f",
|
||||
dockerfile,
|
||||
context_path,
|
||||
]
|
||||
p = await asyncio.subprocess.create_subprocess_exec(
|
||||
*cmd,
|
||||
cwd=context_path,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
)
|
||||
|
||||
assert p.stdout is not None
|
||||
while True:
|
||||
line = await p.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
logger.info(line.decode(errors="ignore").rstrip())
|
||||
|
||||
rc = await p.wait()
|
||||
if rc == 0:
|
||||
logger.info("Build mcp server image success!")
|
||||
return
|
||||
else:
|
||||
raise Exception(f"Build mcp server image error! return code: {rc}")
|
||||
except Exception as e:
|
||||
logger.error(f"Build mcp server image error! {e}")
|
||||
raise
|
||||
|
||||
for _ in range(3):
|
||||
try:
|
||||
await build()
|
||||
break
|
||||
except Exception as e:
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
logger.info(f"Build mcp server image failed after 3 times!")
|
||||
raise Exception("Build mcp server image failed after 3 times!")
|
||||
|
||||
|
||||
def pull(image_id: str):
|
||||
logger.info(f"Pulling image {image_id}")
|
||||
try:
|
||||
if client.images.get(image_id):
|
||||
logger.info(f"Image {image_id} already exists, skipping pull")
|
||||
return
|
||||
img = client.images.pull(image_id)
|
||||
logger.info(f"Pulled image {image_id}, {img}")
|
||||
except:
|
||||
logger.error(f"Failed to pull image {image_id}\n{traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
def run(
|
||||
image_id: str,
|
||||
container_name: str,
|
||||
ports: dict[int, str] = {},
|
||||
network: str = "",
|
||||
volumes: dict[str, str] = {},
|
||||
environments: dict[str, str] = {},
|
||||
):
|
||||
logger.info(
|
||||
f"Creating container {container_name} with args: {{'name': {container_name}, 'image': {image_id}, 'ports': {ports}, 'volumes': {volumes}, 'environments': {environments}}}"
|
||||
)
|
||||
try:
|
||||
container = client.containers.run(
|
||||
name=container_name,
|
||||
image=image_id,
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
ports=ports,
|
||||
network=network,
|
||||
volumes=volumes,
|
||||
environment=environments,
|
||||
cpu_period=100000,
|
||||
cpu_quota=90000,
|
||||
mem_limit="2G",
|
||||
)
|
||||
logger.info(f"Created container {container_name} response: {container}")
|
||||
return container
|
||||
except:
|
||||
logger.error(
|
||||
f"Failed to create container {container_name} with image {image_id}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def exec(container_id: str, cmd: list[str]) -> Tuple[int, str]:
|
||||
logger.info(f"Executing command {cmd} on container {container_id}")
|
||||
try:
|
||||
container = client.containers.get(container_id)
|
||||
exit_code, output = container.exec_run(cmd)
|
||||
logger.info(
|
||||
f"Command {cmd} executed on container {container_id} with result: {exit_code} {output}"
|
||||
)
|
||||
return exit_code, output
|
||||
except:
|
||||
logger.error(
|
||||
f"Failed to execute command {cmd} on container {container_id}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def stop(container_id: str):
|
||||
logger.info(f"Stop container {container_id}")
|
||||
try:
|
||||
container = client.containers.get(container_id)
|
||||
container.stop()
|
||||
logger.info(f"Stopped container {container_id}")
|
||||
except:
|
||||
logger.error(
|
||||
f"Failed to stop container {container_id}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def login(registry_url: str, username: str, password: str):
|
||||
logger.info(f"Logging in to {registry_url} with username {username}")
|
||||
try:
|
||||
result = client.login(
|
||||
registry=registry_url, username=username, password=password
|
||||
)
|
||||
logger.info(
|
||||
f"Logged in to {registry_url} with username {username} result: {result}"
|
||||
)
|
||||
except:
|
||||
logger.error(
|
||||
f"Failed to login to {registry_url} with username {username}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def get_available_port() -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("127.0.0.1", 0))
|
||||
port = s.getsockname()[1]
|
||||
return port
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request
|
||||
import logging
|
||||
|
||||
from .configs import container_server_port
|
||||
from .routers import api_server
|
||||
from . import container_server_manager
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
# Suppress httpx INFO logs
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""FastAPI lifespan context manager for startup and shutdown events"""
|
||||
# Startup
|
||||
await container_server_manager.wait_docker_ready()
|
||||
await container_server_manager.load_mcp_server_image()
|
||||
await container_server_manager.start_mcp_server_life_cycle_manager()
|
||||
await container_server_manager.start_container_server_register_task()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Shutdown
|
||||
# await mcp_server_manager.clean_mcp_server_container()
|
||||
pass
|
||||
|
||||
|
||||
# FastAPI application setup with lifespan
|
||||
app = FastAPI(
|
||||
title="MCP Container Server",
|
||||
description="MCP Container Server",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
app.include_router(api_server.router, prefix="/api")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health(request: Request):
|
||||
return {"status": "success", "message": "Container server is healthy"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
# Run the server
|
||||
uvicorn.run(app, host="0.0.0.0", port=container_server_port)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import logging
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from .. import container_server_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/container/create")
|
||||
async def create_container(request: Request, body: dict):
|
||||
token = body.get("token")
|
||||
logger.info(f"Create container: token={token}")
|
||||
container_id, ip_addr, mcp_port, novnc_port = (
|
||||
await container_server_manager.create_mcp_server_container()
|
||||
)
|
||||
logger.info(
|
||||
f"Container created: container_id={container_id}, ip_addr={ip_addr}, mcp_port={mcp_port}, novnc_port={novnc_port}"
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"MCP server created: {ip_addr}:{mcp_port}",
|
||||
"data": {
|
||||
"ip_addr": ip_addr,
|
||||
"mcp_port": mcp_port,
|
||||
"novnc_port": novnc_port,
|
||||
"container_id": container_id,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.post("/container/shutdown")
|
||||
async def shutdown_container(request: Request, body: dict):
|
||||
token = body.get("token")
|
||||
container_id = body.get("container_id")
|
||||
logger.info(f"Shutdown container: token={token}, container_id={container_id}")
|
||||
await container_server_manager.shutdown_mcp_server_container(container_id)
|
||||
logger.info(f"Container shutdown: container_id={container_id}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"MCP server shutdown: {container_id}",
|
||||
}
|
||||
+1320
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user