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
43 changes: 39 additions & 4 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
# Build context: the Dockerfile only COPYs pyproject.toml, uv.lock,
# README.md, src/, and LICENSE. Everything else listed here is
# excluded from the build context so `docker build` uploads less and
# layer hashes stay stable when these change.

# Python build / runtime artifacts
__pycache__
.env
.git
*.pyc
*.pyo
*.egg-info/
.venv/
dist/
build/

# Tooling caches
.mypy_cache/
.pytest_cache/
.ruff_cache/
.coverage
.coverage.*
htmlcov/

# Tests, CI, editor config — not needed inside the image
tests/
.github/
.vscode/
.idea/

# Local scratch / per-machine markers
.codex
temp.py
.claude/

# VCS / environment
.git/
.gitignore
.venv
LICENSE
.env

# Project docs not shipped in the image (README.md is needed by
# Dockerfile and is NOT listed here).
CLAUDE.md
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# NewsData.io credentials — copy this file to `.env` and fill in.
NEWSDATA_API_KEY="your_newsdata_api_key_here"

# Optional: request timeout in seconds (default: 30)
# REQUEST_TIMEOUT=30

# Optional: override the API base URL (e.g. for staging or a local mock)
# NEWSDATA_BASE_URL=https://newsdata.io/api/1

# Optional: retry policy for transient failures (network, 5xx, 429).
# Defaults sleep ~62s total across 5 attempts (2s → 4s → 8s → 16s → 32s, capped at 60s).
# NEWSDATA_MAX_RETRIES=5
# NEWSDATA_RETRY_BACKOFF=2.0
# NEWSDATA_RETRY_BACKOFF_MAX=60.0
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: ci

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies (runtime + dev)
run: uv sync --all-groups --frozen

- name: Lint (ruff)
run: uv run ruff check src/ tests/

- name: Type-check (mypy)
run: uv run mypy

- name: Test (pytest, unit only by default)
run: uv run pytest --cov=newsdata_mcp --cov-report=term-missing
70 changes: 21 additions & 49 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,54 +1,26 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore

# Node artifact files
node_modules/
dist/

# Compiled Java class files
*.class

# Compiled Python bytecode
# Python build artifacts
__pycache__/
*.py[cod]

# Log files
*.log

# Package files
*.jar

# Maven
target/
*.egg-info/
build/
dist/

# JetBrains IDE
.idea/

# Unit test reports
TEST*.xml

# Generated by MacOS
.DS_Store
# Virtual environment
.venv/

# Generated by Windows
Thumbs.db

# Applications
*.app
*.exe
*.war

# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
# Secrets
.env
.venv
__pycache__
uv.lock
.vscode

# Tooling caches (mypy, pytest, ruff, coverage)
.mypy_cache/
.pytest_cache/
.ruff_cache/
.coverage
.coverage.*
htmlcov/

# Local-only files
.codex
temp.py
CLAUDE.md
.vscode/
51 changes: 45 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,57 @@
FROM python:3.12-slim
# Multistage build: resolve and install with uv against the lockfile in
# the builder, then copy the resulting venv into a minimal runtime stage.

# ---------- Builder ----------
FROM python:3.12-slim AS builder

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
REQUEST_TIMEOUT=30
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy

# uv is a small Rust binary; pip-install it once in the builder.
RUN pip install --no-cache-dir uv

WORKDIR /app

COPY pyproject.toml README.md /app/
# Install dependencies first (without the project itself) so this layer
# stays cached when only application source changes. LICENSE is needed
# at build time because pyproject.toml declares `license = { file = ... }`.
COPY pyproject.toml uv.lock README.md LICENSE /app/
RUN uv sync --frozen --no-install-project --no-dev

# Now copy source and install the project itself into the same venv.
COPY src /app/src
RUN uv sync --frozen --no-dev

RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir .
# ---------- Runtime ----------
FROM python:3.12-slim AS runtime

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
REQUEST_TIMEOUT=30 \
PATH="/app/.venv/bin:$PATH"

WORKDIR /app

# Copy the populated venv + project source from the builder. LICENSE
# is already inside /app from the builder stage.
COPY --from=builder /app /app

# Run as a non-root user.
RUN useradd --create-home --uid 1000 app \
&& chown -R app:app /app
USER app

EXPOSE 8000

# TCP-level liveness probe; only meaningful for streamable-http transport.
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD python -c "import socket; s=socket.socket(); s.settimeout(2); s.connect(('localhost',8000)); s.close()" || exit 1

LABEL org.opencontainers.image.title="newsdata-mcp" \
org.opencontainers.image.description="MCP server for NewsData.io" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.source="https://github.com/newsdataapi/newsdata.io-mcp"

ENTRYPOINT ["newsdata-mcp"]
CMD ["--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8000"]
Loading
Loading