Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ jobs:

- name: Build
run: |
pip install build
python -m pip install --upgrade pip
python -m pip install build
python -m build

- name: Generate SLSA build provenance attestation
Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ jobs:
cache-dependency-path: pyproject.toml

- name: Install dependencies
run: pip install -e . pip-licenses
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

- name: Check licenses
run: pip-licenses --fail-on="GPL-2.0;GPL-3.0;AGPL-3.0"
Expand All @@ -72,12 +74,12 @@ jobs:
cache-dependency-path: pyproject.toml

- name: Install dependencies
run: pip install -e ".[dev]"
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

- name: Dependency audit (pip-audit)
run: |
python -m pip install pip-audit
pip-audit --strict || pip-audit
run: pip-audit . --strict

- name: Lint
run: ruff check .
Expand Down
38 changes: 29 additions & 9 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ MCP 서버를 만들고, 원클릭 배포. 시크릿 불필요.
npx @starter-series/create my-mcp-server --template mcp-server-python
cd my-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
python -m pip install -e '.[dev]'
python -m pytest
```

**또는 직접 클론:**
Expand All @@ -48,7 +49,8 @@ pip install -e '.[dev]'
git clone https://github.com/starter-series/python-mcp-server-starter my-mcp-server
cd my-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
python -m pip install -e '.[dev]'
python -m pytest
```

## 도구 추가
Expand Down Expand Up @@ -137,13 +139,25 @@ def register(mcp: FastMCP) -> None:

```bash
# 테스트 실행
pytest -v
python -m pytest -v

# 린트
ruff check .
python -m ruff check .

# 포맷 확인
python -m ruff format --check .

# 타입 확인
python -m mypy src/

# wheel + sdist 빌드
python -m build

# 서버 실행 (stdio)
python -m my_mcp_server

# 설치된 console script로 동일 서버 실행
my-mcp-server
```

## CI/CD
Expand Down Expand Up @@ -182,11 +196,14 @@ src/my_mcp_server/
tests/
├── test_tools.py # 툴 테스트
├── test_server_info.py # Resource 테스트
└── test_code_review.py # Prompt 테스트
├── test_code_review.py # Prompt 테스트
├── test_runtime_contract.py # 시작/패키지 메타데이터 테스트
└── test_version_resolution.py # 버전 SSOT 테스트
.github/
├── workflows/
│ ├── ci.yml # 린트, 테스트, 보안
│ ├── cd.yml # PyPI OIDC 배포
│ ├── codeql.yml # 정적 분석
│ ├── stale.yml # Stale 이슈 관리
│ └── maintenance.yml # 주간 헬스 체크
└── dependabot.yml # 의존성 업데이트
Expand All @@ -195,11 +212,14 @@ tests/
## 스크립트

```bash
pip install -e ".[dev]" # dev 의존성 포함 설치
python -m pip install -e ".[dev]" # dev 의존성 포함 설치
python -m my_mcp_server # 서버 실행
pytest -v # 테스트 실행
ruff check . # 린트
ruff format . # 포맷
my-mcp-server # 설치된 console script로 서버 실행
python -m pytest -v # 테스트 실행
python -m ruff check . # 린트
python -m ruff format . # 포맷
python -m mypy src/ # 타입 확인
python -m build # wheel + sdist 빌드
```

## 라이선스
Expand Down
38 changes: 29 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Build your MCP server. One-click publish. Zero secrets needed.
npx @starter-series/create my-mcp-server --template mcp-server-python
cd my-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
python -m pip install -e '.[dev]'
python -m pytest
```

**Or clone directly:**
Expand All @@ -51,7 +52,8 @@ pip install -e '.[dev]'
git clone https://github.com/starter-series/python-mcp-server-starter my-mcp-server
cd my-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
python -m pip install -e '.[dev]'
python -m pytest
```

## Adding Tools
Expand Down Expand Up @@ -157,13 +159,25 @@ Add your own in `server.py`.

```bash
# Run tests
pytest -v
python -m pytest -v

# Lint
ruff check .
python -m ruff check .

# Format check
python -m ruff format --check .

# Type check
python -m mypy src/

# Build wheel + sdist
python -m build

# Run the server (stdio)
python -m my_mcp_server

# Same server via installed console script
my-mcp-server
```

## CI/CD
Expand Down Expand Up @@ -202,11 +216,14 @@ src/my_mcp_server/
tests/
├── test_tools.py # Tool tests
├── test_server_info.py # Resource tests
└── test_code_review.py # Prompt tests
├── test_code_review.py # Prompt tests
├── test_runtime_contract.py # Startup + package metadata tests
└── test_version_resolution.py # Version SSOT tests
.github/
├── workflows/
│ ├── ci.yml # Lint, test, security
│ ├── cd.yml # PyPI OIDC publish
│ ├── codeql.yml # Static analysis
│ ├── stale.yml # Stale issue management
│ └── maintenance.yml # Weekly health check
└── dependabot.yml # Dependency updates
Expand All @@ -215,11 +232,14 @@ tests/
## Scripts

```bash
pip install -e ".[dev]" # Install with dev deps
python -m pip install -e ".[dev]" # Install with dev deps
python -m my_mcp_server # Run server
pytest -v # Run tests
ruff check . # Lint
ruff format . # Format
my-mcp-server # Run installed console script
python -m pytest -v # Run tests
python -m ruff check . # Lint
python -m ruff format . # Format
python -m mypy src/ # Type check
python -m build # Build wheel + sdist
```

## License
Expand Down
15 changes: 13 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "README.md"
license = "MIT"
requires-python = ">=3.11"
authors = [
{ name = "Your Name" },
{ name = "Heznpc" },
]
keywords = ["mcp", "mcp-server", "model-context-protocol"]
classifiers = [
Expand All @@ -25,12 +25,20 @@ dependencies = [
"mcp>=1.27.1,<2",
]

[project.urls]
Homepage = "https://github.com/starter-series/python-mcp-server-starter"
Repository = "https://github.com/starter-series/python-mcp-server-starter"
Issues = "https://github.com/starter-series/python-mcp-server-starter/issues"

[project.scripts]
my-mcp-server = "my_mcp_server.server:main"
my-mcp-server = "my_mcp_server.__main__:run"

[project.optional-dependencies]
# Major-bounded so a breaking ruff/mypy/pytest release can't turn CI red overnight.
dev = [
"build>=1.5.0,<2",
"pip-audit>=2.10.1,<3",
"pip-licenses>=5.5.5,<6",
"pytest>=9.0.3,<10",
"pytest-asyncio>=1.4.0,<2",
"pytest-cov>=7.1.0,<8",
Expand Down Expand Up @@ -80,3 +88,6 @@ exclude_also = [
"if TYPE_CHECKING:",
"if __name__ == .__main__.:",
]

[tool.hatch.build.targets.wheel]
packages = ["src/my_mcp_server"]
4 changes: 2 additions & 2 deletions src/my_mcp_server/prompts/code_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts.base import UserMessage
from pydantic import Field, validate_call
from pydantic import StringConstraints, validate_call

NAME = "code-review"
TITLE = "Code Review"
Expand All @@ -30,7 +30,7 @@
@validate_call
def code_review(
language: Language,
code: Annotated[str, Field(min_length=1, description="Source code to review.")],
code: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)],
) -> list[UserMessage]:
"""Render a code-review prompt as a single-user-message template.

Expand Down
39 changes: 29 additions & 10 deletions src/my_mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from mcp.server.fastmcp import FastMCP
from mcp.types import ToolAnnotations
from pydantic import Field
from pydantic import StringConstraints

from my_mcp_server.prompts.code_review import register as register_code_review
from my_mcp_server.resources.server_info import register as register_server_info
Expand All @@ -22,14 +22,30 @@
# ---------------------------------------------------------------------------

DEBUG = os.environ.get("MCP_DEBUG", "false").lower() == "true"
VALID_LOG_LEVELS = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}


def resolve_log_level() -> str:
"""Return a logging level name accepted by ``logging``."""
if DEBUG:
return "DEBUG"

candidate = os.environ.get("LOG_LEVEL", "INFO").upper()
if candidate not in VALID_LOG_LEVELS:
return "INFO"
return candidate


# ---------------------------------------------------------------------------
# Server
# ---------------------------------------------------------------------------

SERVER_NAME = "my-mcp-server"
SERVER_INSTRUCTIONS = "An MCP server. Replace this with your description."

mcp = FastMCP(
"my-mcp-server",
instructions="An MCP server. Replace this with your description.",
SERVER_NAME,
instructions=SERVER_INSTRUCTIONS,
)


Expand All @@ -50,10 +66,10 @@
async def greet(
name: Annotated[
str,
Field(
StringConstraints(
strip_whitespace=True,
min_length=1,
max_length=200,
description="Name to greet (1-200 characters).",
),
],
) -> str:
Expand All @@ -64,8 +80,12 @@ async def greet(
are rejected by the protocol layer before the handler runs. The TS
sibling enforces the same shape via Zod.
"""
logger.info("Greeting %s", name)
return f"Hello, {name}!"
normalized = name.strip()
if not normalized:
raise ValueError("name must contain at least one non-whitespace character")

logger.info("Greeting %s", normalized)
return f"Hello, {normalized}!"


# To add more tools, either decorate inline above or split them into modules
Expand Down Expand Up @@ -94,9 +114,8 @@ async def greet(

def main() -> None:
"""Run the MCP server."""
log_level = "DEBUG" if DEBUG else os.environ.get("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
level=getattr(logging, log_level, logging.INFO),
level=getattr(logging, resolve_log_level()),
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
)
mcp.run()
mcp.run(transport="stdio")
Loading
Loading