feat: add docker support

This commit is contained in:
Sun-ZhenXing
2025-06-15 12:35:56 +08:00
parent e1b112f401
commit b53f744aa7
10 changed files with 187 additions and 31 deletions

29
.dockerignore Normal file
View File

@@ -0,0 +1,29 @@
# Environment files
.env.*
!.env.example
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
# Docker files
Dockerfile
docker-compose.*
# Git files
.git/
.gitignore
# IDE files
.idea/
.vscode/
# misc
.DS_Store

5
.gitignore vendored
View File

@@ -1,3 +1,8 @@
# Environment files
.env
.env.*
!.env.example
# Python-generated files
__pycache__/
*.py[oc]

29
Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
ARG PYPI_MIRROR_URL=https://pypi.org/simple
FROM python:3.12-bookworm AS deps
ARG PYPI_MIRROR_URL
WORKDIR /app
COPY pyproject.toml uv.lock .python-version ./
ENV UV_DEFAULT_INDEX=${PYPI_MIRROR_URL}
# Install dependencies
RUN pip -V && \
pip config set global.index-url ${PYPI_MIRROR_URL} && \
pip install uv
RUN uv sync --no-dev --no-install-project
FROM python:3.12-slim-bookworm
ARG PYPI_MIRROR_URL
WORKDIR /app
RUN pip -V && \
pip config set global.index-url ${PYPI_MIRROR_URL} && \
pip install --no-cache-dir uv
COPY --from=deps /app/.venv/ ./.venv/
COPY . ./
EXPOSE 3001
CMD [ "uv", "run", "prod", "--host", "0.0.0.0" ]

View File

@@ -1,6 +1,52 @@
# MCP FastAPI 应用模板
本项目提供了 FastAPI 集成的 MCP 应用模板。
- [x] 支持多 MCP 挂载
- [x] 支持命令行调用 Stdio 模式
- [x] 支持 SSE / Streamable HTTP 兼容
- [x] 支持打包分发
## 开始
安装依赖:
```bash
uv sync
```
开发:
```bash
uv run dev
```
可通过 <http://127.0.0.1:3001/math/mcp> 访问示例 MCP 接口Streamable HTTP<http://127.0.0.1:3001/math/compatible/sse> 访问 SSE 接口。
## 部署
生产:
```bash
uv run prod
```
构建 Python Wheel 包:
```bash
uv build
```
## Docker 部署
运行:
```bash
docker compose up -d
```
仅构建:
```bash
docker compose build
```

19
docker-compose.yml Normal file
View File

@@ -0,0 +1,19 @@
x-default: &default
restart: unless-stopped
volumes:
- &localtime /etc/localtime:/etc/localtime:ro
- &timezone /etc/timezone:/etc/timezone:ro
logging:
driver: json-file
options:
max-size: 1m
services:
app:
<<: *default
build:
context: .
dockerfile: Dockerfile
image: ${DOCKER_REGISTRY:-docker.io}/local/mcp-template-python:${BUILD_VERSION:-latest}
ports:
- "3001:3001"

View File

@@ -9,12 +9,14 @@ authors = [
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.115.12",
"mcp>=1.9.3",
"mcp>=1.9.4",
"uvicorn[standard]>=0.34.3",
]
[project.scripts]
mcp-template-python = "mcp_template_python.__main__:main"
dev = "mcp_template_python.__main__:dev"
prod = "mcp_template_python.__main__:main"
[build-system]
requires = ["hatchling"]

View File

@@ -1 +1,2 @@
__version__ = "0.1.0"
__module_name__ = "mcp_template_python"

View File

@@ -1,43 +1,72 @@
import argparse
import sys
from .__about__ import __module_name__, __version__
def main():
parser = argparse.ArgumentParser(description="Run a MarkItDown MCP server")
parser = argparse.ArgumentParser(description="MCP Server")
parser.add_argument(
"--http",
"--stdio",
action="store_true",
help="Run the server with Streamable HTTP and SSE transport rather than STDIO (default: False)",
help="Run the server with STDIO (default: False)",
)
parser.add_argument(
"--host", default=None, help="Host to bind to (default: 127.0.0.1)"
"--host",
default="127.0.0.1",
help="Host to bind to (default: 127.0.0.1)",
)
parser.add_argument(
"--port", type=int, default=None, help="Port to listen on (default: 3001)"
"--port",
type=int,
default=3001,
help="Port to listen on (default: 3001)",
)
parser.add_argument(
"--dev",
default=False,
action="store_true",
help="Run the server in development mode (default: False)",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}",
help="Show the version of the MCP server",
)
args = parser.parse_args()
if not args.http and (args.host or args.port):
parser.error(
"Host and port arguments are only valid when using streamable HTTP or SSE transport (see: --http)."
)
sys.exit(1)
if args.dev:
dev(args.host, args.port)
sys.exit(0)
if args.http:
if args.stdio:
from .app.math import mcp
mcp.run()
else:
import uvicorn
from .server import app
uvicorn.run(
app,
host=args.host or "127.0.0.1",
port=args.port or 3001,
host=args.host,
port=args.port,
)
else:
from .app.math import mcp
mcp.run()
def dev(host: str = "127.0.0.1", port: int = 3001):
"""Run the MCP server in development mode."""
import uvicorn
uvicorn.run(
f"{__module_name__}.server:app",
host=host,
port=port,
reload=True,
)
if __name__ == "__main__":

View File

@@ -35,7 +35,3 @@ async def div_nums(a: float, b: float) -> float:
Divides the first number by the second.
"""
return truediv(a, b)
if __name__ == "__main__":
mcp.run()

20
uv.lock generated
View File

@@ -27,11 +27,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2025.4.26"
version = "2025.6.15"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload_time = "2025-04-26T02:12:29.51Z" }
sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload_time = "2025-06-15T02:45:51.329Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload_time = "2025-04-26T02:12:27.662Z" },
{ url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload_time = "2025-06-15T02:45:49.977Z" },
]
[[package]]
@@ -261,7 +261,7 @@ wheels = [
[[package]]
name = "mcp"
version = "1.9.3"
version = "1.9.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -274,9 +274,9 @@ dependencies = [
{ name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f2/df/8fefc0c6c7a5c66914763e3ff3893f9a03435628f6625d5e3b0dc45d73db/mcp-1.9.3.tar.gz", hash = "sha256:587ba38448e81885e5d1b84055cfcc0ca56d35cd0c58f50941cab01109405388", size = 333045, upload_time = "2025-06-05T15:48:25.681Z" }
sdist = { url = "https://files.pythonhosted.org/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f", size = 333294, upload_time = "2025-06-12T08:20:30.158Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/45/823ad05504bea55cb0feb7470387f151252127ad5c72f8882e8fe6cf5c0e/mcp-1.9.3-py3-none-any.whl", hash = "sha256:69b0136d1ac9927402ed4cf221d4b8ff875e7132b0b06edd446448766f34f9b9", size = 131063, upload_time = "2025-06-05T15:48:24.171Z" },
{ url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232, upload_time = "2025-06-12T08:20:28.551Z" },
]
[[package]]
@@ -292,7 +292,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
{ name = "mcp", specifier = ">=1.9.3" },
{ name = "mcp", specifier = ">=1.9.4" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.3" },
]
@@ -307,7 +307,7 @@ wheels = [
[[package]]
name = "pydantic"
version = "2.11.5"
version = "2.11.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -315,9 +315,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload_time = "2025-05-22T21:18:08.761Z" }
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload_time = "2025-06-14T08:33:17.137Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload_time = "2025-05-22T21:18:06.329Z" },
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload_time = "2025-06-14T08:33:14.905Z" },
]
[[package]]