@thelabnyc/redmine-mcp
v0.5.0
Published
An MCP (Model Context Protocol) server that allows AI agents like Claude to interact with Redmine project management data.
Readme
@thelabnyc/redmine-mcp
An MCP (Model Context Protocol) server that allows AI agents like Claude to interact with Redmine project management data.
Features
- Create issues with full field support including fixed versions, categories, and custom fields
- Fetch issue details by ID, including subject, description, status, priority, and assignee
- Update issues: change status, assign users, add notes, set custom fields, and more
- Log time spent on issues with integrated time tracking
- List issues with general Redmine filters, saved queries, and optional includes
- List issues assigned to the current user, with filtering and sorting
- Search Redmine globally across issues, projects, wiki pages, attachments, and other content
- List projects accessible to the current user
- List saved queries visible to the current user
- List project members to find user IDs for assignments
- Manage attachments: fetch metadata, attach local files, download content, update descriptions, and delete files
- Manage issue relations: list, fetch, create, and delete Redmine relation records
- Discover metadata: list available statuses, trackers, priorities, versions, categories, and custom fields
- Retrieve change history (journals) with every request
- Optionally include attachments, watchers, relations, and child issues
Installation
npm install @thelabnyc/redmine-mcpOr clone and build from source:
git clone https://gitlab.com/thelabnyc/redmine-mcp.git
cd redmine-mcp
npm install
npm run buildConfiguration
The server requires two environment variables:
| Variable | Description | Example |
| ------------------------------ | --------------------------------------------------------------------------------------- | --------------------------- |
| REDMINE_URL | Base URL of your Redmine instance | https://mycompany.plan.io |
| REDMINE_API_KEY | Your Redmine API key | abc123def456... |
| REDMINE_MCP_FILE_ROOT | Directory that attachment upload paths must stay within; OS temp files are also allowed | /Users/me/redmine-files |
| REDMINE_MCP_MAX_UPLOAD_BYTES | Maximum local file size allowed for attach-file-to-issue in bytes | 104857600 |
Getting your Redmine API Key
- Log into your Redmine instance
- Go to My Account (click your name in the top right)
- In the right sidebar, find API access key
- Click Show to reveal your key, or Reset to generate a new one
Usage with Claude Code
Add the server to your Claude Code configuration:
Project-level configuration
Create or edit .claude/settings.json in your project:
{
"mcpServers": {
"redmine": {
"command": "npx",
"args": ["@thelabnyc/redmine-mcp"],
"env": {
"REDMINE_URL": "https://your-instance.plan.io",
"REDMINE_API_KEY": "your-api-key-here"
}
}
}
}User-level configuration
Add to ~/.claude.json to make available across all projects:
{
"mcpServers": {
"redmine": {
"command": "npx",
"args": ["@thelabnyc/redmine-mcp"],
"env": {
"REDMINE_URL": "https://your-instance.plan.io",
"REDMINE_API_KEY": "your-api-key-here"
}
}
}
}Using a local build
If you've cloned the repository:
{
"mcpServers": {
"redmine": {
"command": "node",
"args": ["/path/to/redmine-mcp/dist/cli.js"],
"env": {
"REDMINE_URL": "https://your-instance.plan.io",
"REDMINE_API_KEY": "your-api-key-here"
}
}
}
}Available Tools
get-issue
Fetch details about a Redmine issue by ID.
Parameters:
| Parameter | Type | Required | Description |
| -------------------- | ------- | -------- | ------------------------------------ |
| issueId | string | Yes | Issue ID (e.g., #12345 or 12345) |
| includeAttachments | boolean | No | Include file attachments |
| includeWatchers | boolean | No | Include watchers list |
| includeRelations | boolean | No | Include related issues |
| includeChildren | boolean | No | Include child issues |
Note: Change history (journals) is always included by default.
Example usage in Claude:
"Look up Redmine issue #12345 and summarize the recent activity"
get-attachment
Fetch metadata for a Redmine attachment by ID.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------- |
| attachmentId | number | Yes | Attachment record ID |
Example usage in Claude:
"Show me attachment 42"
attach-file-to-issue
Upload a local file to Redmine and attach it to an issue. The local file must be inside REDMINE_MCP_FILE_ROOT or the OS temp directory. Callers must summarize the target issue, file path, filename, description, content type, notes, and private-notes setting in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------------- | -------- | ---------------------------------------------------------------- |
| issueId | string/number | Yes | Issue ID (e.g., #12345 or 12345) |
| filePath | string | Yes | Local path to the file to upload |
| filename | string | No | Filename to store in Redmine; defaults to basename of filePath |
| description | string | No | Attachment description |
| contentType | string | No | Attachment content type |
| notes | string | No | Optional issue note to add with the upload |
| privateNotes | boolean | No | Make the issue note private |
Example usage in Claude:
"Attach
/tmp/error.logto issue #12345 as supporting evidence"
download-attachment
Download a Redmine attachment's content_url to a generated OS temp directory. The tool writes the file using the attachment filename from Redmine and returns the saved path.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------- |
| attachmentId | number | Yes | Attachment record ID |
Example usage in Claude:
"Download attachment 42"
update-attachment
Update a Redmine attachment description. Callers must summarize the attachment ID and new description in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| attachmentId | number | Yes | Attachment record ID |
| description | string | Yes | New attachment description |
Example usage in Claude:
"Update attachment 42's description"
delete-attachment
Delete a Redmine attachment by ID. Callers must summarize the attachment ID and delete action in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------- |
| attachmentId | number | Yes | Attachment record ID |
Example usage in Claude:
"Delete attachment 42"
list-issue-relations
List relation records for a Redmine issue. This exposes Redmine's GET /issues/:issue_id/relations.json endpoint.
Parameters:
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------- |
| issueId | string | Yes | Source issue ID (e.g., #12345 or 12345) |
Example usage in Claude:
"List the related tickets for issue #12345"
get-issue-relation
Fetch one Redmine issue relation record by relation ID.
Parameters:
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------ |
| relationId | number | Yes | Relation record ID |
Example usage in Claude:
"Show me relation 1819"
create-issue-relation
Create a Redmine issue relation. This exposes Redmine's POST /issues/:issue_id/relations.json endpoint. The issueId parameter is the source/from issue, and issueToId is the target/to issue.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | --------------------------------------------------- |
| issueId | string | Yes | Source/from issue ID (e.g., #12345 or 12345) |
| issueToId | number | Yes | Target/to related issue ID |
| relationType | string | No | Relation type; defaults to relates |
| delay | number | No | Delay in days for precedes or follows relations |
Supported relation types are relates, duplicates, duplicated, blocks, blocked, precedes, follows, copied_to, and copied_from.
Example usage in Claude:
"Relate issue #12345 to issue #67890"
delete-issue-relation
Delete a Redmine issue relation record by relation ID. Redmine does not provide an update endpoint for relation records; callers that need to change a relation should delete the old relation and create the desired new relation explicitly.
Parameters:
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ---------------------------- |
| relationId | number | Yes | Relation record ID to delete |
Example usage in Claude:
"Delete relation 1819"
add-issue-watcher
Add a watcher to a Redmine issue. Callers must summarize the target issue and watcher user in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| --------- | ------------- | -------- | ------------------------------------ |
| issueId | string/number | Yes | Issue ID (e.g., #12345 or 12345) |
| userId | number | Yes | Redmine user ID to add as a watcher |
Example usage in Claude:
"Add user 15 as a watcher on issue #12345"
remove-issue-watcher
Remove a watcher from a Redmine issue. Callers must summarize the target issue and watcher user in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| --------- | ------------- | -------- | -------------------------------------- |
| issueId | string/number | Yes | Issue ID (e.g., #12345 or 12345) |
| userId | number | Yes | Redmine user ID to remove as a watcher |
Example usage in Claude:
"Remove user 15 from the watcher list for issue #12345"
create-issue
Create a new Redmine issue.
Parameters:
| Parameter | Type | Required | Description |
| ---------------- | ------------- | -------- | --------------------------------------------------------- |
| projectId | string/number | Yes | Project identifier (string slug or numeric ID) |
| subject | string | Yes | Issue subject/title |
| description | string | No | Issue description |
| statusId | number | No | Status ID to set |
| priorityId | number | No | Priority ID to set |
| assignedToId | number | No | User ID to assign |
| trackerId | number | No | Tracker ID to set |
| parentIssueId | number | No | Parent issue ID |
| fixedVersionId | number | No | Fixed version ID to set |
| categoryId | number | No | Issue category ID to set |
| startDate | string | No | Start date (YYYY-MM-DD format) |
| dueDate | string | No | Due date (YYYY-MM-DD format) |
| doneRatio | number | No | Percent done (0-100) |
| estimatedHours | number | No | Estimated hours for the issue |
| isPrivate | boolean | No | Whether the issue is private |
| watcherUserIds | number[] | No | User IDs to add as issue watchers |
| customFields | array | No | Custom field values (see Custom Fields) |
Example usage in Claude:
"Create a bug in project 'my-app' titled 'Login page crashes on submit'"
update-issue
Update a Redmine issue. Can change fields, add notes, and log time spent.
Parameters:
| Parameter | Type | Required | Description |
| ---------------- | ------- | -------- | --------------------------------------------------------- |
| issueId | string | Yes | Issue ID (e.g., #12345 or 12345) |
| subject | string | No | New issue subject/title |
| description | string | No | New issue description |
| statusId | number | No | Status ID to set |
| priorityId | number | No | Priority ID to set |
| assignedToId | number | No | User ID to assign (use 0 to unassign) |
| trackerId | number | No | Tracker ID to set |
| parentIssueId | number | No | Parent issue ID |
| fixedVersionId | number | No | Fixed version ID to set |
| categoryId | number | No | Issue category ID to set |
| startDate | string | No | Start date (YYYY-MM-DD format) |
| dueDate | string | No | Due date (YYYY-MM-DD format) |
| doneRatio | number | No | Percent done (0-100) |
| estimatedHours | number | No | Estimated hours for the issue |
| notes | string | No | Comment/note to add to the issue journal |
| privateNotes | boolean | No | Make the notes private |
| logHours | number | No | Hours to log as a time entry |
| logActivityId | number | No | Activity ID for time entry (uses default if omitted) |
| logComments | string | No | Comments for the time entry |
| logSpentOn | string | No | Date for time entry (YYYY-MM-DD, defaults to today) |
| customFields | array | No | Custom field values (see Custom Fields) |
Example usage in Claude:
"Update issue #12345 to status 2 and assign to user 5"
"Add a note to issue #6789 saying 'Fixed the bug' and log 1.5 hours"
list-time-entries
List Redmine time entries with optional filters and pagination.
Parameters:
| Parameter | Type | Required | Description |
| ------------ | ------------- | -------- | ---------------------------------- |
| issueId | string/number | No | Filter by issue ID |
| projectId | string/number | No | Filter by project ID or identifier |
| userId | number/string | No | Filter by user ID, or "me" |
| spentOn | string | No | Exact spent date (YYYY-MM-DD) |
| from | string | No | Start date (YYYY-MM-DD) |
| to | string | No | End date (YYYY-MM-DD) |
| activityId | number | No | Filter by time entry activity ID |
| limit | number | No | Maximum results to return |
| offset | number | No | Number of results to skip |
Example usage in Claude:
"List my time entries for project 'my-app' from 2026-06-01 to 2026-06-30"
get-time-entry
Fetch one Redmine time entry by ID.
Parameters:
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ------------- |
| timeEntryId | number | Yes | Time entry ID |
Example usage in Claude:
"Show me time entry 100"
create-time-entry
Create a Redmine time entry for exactly one issue or numeric project ID. Callers must summarize the target issue or project, hours, activity, spent date, comments, and custom fields in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------------- | -------- | ------------------------------------------------------------- |
| issueId | string/number | No | Issue ID; exactly one of issueId or projectId is required |
| projectId | string/number | No | Numeric project ID; exactly one target is required |
| hours | number | Yes | Hours to log |
| activityId | number | No | Time entry activity ID |
| spentOn | string | No | Spent date (YYYY-MM-DD) |
| comments | string | No | Time entry comments |
| customFields | array | No | Custom field values |
Example usage in Claude:
"Log 2.5 hours on issue #12345 for development today"
update-time-entry
Update a Redmine time entry. Redmine returns an empty response to the update request, so this tool fetches and returns the refreshed time entry after the update. Callers must summarize the time entry ID and all planned changes in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ----------------------- |
| timeEntryId | number | Yes | Time entry ID |
| hours | number | No | Hours to set |
| activityId | number | No | Time entry activity ID |
| spentOn | string | No | Spent date (YYYY-MM-DD) |
| comments | string | No | Time entry comments |
| customFields | array | No | Custom field values |
Example usage in Claude:
"Update time entry 100 to 3 hours"
delete-time-entry
Delete a Redmine time entry by ID. Callers must summarize the time entry ID and delete action in human-readable form and get explicit user confirmation before using this tool.
Parameters:
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ------------- |
| timeEntryId | number | Yes | Time entry ID |
Example usage in Claude:
"Delete time entry 100"
list-time-entry-activities
List Redmine time entry activities. Use this to discover activity IDs before creating or updating time entries.
Parameters: None.
Example usage in Claude:
"List available time entry activities"
list-my-issues
List issues assigned to the current user, with optional filtering and sorting.
Parameters:
| Parameter | Type | Required | Description |
| ----------- | ------------- | -------- | ----------------------------------------------------- |
| statusId | number/string | No | Filter by status ID, or "open", "closed", "*" |
| projectId | string/number | No | Filter by project |
| limit | number | No | Maximum results to return |
| offset | number | No | Number of results to skip for pagination |
| sort | string | No | Sort order (default: priority:desc,updated_on:desc) |
Example usage in Claude:
"What issues are assigned to me?"
list-issues
List Redmine issues using general issue filters. This calls Redmine GET /issues.json and returns full issue objects plus total_count, offset, and limit metadata. Use list-queries to discover saved query IDs for the queryId parameter.
Parameters:
| Parameter | Type | Required | Description |
| -------------------- | ------------- | -------- | --------------------------------------------------- |
| issueIds | array | No | Issue IDs; strings may include # prefixes |
| projectId | string/number | No | Filter by project ID or identifier |
| trackerId | number | No | Filter by tracker ID |
| statusId | number/string | No | Filter by status ID, or "open", "closed", "*" |
| assignedToId | number/string | No | Filter by assignee user ID, or "me" |
| parentId | number | No | Filter by parent issue ID |
| createdOn | string | No | Redmine created_on filter expression |
| updatedOn | string | No | Redmine updated_on filter expression |
| customFields | array | No | Custom field filters, serialized as cf_<id>=value |
| queryId | number | No | Saved query ID; use list-queries to discover IDs |
| includeAttachments | boolean | No | Include issue attachments |
| includeRelations | boolean | No | Include issue relations |
| sort | string | No | Sort order, e.g. updated_on:desc |
| limit | number | No | Maximum results to return |
| offset | number | No | Number of results to skip for pagination |
Example usage in Claude:
"List open bugs in project 'my-app' updated this month"
search-redmine
Search Redmine globally using GET /search.json. Returns results, total_count, offset, and limit.
Parameters:
| Parameter | Type | Required | Description |
| ------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| query | string | Yes | Search query, sent as Redmine q |
| scope | string | No | Optional Redmine search scope |
| allWords | boolean | No | true requires all words; false explicitly disables all words |
| titlesOnly | boolean | No | true searches titles only; false is omitted |
| issues | boolean | No | true narrows scope to issues; false is omitted |
| news | boolean | No | true narrows scope to news; false is omitted |
| documents | boolean | No | true narrows scope to documents; false is omitted |
| changesets | boolean | No | true narrows scope to changesets; false is omitted |
| wikiPages | boolean | No | true narrows scope to wiki pages; false is omitted |
| messages | boolean | No | true narrows scope to messages; false is omitted |
| projects | boolean | No | true narrows scope to projects; false is omitted |
| attachments | boolean/"only" | No | true includes attachment content, false disables attachment search, "only" searches attachments only |
| openIssues | boolean | No | true restricts issue results to open issues; false is omitted |
| limit | number | No | Maximum results to return |
| offset | number | No | Number of results to skip for pagination |
Example usage in Claude:
"Search Redmine for cache invalidation across issues and wiki pages"
list-projects
List all projects accessible to the current user.
Parameters:
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------- |
| limit | number | No | Maximum results to return |
| offset | number | No | Number of results to skip for pagination |
Example usage in Claude:
"What Redmine projects do I have access to?"
list-queries
List saved Redmine issue queries visible to the current user.
Parameters:
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------- |
| limit | number | No | Maximum results to return |
| offset | number | No | Number of results to skip for pagination |
Example usage in Claude:
"What saved Redmine queries can I use?"
list-project-members
List all members of a Redmine project. Use this to find user IDs for assigning issues.
Parameters:
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------------------------- |
| projectId | string | Yes | Project ID or identifier (e.g., my-project or 1) |
| limit | number | No | Maximum number of members to return (default 25) |
| offset | number | No | Number of members to skip for pagination |
Example usage in Claude:
"Who can I assign issues to in project 'my-project'?"
list-project-versions
List versions for a Redmine project. Use this to discover fixed version IDs before setting fixedVersionId on create-issue or update-issue.
Parameters:
| Parameter | Type | Required | Description |
| ----------- | ------------- | -------- | ---------------------------------------------- |
| projectId | string/number | Yes | Project identifier (string slug or numeric ID) |
Example usage in Claude:
"What versions are available for project 'my-app'?"
list-project-issue-categories
List issue categories for a Redmine project. Use this to discover category IDs before setting categoryId on create-issue or update-issue.
Parameters:
| Parameter | Type | Required | Description |
| ----------- | ------------- | -------- | ---------------------------------------------- |
| projectId | string/number | Yes | Project identifier (string slug or numeric ID) |
Example usage in Claude:
"What issue categories are available for project 'my-app'?"
list-issue-statuses
List all available issue statuses. Use this to find valid status IDs when updating issues.
Parameters: None
Example usage in Claude:
"What statuses can I set for issues?"
list-trackers
List all available trackers (e.g., Bug, Feature, Support). Use this to find valid tracker IDs.
Parameters: None
Example usage in Claude:
"What trackers are available?"
list-issue-priorities
List all available issue priorities. Use this to find valid priority IDs.
Parameters: None
Example usage in Claude:
"What priority levels can I set?"
list-project-custom-fields
List the custom fields available for issues in a given project. Use this to discover custom field IDs before setting them on create-issue or update-issue.
Parameters:
| Parameter | Type | Required | Description |
| ----------- | ------------- | -------- | ---------------------------------------------- |
| projectId | string/number | Yes | Project identifier (string slug or numeric ID) |
Example usage in Claude:
"What custom fields are available for project 'my-app'?"
whoami
Get information about the currently authenticated Redmine user.
Parameters: None
Example usage in Claude:
"Who am I logged in as in Redmine?"
Custom Fields
The customFields parameter on create-issue and update-issue accepts an array of objects:
[
{ "id": 1, "value": "Sprint 5" },
{ "id": 2, "value": ["option1", "option2"] }
]Use list-project-custom-fields to discover available custom field IDs for a project. Multi-value fields accept an array of strings.
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Build
npm run build
# Lint
npm run lintLicense
ISC
