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.
Files changed (44) hide show
  1. mcp_pcloud_crunchtools-2.1.0/.containerignore +9 -0
  2. mcp_pcloud_crunchtools-2.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
  3. mcp_pcloud_crunchtools-2.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. mcp_pcloud_crunchtools-2.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +50 -0
  5. mcp_pcloud_crunchtools-2.1.0/.github/workflows/ci.yml +62 -0
  6. mcp_pcloud_crunchtools-2.1.0/.github/workflows/container.yml +101 -0
  7. mcp_pcloud_crunchtools-2.1.0/.github/workflows/publish.yml +24 -0
  8. mcp_pcloud_crunchtools-2.1.0/.github/workflows/security.yml +94 -0
  9. mcp_pcloud_crunchtools-2.1.0/.gitignore +13 -0
  10. mcp_pcloud_crunchtools-2.1.0/.pre-commit-config.yaml +9 -0
  11. mcp_pcloud_crunchtools-2.1.0/.specify/memory/constitution.md +200 -0
  12. mcp_pcloud_crunchtools-2.1.0/.specify/specs/000-baseline/spec.md +76 -0
  13. mcp_pcloud_crunchtools-2.1.0/.specify/templates/plan-template.md +117 -0
  14. mcp_pcloud_crunchtools-2.1.0/.specify/templates/spec-template.md +91 -0
  15. mcp_pcloud_crunchtools-2.1.0/CLAUDE.md +56 -0
  16. mcp_pcloud_crunchtools-2.1.0/Containerfile +21 -0
  17. mcp_pcloud_crunchtools-2.1.0/LICENSE +661 -0
  18. mcp_pcloud_crunchtools-2.1.0/PKG-INFO +107 -0
  19. mcp_pcloud_crunchtools-2.1.0/README.md +87 -0
  20. mcp_pcloud_crunchtools-2.1.0/gourmand-exceptions.toml +4 -0
  21. mcp_pcloud_crunchtools-2.1.0/gourmand.toml +22 -0
  22. mcp_pcloud_crunchtools-2.1.0/pyproject.toml +87 -0
  23. mcp_pcloud_crunchtools-2.1.0/server.json +31 -0
  24. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/__init__.py +58 -0
  25. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/__main__.py +6 -0
  26. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/client.py +183 -0
  27. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/config.py +170 -0
  28. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/errors.py +101 -0
  29. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/models.py +129 -0
  30. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/server.py +179 -0
  31. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/__init__.py +42 -0
  32. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/account.py +29 -0
  33. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/files.py +62 -0
  34. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/folders.py +52 -0
  35. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/formatting.py +34 -0
  36. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/links.py +34 -0
  37. mcp_pcloud_crunchtools-2.1.0/src/mcp_pcloud_crunchtools/tools/search.py +15 -0
  38. mcp_pcloud_crunchtools-2.1.0/tests/__init__.py +0 -0
  39. mcp_pcloud_crunchtools-2.1.0/tests/conftest.py +25 -0
  40. mcp_pcloud_crunchtools-2.1.0/tests/helpers.py +33 -0
  41. mcp_pcloud_crunchtools-2.1.0/tests/test_config.py +115 -0
  42. mcp_pcloud_crunchtools-2.1.0/tests/test_models.py +68 -0
  43. mcp_pcloud_crunchtools-2.1.0/tests/test_server.py +44 -0
  44. mcp_pcloud_crunchtools-2.1.0/tests/test_tools.py +267 -0
@@ -0,0 +1,9 @@
1
+ .venv
2
+ .git
3
+ .mypy_cache
4
+ .ruff_cache
5
+ .pytest_cache
6
+ .gourmand-cache
7
+ dist
8
+ build
9
+ *.egg-info
@@ -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,5 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: fastembed Documentation
4
+ url: https://qdrant.github.io/fastembed/
5
+ about: Check fastembed docs for embedding model questions
@@ -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,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .pytest_cache/
10
+ .gourmand-cache/
11
+ .coverage
12
+ htmlcov/
13
+ uv.lock
@@ -0,0 +1,9 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ stages: [pre-commit, pre-merge-commit]
8
+ - id: ruff-format
9
+ stages: [pre-commit, pre-merge-commit]
@@ -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. |