redmine-mcp
Redmine local MCP POC
This server exposes thirty-five MCP tools over stdio:
list_projectslists projects visible to the configured Redmine API user, with pagination and documented project associations.list_querieslists visible saved issue queries with collection pagination.searchsearches visible global Redmine content with documented search filters and pagination.list_issue_statuseslists all issue statuses in Redmine's configured order.list_issue_prioritieslists all issue priorities in Redmine's configured order.list_time_entry_activitieslists all shared time-entry activities in Redmine's configured order.list_document_categorieslists all shared document categories in Redmine's configured order.list_trackerslists all trackers in Redmine's configured order.list_roleslists non-built-in project-member roles in Redmine's configured order.get_rolereads one Redmine role by positive numeric role ID.get_projectreads one visible project by numeric ID or project identifier, with optional documented project associations.list_project_issue_categorieslists one project's issue categories by numeric ID or project identifier.get_issue_categoryreads one visible issue category by positive numeric ID.list_project_membershipslists a project's user and group memberships, roles, and pagination metadata by numeric project ID or project identifier.get_project_membershipreads one project membership by positive membership ID, including its user or group principal and roles.list_project_versionslists a project's visible and shared versions by numeric project ID or project identifier.list_project_fileslists a project's files by numeric project ID or project identifier.list_project_wiki_pageslists a project's wiki page index by numeric project ID or project identifier.get_project_wiki_pagereads one current project wiki page by numeric project ID or project identifier and title.get_project_wiki_page_versionreads one historic project wiki page revision by numeric project ID or project identifier, title, and version.get_attachmentreads one visible attachment by positive numeric attachment ID.get_versionreads one visible Redmine version by positive numeric version ID.get_userreads one visible Redmine user by positive numeric ID, with optional visible project memberships.get_current_userreads the Redmine user represented by the configured API token, with optional visible project memberships.get_my_accountreads the configured API user's own account from Redmine's/my/account.jsonendpoint.get_issuereads one visible Redmine issue and optionally includes documented issue associations and journal history.list_issue_relationslists the visible relations for one Redmine issue.get_issue_relationreads one visible Redmine issue relation by positive numeric relation ID.list_issueslists visible Redmine issues with pagination, sorting, documented filters, custom-field filters, attachments, and relations.list_newslists visible global Redmine news with collection pagination.get_newsreads one visible Redmine news item by positive numeric ID, with optional documented associations.list_project_newslists visible news for one project by numeric ID or project identifier, with collection pagination.list_time_entrieslists visible Redmine time entries with documented pagination, ID, project, activity, and date filters.get_time_entryreads one visible Redmine time entry by positive numeric ID.list_my_assigned_issuesreturns issues assigned to the Redmine user represented byREDMINE_API_TOKEN.
The server is read-only. It sends the token to Redmine in the X-Redmine-API-Key header. The token is not an MCP tool argument and is not included in tool results.
Run it
You need Node.js 20 or newer and pnpm.
Copy-Item .env.example .envEdit .env:
REDMINE_BASE_URL=https://redmine.example.com
REDMINE_API_TOKEN=your-redmine-api-tokenIf Redmine lives under a path, include it in the URL, for example https://example.com/redmine.
Enable the REST API in Redmine under Administration > Settings > API, then install and build:
pnpm install
pnpm buildConnect VS Code over stdio
The repository includes .vscode/mcp.json. VS Code starts dist/stdio.js itself, loads .env, and communicates over stdin and stdout.
After building, open this folder in VS Code and start redmine-test-mcp-server from the MCP server list. Rebuild after changing the TypeScript source.
To run the stdio process manually for troubleshooting:
pnpm startDo not use pnpm start as the VS Code command. Package manager output can interfere with the stdio protocol. The included configuration starts Node directly.
Tool input
list_projects accepts the documented offset, limit, and page pagination controls. All are omitted by default, so Redmine applies its own defaults. limit is capped at 100. The include list is empty by default and accepts trackers, issue_categories, enabled_modules, time_entry_activities, and issue_custom_fields.
{
"page": 2,
"limit": 25,
"include": ["trackers", "enabled_modules"]
}list_queries reads the visible saved issue queries from /queries.json. It accepts only Redmine's documented collection pagination controls: offset, limit, and page. Redmine defaults to an offset of 0 and a limit of 25, caps the limit at 100, and gives offset precedence over page. The endpoint has no documented project, resource-type, or include filters; internal and administrator-only query selectors are not exposed. Results preserve Redmine's name order and map the API fields id, name, is_public, and project_id to id, name, isPublic, and projectId. Explicit null and omitted projectId values are preserved. Each url points to the corresponding filtered issue list (/issues?query_id=:id), which is Redmine's documented way to use a saved query because /queries/:id is not a show route.
Redmine's QueriesController#index accepts ordinary API authentication, uses IssueQuery by default, and obtains visibility through IssueQuery.visible. Public queries are visible to logged-in users, role-visible queries require a matching project membership role, and private queries are visible only to their owner (subject to the query's view permission and project visibility). The client sends an authenticated GET, allowlists only the documented response fields, and returns sealed 401, 403, or 404 errors without upstream response bodies. See Redmine's Queries REST API documentation, REST API pagination documentation, current queries controller, queries API view, routes, query model, issue query model, and application authorization source.
{
"page": 2,
"limit": 25
}search reads the global /search.json endpoint for the configured Redmine API user. Its optional query input maps to Redmine's q parameter, which Redmine strips at both ends; omitting query is valid and returns an empty result collection on current Redmine. The tool accepts only the documented search controls: offset (nonnegative), limit (1 through 100), scope, all_words, titles_only, issues, news, documents, changesets, wiki_pages, messages, projects, open_issues, and attachments. It does not expose page, project_id, format, include, nometa, internal selectors, or administrator-only controls. Unknown, duplicated, malformed, and unsafe query inputs are rejected before the request is sent.
scope accepts all, subprojects, the current controller's my_projects and bookmarks, and the REST wiki's older my_project spelling. The client sends my_project as the current my_projects value. Because this tool intentionally calls the global route, subprojects has no project anchor and therefore behaves as the global scope. all_words defaults to Redmine's true; the client sends all_words=1 for true and an empty all_words value for false, matching Redmine's presence-based controller logic. The other boolean filters are sent as 1 only when true. attachments accepts exactly 0 for descriptions only, 1 for descriptions and attachments, or only for attachment-only search. Redmine applies its own default offset of 0, default limit of 25, and maximum limit of 100 when the pagination controls are omitted.
The output wrapper is results plus paging metadata and an echoed camelCase filters object. Each result allowlists id, title, type, url, description, and datetime; description and datetime preserve Redmine's explicit null or omission. Result type remains an opaque nonempty string so current core values and plugin-provided search types are preserved. The result array is returned in the exact order Redmine supplies. Current Redmine emits absolute result URLs. The client keeps a URL only when it is HTTP(S), same-origin, rooted under REDMINE_BASE_URL, and free of user info, fragments, path traversal, and credential-like query parameter names; an unsafe upstream URL becomes null and is never emitted verbatim.
This is an ordinary authenticated read. Redmine authorizes the global search action, then applies each searchable model's ordinary visibility and project-permission scopes, so results are limited to content visible to the configured API user. Search does not accept a project path in this tool; Redmine's route supports an optional project path, but the REST search contract does not require a project_id query input. The REST search API was added in Redmine 3.3.0. The current controller's plural my_projects and bookmarks scopes are documented in the later scope change. See Redmine's Search REST API documentation, REST API pagination documentation, search controller, search API view, routes, search engine, searchable model support, API authorization source, REST search feature, and search scope change.
{
"query": "release notes",
"offset": 0,
"limit": 25,
"scope": "all",
"all_words": true,
"titles_only": false,
"issues": true,
"wiki_pages": true,
"attachments": "0"
}list_issue_statuses reads all issue statuses from /issue_statuses.json. It accepts no query, include, or pagination controls because Redmine documents none for this collection and the controller loads every status in position order. The current API view returns id, name, is_closed, and description; the tool maps them to id, name, isClosed, and description, preserving the order Redmine sends and preserving an explicit null or an omitted description. It adds no browser links because Redmine's HTML index is admin-or-API-only and the routes define no status show page. Redmine accepts ordinary API authentication for this read and may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Issue Statuses REST API documentation, current issue statuses controller, issue statuses API view, routes, and issue status model.
list_trackers reads all trackers from /trackers.json. It accepts no query, include, or pagination controls because Redmine documents none for this collection. The current API view returns id, name, default_status, description, and enabled_standard_fields; the tool maps them to id, name, defaultStatus, description, and enabledStandardFields, preserves Redmine's position order, and strips all other data. Redmine added description in 4.2.0 and enabled_standard_fields in 5.0.0, so those fields remain optional for older servers. Explicit null values remain null, and omitted fields remain omitted. The nested default status contains only its documented id and name. The result has no browser links because the HTML tracker index requires an administrator or API request and the routes define no tracker show page or ordinary status show page. Redmine accepts ordinary API authentication for this read and may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Trackers REST API documentation, current trackers controller, trackers API view, routes, tracker model, and API authorization source.
list_roles reads /roles.json. It accepts no query, include, or pagination controls. Redmine's current controller returns Role.givable, which includes only non-built-in project-member roles and orders them by position. The current list API view returns only id and name; the tool preserves Redmine's response order, strips detail-only fields such as assignable, visibility settings, and permissions, and returns no browser links. Missing or null required fields are malformed responses, not optional values. Redmine accepts ordinary API authentication for this index, while the HTML roles page is restricted for non-admin users. Redmine may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Roles REST API documentation, current roles controller, roles index API view, Role model, routes, API authorization source, and permission mapping.
get_role reads one role from /roles/:id.json. It accepts exactly one positive safe-integer role_id and sends no query string, include, pagination, or undocumented controls. The detail response is wrapped as role and maps the current API fields id, name, assignable, issues_visibility, time_entries_visibility, users_visibility, and permissions to id, name, assignable, issuesVisibility, timeEntriesVisibility, usersVisibility, and permissions. The visibility values are validated against Redmine's closed model domains: all, default, and own for issue visibility; all and own for time-entry visibility; and all and members_of_visible_projects for user visibility. Permissions remain opaque strings and retain Redmine's exact array order. The four assignability/visibility fields were added to the roles API in Redmine 4.0.0, so the client preserves their omission on older servers and preserves explicit null values when a server returns them. Unlike /roles.json, the detail route loads a role by ID and can return the built-in Non member and Anonymous roles when their IDs are known. The API route is available to ordinary API users, but the HTML role page is restricted to administrators for browser requests, so the result has no browser link. Redmine may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Roles REST API documentation, the original role-detail API change, the Redmine 4.0 field change, current roles controller, role-detail API view, Role model, routes, and API authorization source.
get_project accepts a positive numeric project ID or a valid Redmine project identifier. Its include list is empty by default and accepts trackers, issue_categories, enabled_modules, time_entry_activities, and issue_custom_fields.
{
"project_id": "redmine",
"include": ["trackers", "enabled_modules"]
}list_project_issue_categories reads /projects/:project_id/issue_categories.json. It accepts only the required project_id, using the same positive numeric ID or valid Redmine project identifier rules as the other project tools. The request is an authenticated GET with no query string because Redmine documents no pagination, filter, include, or other query controls for this collection. Redmine's current controller loads @project.issue_categories.to_a, so the result preserves the API response order. The issue_categories wrapper maps to issueCategories, and total_count maps to totalCount only when Redmine returns it, preserving an explicit null or omission. Each category allowlists id, project, name, and optional assigned_to, mapping the last field to assignedTo; unknown upstream fields are removed. A returned project reference gets a safe base-path browser URL. Categories do not get URLs because their HTML index and show routes redirect to project settings, and assignees do not get URLs because Redmine models assigned_to as a Principal, which may be a user or group. The tool returns HTTP 401, 403, or 404 as sealed errors without upstream response bodies.
This is a non-admin read. Redmine authorizes it through the project's view_issues permission with the issue_tracking module enabled, while project visibility and the normal application authorization checks still apply. See Redmine's Issue Categories REST API documentation, current issue categories controller, issue categories API view, issue category model, routes, issue-tracking permission map, and application authorization source.
{
"project_id": "redmine"
}get_issue_category reads /issue_categories/:id.json for one visible issue category. It accepts exactly one positive safe-integer issue_category_id and sends no query string, include, or undocumented controls. Redmine's detail response is wrapped as issue_category; the current serializer emits id, optional project { id, name }, name, and optional assigned_to { id, name }. The result maps these fields to camelCase, preserves explicit null values and omitted associations, and strips unknown upstream fields. Only a returned project reference gets a safe browser URL: the category HTML show action redirects to member-only project settings, while assigned_to is a Principal that may be a user or group, so category and assignee URLs are omitted. No version-dependent detail fields are documented. Redmine may return HTTP 401, 403, or 404, and the tool does not expose upstream error bodies.
{
"issue_category_id": 2
}See Redmine's Issue Categories REST API documentation, current issue categories controller, issue category detail API view, issue category model, routes, issue-tracking and category permission map, and application authorization source.
list_project_memberships accepts a positive numeric project ID or valid Redmine project identifier, plus the documented collection pagination controls offset, limit, and page. These controls are omitted by default so Redmine applies its defaults. Redmine uses a limit of 25 by default, caps it at 100, and gives offset precedence over page. Each result identifies its principal as a user or group and preserves inherited roles. This endpoint is not admin-only, but Redmine still enforces project visibility and permissions, so a user without the relevant project permission may receive HTTP 403.
{
"project_id": "redmine",
"page": 2,
"limit": 25
}get_project_membership accepts only a positive safe-integer membership_id and sends no query parameters. It reads one membership from /memberships/:id.json, distinguishes user and group principals, preserves inherited roles, and returns null for a group principal URL because Redmine's group endpoint is administrative. This endpoint is not admin-only, but Redmine still enforces the membership's project visibility and permissions, so a user without the relevant project permission may receive HTTP 403.
{
"membership_id": 1
}list_project_versions accepts only a positive numeric project ID or valid Redmine project identifier. It reads /projects/:project_id/versions.json and sends no pagination, filter, or include parameters because Redmine's documented versions collection does not define them. Redmine may include shared versions from other projects, adds total_count, and the current API controller preserves the response order rather than applying the HTML roadmap sort. Visible custom fields are returned when Redmine's API view includes them. Redmine handles API authentication, project authorization, and version visibility, so the tool may return HTTP 401, 403, or 404 without exposing upstream error bodies. See Redmine's Versions REST API documentation, current versions controller, versions API view, custom-field API renderer, application authorization source, project shared-version source, and version model.
{
"project_id": "redmine"
}list_project_files accepts only a positive numeric project ID or valid Redmine project identifier and reads /projects/:project_id/files.json. Redmine documents no pagination, filters, include, or other query controls for this collection, so the request sends no query string. The files array is returned in the exact order Redmine supplies it; the current controller assembles project attachments and version attachments without this client reordering them. The tool returns the documented id, filename, filesize, content_type, description, content_url, optional thumbnail_url, optional author { id, name }, created_on, optional version { id, name }, digest, and downloads fields with camelCase names. Optional fields remain omitted when Redmine omits them, and explicit nulls remain null. The result also includes filters.projectId, safe attachment url and downloadUrl links, and safe browser links for returned authors and versions. Upstream content and thumbnail URLs are kept only when they use the configured Redmine origin and subpath, have no credentials or fragments, contain no path traversal, and have no credential-like query parameter names. No pagination metadata is added.
This is an ordinary authenticated, non-admin read. FilesController#index accepts API authentication and authorizes the project with the view_files permission. Project and version attachments use the same project-level attachment visibility rules, and Redmine may return HTTP 401, 403, or 404 without the tool exposing the upstream response body. See Redmine's Files REST API documentation, current FilesController#index, files API view, routes, attachment model, version model, file permission map, and application authorization source.
{
"project_id": "redmine"
}list_project_wiki_pages accepts only a positive numeric project ID or valid Redmine project identifier and reads /projects/:project_id/wiki/index.json. Redmine's documented wiki-page index defines no pagination, filters, include, or nometa controls, so the tool sends no query parameters. The current API view returns wiki_pages in Redmine's title-and-parent order with title, optional parent.title, version, optional project { id, name }, created_on, and updated_on; the tool maps these fields to camelCase, preserves explicit null values and omitted optional fields, and adds safe base-path browser URLs for the page and returned project. This is an ordinary authenticated API read authorized by the project's view_wiki_pages permission; Redmine may return HTTP 401, 403, or 404 without exposing upstream error bodies. See Redmine's Wiki Pages REST API documentation, current wiki controller, wiki index API view, routes, and application authorization source.
{
"project_id": "redmine"
}get_project_wiki_page reads /projects/:project_id/wiki/:title.json for the configured Redmine API user. project_id accepts a positive numeric project ID or valid Redmine project identifier. title must be a safe, nonempty string. The client rejects path separators and control characters, applies Redmine's title lookup normalization, and percent-encodes the resulting path segment. The only documented include is attachments.
The result unwraps Redmine's wiki_page response and includes title, parent, text, version, author, comments, project, createdOn, updatedOn, and url. attachments is returned only when Redmine includes it. The mapper keeps explicit null values and does not create fields that Redmine omitted. It removes credentials, fragments, and credential-like query parameters from attachment URLs, and adds browser links rooted under REDMINE_BASE_URL.
This is an ordinary authenticated read authorized by the project's view_wiki_pages permission. It does not read historic versions or change Redmine data. Redmine may return HTTP 401, 403, or 404, and the server does not expose those response bodies. See Redmine's Wiki Pages REST API documentation, current wiki controller, wiki detail API view, wiki index API view, routes, application authorization source, and attachment API renderer.
{
"project_id": "redmine",
"title": "UsersGuide",
"include": ["attachments"]
}get_project_wiki_page_version reads /projects/:project_id/wiki/:title/:version.json for one historic project wiki page revision. It accepts the same project selector, title, and attachments include as get_project_wiki_page, plus a positive safe-integer version. The client sends the exact versioned path after applying the shared Redmine title normalization and percent-encoding. The response uses the same wiki_page wrapper and allowlisted fields as the current-page detail response, but its url includes the returned historic version. Explicit null values remain null, omitted fields remain omitted, and attachment URLs are sanitized before they reach the tool result.
Redmine's WikiController#show authorizes the project normally with view_wiki_pages, then requires view_wiki_edits whenever a version is requested. A missing revision returns 404. This tool does not read the history listing and never writes to Redmine. Redmine may return HTTP 401, 403, or 404 without exposing upstream error bodies. See Redmine's Wiki Pages REST API documentation, the current wiki controller, wiki detail API view, routes, application authorization source, and attachment API renderer.
{
"project_id": "redmine",
"title": "UsersGuide",
"version": 23,
"include": ["attachments"]
}get_attachment reads /attachments/:id.json for the configured Redmine API user. attachment_id must be one positive safe integer. Redmine documents no query or include controls for this GET, so the client sends the exact path and no query string.
The response unwraps Redmine's attachment wrapper and allowlists id, filename, filesize, content_type, description, content_url, optional thumbnail_url, optional author { id, name }, and created_on. The result uses camelCase field names, preserves explicit null values and omitted optional fields, sanitizes the returned content and thumbnail URLs, and adds safe base-path url and downloadUrl links for Redmine's browser and named-download routes. It does not download file bytes or implement PATCH or DELETE.
Redmine accepts ordinary API authentication for this read and checks Attachment#visible?, which delegates to the owning object's attachment visibility rules or limits unattached files to their author. Redmine may return HTTP 401, 403, or 404; the client does not expose those response bodies. See Redmine's Attachments REST API documentation, current attachments controller, attachment API view, attachment helper, routes, attachment model, and application authentication source.
{
"attachment_id": 6243
}get_version accepts only a positive safe-integer version_id and reads /versions/:id.json with no query parameters because Redmine's documented version detail GET defines no pagination, filters, or include controls. The response wrapper is version; the result reuses the documented version fields and visible custom fields from the versions API, and adds estimatedHours and spentHours only when the detail API view returns them for an API user allowed to view time entries. Explicit Redmine null values remain null, while omitted fields remain omitted. The tool adds safe browser links rooted under REDMINE_BASE_URL, and Redmine enforces ordinary API authentication plus version/project visibility, so it may return HTTP 401, 403, or 404 without exposing upstream error bodies. See Redmine's Versions REST API documentation, current versions controller, version detail API view, custom-field API renderer, application authorization source, and version model.
{
"version_id": 2
}get_user accepts only a positive safe-integer user_id. Its only optional include is memberships, which Redmine documents and permits for non-admin callers; the result contains only memberships in projects visible to the requesting user. The server rejects administrator-only groups and auth_source includes and omits administrator-only or private fields such as admin, status, API keys, two-factor settings, password metadata, and authentication-source details. Redmine may omit email when the user's profile hides it, and it may omit version-dependent profile fields or custom values when Redmine does not return them. A visible user can be read by an ordinary authenticated API user, while an invisible or locked user may produce HTTP 404 and insufficient visibility may produce HTTP 403. See Redmine's Users REST API documentation and the current users API view for the upstream visibility and include rules.
{
"user_id": 3,
"include": ["memberships"]
}get_current_user reads the user represented by the configured API token from /users/current.json; it does not accept a user_id. Its only optional include is memberships, which Redmine also permits for ordinary authenticated callers and filters to projects visible to the requesting user. The result shares get_user's safe public profile contract, so it omits self-only fields such as the API key, administrator status, two-factor settings, password metadata, authentication-source details, and groups. Redmine may return HTTP 401 or 403 when the token is rejected or the endpoint is not permitted. See Redmine's Users REST API documentation and users API view.
{
"include": ["memberships"]
}get_my_account reads the configured API user's own account from /my/account.json. It accepts an empty object only and sends no query, include, pagination, or administrator controls. The result maps the official account view's safe profile fields and visible custom fields to camelCase, preserves explicit nulls and omitted fields, strips the API key and administrator flag, and adds the ordinary-user /my/account browser URL. The account controller accepts ordinary API authentication for this GET action. Preferences are not returned because the official API view does not serialize them. Redmine may return HTTP 401 or 403 without exposing the upstream response body. See Redmine's MyAccount REST API documentation, current routes, MyController, account API view, User model, UserPreference model, and custom-field API renderer.
{}get_issue requires one positive Redmine issue ID. Its include list is empty by default and accepts children, attachments, relations, changesets, journals, watchers, allowed_statuses, and reactions. Use journals to retrieve the issue journal history; reactions also adds reactions to returned journals when both associations are requested.
{
"issue_id": 42,
"include": ["children", "journals", "reactions"]
}list_issue_relations reads /issues/:issue_id/relations.json for one issue. It accepts exactly one positive safe numeric issue_id and sends no query parameters because Redmine's issue-relations route documents no filters or pagination. The result preserves Redmine's relation order, which the current source sorts by relation type order and then relation ID. It supports the documented relates, duplicates, duplicated, blocks, blocked, precedes, follows, copied_to, and copied_from relation types, preserves explicit delay nulls and omitted delays, and adds url, issueUrl, and issueToUrl only for valid IDs. These links are rooted under REDMINE_BASE_URL; the tool does not follow them or implement relation detail or write operations. Redmine authenticates the ordinary API user, enforces requested-issue and related-project visibility, and may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Issue Relations REST API documentation, current issue-relations controller, issue-relations API view, issue model, issue-relation model, and routes.
{
"issue_id": 42
}get_issue_relation reads /relations/:id.json for one relation. It accepts exactly one positive safe numeric relation_id and sends no query parameters. The result preserves Redmine's documented relation fields, including all nine directional relation types and the explicit delay value or omission returned by Redmine, and adds url, issueUrl, and issueToUrl only for valid IDs. These links are rooted under REDMINE_BASE_URL. Redmine's current controller accepts API authentication for this GET action and checks that both linked issues are visible to the authenticated user; it does not require an administrator-only action. The tool does not implement relation creation or deletion and does not expose upstream response bodies on HTTP 401, 403, or 404. See Redmine's Issue Relations REST API documentation, REST API authentication documentation, current issue-relations controller, detail API view, issue-relation model, and routes.
{
"relation_id": 1819
}All list_issues fields are optional. The defaults are an open-issue page of 25 items, sorted by updated_on:desc, with no associations included.
{
"status_id": "open",
"project_id": 12,
"assigned_to_id": "me",
"created_on": ">=2026-01-01",
"custom_fields": [
{ "id": 4, "value": "~customer" }
],
"sort": "updated_on:desc,priority:desc",
"include": ["attachments", "relations"],
"limit": 25,
"offset": 0
}Supported filters are issue_id, project_id, subproject_id, tracker_id, status_id, assigned_to_id, parent_id, query_id, created_on, and updated_on. Single-ID filters accept one positive numeric ID. issue_id also accepts comma-separated IDs. subproject_id also accepts !*. status_id accepts open, closed, *, or one status ID. assigned_to_id accepts me or one user ID. Date filters accept Redmine operators such as >=2026-01-01 and ranges such as ><2026-01-01|2026-01-31.
Custom-field filters use { "id": number, "value": string | number | boolean } entries. The server turns each entry into the Redmine cf_<id> query parameter and rejects non-numeric field IDs. include is empty by default and accepts attachments and relations when requested; limit is capped at 100. list_my_assigned_issues keeps its existing status, limit, and offset input.
list_news reads the global /news.json collection for the configured API user. It accepts only the documented offset, limit, and page pagination controls. Redmine uses a default limit of 25, caps it at 100, and gives offset precedence over page. The result contains only the documented news fields, maps visible project and author references to base-path-safe browser URLs, and preserves fields Redmine omits or returns as null. Redmine applies its ordinary project and news visibility rules. See Redmine's News REST API documentation, the current news API view, news controller, and news model.
{
"page": 2,
"limit": 25
}list_project_news reads /projects/:project_id/news.json for one project. project_id accepts a positive numeric project ID or valid Redmine project identifier. It accepts only the documented offset, limit, and page collection pagination controls. Redmine uses a default limit of 25, caps it at 100, and gives offset precedence over page. The result uses the same news fields, mapping, null and omission behavior, and base-path-safe browser URLs as list_news. Redmine enforces the configured API user's project and news visibility and may return HTTP 401, 403, or 404. See Redmine's News REST API documentation, current news controller, and news model.
{
"project_id": "redmine",
"page": 2,
"limit": 25
}get_news reads /news/:id.json for one news item. news_id must be a positive safe numeric ID. Its include list is empty by default and accepts attachments, comments, and reactions. Reactions on comments are returned only when both comments and reactions are requested. The tool sends no other query controls. It reuses the list-news fields and mappings, preserves fields Redmine omits or returns as null, and adds only the association fields returned by Redmine's detail serializer. Attachment URLs are sanitized before they reach the tool result. Redmine enforces the item's news and project visibility and may return HTTP 401, 403, or 404. See Redmine's News REST API documentation, news detail API view, news controller, news model, and the official reactions REST API change.
{
"news_id": 54,
"include": ["attachments", "comments", "reactions"]
}list_time_entries reads /time_entries.json for the configured API user. It accepts the collection pagination controls offset, limit, and page, plus user_id, project_id, issue_id, activity_id, spent_on, from, and to. user_id accepts one positive numeric ID or me; the other ID filters accept one positive numeric ID. project_id also accepts a Redmine project identifier. spent_on accepts only a bare YYYY-MM-DD date, =, >=, or <= followed by one date, or the exact ><YYYY-MM-DD|YYYY-MM-DD range form with the dates in chronological order. from and to accept only real YYYY-MM-DD calendar dates. Surrounding whitespace is trimmed, but timestamps, impossible dates, operators on from/to, internal whitespace, malformed ranges, and unknown or admin-only query controls are rejected. Redmine still enforces the API user's view time entries permission and time-entry visibility rules.
The result contains allowlisted time-entry fields, visible custom fields when Redmine returns them, browser URLs rooted under REDMINE_BASE_URL, and paging metadata with the upstream offset, limit, and total_count. The server sends only GET /time_entries.json with the shared API-key transport and does not expose upstream error bodies. See Redmine's Time Entries REST API documentation, the current time-entry list API view, and the current time-entry query source.
get_time_entry reads /time_entries/:id.json for the configured API user. It accepts exactly one time_entry_id, which must be a positive safe numeric ID, and sends no query parameters. Its result uses the same allowlisted time-entry fields and browser-link rules as list_time_entries, preserves fields that Redmine omits or returns as null, and includes visible custom fields when Redmine returns them. Redmine enforces the API user's view_time_entries permission and time-entry visibility; 401, 403, and 404 responses are returned as safe status errors without upstream response bodies. See Redmine's time-entry REST API documentation, detail API view, time-entry controller, and time-entry visibility source.
{
"project_id": "redmine",
"user_id": 3,
"activity_id": 9,
"from": "2026-01-01",
"to": "2026-01-31",
"page": 2,
"limit": 25
}list_issue_priorities reads all issue priorities from /enumerations/issue_priorities.json. It accepts no query, include, or pagination controls. Redmine's current controller selects shared global IssuePriority records and returns them in the model's position order, which the Redmine guide describes as lower priority to higher priority. The API view returns id, name, is_default, active, and visible custom_fields when present; the tool maps them to id, name, isDefault, active, and customFields, strips every other field, preserves the upstream order, and keeps custom-field null values and omitted version-dependent fields. Redmine added active to the enumeration API in 4.1.0 and enumeration custom-field values in 3.4.0, so older servers may omit those fields. The API index has no ordinary priority show route, and its HTML view is restricted to administrators or API requests, so results have no browser links. Redmine may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Enumerations REST API documentation, enumerations controller, enumerations API view, IssuePriority model, Enumeration model and ordering scopes, custom-field API renderer, routes, API authorization source, active-field change, and enumeration custom-field change.
list_time_entry_activities reads all shared time-entry activities from /enumerations/time_entry_activities.json. It accepts no query, include, or pagination controls. Redmine's current controller selects shared TimeEntryActivity records and returns them in the enumeration position order. That global collection includes inactive activities and does not return project-specific activity overrides; use the documented project include=time_entry_activities response when project-specific activity availability is needed. The API view returns id, name, is_default, active, and visible custom_fields when present; the tool maps them to id, name, isDefault, active, and customFields, strips every other field, preserves the upstream order, explicit custom-field null values, and omitted version-dependent fields. Redmine added active to the enumeration API in 4.1.0 and enumeration custom-field values in 3.4.0, so older servers may omit those fields. Ordinary API authentication can read this index, while the HTML index is restricted to administrators or API requests. Redmine defines no time-entry-activity show route, so results have no browser links. Redmine may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Enumerations REST API documentation, Projects REST API documentation, enumerations controller, enumerations API view, TimeEntryActivity model, Enumeration model and ordering scopes, custom-field API renderer, routes, API authorization source, project time-entry activity source, active-field change, and enumeration custom-field change.
list_document_categories reads all shared document categories from /enumerations/document_categories.json. It accepts no query, include, or pagination controls because Redmine's documented endpoint defines none. The current controller selects DocumentCategory.shared.sorted, so the result contains global/shared categories in Redmine's position order and excludes project-specific enumeration overrides. The collection includes inactive categories; current Redmine returns active, while servers older than 4.1.0 may omit it. The current API view returns id, name, is_default, active, and visible custom_fields; enumeration custom-field values were added in Redmine 3.4.0, so older servers may omit custom_fields. The tool maps these fields to id, name, isDefault, active, and customFields, preserves order, explicit custom-field null values, and omitted version-dependent fields, and strips all other upstream data. Redmine accepts ordinary API authentication for this read. Its HTML index is admin-or-API-only for non-admin users and the routes define no document-category show page, so results have no browser links. Redmine may return HTTP 401, 403, or 404 without exposing the upstream response body. See Redmine's Enumerations REST API documentation, enumerations controller, enumerations API view, DocumentCategory model, Enumeration model and ordering scopes, DocumentCategory custom-field model, custom-field API renderer, routes, API authorization source, active-field change, and enumeration custom-field change.
Quick checks
$env:NODE_EXTRA_CA_CERTS = (Resolve-Path .cert\localhost-cert.pem)
node -e 'fetch("https://127.0.0.1:3000/health").then(async response => console.log(await response.text()))'
pnpm testFor HTTP clients, point the MCP client at https://127.0.0.1:3000/mcp. Configure that client to trust the local certificate, or supply a certificate signed by a CA it already trusts. The server supports current MCP HTTP requests and the stateless legacy fallback supplied by the official SDK.
POC boundaries
This cut intentionally has no issue updates, time-entry writes, token rotation, MCP-side authentication, caching, or retries. Anyone who can access the local HTTPS endpoint can read whatever the configured Redmine token can read, which is why the HTTPS process only listens on loopback. Stdio does not open a network port.