redmine-mcp
# Redmine local MCP POC
This server exposes thirty-five MCP tools over stdio:
- `list_projects` lists projects visible to the configured Redmine API user, with pagination and documented project associations.
- `list_queries` lists visible saved issue queries with collection pagination.
- `search` searches visible global Redmine content with documented search filters and pagination.
- `list_issue_statuses` lists all issue statuses in Redmine's configured order.
- `list_issue_priorities` lists all issue priorities in Redmine's configured order.
- `list_time_entry_activities` lists all shared time-entry activities in Redmine's configured order.
- `list_document_categories` lists all shared document categories in Redmine's configured order.
- `list_trackers` lists all trackers in Redmine's configured order.
- `list_roles` lists non-built-in project-member roles in Redmine's configured order.
- `get_role` reads one Redmine role by positive numeric role ID.
- `get_project` reads one visible project by numeric ID or project identifier, with optional documented project associations.
- `list_project_issue_categories` lists one project's issue categories by numeric ID or project identifier.
- `get_issue_category` reads one visible issue category by positive numeric ID.
- `list_project_memberships` lists a project's user and group memberships, roles, and pagination metadata by numeric project ID or project identifier.
- `get_project_membership` reads one project membership by positive membership ID, including its user or group principal and roles.
- `list_project_versions` lists a project's visible and shared versions by numeric project ID or project identifier.
- `list_project_files` lists a project's files by numeric project ID or project identifier.
- `list_project_wiki_pages` lists a project's wiki page index by numeric project ID or project identifier.
- `get_project_wiki_page` reads one current project wiki page by numeric project ID or project identifier and title.
- `get_project_wiki_page_version` reads one historic project wiki page revision by numeric project ID or project identifier, title, and version.
- `get_attachment` reads one visible attachment by positive numeric attachment ID.
- `get_version` reads one visible Redmine version by positive numeric version ID.
- `get_user` reads one visible Redmine user by positive numeric ID, with optional visible project memberships.
- `get_current_user` reads the Redmine user represented by the configured API token, with optional visible project memberships.
- `get_my_account` reads the configured API user's own account from Redmine's `/my/account.json` endpoint.
- `get_issue` reads one visible Redmine issue and optionally includes documented issue associations and journal history.
- `list_issue_relations` lists the visible relations for one Redmine issue.
- `get_issue_relation` reads one visible Redmine issue relation by positive numeric relation ID.
- `list_issues` lists visible Redmine issues with pagination, sorting, documented filters, custom-field filters, attachments, and relations.
- `list_news` lists visible global Redmine news with collection pagination.
- `get_news` reads one visible Redmine news item by positive numeric ID, with optional documented associations.
- `list_project_news` lists visible news for one project by numeric ID or project identifier, with collection pagination.
- `list_time_entries` lists visible Redmine time entries with documented pagination, ID, project, activity, and date filters.
- `get_time_entry` reads one visible Redmine time entry by positive numeric ID.
- `list_my_assigned_issues` returns issues assigned to the Redmine user represented by `REDMINE_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.
```powershell
Copy-Item .env.example .env
```
Edit `.env`:
```dotenv
REDMINE_BASE_URL=https://redmine.example.com
REDMINE_API_TOKEN=your-redmine-api-token
```
If 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:
```powershell
pnpm install
pnpm build
```
## Connect 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:
```powershell
pnpm start
```
Do 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`.
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Queries), [REST API pagination documentation](https://www.redmine.org/projects/redmine/wiki/Rest_api#Collection_resources_and_pagination), current [queries controller](https://github.com/redmine/redmine/blob/master/app/controllers/queries_controller.rb), [queries API view](https://github.com/redmine/redmine/blob/master/app/views/queries/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [query model](https://github.com/redmine/redmine/blob/master/app/models/query.rb), [issue query model](https://github.com/redmine/redmine/blob/master/app/models/issue_query.rb), and [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Search), [REST API pagination documentation](https://www.redmine.org/projects/redmine/wiki/Rest_api#Collection_resources_and_pagination), [search controller](https://github.com/redmine/redmine/blob/master/app/controllers/search_controller.rb), [search API view](https://github.com/redmine/redmine/blob/master/app/views/search/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [search engine](https://github.com/redmine/redmine/blob/master/lib/redmine/search.rb), [searchable model support](https://github.com/redmine/redmine/blob/master/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb), [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), [REST search feature](https://www.redmine.org/issues/6277), and [search scope change](https://www.redmine.org/issues/38459).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_IssueStatuses), current [issue statuses controller](https://github.com/redmine/redmine/blob/master/app/controllers/issue_statuses_controller.rb), [issue statuses API view](https://github.com/redmine/redmine/blob/master/app/views/issue_statuses/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), and [issue status model](https://github.com/redmine/redmine/blob/master/app/models/issue_status.rb).
`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](https://www.redmine.org/projects/redmine/wiki/Rest_Trackers), current [trackers controller](https://github.com/redmine/redmine/blob/master/app/controllers/trackers_controller.rb), [trackers API view](https://github.com/redmine/redmine/blob/master/app/views/trackers/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [tracker model](https://github.com/redmine/redmine/blob/master/app/models/tracker.rb), and [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
`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](https://www.redmine.org/projects/redmine/wiki/Rest_Roles), current [roles controller](https://github.com/redmine/redmine/blob/master/app/controllers/roles_controller.rb), [roles index API view](https://github.com/redmine/redmine/blob/master/app/views/roles/index.api.rsb), [Role model](https://github.com/redmine/redmine/blob/master/app/models/role.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), and [permission mapping](https://github.com/redmine/redmine/blob/master/lib/redmine/access_control.rb).
`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](https://www.redmine.org/projects/redmine/wiki/Rest_Roles), the original [role-detail API change](https://www.redmine.org/issues/11502), the [Redmine 4.0 field change](https://www.redmine.org/issues/28191), current [roles controller](https://github.com/redmine/redmine/blob/master/app/controllers/roles_controller.rb), [role-detail API view](https://github.com/redmine/redmine/blob/master/app/views/roles/show.api.rsb), [Role model](https://github.com/redmine/redmine/blob/master/app/models/role.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), and [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
`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`.
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_IssueCategories), current [issue categories controller](https://github.com/redmine/redmine/blob/master/app/controllers/issue_categories_controller.rb), [issue categories API view](https://github.com/redmine/redmine/blob/master/app/views/issue_categories/index.api.rsb), [issue category model](https://github.com/redmine/redmine/blob/master/app/models/issue_category.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [issue-tracking permission map](https://github.com/redmine/redmine/blob/master/lib/redmine/preparation.rb), and [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
```json
{
"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.
```json
{
"issue_category_id": 2
}
```
See Redmine's [Issue Categories REST API documentation](https://www.redmine.org/projects/redmine/wiki/Rest_IssueCategories), current [issue categories controller](https://github.com/redmine/redmine/blob/master/app/controllers/issue_categories_controller.rb), [issue category detail API view](https://github.com/redmine/redmine/blob/master/app/views/issue_categories/show.api.rsb), [issue category model](https://github.com/redmine/redmine/blob/master/app/models/issue_category.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [issue-tracking and category permission map](https://github.com/redmine/redmine/blob/master/lib/redmine/preparation.rb), and [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
`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.
```json
{
"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.
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Versions), current [versions controller](https://github.com/redmine/redmine/blob/master/app/controllers/versions_controller.rb), [versions API view](https://github.com/redmine/redmine/blob/master/app/views/versions/index.api.rsb), [custom-field API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/custom_fields_helper.rb), [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), [project shared-version source](https://github.com/redmine/redmine/blob/master/app/models/project.rb), and [version model](https://github.com/redmine/redmine/blob/master/app/models/version.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Files), current [FilesController#index](https://github.com/redmine/redmine/blob/master/app/controllers/files_controller.rb), [files API view](https://github.com/redmine/redmine/blob/master/app/views/files/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [attachment model](https://github.com/redmine/redmine/blob/master/app/models/attachment.rb), [version model](https://github.com/redmine/redmine/blob/master/app/models/version.rb), [file permission map](https://github.com/redmine/redmine/blob/master/lib/redmine/preparation.rb), and [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_WikiPages), current [wiki controller](https://github.com/redmine/redmine/blob/master/app/controllers/wiki_controller.rb), [wiki index API view](https://github.com/redmine/redmine/blob/master/app/views/wiki/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), and [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_WikiPages), current [wiki controller](https://github.com/redmine/redmine/blob/master/app/controllers/wiki_controller.rb), [wiki detail API view](https://github.com/redmine/redmine/blob/master/app/views/wiki/show.api.rsb), [wiki index API view](https://github.com/redmine/redmine/blob/master/app/views/wiki/index.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), and [attachment API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/attachments_helper.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_WikiPages), the current [wiki controller](https://github.com/redmine/redmine/blob/master/app/controllers/wiki_controller.rb), [wiki detail API view](https://github.com/redmine/redmine/blob/master/app/views/wiki/show.api.rsb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), and [attachment API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/attachments_helper.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Attachments), current [attachments controller](https://github.com/redmine/redmine/blob/master/app/controllers/attachments_controller.rb), [attachment API view](https://github.com/redmine/redmine/blob/master/app/views/attachments/show.api.rsb), [attachment helper](https://github.com/redmine/redmine/blob/master/app/helpers/attachments_helper.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [attachment model](https://github.com/redmine/redmine/blob/master/app/models/attachment.rb), and [application authentication source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Versions), current [versions controller](https://github.com/redmine/redmine/blob/master/app/controllers/versions_controller.rb), [version detail API view](https://github.com/redmine/redmine/blob/master/app/views/versions/show.api.rsb), [custom-field API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/custom_fields_helper.rb), [application authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), and [version model](https://github.com/redmine/redmine/blob/master/app/models/version.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Users) and the current [users API view](https://github.com/redmine/redmine/blob/master/app/views/users/show.api.rsb) for the upstream visibility and include rules.
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Users) and [users API view](https://github.com/redmine/redmine/blob/master/app/views/users/show.api.rsb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_MyAccount), current [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [MyController](https://github.com/redmine/redmine/blob/master/app/controllers/my_controller.rb), [account API view](https://github.com/redmine/redmine/blob/master/app/views/my/account.api.rsb), [User model](https://github.com/redmine/redmine/blob/master/app/models/user.rb), [UserPreference model](https://github.com/redmine/redmine/blob/master/app/models/user_preference.rb), and [custom-field API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/custom_fields_helper.rb).
```json
{}
```
`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.
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_IssueRelations), current [issue-relations controller](https://github.com/redmine/redmine/blob/master/app/controllers/issue_relations_controller.rb), [issue-relations API view](https://github.com/redmine/redmine/blob/master/app/views/issue_relations/index.api.rsb), [issue model](https://github.com/redmine/redmine/blob/master/app/models/issue.rb), [issue-relation model](https://github.com/redmine/redmine/blob/master/app/models/issue_relation.rb), and [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_IssueRelations), [REST API authentication documentation](https://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication), current [issue-relations controller](https://github.com/redmine/redmine/blob/master/app/controllers/issue_relations_controller.rb), [detail API view](https://github.com/redmine/redmine/blob/master/app/views/issue_relations/show.api.rsb), [issue-relation model](https://github.com/redmine/redmine/blob/master/app/models/issue_relation.rb), and [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb).
```json
{
"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.
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_News), the current [news API view](https://github.com/redmine/redmine/blob/master/app/views/news/index.api.rsb), [news controller](https://github.com/redmine/redmine/blob/master/app/controllers/news_controller.rb), and [news model](https://github.com/redmine/redmine/blob/master/app/models/news.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_News), current [news controller](https://github.com/redmine/redmine/blob/master/app/controllers/news_controller.rb), and [news model](https://github.com/redmine/redmine/blob/master/app/models/news.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_News), [news detail API view](https://github.com/redmine/redmine/blob/master/app/views/news/show.api.rsb), [news controller](https://github.com/redmine/redmine/blob/master/app/controllers/news_controller.rb), [news model](https://github.com/redmine/redmine/blob/master/app/models/news.rb), and the official [reactions REST API change](https://www.redmine.org/issues/44288).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_TimeEntries), the current [time-entry list API view](https://github.com/redmine/redmine/blob/master/app/views/timelog/index.api.rsb), and the current [time-entry query source](https://github.com/redmine/redmine/blob/master/app/models/time_entry_query.rb).
`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](https://www.redmine.org/projects/redmine/wiki/Rest_TimeEntries), [detail API view](https://github.com/redmine/redmine/blob/master/app/views/timelog/show.api.rsb), [time-entry controller](https://github.com/redmine/redmine/blob/master/app/controllers/timelog_controller.rb), and [time-entry visibility source](https://github.com/redmine/redmine/blob/master/app/models/time_entry.rb).
```json
{
"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](https://www.redmine.org/projects/redmine/wiki/Rest_Enumerations), [enumerations controller](https://github.com/redmine/redmine/blob/master/app/controllers/enumerations_controller.rb), [enumerations API view](https://github.com/redmine/redmine/blob/master/app/views/enumerations/index.api.rsb), [IssuePriority model](https://github.com/redmine/redmine/blob/master/app/models/issue_priority.rb), [Enumeration model and ordering scopes](https://github.com/redmine/redmine/blob/master/app/models/enumeration.rb), [custom-field API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/custom_fields_helper.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), [active-field change](https://www.redmine.org/issues/31559), and [enumeration custom-field change](https://www.redmine.org/issues/22795).
`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](https://www.redmine.org/projects/redmine/wiki/Rest_Enumerations), [Projects REST API documentation](https://www.redmine.org/projects/redmine/wiki/Rest_Projects), [enumerations controller](https://github.com/redmine/redmine/blob/master/app/controllers/enumerations_controller.rb), [enumerations API view](https://github.com/redmine/redmine/blob/master/app/views/enumerations/index.api.rsb), [TimeEntryActivity model](https://github.com/redmine/redmine/blob/master/app/models/time_entry_activity.rb), [Enumeration model and ordering scopes](https://github.com/redmine/redmine/blob/master/app/models/enumeration.rb), [custom-field API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/custom_fields_helper.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), [project time-entry activity source](https://github.com/redmine/redmine/blob/master/app/models/project.rb), [active-field change](https://www.redmine.org/issues/31559), and [enumeration custom-field change](https://www.redmine.org/issues/22795).
`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](https://www.redmine.org/projects/redmine/wiki/Rest_Enumerations), [enumerations controller](https://github.com/redmine/redmine/blob/master/app/controllers/enumerations_controller.rb), [enumerations API view](https://github.com/redmine/redmine/blob/master/app/views/enumerations/index.api.rsb), [DocumentCategory model](https://github.com/redmine/redmine/blob/master/app/models/document_category.rb), [Enumeration model and ordering scopes](https://github.com/redmine/redmine/blob/master/app/models/enumeration.rb), [DocumentCategory custom-field model](https://github.com/redmine/redmine/blob/master/app/models/document_category_custom_field.rb), [custom-field API renderer](https://github.com/redmine/redmine/blob/master/app/helpers/custom_fields_helper.rb), [routes](https://github.com/redmine/redmine/blob/master/config/routes.rb), [API authorization source](https://github.com/redmine/redmine/blob/master/app/controllers/application_controller.rb), [active-field change](https://www.redmine.org/issues/31559), and [enumeration custom-field change](https://www.redmine.org/issues/22795).
## Quick checks
```powershell
$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 test
```
For 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.
TDQS
Scored across 35 tools
Every tool is clearly distinct: list_* for collections, get_* for single resources, and each maps to a specific Redmine API endpoint. Even similar tools like get_project and list_projects differ obviously by cardinality. Semantic overlaps (e.g., get_version vs get_project_wiki_page_version) are unambiguous from context.
The naming pattern is overwhelmingly consistent: list_* for index endpoints, get_* for show endpoints, with clear resource nouns. Minor deviations like get_my_account instead of get_current_user and list_my_assigned_issues being slightly longer than other list_* names are easy to predict but not perfectly uniform.
With 35 tools, this is above the 15-25 range considered 'heavy' and closer to the chaotic end. While Redmine has many resource types, the count feels bloated—several read-only variants (e.g., list_project_issues vs list_all_issues) could be consolidated. Users face a steep CLI surface.
The tool set is exclusively read-only. While it covers a broad set of GET endpoints (projects, issues, wiki, memberships, etc.), there are no create, update, or delete operations. A complete Redmine integration would at least allow issue mutations, leaving major dead ends for agents that need to take actions.