v1.3.0
What's New in v1.3.0
Added
- Wiki management (2 new tools, no plugin required):
list_wiki_pages— list every wiki page in a project (titles, versions, parents, timestamps)rename_wiki_page— rename/move a wiki page with optional redirect (PUT /projects/{id}/wiki/{old}.jsonwithtitleparameter); detects silent permission failures by re-fetching the page after the rename
REDMINE_PRODUCTS_ENABLED=trueopt-in support for RedmineUP Products plugin:list_products— list products, optionally filtered by projectget_product— retrieve a single product by IDadd_product— create a new product (name + status_id required; supports description, price, currency, code, project_id, category_id, tag_list, custom_fields)edit_product— update product fields (whitelist filter on writable fields)
- Gantt chart (1 new tool, no plugin required):
get_gantt_chart— composite tool that aggregates issues + versions + relations into a structured Gantt response (start/due dates, progress, parent_id, precedes/blocks dependencies, milestones); supports date-range filters andinclude_closedflag
REDMINE_CRM_ENABLED=trueopt-in support for RedmineUP CRM plugin:list_contacts— list contacts with project/search/tags/assignee filtersget_contact— retrieve a single contact (with optionalinclude=notes,deals,contacts)edit_contact— update contact fields (whitelist filter)create_contact— create a new contact in a project (with first_name, last_name, company, email, phone, visibility, etc.)delete_contact— delete a contact entirelyassign_contact_to_project— add an existing contact to an additional projectremove_contact_from_project— remove a contact from a project (without deleting it)
manage_redmine_version: single MCP tool for full version lifecycle management (create, update, delete) via anactionparameteraction="create": create a version in a project with optionaldescription,status,due_date,sharing,wiki_page_title; defaults tostatus="open"andsharing="none"action="update": update any subset of fields on an existing version byversion_idaction="delete": delete a version byversion_id- Validates
actionandstatusvalues client-side with actionable error messages - Respects
REDMINE_MCP_READ_ONLYmode - 21 new unit tests covering all three actions, defaults enforcement, meta-param exclusion, read-only mode, and API error paths
REDMINE_CHECKLISTS_ENABLED=trueopt-in support for RedmineUP Checklists Pro plugin:get_checklist: retrieve all checklist items for an issue (id, subject, is_done, position, timestamps)update_checklist_item: update a checklist item's text, done state, or positionmark_checklist_done: convenience tool to toggle done/undone state of a checklist item
REDMINE_AGILE_ENABLED=trueopt-in support for RedmineUP Agile plugin:get_redmine_issueauto-includesstory_points,agile_sprint_id, andagile_position;update_redmine_issueacceptsstory_pointsin thefieldsdict- 14 new MCP tools for Issue Tracking:
- Copying and hierarchy:
copy_issue— duplicate an existing issue via Redmine's nativecopy_frommechanism, with optional field overrides and support for copying subtasks/attachmentslist_subtasks— list child issues of a given parent (subtasks are created via existingcreate_redmine_issuewithparent_issue_id)
- Issue relations (blocks, duplicates, precedes, etc.):
list_issue_relations— list all relations for an issuecreate_issue_relation— create a relation between two issues; validatesrelation_typeagainst Redmine's taxonomydelete_issue_relation— delete a relation by ID
- Watchers:
add_watcher— add a user to an issue's watcher list (Redmine 2.3.0+)remove_watcher— remove a user from an issue's watcher list
- Journal/note management:
edit_note— update an existing journal note's text and/orprivate_notesflag viaPUT /journals/{id}.jsonget_private_notes— retrieve only the private notes on an issue (requires "View private notes" permission)set_note_private— toggle the private/public state of an existing journal note
- Issue categories:
list_issue_categories— list all categories for a projectcreate_issue_category— create a new category (optionally with a default assignee)update_issue_category— rename a category or change its default assigneedelete_issue_category— delete a category with optionalreassign_to_idto move existing issues
- Copying and hierarchy:
- 55 new unit tests covering all new tools (read-only mode enforcement, success paths, error paths, helper conversions)
- 5 new MCP tools for Projects:
list_redmine_roles— list all roles defined in the Redmine instance; use beforeadd_project_member/update_project_memberto discover validrole_ids(role IDs vary between Redmine instances)get_project_modules— retrieve enabled modules for a project via?include=enabled_modulesadd_project_member— add a user or group to a project with assigned roles; validates that exactly one ofuser_idorgroup_idis providedupdate_project_member— update the roles of an existing membershipremove_project_member— remove a membership (inherited memberships from parent projects surface as a 422 validation error)
role_idsvalidation errors inadd_project_member/update_project_membernow hint atlist_redmine_rolesto prevent AI agents from hallucinating role IDs- 33 new unit tests for project tools covering modules retrieval (including dict-format fallback for older Redmine versions), role discovery, membership CRUD, validation errors, read-only mode enforcement, error paths, and error-message discoverability hints
- 2 new MCP tools for Time Tracking:
log_time_for_user— create a time entry on behalf of another user via theuser_idparameter onPOST /time_entries.json; requireslog_time_for_other_userspermission on the target projectimport_time_entries— bulk import multiple time entries via sequential API calls (Redmine has no native bulk endpoint); accepts a list of dicts or JSON array string, captures per-entry errors, and returns{total, succeeded, failed, created, errors}so partial imports still yield useful feedback; supportsstop_on_errorflag
- Add missing
REDMINE_MCP_READ_ONLYenforcement to existingcreate_time_entrytool - 23 new unit tests for time tracking tools covering success paths, per-entry validation (missing hours, negative hours, missing target), JSON string input, partial failure handling,
stop_on_error, field whitelisting, read-only mode, and Redmine-version permission quirks - 3 new MCP tools for Files:
list_files— list files uploaded to a project's Files section (core Redmine "Files" module, distinct from issue attachments and DMSF documents); returns filename, filesize, content type, description, download URL, author, optional version/releaseupload_file— upload a new file via Redmine's two-step upload (POST /uploads.jsonfor token, thenPOST /projects/{id}/files.json). Accepts eithersource_url(HTTP/HTTPS URL the server downloads from) orcontent_base64(raw bytes encoded as base64); the URL path enables chaining with other MCP tools that return download URLs (e.g., Google Drive MCP). Streaming download with 30s timeout, follows redirects, infers filename from URL path orContent-Dispositionheader. 50 MiB size capdelete_file— delete a project file viaDELETE /attachments/{id}.json
- 36 new unit tests for file tools covering base64 encoding/decoding, URL-based download (success, invalid schemes, HTTP errors, timeouts, empty body, Content-Disposition filename fallback), size limits, read-only mode, missing/conflicting content sources, and Redmine error paths
- 6 new MCP Discovery / Enumeration tools to help LLMs find valid IDs without guessing:
list_redmine_trackers— list all trackers (issue types like Bug, Feature, Support) for discovering validtracker_idvalueslist_redmine_issue_statuses— list all issue statuses with theiris_closedflag for discovering validstatus_idvalueslist_redmine_issue_priorities— list all priority levels viaenumeration.filter(resource="issue_priorities")list_redmine_users— filter/list users with optionalnameandgroup_idfilters (admin-only, limit clamped to 1-100)get_current_user— retrieve the authenticated user's profile viaGET /my/account.json(works for non-admins; useful when a user says "do X for me")list_redmine_queries— list all saved custom queries visible to the current user (read-only; Redmine's API does not support CRUD on queries)
- 20 new unit tests for discovery tools covering success paths, empty results, limit clamping, filter parameters, and permission-denied error paths
Security
- SSRF protection for
upload_file(source_url=...): The server now resolves every URL hop and rejects non-public destinations (loopback, RFC1918, link-local including cloud metadata services like169.254.169.254, reserved, multicast). Redirects are followed manually with per-hop revalidation to defeat public-to-private 302 bypasses. Capped at 5 redirect hops. URLs with embedded credentials (http://user:pass@host) are refused up front to prevent credential leakage across redirects. SSRF error messages no longer include the resolved IP (logged at WARNING level instead) to avoid leaking internal network topology. Opt-in dev override:REDMINE_ALLOW_PRIVATE_FETCH_URLS=true. (Note: DNS rebinding between our check and httpx's connect is a theoretical residual risk; IP pinning was evaluated but broke TLS SNI for real CDNs.) delete_fileis now fail-closed on ambiguouscontainer_type: Previously, if Redmine (or an older python-redmine version) returnedNoneor an empty string forcontainer_type, the project-scope guard was skipped and the attachment was deleted. Now any non-"Project"value refuses the delete, with an explicitconfirm_delete_any_attachment=Trueflag for bypass.- Int-ID validators reject booleans and non-positive values: Python treats
True/Falseasint, sorole_ids=[True]would silently assign role ID 1 (often an elevated role). New_is_positive_inthelper is applied torole_ids,user_id, andgroup_idparameters inadd_project_member,update_project_member,add_watcher,remove_watcher, andlog_time_for_user. - Attacker-controlled display names wrapped: New
_named_ref()helper wrapsuser.name,author.name, andversion.namefields (all user-controlled) in<insecure-content>boundary tags. Applied via_named_refto_file_to_dictand_attachments_to_list(previously, attachment filenames/descriptions were only wrapped in the newlist_filesoutput but NOT inget_redmine_issue(include_attachments=True)-- now consistent across both). - Prompt-injection hardening:
filename,description, and time-entrycommentsreturned fromlist_files/upload_file/log_time_for_user/import_time_entriesare now wrapped in<insecure-content>boundary tags (matching existing issue/journal/description handling). Prevents attacker-controllable Redmine metadata from being treated as trusted instructions by downstream LLMs. - Error-message secret scrubbing:
_handle_redmine_errornow redacts API keys (?key=,X-Redmine-API-Key), Bearer tokens, HTTP basic-auth credentials, and the configuredREDMINE_API_KEYbefore returning errors to MCP callers. Logs still see the raw message. - Content-Disposition filename sanitization: URL-inferred and header-derived filenames are URL-decoded, stripped of path components (defeats
../../../etc/passwdtraversal on both POSIX and Windows), rejected if they contain null bytes or control characters, and capped at 255 chars. delete_filecontainer-type check: Since Redmine'sDELETE /attachments/{id}.jsonremoves any attachment by ID (including issue/wiki attachments),delete_filenow verifies the target is a project file before deleting. Callers can bypass withconfirm_delete_any_attachment=True.- Bump
cryptographyfrom 46.0.6 to 46.0.7, patching CVE-2026-39892 (out-of-bounds read via non-contiguous buffers)
Fixed
_is_valid_project_idURL-path safety: Restricts string identifiers to Redmine's documented charset (^[a-z0-9][a-z0-9_-]{0,99}$), rejecting/,?,#,.., whitespace, and uppercase before they can be interpolated into URL paths in the new Wiki/Products/CRM tools.add_productstatus_idconstraint: Now rejects values other than1(Active) or2(Inactive) instead of forwarding arbitrary positive integers to the API.copy_issuedata-integrity bug: When bothcopy_subtasks=Falseandcopy_attachments=Falsewere passed, python-redmine'sinclude or (...)fallback silently copied both anyway. Now passes a non-empty sentinel so the fallback does not trigger.log_time_for_user/import_time_entrieshours validation: Now rejects NaN, Infinity, booleans (which Python treats asint), and non-numeric types before hitting the API.import_time_entriesbulk safeguards: Added a 500-entry batch cap (returns a clear error instead of pinning the event loop for minutes on a massive request). Yields the event loop between entries viaasyncio.sleep(0)so concurrent MCP requests are not starved. Split the create/serialize try blocks so a post-create serialization failure does not flip a successful create into a reported failure (which would tempt callers to retry and create duplicates).
Changed
get_gantt_chartdefaultinclude_closedis nowFalse. Passinclude_closed=Trueto retain prior behavior. Keeps response size and pagination cost low on long-lived projects.- List tools now return
Union[List, Dict]on error instead of[{"error": "..."}]. Affectslist_issue_relations,list_subtasks,list_issue_categories,list_files,list_redmine_roles,list_redmine_trackers,list_redmine_issue_statuses,list_redmine_issue_priorities,list_redmine_users,list_redmine_queries. Callers should checkisinstance(result, dict)or"error" in resultto distinguish failure from an empty list. Matches the pre-existing convention oflist_time_entries,list_redmine_issues, andsearch_redmine_issues. - List tools now cap results at 500 items (configurable via
_DEFAULT_LIST_RESULT_CAPconstant) via the new_iter_cappedhelper. Previously unbounded iteration could OOM on projects with tens of thousands of subtasks/relations/files. - Module-level constants consolidated:
_FILE_UPLOAD_MAX_SIZE_BYTES,_MAX_FILENAME_LEN,_IMPORT_TIME_ENTRIES_MAX_BATCH,_DEFAULT_LIST_RESULT_CAP,_DOWNLOAD_TIMEOUT, and_FILE_DOWNLOAD_MAX_REDIRECTSare all declared once at the top of the module instead of scattered across different sections. Top-levelimport httpxandfrom urllib.parse import unquote, urlparsehoisted out of function-local imports.
CI
- Fix
pip-auditfailing on packages not published to PyPI by adding--no-emit-projecttouv exportindependency-audit.yml
Acknowledgements
Thanks to @mihajlovicjj for contributing:
- 30 new MCP tools, security hardening, and 82+ new tests (#89)
Installation
pip install redmine-mcp-server==1.3.0