mcp-pcloud-crunchtools 2.1.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.
- mcp_pcloud_crunchtools-2.1.0/.containerignore +9 -0
- mcp_pcloud_crunchtools-2.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
- mcp_pcloud_crunchtools-2.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- mcp_pcloud_crunchtools-2.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +50 -0
- mcp_pcloud_crunchtools-2.1.0/.github/workflows/ci.yml +62 -0
- mcp_pcloud_crunchtools-2.1.0/.github/workflows/container.yml +101 -0
- mcp_pcloud_crunchtools-2.1.0/.github/workflows/publish.yml +24 -0
- mcp_pcloud_crunchtools-2.1.0/.github/workflows/security.yml +94 -0
- mcp_pcloud_crunchtools-2.1.0/.gitignore +13 -0
- mcp_pcloud_crunchtools-2.1.0/.pre-commit-config.yaml +9 -0
- mcp_pcloud_crunchtools-2.1.0/.specify/memory/constitution.md +200 -0
- mcp_pcloud_crunchtools-2.1.0/.specify/specs/000-baseline/spec.md +76 -0
- mcp_pcloud_crunchtools-2.1.0/.specify/templates/plan-template.md +117 -0
- mcp_pcloud_crunchtools-2.1.0/.specify/templates/spec-template.md +91 -0
- mcp_pcloud_crunchtools-2.1.0/CLAUDE.md +56 -0
- mcp_pcloud_crunchtools-2.1.0/Containerfile +21 -0
- mcp_pcloud_crunchtools-2.1.0/LICENSE +661 -0
- mcp_pcloud_crunchtools-2.1.0/PKG-INFO +107 -0
- mcp_pcloud_crunchtools-2.1.0/README.md +87 -0
- mcp_pcloud_crunchtools-2.1.0/gourmand-exceptions.toml +4 -0
- mcp_pcloud_crunchtools-2.1.0/gourmand.toml +22 -0
- mcp_pcloud_crunchtools-2.1.0/pyproject.toml +87 -0
- mcp_pcloud_crunchtools-2.1.0/server.json +31 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/__init__.py +58 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/__main__.py +6 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/client.py +183 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/config.py +170 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/errors.py +101 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/models.py +129 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/server.py +179 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/__init__.py +42 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/account.py +29 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/files.py +62 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/folders.py +52 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/formatting.py +34 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/links.py +34 -0
- mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/search.py +15 -0
- mcp_pcloud_crunchtools-2.1.0/tests/__init__.py +0 -0
- mcp_pcloud_crunchtools-2.1.0/tests/conftest.py +25 -0
- mcp_pcloud_crunchtools-2.1.0/tests/helpers.py +33 -0
- mcp_pcloud_crunchtools-2.1.0/tests/test_config.py +115 -0
- mcp_pcloud_crunchtools-2.1.0/tests/test_models.py +68 -0
- mcp_pcloud_crunchtools-2.1.0/tests/test_server.py +44 -0
- mcp_pcloud_crunchtools-2.1.0/tests/test_tools.py +267 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Bug Report
|
|
2
|
+
description: Report a bug or unexpected behavior
|
|
3
|
+
title: "[Bug]: "
|
|
4
|
+
labels: ["bug", "triage"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: description
|
|
8
|
+
attributes:
|
|
9
|
+
label: Bug Description
|
|
10
|
+
description: A clear description of the bug
|
|
11
|
+
placeholder: What happened?
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: steps
|
|
17
|
+
attributes:
|
|
18
|
+
label: Steps to Reproduce
|
|
19
|
+
description: How can we reproduce this bug?
|
|
20
|
+
placeholder: |
|
|
21
|
+
1. Call tool '...' with args '...'
|
|
22
|
+
2. Observe error '...'
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
|
|
26
|
+
- type: textarea
|
|
27
|
+
id: expected
|
|
28
|
+
attributes:
|
|
29
|
+
label: Expected Behavior
|
|
30
|
+
description: What did you expect to happen?
|
|
31
|
+
validations:
|
|
32
|
+
required: true
|
|
33
|
+
|
|
34
|
+
- type: textarea
|
|
35
|
+
id: actual
|
|
36
|
+
attributes:
|
|
37
|
+
label: Actual Behavior
|
|
38
|
+
description: What actually happened? Include error messages if available.
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
|
|
42
|
+
- type: dropdown
|
|
43
|
+
id: transport
|
|
44
|
+
attributes:
|
|
45
|
+
label: Transport
|
|
46
|
+
description: How are you running the MCP server?
|
|
47
|
+
options:
|
|
48
|
+
- stdio (uvx / pip)
|
|
49
|
+
- streamable-http (container / systemd)
|
|
50
|
+
- SSE
|
|
51
|
+
validations:
|
|
52
|
+
required: true
|
|
53
|
+
|
|
54
|
+
- type: input
|
|
55
|
+
id: version
|
|
56
|
+
attributes:
|
|
57
|
+
label: Server Version
|
|
58
|
+
description: Output of `mcp-pcloud-crunchtools --version` or pip show
|
|
59
|
+
placeholder: "0.1.0"
|
|
60
|
+
validations:
|
|
61
|
+
required: true
|
|
62
|
+
|
|
63
|
+
- type: textarea
|
|
64
|
+
id: additional
|
|
65
|
+
attributes:
|
|
66
|
+
label: Additional Context
|
|
67
|
+
description: Any other relevant information (logs, config, environment)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Feature Request
|
|
2
|
+
description: Suggest a new tool or enhancement
|
|
3
|
+
title: "[Feature]: "
|
|
4
|
+
labels: ["enhancement", "triage"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: problem
|
|
8
|
+
attributes:
|
|
9
|
+
label: Problem Statement
|
|
10
|
+
description: What problem does this feature solve?
|
|
11
|
+
placeholder: I need to...
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: solution
|
|
17
|
+
attributes:
|
|
18
|
+
label: Proposed Solution
|
|
19
|
+
description: Describe the tool or change you'd like
|
|
20
|
+
placeholder: |
|
|
21
|
+
Tool name: `pcloud_something`
|
|
22
|
+
Parameters: ...
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
|
|
26
|
+
- type: textarea
|
|
27
|
+
id: alternatives
|
|
28
|
+
attributes:
|
|
29
|
+
label: Alternatives Considered
|
|
30
|
+
description: What alternatives have you considered?
|
|
31
|
+
|
|
32
|
+
- type: dropdown
|
|
33
|
+
id: scope
|
|
34
|
+
attributes:
|
|
35
|
+
label: Tool Category
|
|
36
|
+
description: Which area does this feature belong to?
|
|
37
|
+
options:
|
|
38
|
+
- Search
|
|
39
|
+
- Index Management
|
|
40
|
+
- Status
|
|
41
|
+
- File Extraction
|
|
42
|
+
- New Category
|
|
43
|
+
validations:
|
|
44
|
+
required: true
|
|
45
|
+
|
|
46
|
+
- type: textarea
|
|
47
|
+
id: additional
|
|
48
|
+
attributes:
|
|
49
|
+
label: Additional Context
|
|
50
|
+
description: Any other relevant information or examples
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v4
|
|
20
|
+
with:
|
|
21
|
+
version: "latest"
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
run: uv python install ${{ matrix.python-version }}
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync --all-extras
|
|
26
|
+
- name: Run linter
|
|
27
|
+
run: uv run ruff check src tests
|
|
28
|
+
- name: Run type checker
|
|
29
|
+
run: uv run mypy src
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: uv run pytest -v
|
|
32
|
+
|
|
33
|
+
gourmand:
|
|
34
|
+
name: Code Quality (Gourmand)
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
container:
|
|
37
|
+
image: quay.io/crunchtools/gourmand:latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- name: Run Gourmand
|
|
41
|
+
run: gourmand --full
|
|
42
|
+
|
|
43
|
+
build-container:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- name: Build container image
|
|
48
|
+
run: docker build -f Containerfile -t mcp-pcloud:test .
|
|
49
|
+
- name: Verify container
|
|
50
|
+
run: timeout 5 docker run --rm mcp-pcloud:test || true
|
|
51
|
+
|
|
52
|
+
validate-constitution:
|
|
53
|
+
name: Constitution Validation
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
with:
|
|
59
|
+
repository: crunchtools/constitution
|
|
60
|
+
path: .constitution
|
|
61
|
+
- name: Validate constitution
|
|
62
|
+
run: python3 .constitution/validate-constitution.py .specify/memory/constitution.md --verbose
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
name: Container Build & Push
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
QUAY_IMAGE: quay.io/crunchtools/mcp-pcloud
|
|
13
|
+
GHCR_IMAGE: ghcr.io/crunchtools/mcp-pcloud
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build-and-push-quay:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout repository
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
- name: Set up Docker Buildx
|
|
24
|
+
uses: docker/setup-buildx-action@v3
|
|
25
|
+
- name: Log in to Quay.io
|
|
26
|
+
if: github.event_name != 'pull_request'
|
|
27
|
+
uses: docker/login-action@v3
|
|
28
|
+
with:
|
|
29
|
+
registry: quay.io
|
|
30
|
+
username: ${{ secrets.QUAY_USERNAME }}
|
|
31
|
+
password: ${{ secrets.QUAY_PASSWORD }}
|
|
32
|
+
- name: Extract metadata
|
|
33
|
+
id: meta
|
|
34
|
+
uses: docker/metadata-action@v5
|
|
35
|
+
with:
|
|
36
|
+
images: ${{ env.QUAY_IMAGE }}
|
|
37
|
+
tags: |
|
|
38
|
+
type=ref,event=branch
|
|
39
|
+
type=ref,event=pr
|
|
40
|
+
type=semver,pattern={{version}}
|
|
41
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
42
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
43
|
+
- name: Build and push
|
|
44
|
+
uses: docker/build-push-action@v6
|
|
45
|
+
with:
|
|
46
|
+
context: .
|
|
47
|
+
file: ./Containerfile
|
|
48
|
+
push: ${{ github.event_name != 'pull_request' }}
|
|
49
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
50
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
51
|
+
cache-from: type=gha
|
|
52
|
+
cache-to: type=gha,mode=max
|
|
53
|
+
- name: Run Trivy vulnerability scanner
|
|
54
|
+
continue-on-error: true
|
|
55
|
+
if: github.event_name != 'pull_request'
|
|
56
|
+
uses: aquasecurity/trivy-action@0.35.0
|
|
57
|
+
with:
|
|
58
|
+
image-ref: ${{ env.QUAY_IMAGE }}:latest
|
|
59
|
+
format: "table"
|
|
60
|
+
exit-code: "0"
|
|
61
|
+
severity: "CRITICAL,HIGH"
|
|
62
|
+
version: "v0.68.2"
|
|
63
|
+
|
|
64
|
+
build-and-push-ghcr:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
needs: build-and-push-quay
|
|
67
|
+
if: github.event_name != 'pull_request'
|
|
68
|
+
permissions:
|
|
69
|
+
contents: read
|
|
70
|
+
packages: write
|
|
71
|
+
steps:
|
|
72
|
+
- name: Checkout repository
|
|
73
|
+
uses: actions/checkout@v4
|
|
74
|
+
- name: Set up Docker Buildx
|
|
75
|
+
uses: docker/setup-buildx-action@v3
|
|
76
|
+
- name: Log in to GitHub Container Registry
|
|
77
|
+
uses: docker/login-action@v3
|
|
78
|
+
with:
|
|
79
|
+
registry: ghcr.io
|
|
80
|
+
username: ${{ github.repository_owner }}
|
|
81
|
+
password: ${{ github.token }}
|
|
82
|
+
- name: Extract metadata
|
|
83
|
+
id: meta
|
|
84
|
+
uses: docker/metadata-action@v5
|
|
85
|
+
with:
|
|
86
|
+
images: ${{ env.GHCR_IMAGE }}
|
|
87
|
+
tags: |
|
|
88
|
+
type=ref,event=branch
|
|
89
|
+
type=ref,event=pr
|
|
90
|
+
type=semver,pattern={{version}}
|
|
91
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
92
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
93
|
+
- name: Build and push
|
|
94
|
+
uses: docker/build-push-action@v6
|
|
95
|
+
with:
|
|
96
|
+
context: .
|
|
97
|
+
file: ./Containerfile
|
|
98
|
+
push: true
|
|
99
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
100
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
101
|
+
cache-from: type=gha
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
with:
|
|
18
|
+
version: "latest"
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
run: uv python install 3.12
|
|
21
|
+
- name: Build package
|
|
22
|
+
run: uv build
|
|
23
|
+
- name: Publish to PyPI
|
|
24
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: Security Scan
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: "0 9 * * 1"
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
issues: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
security-events: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
dependency-audit:
|
|
20
|
+
name: Dependency CVE Scan
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v4
|
|
26
|
+
with:
|
|
27
|
+
version: "latest"
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
run: uv python install 3.12
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv sync
|
|
32
|
+
- name: Install pip-audit
|
|
33
|
+
run: uv pip install pip-audit
|
|
34
|
+
- name: Run pip-audit
|
|
35
|
+
id: audit
|
|
36
|
+
continue-on-error: true
|
|
37
|
+
run: |
|
|
38
|
+
uv run pip-audit --format=json --output=audit-results.json || true
|
|
39
|
+
uv run pip-audit --format=markdown --output=audit-results.md || true
|
|
40
|
+
if [ -f audit-results.json ]; then
|
|
41
|
+
VULN_COUNT=$(cat audit-results.json | python -c "import sys,json; data=json.load(sys.stdin); print(len([d for d in data if d.get('vulns', [])]))" 2>/dev/null || echo "0")
|
|
42
|
+
echo "vuln_count=$VULN_COUNT" >> $GITHUB_OUTPUT
|
|
43
|
+
else
|
|
44
|
+
echo "vuln_count=0" >> $GITHUB_OUTPUT
|
|
45
|
+
fi
|
|
46
|
+
- name: Upload audit results
|
|
47
|
+
if: always()
|
|
48
|
+
uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: security-audit-results
|
|
51
|
+
path: |
|
|
52
|
+
audit-results.json
|
|
53
|
+
audit-results.md
|
|
54
|
+
retention-days: 30
|
|
55
|
+
- name: Fail on vulnerabilities (PRs only)
|
|
56
|
+
if: steps.audit.outputs.vuln_count != '0' && github.event_name == 'pull_request'
|
|
57
|
+
run: |
|
|
58
|
+
echo "::error::Security vulnerabilities found in dependencies."
|
|
59
|
+
exit 1
|
|
60
|
+
|
|
61
|
+
container-scan:
|
|
62
|
+
name: Container Security Scan
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- name: Build container image
|
|
67
|
+
run: docker build -f Containerfile -t mcp-pcloud:scan .
|
|
68
|
+
- name: Run Trivy vulnerability scanner
|
|
69
|
+
uses: aquasecurity/trivy-action@master
|
|
70
|
+
with:
|
|
71
|
+
image-ref: "mcp-pcloud:scan"
|
|
72
|
+
format: "sarif"
|
|
73
|
+
output: "trivy-results.sarif"
|
|
74
|
+
severity: "CRITICAL,HIGH"
|
|
75
|
+
- name: Upload Trivy scan results
|
|
76
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
77
|
+
if: always()
|
|
78
|
+
with:
|
|
79
|
+
sarif_file: "trivy-results.sarif"
|
|
80
|
+
|
|
81
|
+
codeql:
|
|
82
|
+
name: CodeQL Analysis
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
- name: Initialize CodeQL
|
|
87
|
+
uses: github/codeql-action/init@v3
|
|
88
|
+
with:
|
|
89
|
+
languages: python
|
|
90
|
+
queries: security-extended
|
|
91
|
+
- name: Perform CodeQL Analysis
|
|
92
|
+
uses: github/codeql-action/analyze@v3
|
|
93
|
+
with:
|
|
94
|
+
category: "/language:python"
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# mcp-pcloud-crunchtools Constitution
|
|
2
|
+
|
|
3
|
+
> **Version:** 1.1.0
|
|
4
|
+
> **Ratified:** 2026-09-05
|
|
5
|
+
> **Last Amended:** 2026-09-05
|
|
6
|
+
> **Status:** Active
|
|
7
|
+
> **Inherits:** [crunchtools/constitution](https://github.com/crunchtools/constitution) v1.10.0
|
|
8
|
+
> **Profile:** MCP Server
|
|
9
|
+
|
|
10
|
+
This constitution establishes the core principles, constraints, and workflows that govern all development on mcp-pcloud-crunchtools.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## I. Core Principles
|
|
15
|
+
|
|
16
|
+
### 1. Five-Layer Security Model
|
|
17
|
+
|
|
18
|
+
Every change MUST preserve all five security layers. No exceptions.
|
|
19
|
+
|
|
20
|
+
**Layer 1 — Credential Protection:**
|
|
21
|
+
- The pCloud OAuth access token is held as a Pydantic `SecretStr` and is never logged.
|
|
22
|
+
- `PCLOUD_ACCESS_TOKEN_FILE` is supported and takes precedence over `PCLOUD_ACCESS_TOKEN`. File contents are stripped on read; a token file readable beyond its owner raises a warning, never a failure.
|
|
23
|
+
- `Config.__repr__` and `Config.__str__` render the token as `***`.
|
|
24
|
+
- `errors._scrub` removes any configured credential value from every outgoing error message.
|
|
25
|
+
|
|
26
|
+
**Layer 2 — Input Validation:**
|
|
27
|
+
- Every tool validates input through a Pydantic v2 model with `extra="forbid"`.
|
|
28
|
+
- Paths MUST be absolute, free of NUL bytes, length-bounded, and free of `..` traversal segments. Traversal rejection is what keeps a caller from escaping a folder they were scoped to by a gateway tool allowlist.
|
|
29
|
+
- Search queries are length-bounded and non-empty.
|
|
30
|
+
|
|
31
|
+
**Layer 3 — API Hardening:**
|
|
32
|
+
- Credentials MUST NOT appear in a URL. An OAuth access token is sent in an `Authorization: Bearer` header; a pCloud session token is sent in a POST body. Password-derived tokens travelled in the query string; that authentication mode was removed in 2.0.0 and MUST NOT return.
|
|
33
|
+
- TLS certificate validation is left at httpx defaults and MUST NOT be made configurable off.
|
|
34
|
+
- Requests carry a 30 second timeout; responses above 10 MB are rejected.
|
|
35
|
+
- pCloud result codes are mapped onto the safe error hierarchy rather than surfaced raw.
|
|
36
|
+
|
|
37
|
+
**Layer 4 — Dangerous Operation Prevention:**
|
|
38
|
+
- No shell execution, no code evaluation, no `eval()`/`exec()`, no local filesystem access beyond reading the credential file.
|
|
39
|
+
- Tools are pure pCloud API wrappers.
|
|
40
|
+
|
|
41
|
+
**Layer 5 — Supply Chain Security:**
|
|
42
|
+
- Weekly automated CVE scanning via GitHub Actions.
|
|
43
|
+
- Hummingbird FIPS container base images, distroless runtime.
|
|
44
|
+
- Gourmand AI slop detection gates every PR at zero violations.
|
|
45
|
+
|
|
46
|
+
### 2. Two-Layer Tool Architecture
|
|
47
|
+
|
|
48
|
+
- `server.py` holds `@mcp.tool()` wrappers that validate arguments and delegate.
|
|
49
|
+
- `tools/*.py` holds pure async functions that call `client.py`.
|
|
50
|
+
|
|
51
|
+
Business logic MUST NOT live in `server.py`. MCP registration MUST NOT live in `tools/*.py`.
|
|
52
|
+
|
|
53
|
+
### 3. Authentication Is Token-Based, OAuth Preferred
|
|
54
|
+
|
|
55
|
+
pCloud accounts with two-factor authentication enabled cannot complete the legacy digest login, and that flow placed the resulting token in the URL query string. This server never derives a credential from a password and never places one in a URL.
|
|
56
|
+
|
|
57
|
+
Two credential kinds are accepted:
|
|
58
|
+
|
|
59
|
+
| Kind | Variable | Transport |
|
|
60
|
+
|------|----------|-----------|
|
|
61
|
+
| OAuth access token | `PCLOUD_ACCESS_TOKEN` | `Authorization: Bearer` header, GET |
|
|
62
|
+
| pCloud session token | `PCLOUD_AUTH_TOKEN` | `auth` field in a POST body |
|
|
63
|
+
|
|
64
|
+
Both honor the `_FILE` convention. When both are configured the OAuth token wins; the session token is a fallback for accounts that have no OAuth application provisioned.
|
|
65
|
+
|
|
66
|
+
A session token is accepted because pCloud issues it to its own desktop client and rejects it as an `access_token` (`result 2094`), so it cannot be exchanged for an OAuth token. It carries the same authority as an OAuth token and MUST be protected identically. The security property the constitution actually defends is that a credential never enters a URL, where it would leak into access logs, proxies, and referrers; a POST body preserves that property.
|
|
67
|
+
|
|
68
|
+
Reintroducing username/password authentication, or moving any credential into a URL or query string, requires a constitutional amendment.
|
|
69
|
+
|
|
70
|
+
### 4. Three Distribution Channels
|
|
71
|
+
|
|
72
|
+
Every release ships through uvx, pip (PyPI), and container (Quay.io + GHCR) simultaneously.
|
|
73
|
+
|
|
74
|
+
### 5. Three Transport Modes
|
|
75
|
+
|
|
76
|
+
stdio (default), SSE, and streamable-http MUST all remain supported.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## II. Technology Stack
|
|
81
|
+
|
|
82
|
+
| Layer | Technology |
|
|
83
|
+
|-------|------------|
|
|
84
|
+
| Language | Python 3.11+ |
|
|
85
|
+
| MCP Framework | FastMCP (`fastmcp>=2.0,<3.0`) |
|
|
86
|
+
| HTTP Client | httpx |
|
|
87
|
+
| Validation | Pydantic v2 |
|
|
88
|
+
| Container Base | Hummingbird FIPS |
|
|
89
|
+
| Package Manager | uv |
|
|
90
|
+
| Build System | hatchling |
|
|
91
|
+
| Linter | ruff |
|
|
92
|
+
| Type Checker | mypy (strict) |
|
|
93
|
+
| Tests | pytest + pytest-asyncio |
|
|
94
|
+
| Slop Detector | gourmand |
|
|
95
|
+
|
|
96
|
+
`fastmcp` is pinned below 3.0 deliberately: fastmcp 3.x pulls `mcp>=2.0`, which renames `FastMCP` to `MCPServer` and changes the client stream contract. The lotor backend fleet runs the v1 protocol.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## III. Container Conventions
|
|
101
|
+
|
|
102
|
+
- Build file is `Containerfile`.
|
|
103
|
+
- Multi-stage venv build; builder and runtime MUST both be Hummingbird FIPS images.
|
|
104
|
+
- Runtime is distroless — no shell-form `RUN` in the runtime stage.
|
|
105
|
+
- Required labels: `name`, `version`, `summary`, `maintainer`, plus `org.opencontainers.image.source`, `.description`, and `.licenses`.
|
|
106
|
+
- `EXPOSE 8028` — the assigned HTTP port for this server.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## IV. Testing Standards
|
|
111
|
+
|
|
112
|
+
- Every tool has a mocked test; no live pCloud calls in CI and no token required.
|
|
113
|
+
- `httpx.AsyncClient.get` is patched; tool functions are called directly rather than through the MCP wrapper.
|
|
114
|
+
- An autouse fixture resets the `config` and `client` singletons between every test.
|
|
115
|
+
- `test_tool_count` MUST be updated whenever tools are added or removed.
|
|
116
|
+
- Error-code mapping, token scrubbing, and identifier truncation MUST each retain explicit tests.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## V. Code Quality Gates
|
|
121
|
+
|
|
122
|
+
| Gate | Command |
|
|
123
|
+
|------|---------|
|
|
124
|
+
| Lint | `uv run ruff check src tests` |
|
|
125
|
+
| Type Check | `uv run mypy src` |
|
|
126
|
+
| Tests | `uv run pytest -v` |
|
|
127
|
+
| Gourmand | `gourmand --full` (zero violations) |
|
|
128
|
+
| Code Review | Gatehouse AI code review on every PR |
|
|
129
|
+
| Container Build | `podman build -f Containerfile .` |
|
|
130
|
+
|
|
131
|
+
### Gourmand Exception Policy
|
|
132
|
+
|
|
133
|
+
Exceptions MUST carry a documented justification in `gourmand-exceptions.toml`. Acceptable reasons are standard API patterns, test-specific patterns, and framework requirements. Unacceptable reasons are "the code is special", "the threshold is too strict", and rewording to evade detection. This repository currently declares no exceptions and MUST stay at zero violations without them.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## VI. Naming
|
|
138
|
+
|
|
139
|
+
| Context | Value |
|
|
140
|
+
|---------|-------|
|
|
141
|
+
| GitHub repo | `crunchtools/mcp-pcloud` |
|
|
142
|
+
| PyPI package | `mcp-pcloud-crunchtools` |
|
|
143
|
+
| Python module | `mcp_pcloud_crunchtools` |
|
|
144
|
+
| Container | `quay.io/crunchtools/mcp-pcloud`, `ghcr.io/crunchtools/mcp-pcloud` |
|
|
145
|
+
| MCP Registry | `io.github.crunchtools/pcloud` |
|
|
146
|
+
| systemd service | `mcp-pcloud.crunchtools.com.service` |
|
|
147
|
+
| License | AGPL-3.0-or-later |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## VII. Semantic Versioning
|
|
152
|
+
|
|
153
|
+
Releases follow Semantic Versioning 2.0.0. MAJOR for incompatible changes to tool signatures or authentication, MINOR for backwards-compatible tools or capabilities, PATCH for backwards-compatible fixes. Version 2.0.0 records the removal of username/password authentication and the port from TypeScript to Python.
|
|
154
|
+
|
|
155
|
+
The version MUST be identical in `pyproject.toml`, `__init__.py`, `server.py`, `server.json`, and the `Containerfile` label.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## VIII. Development Workflow
|
|
160
|
+
|
|
161
|
+
### Adding a Tool
|
|
162
|
+
|
|
163
|
+
1. Add the async function to the appropriate `tools/*.py`.
|
|
164
|
+
2. Export it from `tools/__init__.py`.
|
|
165
|
+
3. Register an `@mcp.tool()` wrapper in `server.py` that validates through a Pydantic model.
|
|
166
|
+
4. Add a mocked test in `tests/test_tools.py`.
|
|
167
|
+
5. Update `EXPECTED_TOOL_COUNT` and `EXPECTED_TOOLS` in `tests/test_server.py`.
|
|
168
|
+
6. Run all quality gates.
|
|
169
|
+
|
|
170
|
+
### Deprecation Policy
|
|
171
|
+
|
|
172
|
+
A removed or renamed tool is announced in the release notes of one MINOR release before removal, and removal lands in the next MAJOR.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## IX. Governance
|
|
177
|
+
|
|
178
|
+
This repository carries the full text of its governance. The `Inherits` header declares alignment with the MCP Server profile, not a runtime dependency on it.
|
|
179
|
+
|
|
180
|
+
Files:
|
|
181
|
+
- `.specify/memory/constitution.md` — this document
|
|
182
|
+
- `.specify/specs/000-baseline/spec.md` — tool inventory and architecture baseline
|
|
183
|
+
- `.specify/templates/` — `plan-template.md` and `spec-template.md`
|
|
184
|
+
|
|
185
|
+
### Specification-Driven Development
|
|
186
|
+
|
|
187
|
+
New tool groups, new subsystems, changes to the security model, and changes spanning multiple modules each require a numbered spec under `.specify/specs/` before implementation. Bug fixes, dependency updates, CI changes, documentation, and single-tool additions following Section VIII are exempt.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## X. Amendment Process
|
|
192
|
+
|
|
193
|
+
Amendments follow the universal constitution's process. Changes to the five-layer security model or to the authentication principle require an explicit version bump of this document and a recorded justification.
|
|
194
|
+
|
|
195
|
+
### Amendment History
|
|
196
|
+
|
|
197
|
+
| Version | Date | Change |
|
|
198
|
+
|---------|------|--------|
|
|
199
|
+
| 1.0.0 | 2026-09-05 | Initial ratification alongside the 2.0.0 Python rewrite. |
|
|
200
|
+
| 1.1.0 | 2026-09-05 | Section I.3 widened from "OAuth only" to "token-based, OAuth preferred", admitting pCloud session tokens transported in a POST body. Rationale: pCloud rejects its own desktop-client session token as an `access_token` (`result 2094`), so an account without a provisioned OAuth application had no usable credential. The no-credentials-in-URL rule is unchanged and is what Layer 3 continues to enforce. |
|