pwpush-mcp 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pwpush_mcp-0.2.0/.env.example +45 -0
- pwpush_mcp-0.2.0/.github/dependabot.yml +26 -0
- pwpush_mcp-0.2.0/.github/workflows/ci.yml +43 -0
- pwpush_mcp-0.2.0/.github/workflows/pypi.yml +50 -0
- pwpush_mcp-0.2.0/.github/workflows/release.yml +91 -0
- pwpush_mcp-0.2.0/.gitignore +31 -0
- pwpush_mcp-0.2.0/.pre-commit-config.yaml +26 -0
- pwpush_mcp-0.2.0/CHANGELOG.md +55 -0
- pwpush_mcp-0.2.0/CONTRIBUTING.md +65 -0
- pwpush_mcp-0.2.0/Dockerfile +30 -0
- pwpush_mcp-0.2.0/LICENSE +21 -0
- pwpush_mcp-0.2.0/PKG-INFO +223 -0
- pwpush_mcp-0.2.0/README.md +190 -0
- pwpush_mcp-0.2.0/SUPPORT.md +37 -0
- pwpush_mcp-0.2.0/UPGRADING.md +35 -0
- pwpush_mcp-0.2.0/catalog/metadata.json +1 -0
- pwpush_mcp-0.2.0/catalog/readme.md +39 -0
- pwpush_mcp-0.2.0/catalog/server.yaml +61 -0
- pwpush_mcp-0.2.0/compose.yml +12 -0
- pwpush_mcp-0.2.0/pyproject.toml +87 -0
- pwpush_mcp-0.2.0/scripts/gen_metadata.py +93 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/__init__.py +5 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/__main__.py +97 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/audit.py +110 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/client.py +448 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/config.py +143 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/durations.py +123 -0
- pwpush_mcp-0.2.0/src/pwpush_mcp/server.py +383 -0
- pwpush_mcp-0.2.0/tests/test_audit.py +100 -0
- pwpush_mcp-0.2.0/tests/test_catalog_metadata.py +66 -0
- pwpush_mcp-0.2.0/tests/test_client.py +183 -0
- pwpush_mcp-0.2.0/tests/test_config.py +96 -0
- pwpush_mcp-0.2.0/tests/test_durations.py +59 -0
- pwpush_mcp-0.2.0/tests/test_main.py +72 -0
- pwpush_mcp-0.2.0/tests/test_packaging.py +66 -0
- pwpush_mcp-0.2.0/tests/test_reliability.py +110 -0
- pwpush_mcp-0.2.0/tests/test_server.py +139 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Password Pusher API token (generate at <base-url>/api_tokens).
|
|
2
|
+
# Whether it is required depends on the instance: some allow anonymous
|
|
3
|
+
# create/preview/expire. Listing and audit always require a token.
|
|
4
|
+
PWPUSH_API_TOKEN=
|
|
5
|
+
|
|
6
|
+
# Email tied to the API token. Required for authenticated calls on legacy
|
|
7
|
+
# (v1) instances, which use X-User-Email + X-User-Token headers.
|
|
8
|
+
PWPUSH_API_EMAIL=
|
|
9
|
+
|
|
10
|
+
# Base URL of the Password Pusher instance.
|
|
11
|
+
# Default: https://pwpush.com
|
|
12
|
+
# EU users: https://eu.pwpush.com
|
|
13
|
+
# Self-hosted: https://pwpush.example.com
|
|
14
|
+
PWPUSH_BASE_URL=https://pwpush.com
|
|
15
|
+
|
|
16
|
+
# API generation: auto (default), v1, or v2. 'auto' probes /api/v2/version
|
|
17
|
+
# and falls back to the legacy v1 endpoints when absent.
|
|
18
|
+
PWPUSH_API_VERSION=auto
|
|
19
|
+
|
|
20
|
+
# TLS verification. Set to false ONLY for internal instances with an untrusted
|
|
21
|
+
# certificate chain. Prefer PWPUSH_CA_BUNDLE with the instance's CA instead.
|
|
22
|
+
PWPUSH_VERIFY_SSL=true
|
|
23
|
+
# PWPUSH_CA_BUNDLE=/path/to/ca-bundle.pem
|
|
24
|
+
|
|
25
|
+
# --- Security / multi-tenant knobs ------------------------------------------
|
|
26
|
+
# Read-only mode: removes write tools (create_push, expire_push). Preview,
|
|
27
|
+
# list, audit and version remain available.
|
|
28
|
+
PWPUSH_READ_ONLY=false
|
|
29
|
+
|
|
30
|
+
# Tool allowlist (comma-separated fnmatch globs). Empty = all tools enabled.
|
|
31
|
+
# Example: list_*,get_version,preview_push
|
|
32
|
+
PWPUSH_ENABLED_TOOLS=
|
|
33
|
+
|
|
34
|
+
# Emit one JSON audit line per write-tool invocation on stderr.
|
|
35
|
+
PWPUSH_AUDIT_LOG=true
|
|
36
|
+
|
|
37
|
+
# --- Reliability knobs ------------------------------------------------------
|
|
38
|
+
# Cap concurrent in-flight HTTP requests. 0 = unlimited.
|
|
39
|
+
PWPUSH_MAX_CONCURRENT=0
|
|
40
|
+
|
|
41
|
+
# Retries for transient failures (connection errors, 429, 5xx) with backoff.
|
|
42
|
+
PWPUSH_MAX_RETRIES=2
|
|
43
|
+
|
|
44
|
+
# Per-request HTTP timeout, in seconds.
|
|
45
|
+
PWPUSH_TIMEOUT=30
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
# Keep GitHub Actions SHA pins up to date automatically. Dependabot updates
|
|
4
|
+
# both the SHA and the inline version comment.
|
|
5
|
+
- package-ecosystem: github-actions
|
|
6
|
+
directory: /
|
|
7
|
+
schedule:
|
|
8
|
+
interval: weekly
|
|
9
|
+
day: monday
|
|
10
|
+
commit-message:
|
|
11
|
+
prefix: "chore(deps)"
|
|
12
|
+
labels:
|
|
13
|
+
- "ci/cd"
|
|
14
|
+
- "security"
|
|
15
|
+
|
|
16
|
+
# Keep Python dependencies up to date within the declared bounds. Upper bounds
|
|
17
|
+
# on mcp and httpx prevent silent breaking upgrades (see tests/test_packaging.py).
|
|
18
|
+
- package-ecosystem: pip
|
|
19
|
+
directory: /
|
|
20
|
+
schedule:
|
|
21
|
+
interval: weekly
|
|
22
|
+
day: monday
|
|
23
|
+
commit-message:
|
|
24
|
+
prefix: "chore(deps)"
|
|
25
|
+
labels:
|
|
26
|
+
- "ci/cd"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ci-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint-test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
21
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
24
|
+
cache: pip
|
|
25
|
+
- run: pip install -e ".[dev]"
|
|
26
|
+
- run: ruff check src tests
|
|
27
|
+
- run: ruff format --check src tests
|
|
28
|
+
- run: mypy src
|
|
29
|
+
- name: pytest with coverage
|
|
30
|
+
run: pytest -q --cov=pwpush_mcp --cov-report=term --cov-report=xml --cov-fail-under=80
|
|
31
|
+
- name: catalog metadata is up to date
|
|
32
|
+
run: |
|
|
33
|
+
python scripts/gen_metadata.py >/dev/null
|
|
34
|
+
git diff --exit-code catalog/metadata.json \
|
|
35
|
+
|| { echo "catalog/metadata.json is stale — run scripts/gen_metadata.py and commit"; exit 1; }
|
|
36
|
+
|
|
37
|
+
docker:
|
|
38
|
+
needs: lint-test
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
42
|
+
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
|
43
|
+
- run: docker build -t pwpush-mcp:ci .
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI (manual fallback)
|
|
2
|
+
|
|
3
|
+
# Automatic PyPI publishing on vX.Y.Z tags is handled by release.yml
|
|
4
|
+
# (publish-pypi job). This workflow exists only as a manual fallback via
|
|
5
|
+
# workflow_dispatch in case that job needs to be re-run independently.
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write # required for PyPI trusted publishing
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
name: Build wheel + sdist
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
19
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- run: pip install --upgrade build
|
|
23
|
+
- run: python -m build
|
|
24
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
name: Publish to PyPI (trusted publisher)
|
|
31
|
+
needs: build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment:
|
|
34
|
+
name: pypi
|
|
35
|
+
url: https://pypi.org/p/pwpush-mcp
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
42
|
+
with:
|
|
43
|
+
# OIDC trusted publishing — no token needed once the pending publisher
|
|
44
|
+
# is configured on pypi.org (Account Settings -> Publishing):
|
|
45
|
+
# PyPI project name: pwpush-mcp
|
|
46
|
+
# Repository owner: k9fr4n
|
|
47
|
+
# Repository name: pwpush-mcp
|
|
48
|
+
# Workflow name: pypi.yml
|
|
49
|
+
# Environment: pypi
|
|
50
|
+
attestations: true
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write # create GitHub Releases
|
|
10
|
+
packages: write # push to GHCR
|
|
11
|
+
id-token: write # PyPI OIDC trusted publishing
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
image:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
18
|
+
|
|
19
|
+
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
|
20
|
+
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
|
21
|
+
|
|
22
|
+
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
|
23
|
+
with:
|
|
24
|
+
registry: ghcr.io
|
|
25
|
+
username: ${{ github.actor }}
|
|
26
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
|
|
28
|
+
- id: meta
|
|
29
|
+
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
|
30
|
+
with:
|
|
31
|
+
images: ghcr.io/${{ github.repository_owner }}/pwpush-mcp
|
|
32
|
+
tags: |
|
|
33
|
+
type=ref,event=branch
|
|
34
|
+
type=semver,pattern=v{{version}}
|
|
35
|
+
type=semver,pattern=v{{major}}.{{minor}}
|
|
36
|
+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
37
|
+
|
|
38
|
+
- name: Generate Docker MCP Gateway metadata label
|
|
39
|
+
run: |
|
|
40
|
+
pip install -e . -q
|
|
41
|
+
SERVER_METADATA=$(python scripts/gen_metadata.py)
|
|
42
|
+
echo "SERVER_METADATA=${SERVER_METADATA}" >> "$GITHUB_ENV"
|
|
43
|
+
|
|
44
|
+
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
|
45
|
+
with:
|
|
46
|
+
context: .
|
|
47
|
+
platforms: linux/amd64,linux/arm64
|
|
48
|
+
push: true
|
|
49
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
50
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
51
|
+
build-args: |
|
|
52
|
+
SERVER_METADATA=${{ env.SERVER_METADATA }}
|
|
53
|
+
provenance: true
|
|
54
|
+
sbom: true
|
|
55
|
+
|
|
56
|
+
# Create a GitHub Release only when a vX.Y.Z tag is pushed.
|
|
57
|
+
# --generate-notes builds the changelog from commits since the previous tag.
|
|
58
|
+
# `gh release create` is idempotent: it exits 0 if the release already exists.
|
|
59
|
+
- name: Create GitHub Release
|
|
60
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
61
|
+
env:
|
|
62
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
63
|
+
run: |
|
|
64
|
+
gh release create "${GITHUB_REF_NAME}" \
|
|
65
|
+
--title "${GITHUB_REF_NAME}" \
|
|
66
|
+
--generate-notes \
|
|
67
|
+
--latest \
|
|
68
|
+
|| echo "Release already exists, skipping."
|
|
69
|
+
|
|
70
|
+
# Publish to PyPI on every vX.Y.Z tag. Runs in the same workflow so the
|
|
71
|
+
# GITHUB_TOKEN cross-workflow event limitation is irrelevant.
|
|
72
|
+
publish-pypi:
|
|
73
|
+
name: Publish to PyPI
|
|
74
|
+
needs: image
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
77
|
+
environment:
|
|
78
|
+
name: pypi
|
|
79
|
+
url: https://pypi.org/p/pwpush-mcp
|
|
80
|
+
permissions:
|
|
81
|
+
id-token: write # OIDC trusted publishing
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
84
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
85
|
+
with:
|
|
86
|
+
python-version: "3.12"
|
|
87
|
+
- run: pip install --upgrade build
|
|
88
|
+
- run: python -m build
|
|
89
|
+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
90
|
+
with:
|
|
91
|
+
attestations: true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtualenvs
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# Tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
coverage.xml
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# Local planning notes
|
|
23
|
+
.claude/plans/
|
|
24
|
+
|
|
25
|
+
# Env / secrets
|
|
26
|
+
.env
|
|
27
|
+
.env.local
|
|
28
|
+
.mcp.json
|
|
29
|
+
|
|
30
|
+
# OS
|
|
31
|
+
.DS_Store
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-json
|
|
9
|
+
- id: check-added-large-files
|
|
10
|
+
- id: check-merge-conflict
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
13
|
+
rev: v0.8.0
|
|
14
|
+
hooks:
|
|
15
|
+
- id: ruff
|
|
16
|
+
args: [--fix]
|
|
17
|
+
- id: ruff-format
|
|
18
|
+
|
|
19
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
20
|
+
rev: v1.13.0
|
|
21
|
+
hooks:
|
|
22
|
+
- id: mypy
|
|
23
|
+
files: ^src/
|
|
24
|
+
additional_dependencies:
|
|
25
|
+
- httpx>=0.27
|
|
26
|
+
- mcp[cli]>=1.2.0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.0] - 2026-06-05
|
|
10
|
+
|
|
11
|
+
First packaged, distributable release. The server moves to the low-level MCP
|
|
12
|
+
SDK, gains an HTTP transport, security/multi-tenant knobs, and a full
|
|
13
|
+
distribution + governance setup inspired by
|
|
14
|
+
[thruk-mcp](https://github.com/k9fr4n/thruk-mcp).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **HTTP transport**: `pwpush-mcp --listen PORT` exposes the server over
|
|
19
|
+
Streamable-HTTP/SSE (Starlette + uvicorn). `--host` / `--log-level` flags.
|
|
20
|
+
stdio remains the default.
|
|
21
|
+
- **Docker image**: multi-stage `Dockerfile` (non-root user, wheel build) and
|
|
22
|
+
`compose.yml`. Published to `ghcr.io/k9fr4n/pwpush-mcp` on each `vX.Y.Z` tag.
|
|
23
|
+
- **Docker MCP Gateway**: `catalog/server.yaml` + generated `catalog/metadata.json`
|
|
24
|
+
and the `io.docker.server.metadata` OCI label so the gateway forwards tool
|
|
25
|
+
arguments correctly. `scripts/gen_metadata.py` regenerates the catalog.
|
|
26
|
+
- **Release automation**: `release.yml` builds multi-arch (amd64/arm64) images
|
|
27
|
+
with SBOM + provenance, creates the GitHub Release, and publishes to PyPI via
|
|
28
|
+
OIDC trusted publishing. `pypi.yml` is a manual fallback. `dependabot.yml`
|
|
29
|
+
keeps Actions (SHA-pinned) and Python deps current.
|
|
30
|
+
- **Security / multi-tenant knobs**:
|
|
31
|
+
- `PWPUSH_READ_ONLY=true` removes write tools (`create_push`, `expire_push`).
|
|
32
|
+
- `PWPUSH_ENABLED_TOOLS=list_*,get_version` restricts the exposed tool surface
|
|
33
|
+
via fnmatch globs.
|
|
34
|
+
- `PWPUSH_AUDIT_LOG=true` (default) emits one JSON line per write-tool
|
|
35
|
+
invocation on the `pwpush_mcp.audit` logger; secrets are redacted.
|
|
36
|
+
- **Reliability knobs**: `PWPUSH_MAX_CONCURRENT` (concurrency cap),
|
|
37
|
+
`PWPUSH_MAX_RETRIES` (transport retries + 429/5xx backoff honouring
|
|
38
|
+
`Retry-After`), `PWPUSH_TIMEOUT`.
|
|
39
|
+
- **Governance**: `CONTRIBUTING.md`, `SUPPORT.md`, `UPGRADING.md`,
|
|
40
|
+
`.pre-commit-config.yaml`, this `CHANGELOG.md`.
|
|
41
|
+
- New tests for config knobs, tool filtering, audit logging, retries/concurrency,
|
|
42
|
+
catalog drift, and dependency bounds.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **Server rewritten on the low-level MCP SDK** (`mcp.server.Server`) instead of
|
|
47
|
+
FastMCP, with explicit `inputSchema` per tool. Dependency `fastmcp` replaced by
|
|
48
|
+
`mcp[cli]` (+ `uvicorn`, `starlette`).
|
|
49
|
+
- `Config.__repr__` / `__str__` now redact the API token unconditionally.
|
|
50
|
+
- CI gained `ruff format --check`, `mypy src`, a 80% coverage gate, a catalog
|
|
51
|
+
drift check, and a Docker build job; Actions are pinned to commit SHAs.
|
|
52
|
+
- Dependency upper bounds added for `mcp` (`<2.0`) and `httpx` (`<1.0`).
|
|
53
|
+
|
|
54
|
+
[Unreleased]: https://github.com/k9fr4n/pwpush-mcp/compare/v0.2.0...HEAD
|
|
55
|
+
[0.2.0]: https://github.com/k9fr4n/pwpush-mcp/releases/tag/v0.2.0
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Contributing to pwpush-mcp
|
|
2
|
+
|
|
3
|
+
Thanks for your interest! This is a small, focused MCP server — contributions
|
|
4
|
+
that keep it simple and secure are very welcome.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
python -m venv .venv && source .venv/bin/activate
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
pre-commit install # optional but recommended
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Before opening a PR
|
|
15
|
+
|
|
16
|
+
Run the same checks CI runs:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
ruff check src tests
|
|
20
|
+
ruff format --check src tests
|
|
21
|
+
mypy src
|
|
22
|
+
pytest -q --cov=pwpush_mcp --cov-fail-under=80
|
|
23
|
+
python scripts/gen_metadata.py >/dev/null # if you touched the tool surface
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`pre-commit run --all-files` runs the lint/format/type subset automatically.
|
|
27
|
+
|
|
28
|
+
## Design invariants (please preserve)
|
|
29
|
+
|
|
30
|
+
- **Never retrieve a push payload.** Retrieving consumes a view irreversibly; it
|
|
31
|
+
must stay a human action via the secret URL. There is intentionally no
|
|
32
|
+
`retrieve` tool.
|
|
33
|
+
- **The API token is never a tool argument.** It comes only from
|
|
34
|
+
`PWPUSH_API_TOKEN` so the model can neither supply nor log it.
|
|
35
|
+
- **Secrets never reach logs.** `payload` / `passphrase` / file contents are
|
|
36
|
+
stripped from returned objects (`client._public`) and from audit lines
|
|
37
|
+
(`audit._redact` / `audit.scrub`). `Config.__repr__` redacts the token.
|
|
38
|
+
|
|
39
|
+
## Adding a tool
|
|
40
|
+
|
|
41
|
+
1. Add an async handler and a `ToolSpec` to `TOOL_REGISTRY` in
|
|
42
|
+
`src/pwpush_mcp/server.py` (set `is_write` / `destructive` correctly).
|
|
43
|
+
2. Regenerate the catalog: `python scripts/gen_metadata.py >/dev/null` and commit
|
|
44
|
+
`catalog/metadata.json` (CI fails on drift).
|
|
45
|
+
3. Add tests under `tests/`.
|
|
46
|
+
|
|
47
|
+
## Adding an env var
|
|
48
|
+
|
|
49
|
+
1. Add the field + parsing in `src/pwpush_mcp/config.py`.
|
|
50
|
+
2. Document it in `.env.example` and the README configuration table.
|
|
51
|
+
3. If it is operator-facing, add it to `catalog/server.yaml`.
|
|
52
|
+
|
|
53
|
+
## Commit / PR conventions
|
|
54
|
+
|
|
55
|
+
- Conventional-commit style prefixes (`feat:`, `fix:`, `chore:`, `docs:`, `ci:`).
|
|
56
|
+
- One logical change per PR; update `CHANGELOG.md` under `[Unreleased]`.
|
|
57
|
+
|
|
58
|
+
## Releasing (maintainers)
|
|
59
|
+
|
|
60
|
+
1. Move `[Unreleased]` notes to a new `[X.Y.Z]` section in `CHANGELOG.md`.
|
|
61
|
+
2. Bump `version` in `pyproject.toml` and `__version__` in
|
|
62
|
+
`src/pwpush_mcp/__init__.py`.
|
|
63
|
+
3. Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`.
|
|
64
|
+
4. `release.yml` builds/pushes the multi-arch image, creates the GitHub Release,
|
|
65
|
+
and publishes to PyPI (OIDC). No manual token handling.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
FROM python:3.12-slim AS build
|
|
3
|
+
WORKDIR /src
|
|
4
|
+
RUN pip install --no-cache-dir build
|
|
5
|
+
COPY pyproject.toml README.md ./
|
|
6
|
+
COPY src ./src
|
|
7
|
+
RUN python -m build --wheel --outdir /dist
|
|
8
|
+
|
|
9
|
+
FROM python:3.12-slim
|
|
10
|
+
RUN useradd -r -u 1001 -m pwpush
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
COPY --from=build /dist/*.whl /tmp/
|
|
13
|
+
RUN pip install --no-cache-dir /tmp/*.whl && rm -f /tmp/*.whl
|
|
14
|
+
|
|
15
|
+
# Embed the Docker MCP Gateway catalog metadata so that self-configured
|
|
16
|
+
# deployments (docker:// references) expose correct tool argument schemas.
|
|
17
|
+
# Without this label the gateway uses empty schemas and strips all arguments
|
|
18
|
+
# before forwarding tool calls, causing "Field required" errors at runtime.
|
|
19
|
+
# The value is produced by scripts/gen_metadata.py and injected at build time:
|
|
20
|
+
# --build-arg SERVER_METADATA="$(python scripts/gen_metadata.py)"
|
|
21
|
+
ARG SERVER_METADATA="{}"
|
|
22
|
+
LABEL io.docker.server.metadata="$SERVER_METADATA"
|
|
23
|
+
|
|
24
|
+
USER pwpush
|
|
25
|
+
ENV PYTHONUNBUFFERED=1
|
|
26
|
+
# Default = stdio transport (Docker MCP Gateway / Claude Desktop / Claude Code).
|
|
27
|
+
# For HTTP/Streamable-HTTP, override CMD: ["--listen", "8000"]
|
|
28
|
+
EXPOSE 8000
|
|
29
|
+
ENTRYPOINT ["pwpush-mcp"]
|
|
30
|
+
CMD []
|
pwpush_mcp-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pwpush-mcp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|