Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1e99253
fix: clean route correctness lint
STELS37 Sep 27, 2026
243f831
fix: clean SSE transport imports
STELS37 Sep 27, 2026
108b496
fix: clean auth middleware imports
STELS37 Sep 27, 2026
f5c3b7c
fix: clean OAuth imports
STELS37 Sep 27, 2026
44ef9a2
fix: clean application imports
STELS37 Sep 27, 2026
6528423
fix: tighten direct operations lint
STELS37 Sep 27, 2026
d390fc8
fix: clean executor import
STELS37 Sep 27, 2026
9e5e099
fix: clean extra tools import
STELS37 Sep 27, 2026
c72e2f9
fix: clean MCP tool runner lint
STELS37 Sep 27, 2026
55fc43e
fix: clean remote SSH import
STELS37 Sep 27, 2026
cfaacb0
fix: clean SSH client correctness lint
STELS37 Sep 27, 2026
5a2fd9c
build: freeze Ruff correctness policy
STELS37 Sep 27, 2026
0937e28
ci: gate production deploy on verified exact commit
STELS37 Sep 27, 2026
166623c
ci: make secondary deploy path manual only
STELS37 Sep 27, 2026
6e8a873
ci: centralize production deployment verification
STELS37 Sep 27, 2026
8e884c1
chore: prevent repository-local scratch and backup archives
STELS37 Sep 27, 2026
02b9e62
docs: define security and deployment policy
STELS37 Sep 27, 2026
5bf9736
chore: remove tracked scratch file tmp.txt
STELS37 Sep 27, 2026
7684de8
chore: remove tracked scratch file test_write.txt
STELS37 Sep 27, 2026
08fc9c6
build: keep lint gate correctness-focused
STELS37 Sep 27, 2026
10e7353
ci: isolate import smoke from production SSH secrets
STELS37 Sep 27, 2026
8a0e282
test: make settings hermetic without production secrets
STELS37 Sep 27, 2026
c4a55bb
test: isolate direct operation contracts from runner host
STELS37 Sep 27, 2026
64658c4
chore: remove stale public repository backup archive
STELS37 Sep 27, 2026
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
168 changes: 50 additions & 118 deletions .github/workflows/auto-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,130 +1,62 @@
name: Auto Deploy on Push
name: Manual Production Deploy

permissions:
contents: read
packages: write


on:
push:
branches: [ main, master ]
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'production'
type: choice
options:
- production
- staging

jobs:
build:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.tags }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha,prefix=
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64

deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'production' }}

environment: production
concurrency:
group: mcp-server-production
cancel-in-progress: false
steps:
- name: Deploy to server
if: ${{ env.SERVER_HOST != '' && env.SERVER_USER != '' && env.SERVER_SSH_KEY != '' }}
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USER }}
key: ${{ env.SERVER_SSH_KEY }}
port: ${{ secrets.SERVER_PORT || 22 }}
script: |
set -e

echo "🚀 Starting deployment..."

# Navigate to project directory
cd /opt/mcp-server

# Pull latest code
git pull origin main

# Pull latest Docker image
docker compose pull

# Restart services
docker compose up -d --remove-orphans

# Wait for health check
echo "⏳ Waiting for health check..."
sleep 10

# Verify deployment
if curl -sf http://localhost:8000/health > /dev/null; then
echo "✅ Deployment successful!"
else
echo "❌ Health check failed!"
docker compose logs --tail=50
exit 1
fi

# Clean up old images
docker image prune -f

echo "🎉 Deployment complete!"
- name: Require deployment secrets
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
run: |
test -n "$SERVER_HOST"
test -n "$SERVER_USER"
test -n "$SERVER_SSH_KEY"

notify:
needs: [build, deploy]
runs-on: ubuntu-latest
if: always()

steps:
- name: Send notification
run: |
if [ "${{ needs.deploy.result }}" == "success" ]; then
echo "✅ Deployment successful!"
elif [ "${{ needs.deploy.result }}" == "skipped" ]; then
echo "ℹ️ Deployment skipped (likely missing deploy secrets/environment)."
else
echo "⚠️ Deployment not successful: ${{ needs.deploy.result }}"
fi
- name: Deploy selected main commit
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
EXPECTED_SHA: ${{ github.sha }}
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USER }}
key: ${{ env.SERVER_SSH_KEY }}
port: ${{ secrets.SERVER_PORT || 22 }}
envs: EXPECTED_SHA
script: |
set -euo pipefail
cd /opt/mcp-server

if [ -n "$(git status --porcelain)" ]; then
echo "Refusing deployment: production worktree is dirty"
git status --short
exit 1
fi

git fetch origin main
remote_sha="$(git rev-parse origin/main)"
if [ "$remote_sha" != "$EXPECTED_SHA" ]; then
echo "Refusing stale manual deployment: expected=$EXPECTED_SHA remote=$remote_sha"
exit 1
fi

git checkout main
git merge --ff-only origin/main
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"

bash scripts/deploy-production.sh "$EXPECTED_SHA"
137 changes: 85 additions & 52 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ name: CI/CD Pipeline

permissions:
contents: read
packages: read

on:
push:
branches: [ main, master ]
branches: [main]
pull_request:
branches: [ main, master ]
branches: [main]
workflow_dispatch:

env:
Expand All @@ -18,69 +17,103 @@ jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest pytest-asyncio ruff mypy
- name: Install project and validation tools
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

- name: Lint with ruff
run: ruff check src/
- name: Lint correctness rules
run: ruff check src/

- name: Compile sources
run: python -m compileall -q src
- name: Compile sources
run: python -m compileall -q src

- name: Import smoke
run: |
python -c "import mcp_server.main"
python -c "import mcp_server.api.routes"
python -c "import mcp_server.tools.mcp_tools"
- name: Import smoke
env:
MCP_SSH__HOST: ci.invalid
MCP_SSH__USER: ci-smoke
run: |
python -c "import mcp_server.main"
python -c "import mcp_server.api.routes"
python -c "import mcp_server.tools.mcp_tools"

- name: Run tests
run: pytest -q
- name: Run tests
run: pytest -q

build:
needs: verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: mcp-server:${{ github.sha }}
- name: Build exact commit image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: mcp-server:${{ github.sha }}

deploy:
needs: build
needs: [verify, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
environment: production
concurrency:
group: mcp-server-production
cancel-in-progress: false
steps:
- name: Deploy to server
if: ${{ env.SERVER_HOST != '' }}
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USER }}
key: ${{ env.SERVER_SSH_KEY }}
script: |
cd /opt/mcp-server
git pull origin main
docker compose pull
docker compose up -d --build
curl -sf http://localhost:8000/health || exit 1
- name: Require deployment secrets
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
run: |
test -n "$SERVER_HOST"
test -n "$SERVER_USER"
test -n "$SERVER_SSH_KEY"

- name: Deploy exact verified commit
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
EXPECTED_SHA: ${{ github.sha }}
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USER }}
key: ${{ env.SERVER_SSH_KEY }}
port: ${{ secrets.SERVER_PORT || 22 }}
envs: EXPECTED_SHA
script: |
set -euo pipefail
cd /opt/mcp-server

if [ -n "$(git status --porcelain)" ]; then
echo "Refusing deployment: production worktree is dirty"
git status --short
exit 1
fi

git fetch origin main
remote_sha="$(git rev-parse origin/main)"
if [ "$remote_sha" != "$EXPECTED_SHA" ]; then
echo "Deployment superseded by newer main: expected=$EXPECTED_SHA remote=$remote_sha"
exit 0
fi

git checkout main
git merge --ff-only origin/main
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"

bash scripts/deploy-production.sh "$EXPECTED_SHA"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ temp/
*.bak
*.backup
.runtime/ssh_targets.json

# Repository hygiene
/test_write.txt
/tmp.txt
/*_backup.zip
19 changes: 19 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Security Policy

## Supported version

The `main` branch is the supported source line for this repository.

## Reporting a vulnerability

Do not open a public issue containing vulnerability details, credentials, private keys, access tokens, server addresses, or reproduction data that exposes infrastructure.

Use GitHub's private vulnerability reporting / Security Advisory flow when it is available for this repository. If private reporting is unavailable, contact the repository owner privately through GitHub before publishing technical details.

## Credential handling

Real credentials and runtime secrets must never be committed. If a secret is committed or exposed in Actions logs, treat it as compromised: revoke or rotate it first, then remove the exposed material from the repository/history as a separate cleanup step.

## Deployment invariant

Production deployment must originate from an exact, verified `main` commit and must fail closed on dirty/diverged production worktrees or source-SHA mismatch.
Binary file removed mcp_server_backup.zip
Binary file not shown.
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ target-version = ["py310", "py311", "py312"]
line-length = 100
target-version = "py310"

[tool.ruff.lint]
# Keep CI semantics stable across Ruff releases. Correctness rules are blocking;
# broad modernization/style migrations are deliberate changes, not surprise CI drift.
select = ["E9", "F", "W605", "E722"]

[tool.ruff.lint.per-file-ignores]
# Legacy compatibility modules are not part of the lint modernization campaign.
# Do not expand these exceptions without an explicit security/design review.
"src/mcp_server/tools/single_router_tool.py" = ["F401"]
"src/mcp_server/tools/unified_whitelist_tools.py" = ["F401", "F541", "W605"]

[tool.mypy]
python_version = "3.10"
strict = true
Loading
Loading