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
17 changes: 17 additions & 0 deletions .github/actions/validate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Validate python-mcp-server-starter
description: Deliverable-specific checks; runtime and security policy live in starter-series/.github.
runs:
using: composite
steps:
- name: Lint
run: ruff check .
shell: bash
- name: Format check
run: ruff format --check .
shell: bash
- name: Type check
run: mypy src/
shell: bash
- name: Test (with coverage)
run: pytest -v --cov=my_mcp_server --cov-report=term-missing --cov-fail-under=70
shell: bash
102 changes: 16 additions & 86 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,94 +1,24 @@
name: CI

on:
'on':
push:
branches: [main]
branches:
- main
pull_request:
branches: [main]
workflow_call:

branches:
- main
workflow_call: {}
workflow_dispatch: {}
permissions:
contents: read

jobs:
security:
checks:
uses: starter-series/.github/.github/workflows/reusable-python-ci.yml@main
ci:
if: ${{ always() }}
needs:
- checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Scan for secrets
env:
# Bump together; checksum from
# https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_checksums.txt
GITLEAKS_VERSION: 8.30.1
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
run: |
set -euo pipefail
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tgz
echo "${GITLEAKS_SHA256} /tmp/gitleaks.tgz" | sha256sum -c -
tar xz -C /tmp -f /tmp/gitleaks.tgz
/tmp/gitleaks detect --source . --verbose --redact

- name: Check for large files
run: |
LARGE=$(find . -not -path './.git/*' -not -path './.venv/*' -type f -size +5M)
if [ -n "$LARGE" ]; then
echo "::error::Large files detected (>5 MB):"
echo "$LARGE"
exit 1
fi

licenses:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v6
with:
python-version: "3.13"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install dependencies
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"

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

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

- name: Dependency audit (pip-audit)
run: pip-audit . --strict

- name: Lint
run: ruff check .

- name: Format check
run: ruff format --check .

- name: Type check
run: mypy src/

- name: Test (with coverage)
run: pytest -v --cov=my_mcp_server --cov-report=term-missing --cov-fail-under=70
- uses: starter-series/.github/.github/actions/require-checks@main
with:
results: ${{ toJSON(needs) }}
40 changes: 9 additions & 31 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
name: CodeQL

on:
'on':
push:
branches: [main]
branches:
- main
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Every Monday 06:00 UTC

branches:
- main
workflow_dispatch: {}
permissions:
contents: read
security-events: write

jobs:
analyze:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# `actions` scans .github/workflows/ for workflow-level issues
# (script injection, missing permissions, etc.).
language: [python, actions]
steps:
- uses: actions/checkout@v6

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}

- name: Autobuild
if: matrix.language == 'python'
uses: github/codeql-action/autobuild@v4

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
uses: starter-series/.github/.github/workflows/reusable-codeql.yml@main
with:
language: python
90 changes: 3 additions & 87 deletions .github/workflows/maintenance.yml
Original file line number Diff line number Diff line change
@@ -1,92 +1,8 @@
name: Maintenance

on:
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:

name: Maintenance (manual)
'on':
workflow_dispatch: {}
permissions:
contents: read
issues: write

jobs:
ci:
uses: ./.github/workflows/ci.yml

open-issue-on-failure:
needs: ci
if: failure()
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'maintenance',
});
if (existing.data.length > 0) {
console.log('Open maintenance issue already exists, skipping.');
return;
}
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Maintenance CI failed',
body: `The scheduled maintenance CI check failed.\n\n` +
`**Run:** ${run.data.html_url}\n\n` +
`Please check for dependency vulnerabilities, license issues, or build failures.\n\n` +
`---\n_Opened automatically by the Maintenance workflow._`,
labels: ['maintenance'],
});

close-issue-on-success:
needs: ci
if: success()
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
// Close any open maintenance issues — the most recent CI run
// proves the underlying problem is resolved. Without this job
// the issues opened by `open-issue-on-failure` linger forever
// and the repo looks broken to outside visitors even after the
// fix lands.
const open = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'maintenance',
});
if (open.data.length === 0) {
console.log('No open maintenance issues to close.');
return;
}
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
for (const issue of open.data) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Maintenance CI is green again — closing automatically.\n\n**Run:** ${run.data.html_url}`,
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
});
console.log(`Closed issue #${issue.number}`);
}
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,11 @@ For larger servers, copy the `register(mcp)` pattern from
checks without documenting the tradeoff.
- Do not add required secrets for normal CI. Publishing may require platform
setup, but CI should stay secret-free.

## Fleet CI policy

Common runtime, audit, license, secret-scan and CodeQL policy lives in
[starter-series/.github](https://github.com/starter-series/.github).
Keep deliverable checks in `.github/actions/validate/action.yml`.
Weekly health and failures are aggregated in the central Fleet maintenance workflow;
this repository retains a manual maintenance runner without issue automation.
1 change: 1 addition & 0 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def register(mcp: FastMCP) -> None:

```python
from my_mcp_server.resources.your_resource import register as register_your_resource

register_your_resource(mcp)
```

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Then in `server.py`:

```python
from my_mcp_server.resources.your_resource import register as register_your_resource

register_your_resource(mcp)
```

Expand Down
Loading