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
|
||||
.env
|
||||
.env.*
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.12
|
||||
3.13
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -4,6 +4,7 @@
|
||||
"charliermarsh.ruff",
|
||||
"fill-labs.dependi",
|
||||
"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.typeCheckingMode": "standard",
|
||||
"python.analysis.importFormat": "absolute",
|
||||
"python.testing.pytestArgs": [
|
||||
"."
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"source.organizeImports": "never",
|
||||
"source.convertImportFormat": "never",
|
||||
"source.organizeImports.ruff": "explicit"
|
||||
}
|
||||
},
|
||||
@@ -27,6 +32,7 @@
|
||||
"PYPI",
|
||||
"pyproject",
|
||||
"streamable",
|
||||
"trixie",
|
||||
"uvicorn",
|
||||
"venv"
|
||||
]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
ARG PYPI_MIRROR_URL=https://pypi.org/simple
|
||||
ARG DEBIAN_MIRROR=ftp.cn.debian.org
|
||||
ARG DEBIAN_MIRROR=deb.debian.org
|
||||
|
||||
# Base stage
|
||||
FROM python:3.12-bookworm AS deps
|
||||
FROM python:3.13-trixie AS deps
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG PYPI_MIRROR_URL
|
||||
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
|
||||
|
||||
# Runner stage
|
||||
FROM python:3.12-slim-bookworm AS runner
|
||||
FROM python:3.13-slim-trixie AS runner
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG DEBIAN_MIRROR
|
||||
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
|
||||
|
||||
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:
|
||||
uv sync --all-extras --all-packages $(filter-out i,$(MAKECMDGOALS))
|
||||
uv sync --all-extras --all-packages $(args)
|
||||
|
||||
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:
|
||||
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:
|
||||
uv build $(filter-out build,$(MAKECMDGOALS))
|
||||
uv build $(args)
|
||||
|
||||
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:
|
||||
uv sync --all-extras --all-packages -U $(filter-out update,$(MAKECMDGOALS))
|
||||
uv sync --all-extras --all-packages -U $(args)
|
||||
|
||||
lint:
|
||||
uv run ruff check . $(filter-out lint,$(MAKECMDGOALS))
|
||||
uv run ruff check . $(args)
|
||||
|
||||
docker-build:
|
||||
docker compose build $(filter-out docker-build,$(MAKECMDGOALS))
|
||||
docker compose build $(args)
|
||||
|
||||
docker-run:
|
||||
docker compose up -d $(filter-out docker-run,$(MAKECMDGOALS))
|
||||
docker compose up -d $(args)
|
||||
|
||||
# Helm deployment commands
|
||||
helm-lint:
|
||||
helm lint helm/mcp-template-python
|
||||
helm lint helm/mcp-template-python $(args)
|
||||
|
||||
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 mcp-template-python helm/mcp-template-python
|
||||
helm upgrade mcp-template-python helm/mcp-template-python $(args)
|
||||
|
||||
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 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 mcp-template-python
|
||||
helm uninstall mcp-template-python $(args)
|
||||
|
||||
rename:
|
||||
uv run python tools/rename.py $(args)
|
||||
|
||||
%:
|
||||
@:
|
||||
@true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mcp-template-python"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
description = "MCP Template for Python Projects"
|
||||
readme = "README.md"
|
||||
authors = []
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__version__ = "0.1.2"
|
||||
__version__ = "0.1.4"
|
||||
__module_name__ = "mcp_template_python"
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
from operator import add, mul, sub, truediv
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
|
||||
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()
|
||||
|
||||
@@ -15,7 +15,7 @@ class AppSettings(BaseSettings):
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="APP_",
|
||||
extra="allow",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
mcp: MCPSettings = MCPSettings()
|
||||
|
||||
@@ -8,9 +8,12 @@ class CORSSettings(BaseSettings):
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="CORS_",
|
||||
extra="allow",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
allow_hosts: str = "*"
|
||||
"""CORS allow hosts, defaults to '*'."""
|
||||
|
||||
allow_origins: str = "*"
|
||||
"""CORS allow origins, defaults to '*'."""
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class MCPSettings(BaseSettings):
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="MCP_",
|
||||
extra="allow",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
default_mcp: str = "math"
|
||||
@@ -25,3 +25,6 @@ class MCPSettings(BaseSettings):
|
||||
|
||||
enable_streamable_http: bool = True
|
||||
"""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