mirror of
https://github.com/Sun-ZhenXing/mcp-template-python.git
synced 2026-02-04 10:13:31 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac1de02173 | ||
|
|
66b47a16bf | ||
|
|
9c793622d7 | ||
|
|
14ebafdc7a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
# Trace files
|
||||||
|
radar.duckdb*
|
||||||
|
|
||||||
# Environment files
|
# Environment files
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.12
|
3.13
|
||||||
|
|||||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -4,6 +4,7 @@
|
|||||||
"charliermarsh.ruff",
|
"charliermarsh.ruff",
|
||||||
"fill-labs.dependi",
|
"fill-labs.dependi",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"tamasfe.even-better-toml"
|
"tamasfe.even-better-toml",
|
||||||
|
"streetsidesoftware.code-spell-checker"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -2,12 +2,17 @@
|
|||||||
"python.analysis.autoImportCompletions": true,
|
"python.analysis.autoImportCompletions": true,
|
||||||
"python.analysis.typeCheckingMode": "standard",
|
"python.analysis.typeCheckingMode": "standard",
|
||||||
"python.analysis.importFormat": "absolute",
|
"python.analysis.importFormat": "absolute",
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": "explicit",
|
"source.fixAll": "explicit",
|
||||||
"source.organizeImports": "never",
|
"source.convertImportFormat": "never",
|
||||||
"source.organizeImports.ruff": "explicit"
|
"source.organizeImports.ruff": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -27,6 +32,7 @@
|
|||||||
"PYPI",
|
"PYPI",
|
||||||
"pyproject",
|
"pyproject",
|
||||||
"streamable",
|
"streamable",
|
||||||
|
"trixie",
|
||||||
"uvicorn",
|
"uvicorn",
|
||||||
"venv"
|
"venv"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
ARG PYPI_MIRROR_URL=https://pypi.org/simple
|
ARG PYPI_MIRROR_URL=https://pypi.org/simple
|
||||||
ARG DEBIAN_MIRROR=ftp.cn.debian.org
|
ARG DEBIAN_MIRROR=deb.debian.org
|
||||||
|
|
||||||
# Base stage
|
# Base stage
|
||||||
FROM python:3.12-bookworm AS deps
|
FROM python:3.13-trixie AS deps
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG PYPI_MIRROR_URL
|
ARG PYPI_MIRROR_URL
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -21,7 +21,7 @@ RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache,sharing=locked \
|
|||||||
uv sync --no-dev --no-install-project
|
uv sync --no-dev --no-install-project
|
||||||
|
|
||||||
# Runner stage
|
# Runner stage
|
||||||
FROM python:3.12-slim-bookworm AS runner
|
FROM python:3.13-slim-trixie AS runner
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG DEBIAN_MIRROR
|
ARG DEBIAN_MIRROR
|
||||||
ARG PYPI_MIRROR_URL
|
ARG PYPI_MIRROR_URL
|
||||||
@@ -70,4 +70,4 @@ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
|||||||
CMD curl -f http://localhost:${PORT}/health || exit 1
|
CMD curl -f http://localhost:${PORT}/health || exit 1
|
||||||
|
|
||||||
EXPOSE ${PORT}
|
EXPOSE ${PORT}
|
||||||
CMD ["sh", "-c", "uv run --no-sync prod --host 0.0.0.0 --port ${PORT}"]
|
CMD ["sh", "-c", "uv run --no-sync mcp-template-python --host 0.0.0.0 --port ${PORT}"]
|
||||||
|
|||||||
40
Makefile
40
Makefile
@@ -1,50 +1,54 @@
|
|||||||
.PHONY: i dev prod build clean update lint docker-build docker-run helm-install helm-upgrade helm-uninstall helm-lint
|
.PHONY: i dev prod build clean update lint docker-build docker-run helm-install helm-upgrade helm-uninstall helm-lint rename
|
||||||
|
|
||||||
|
args := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
|
||||||
|
|
||||||
i:
|
i:
|
||||||
uv sync --all-extras --all-packages $(filter-out i,$(MAKECMDGOALS))
|
uv sync --all-extras --all-packages $(args)
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
uv run --no-sync python -c "__import__('mcp_template_python.__main__').__main__.dev()" $(filter-out dev,$(MAKECMDGOALS)) || echo shutdown
|
uv run --no-sync python -c "__import__('mcp_template_python.__main__').__main__.dev()" $(args) || echo shutdown
|
||||||
|
|
||||||
prod:
|
prod:
|
||||||
uv run --no-sync python -c "__import__('mcp_template_python.__main__').__main__.main()" $(filter-out prod,$(MAKECMDGOALS))
|
uv run --no-sync python -c "__import__('mcp_template_python.__main__').__main__.main()" $(args)
|
||||||
|
|
||||||
build:
|
build:
|
||||||
uv build $(filter-out build,$(MAKECMDGOALS))
|
uv build $(args)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf .venv .ruff_cache dist/ build/ *.egg-info $(filter-out clean,$(MAKECMDGOALS))
|
rm -rf .venv .ruff_cache dist/ build/ *.egg-info $(args)
|
||||||
|
|
||||||
update:
|
update:
|
||||||
uv sync --all-extras --all-packages -U $(filter-out update,$(MAKECMDGOALS))
|
uv sync --all-extras --all-packages -U $(args)
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
uv run ruff check . $(filter-out lint,$(MAKECMDGOALS))
|
uv run ruff check . $(args)
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
docker compose build $(filter-out docker-build,$(MAKECMDGOALS))
|
docker compose build $(args)
|
||||||
|
|
||||||
docker-run:
|
docker-run:
|
||||||
docker compose up -d $(filter-out docker-run,$(MAKECMDGOALS))
|
docker compose up -d $(args)
|
||||||
|
|
||||||
# Helm deployment commands
|
|
||||||
helm-lint:
|
helm-lint:
|
||||||
helm lint helm/mcp-template-python
|
helm lint helm/mcp-template-python $(args)
|
||||||
|
|
||||||
helm-install:
|
helm-install:
|
||||||
helm install mcp-template-python helm/mcp-template-python
|
helm install mcp-template-python helm/mcp-template-python $(args)
|
||||||
|
|
||||||
helm-upgrade:
|
helm-upgrade:
|
||||||
helm upgrade mcp-template-python helm/mcp-template-python
|
helm upgrade mcp-template-python helm/mcp-template-python $(args)
|
||||||
|
|
||||||
helm-install-prod:
|
helm-install-prod:
|
||||||
helm install mcp-template-python helm/mcp-template-python -f values-production.yaml
|
helm install mcp-template-python helm/mcp-template-python -f values-production.yaml $(args)
|
||||||
|
|
||||||
helm-upgrade-prod:
|
helm-upgrade-prod:
|
||||||
helm upgrade mcp-template-python helm/mcp-template-python -f values-production.yaml
|
helm upgrade mcp-template-python helm/mcp-template-python -f values-production.yaml $(args)
|
||||||
|
|
||||||
helm-uninstall:
|
helm-uninstall:
|
||||||
helm uninstall mcp-template-python
|
helm uninstall mcp-template-python $(args)
|
||||||
|
|
||||||
|
rename:
|
||||||
|
uv run python tools/rename.py $(args)
|
||||||
|
|
||||||
%:
|
%:
|
||||||
@:
|
@true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcp-template-python"
|
name = "mcp-template-python"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
description = "MCP Template for Python Projects"
|
description = "MCP Template for Python Projects"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = []
|
authors = []
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
__version__ = "0.1.2"
|
__version__ = "0.1.4"
|
||||||
__module_name__ = "mcp_template_python"
|
__module_name__ = "mcp_template_python"
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
from operator import add, mul, sub, truediv
|
from operator import add, mul, sub, truediv
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from mcp.server.transport_security import TransportSecuritySettings
|
||||||
|
|
||||||
from mcp_template_python.config import settings
|
from mcp_template_python.config import settings
|
||||||
|
|
||||||
mcp = FastMCP("math", instructions=settings.mcp.instructions)
|
mcp = FastMCP(
|
||||||
|
"math",
|
||||||
|
instructions=settings.mcp.instructions,
|
||||||
|
transport_security=TransportSecuritySettings(
|
||||||
|
allowed_hosts=settings.cors.allow_hosts.split(","),
|
||||||
|
allowed_origins=settings.cors.allow_origins.split(","),
|
||||||
|
enable_dns_rebinding_protection=settings.mcp.enable_dns_rebinding_protection,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class AppSettings(BaseSettings):
|
|||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="APP_",
|
env_prefix="APP_",
|
||||||
extra="allow",
|
extra="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
mcp: MCPSettings = MCPSettings()
|
mcp: MCPSettings = MCPSettings()
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ class CORSSettings(BaseSettings):
|
|||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="CORS_",
|
env_prefix="CORS_",
|
||||||
extra="allow",
|
extra="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
allow_hosts: str = "*"
|
||||||
|
"""CORS allow hosts, defaults to '*'."""
|
||||||
|
|
||||||
allow_origins: str = "*"
|
allow_origins: str = "*"
|
||||||
"""CORS allow origins, defaults to '*'."""
|
"""CORS allow origins, defaults to '*'."""
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class MCPSettings(BaseSettings):
|
|||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="MCP_",
|
env_prefix="MCP_",
|
||||||
extra="allow",
|
extra="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
default_mcp: str = "math"
|
default_mcp: str = "math"
|
||||||
@@ -25,3 +25,6 @@ class MCPSettings(BaseSettings):
|
|||||||
|
|
||||||
enable_streamable_http: bool = True
|
enable_streamable_http: bool = True
|
||||||
"""Enable streamable HTTP for the MCP server."""
|
"""Enable streamable HTTP for the MCP server."""
|
||||||
|
|
||||||
|
enable_dns_rebinding_protection: bool = True
|
||||||
|
"""Enable DNS rebinding protection for MCP server."""
|
||||||
|
|||||||
255
tools/rename.py
Normal file
255
tools/rename.py
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
IGNORED_DIRS = {
|
||||||
|
".venv",
|
||||||
|
"venv",
|
||||||
|
".git",
|
||||||
|
"__pycache__",
|
||||||
|
".pytest_cache",
|
||||||
|
".mypy_cache",
|
||||||
|
".ruff_cache",
|
||||||
|
"node_modules",
|
||||||
|
".idea",
|
||||||
|
".vscode",
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
"*.egg-info",
|
||||||
|
}
|
||||||
|
|
||||||
|
IGNORED_EXTENSIONS = {
|
||||||
|
".pyc",
|
||||||
|
".pyo",
|
||||||
|
".pyd",
|
||||||
|
".so",
|
||||||
|
".dll",
|
||||||
|
".dylib",
|
||||||
|
".exe",
|
||||||
|
".bin",
|
||||||
|
".dat",
|
||||||
|
".db",
|
||||||
|
".sqlite",
|
||||||
|
".lock",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".png",
|
||||||
|
".gif",
|
||||||
|
".ico",
|
||||||
|
".svg",
|
||||||
|
".pdf",
|
||||||
|
".zip",
|
||||||
|
".tar",
|
||||||
|
".gz",
|
||||||
|
".bz2",
|
||||||
|
".xz",
|
||||||
|
}
|
||||||
|
|
||||||
|
TEXT_EXTENSIONS = {
|
||||||
|
".py",
|
||||||
|
".txt",
|
||||||
|
".md",
|
||||||
|
".rst",
|
||||||
|
".toml",
|
||||||
|
".yaml",
|
||||||
|
".yml",
|
||||||
|
".json",
|
||||||
|
".ini",
|
||||||
|
".cfg",
|
||||||
|
".conf",
|
||||||
|
".sh",
|
||||||
|
".bat",
|
||||||
|
".ps1",
|
||||||
|
".html",
|
||||||
|
".css",
|
||||||
|
".js",
|
||||||
|
".ts",
|
||||||
|
".xml",
|
||||||
|
".Dockerfile",
|
||||||
|
".gitignore",
|
||||||
|
".dockerignore",
|
||||||
|
"Makefile",
|
||||||
|
"Dockerfile",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_name(name: str) -> tuple[str, str]:
|
||||||
|
"""Normalize name to underscore and hyphen versions."""
|
||||||
|
underscore_name = name.replace("-", "_")
|
||||||
|
hyphen_name = name.replace("_", "-")
|
||||||
|
return underscore_name, hyphen_name
|
||||||
|
|
||||||
|
|
||||||
|
def should_ignore_path(path: Path, root: Path) -> bool:
|
||||||
|
try:
|
||||||
|
relative = path.relative_to(root)
|
||||||
|
parts = relative.parts
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
for ignored in IGNORED_DIRS:
|
||||||
|
if ignored.endswith("*"):
|
||||||
|
pattern = ignored.replace("*", ".*")
|
||||||
|
if re.match(pattern, part):
|
||||||
|
return True
|
||||||
|
elif part == ignored:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except ValueError:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def should_process_file(file_path: Path) -> bool:
|
||||||
|
if file_path.suffix in IGNORED_EXTENSIONS:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if file_path.suffix in TEXT_EXTENSIONS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not file_path.suffix and file_path.name in TEXT_EXTENSIONS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def collect_paths_to_rename(
|
||||||
|
root: Path, old_underscore: str, old_hyphen: str
|
||||||
|
) -> List[Path]:
|
||||||
|
"""Collect all paths that need to be renamed."""
|
||||||
|
paths_to_rename = []
|
||||||
|
|
||||||
|
for path in root.rglob("*"):
|
||||||
|
if should_ignore_path(path, root):
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = path.name
|
||||||
|
if old_underscore in name or old_hyphen in name:
|
||||||
|
paths_to_rename.append(path)
|
||||||
|
|
||||||
|
paths_to_rename.sort(key=lambda p: len(p.parts), reverse=True)
|
||||||
|
|
||||||
|
return paths_to_rename
|
||||||
|
|
||||||
|
|
||||||
|
def rename_path(
|
||||||
|
old_path: Path,
|
||||||
|
old_underscore: str,
|
||||||
|
old_hyphen: str,
|
||||||
|
new_underscore: str,
|
||||||
|
new_hyphen: str,
|
||||||
|
dry_run: bool = False,
|
||||||
|
) -> Path:
|
||||||
|
"""Rename a single path."""
|
||||||
|
old_name = old_path.name
|
||||||
|
new_name = old_name
|
||||||
|
|
||||||
|
new_name = new_name.replace(old_underscore, new_underscore)
|
||||||
|
new_name = new_name.replace(old_hyphen, new_hyphen)
|
||||||
|
|
||||||
|
if new_name != old_name:
|
||||||
|
new_path = old_path.parent / new_name
|
||||||
|
|
||||||
|
if not dry_run:
|
||||||
|
if old_path.exists() and not new_path.exists():
|
||||||
|
shutil.move(str(old_path), str(new_path))
|
||||||
|
|
||||||
|
return new_path
|
||||||
|
|
||||||
|
return old_path
|
||||||
|
|
||||||
|
|
||||||
|
def replace_in_file(
|
||||||
|
file_path: Path,
|
||||||
|
old_underscore: str,
|
||||||
|
old_hyphen: str,
|
||||||
|
new_underscore: str,
|
||||||
|
new_hyphen: str,
|
||||||
|
dry_run: bool = False,
|
||||||
|
) -> int:
|
||||||
|
"""Replace old name in file content."""
|
||||||
|
if not should_process_file(file_path):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
except (UnicodeDecodeError, PermissionError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
new_content = content
|
||||||
|
new_content = new_content.replace(old_underscore, new_underscore)
|
||||||
|
new_content = new_content.replace(old_hyphen, new_hyphen)
|
||||||
|
|
||||||
|
changes = content.count(old_underscore) + content.count(old_hyphen)
|
||||||
|
|
||||||
|
if new_content != content and changes > 0:
|
||||||
|
if not dry_run:
|
||||||
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
return changes
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def rename_project(root: Path, old_name: str, new_name: str, dry_run: bool = False):
|
||||||
|
"""Rename the entire project."""
|
||||||
|
old_underscore, old_hyphen = normalize_name(old_name)
|
||||||
|
new_underscore, new_hyphen = normalize_name(new_name)
|
||||||
|
|
||||||
|
print(f"\nRenaming project{' (dry-run)' if dry_run else ''}:")
|
||||||
|
print(f" {old_underscore} / {old_hyphen} -> {new_underscore} / {new_hyphen}")
|
||||||
|
|
||||||
|
paths_to_rename = collect_paths_to_rename(root, old_underscore, old_hyphen)
|
||||||
|
|
||||||
|
if paths_to_rename:
|
||||||
|
for path in paths_to_rename:
|
||||||
|
rename_path(
|
||||||
|
path, old_underscore, old_hyphen, new_underscore, new_hyphen, dry_run
|
||||||
|
)
|
||||||
|
|
||||||
|
total_changes = 0
|
||||||
|
|
||||||
|
for file_path in root.rglob("*"):
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if should_ignore_path(file_path, root):
|
||||||
|
continue
|
||||||
|
|
||||||
|
changes = replace_in_file(
|
||||||
|
file_path, old_underscore, old_hyphen, new_underscore, new_hyphen, dry_run
|
||||||
|
)
|
||||||
|
|
||||||
|
if changes > 0:
|
||||||
|
total_changes += changes
|
||||||
|
|
||||||
|
print(f"\nCompleted: {total_changes} replacements")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function."""
|
||||||
|
parser = argparse.ArgumentParser(description="Rename project package name")
|
||||||
|
|
||||||
|
parser.add_argument("old_name", help="Old project name (xx_xx_xx or xx-xx-xx)")
|
||||||
|
parser.add_argument("new_name", help="New project name (yy_yy_yy or yy-yy-yy)")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Preview mode only")
|
||||||
|
parser.add_argument("--root", type=str, default=".", help="Project root directory")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
root = Path(args.root).resolve()
|
||||||
|
|
||||||
|
if not root.exists():
|
||||||
|
print(f"Error: Directory not found: {root}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
rename_project(root, args.old_name, args.new_name, args.dry_run)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit(main())
|
||||||
Reference in New Issue
Block a user