Skip to content

Merge open PRs: retry/backoff, custom fields, relationship tools, packaging, DevOps#19

Open
kingfly55 wants to merge 13 commits intoAndyEverything:mainfrom
kingfly55:main
Open

Merge open PRs: retry/backoff, custom fields, relationship tools, packaging, DevOps#19
kingfly55 wants to merge 13 commits intoAndyEverything:mainfrom
kingfly55:main

Conversation

@kingfly55
Copy link
Copy Markdown

Summary

This PR merges and ports all 4 open pull requests into the upstream codebase. Each PR is addressed below.


Closes #17 — Upgrade Dockerfile to Python 3.11 and add HTTP retry/backoff (gmt1996)

Merged cleanly (fast-forward).

  • Dockerfile: pin base image from python:3.14-slim (pre-release) → python:3.11-slim (stable); fix CMD entry point to openproject-mcp-fastmcp.py
  • src/client.py: _request() now retries up to 3× with exponential backoff (1 s, 2 s, 4 s) on 500/502/503/504 and network/timeout failures; non-retryable 4xx errors raise immediately
  • src/client.py + src/server.py: new READ_ONLY_MODE env var — when true, all POST/PATCH/PUT/DELETE calls are blocked before any network call
  • pyproject.toml: add pytest-asyncio dev dep and asyncio_mode = "auto" config
  • env_example.txt: document READ_ONLY_MODE=false
  • tests/: 14 new test modules covering connection, hierarchy, memberships, news, projects, readonly mode, relations, time entries, users, versions, weekly reports, and work packages

Closes #16 — Fix custom fields not being sent in API payload (roromedia)

Ported to current src/ layout (original targeted the legacy openproject-mcp.py).

  • src/client.py: create_work_package and update_work_package now iterate over customField* kwargs — list-type fields (dicts with href) are routed to payload["_links"]; text/number values go directly to payload

Closes #4 — Add work package relationship tools (addictivedev)

Core relationship tools were already present in src/client.py and src/tools/relations.py (with an even more complete implementation — includes get_work_package_relation and update_work_package_relation beyond what the PR added). The DevOps infrastructure from the PR was ported:

  • docker-compose.yml: full-stack local/CI environment (postgres, redis, OpenProject, mcp-server, test-runner)
  • Dockerfile.test: isolated test runner container
  • run-e2e-tests.sh: convenience E2E test script
  • pytest.ini: pytest configuration
  • TESTING.md: testing documentation
  • .flake8: linter configuration (120-char line length)
  • .pre-commit-config.yaml: Black + Flake8 + standard pre-commit hooks
  • .github/ISSUE_TEMPLATE/: bug report and feature request templates
  • .github/pull_request_template.md
  • .github/workflows/: e2e-tests.yml, release.yml, dependency-updates.yml
  • tests/test_unit.py: 12 unit tests adapted for the modular src/ layout
  • tests/e2e_test.py, simple_e2e_test.py, setup_test_data.py: Docker-based E2E suite

Closes #15 — Add uvx/pip installation support with entry point (Compass-Rose-Systems)

Ported non-rename parts (the openproject-mcp.pyopenproject_mcp.py rename doesn't apply; the codebase already uses a src/ layout).

  • pyproject.toml: [project.scripts] entry point (openproject-mcp = "src.server:main"), Python 3.13 classifier, [tool.ruff] + [tool.mypy] config sections, updated dev deps (ruff, mypy, cyclonedx-bom)
  • src/server.py: main() function and if __name__ == "__main__" block for CLI invocation
  • LICENSE: MIT license
  • .github/workflows/ci.yml: lint (ruff), type-check (mypy), test matrix across Python 3.10–3.13, build verification
  • .github/workflows/publish.yml: PyPI Trusted Publishing on v* tags
  • .github/workflows/security.yml: dependency review, SBOM generation (cyclonedx-py), CodeQL analysis (merged with Add tools #4's security workflow)
  • .github/dependabot.yml: weekly updates for Actions and pip
  • README.md: new "Quick Start (uvx / pip)" section with uvx openproject-mcp-server, pip install, and Claude Code/Claude Desktop config JSON examples
  • CI_CD.md, RELEASING.md, TROUBLESHOOTING.md: new documentation

Conflict resolutions

Two conflicts arose when combining the above PRs:

  1. .github/workflows/security.ymlAdd tools #4 contributed a CodeQL-only workflow; Add uvx/pip installation support with entry point #15 contributed a more comprehensive one (dependency-review + SBOM + CodeQL). Resolved by merging all three jobs into a single workflow, keeping the uv-based dependency install from Add tools #4's CodeQL job.

  2. pyproject.toml [project.optional-dependencies].devupgrade Dockerfile to Python 3.11 and add HTTP retry/backoff #17 added pytest-asyncio, black, flake8; Add uvx/pip installation support with entry point #15 replaced with ruff, mypy, cyclonedx-bom. Resolved by keeping pytest-asyncio (still needed) and adopting ruff/mypy/cyclonedx-bom (superseding black/flake8).

Test plan

  • uv sync && uv run pytest tests/ -v — unit tests pass
  • uv run openproject-mcp starts without error when OPENPROJECT_URL and OPENPROJECT_API_KEY are set
  • uvx openproject-mcp-server installs and runs from PyPI entry point
  • Docker E2E: ./run-e2e-tests.sh with a live OpenProject instance
  • Verify customField* values appear in work package create/update API payloads
  • Verify READ_ONLY_MODE=true blocks all write tools

🤖 Generated with Claude Code

claude and others added 13 commits April 15, 2026 10:07
- Dockerfile: pin base image to python:3.11-slim (stable) instead of
  the pre-release python:3.14-slim; fix CMD to point to the correct
  entry point openproject-mcp-fastmcp.py
- client.py: add exponential backoff retry to _request(); retries up
  to 3 times (delays 1s, 2s, 4s) on transient server errors (500/502/
  503/504) and network/timeout failures; non-retryable 4xx errors are
  raised immediately without retry

https://claude.ai/code/session_01WE3p81Gk65b4fmA395dg7w
…itory-IFyU2

claude/analyze-repository-IFyU2
Create tests/ directory with 155 tests covering all 12 MCP tool modules:

- conftest.py: shared fixtures and dummy env vars for offline testing
- test_connection.py: test_connection, check_permissions (5 tests)
- test_projects.py: CRUD + input validation (14 tests)
- test_users.py: list/get users, roles, memberships (14 tests)
- test_work_packages.py: CRUD, filters, types/statuses/priorities (17 tests)
- test_memberships.py: CRUD + validation edge cases (13 tests)
- test_hierarchy.py: parent/child relationships (8 tests)
- test_relations.py: relation CRUD + validation (14 tests)
- test_time_entries.py: time tracking CRUD + activities fallback (14 tests)
- test_versions.py: versions CRUD + validation (8 tests)
- test_news.py: news CRUD + formatting utilities (20 tests)
- test_weekly_reports.py: report generation, JSON format, date validation (13 tests)

All tests use AsyncMock to patch get_client() — no live API required.
pytest-asyncio added to dev dependencies with asyncio_mode = "auto".
All 155 tests pass with 0 warnings.

https://claude.ai/code/session_01WE3p81Gk65b4fmA395dg7w
…e-and-fixes

test: add comprehensive pytest test suite for all 12 tool modules
When READ_ONLY_MODE=true, every POST/PATCH/PUT/DELETE request made by
OpenProjectClient._request raises immediately with a clear error message
before any network call is attempted. GET requests are unaffected.

Changes:
- src/client.py: add _WRITE_METHODS frozenset; add `readonly` param to
  __init__; insert guard at the top of _request
- src/server.py: read READ_ONLY_MODE env var; pass readonly= to client
  constructor; log ⚠️  warning on startup; export is_readonly() helper
- env_example.txt: document READ_ONLY_MODE=false
- pyproject.toml: add pytest-asyncio dev dependency + pytest config
- tests/conftest.py + tests/__init__.py: test infrastructure
- tests/test_readonly_mode.py: 31 tests across 3 layers
    Layer 1 — client unit tests (POST/PATCH/PUT/DELETE blocked, GET allowed)
    Layer 2 — tool-level tests (write tools return ❌, read tools unaffected)
    Layer 3 — server helpers (is_readonly(), client.readonly attribute)

https://claude.ai/code/session_01WE3p81Gk65b4fmA395dg7w
…-only

feat: add READ_ONLY_MODE env var to block all write operations
…yload

Custom fields were passed through the tool handler but never copied
to the API payload in create_work_package and update_work_package
client methods. Added handling for customField* keys: list-type fields
(with href) go to payload["_links"], text/number fields go directly to payload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/list/delete)

Core relationship functionality (create_work_package_relation,
list_work_package_relations, delete_work_package_relation) was already
present in src/client.py and src/tools/relations.py from prior work.

This commit ports the supporting infrastructure from PR AndyEverything#4
(addictivedev/openproject-mcp-server):
- docker-compose.yml for full-stack local/CI testing
- Dockerfile.test for test runner container
- run-e2e-tests.sh convenience script
- pytest.ini test configuration
- TESTING.md testing documentation
- .flake8 linter configuration
- .pre-commit-config.yaml (Black + Flake8 + pre-commit hooks)
- .github/ISSUE_TEMPLATE/{bug_report,feature_request}.md
- .github/pull_request_template.md
- .github/workflows/{e2e-tests,release,security,dependency-updates}.yml
- tests/test_unit.py (adapted for modular src/ structure; 12/12 pass)
- tests/{e2e_test,simple_e2e_test,setup_test_data}.py
…y point

- pyproject.toml: add [project.scripts] entry point (openproject-mcp -> src.server:main), Python 3.13 classifier, ruff/mypy tool config, [dependency-groups]
- src/server.py: add main() entry point function for CLI invocation
- LICENSE: add MIT license file (AndyEverything + Compass Rose Systems)
- .github/workflows/ci.yml: CI with lint, test matrix (3.10-3.13), build jobs
- .github/workflows/publish.yml: PyPI Trusted Publishing on version tags
- .github/workflows/security.yml: dependency review, SBOM, CodeQL
- .github/dependabot.yml: weekly updates for Actions and pip
- CI_CD.md, RELEASING.md, TROUBLESHOOTING.md: new CI/CD documentation
- .gitignore: minor additions (uv.lock comment, GITHUB_ACTIONS_PLAN.md)
- README.md: add uvx/pip Quick Start and Claude Code/Desktop config examples
- tests/: add test_basic.py and test_client.py (adapted for src/ layout)

Skipped: file rename (openproject-mcp.py -> openproject_mcp.py), uv.lock regeneration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants