Add redmine-communicator skill docs and setup tooling

This commit is contained in:
Jason Thistlethwaite
2026-05-04 09:50:17 -04:00
parent 4c931bae1a
commit 42fc8318fa
6 changed files with 430 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
---
name: redmine-communicator
description: Use when an agent needs to install, configure, or operate the redMCP MCP server to communicate with Redmine, including Helpdesk-aware issue reads, safe issue updates, attachment handling, and explicit customer-visible Helpdesk responses.
---
# Redmine Communicator
## Overview
Use this skill to connect an agent to Redmine through `redMCP`, a PHP MCP server
that wraps Redmine's REST API and LDR's Helpdesk-aware extensions.
Use `redMCP` instead of ad hoc HTTP calls when the task involves Redmine issues,
projects, users, attachments, project categories, issue relations, or Helpdesk
customer communications.
## Setup Workflow
1. Install or stage `redMCP` from this repository:
```sh
python3 skills/redmine-communicator/scripts/setup_redmcp.py \
--redmine-url http://redmine.example.test \
--redmine-api-key "$REDMINE_API_KEY"
```
The default is dry-run. Add `--apply` to copy files and write `.env`.
2. Configure the MCP client with the printed stdio config. The command points to:
```text
<install-dir>/bin/redmcp-server.php
```
3. Verify the server from the agent or client by listing tools. If using the
Streamable HTTP transport, generate and configure `MCP_SERVER_TOKEN`.
4. Read [references/redmcp-tools.md](references/redmcp-tools.md) before making
customer-visible changes or using less common tools.
## Operating Rules
- Prefer read-only tools first: list/search projects, issues, users, categories,
memberships, and Helpdesk context before changing anything.
- For Helpdesk-backed issues, use `redmine_issue_with_helpdesk` instead of a
plain issue read when customer identity or email context matters.
- `redmine_update_issue` is internal-note safe by default. It does **not** send
Helpdesk customer email unless `options.send_helpdesk_email=true` is passed.
- Use `redmine_send_helpdesk_response` only when the user explicitly wants a
customer-visible Helpdesk email.
- Do not invent Redmine project identifiers, tracker ids, category ids, or user
ids. Discover them with redMCP tools first.
- Do not put Redmine API keys, MCP bearer tokens, passwords, or customer secrets
in logs, committed files, or final answers.
## Common Tool Choices
- Find work: `redmine_list_issues`, `redmine_search`, `redmine_search_issues`.
- Read one issue: `redmine_get_issue`; use `redmine_issue_with_helpdesk` for
Helpdesk/customer context.
- Internal update: `redmine_update_issue` with fields only.
- Customer reply: `redmine_send_helpdesk_response`, or
`redmine_update_issue` with `options.send_helpdesk_email=true`.
- Attachments: `redmine_upload_attachment`, then include returned upload token
in issue create/update; `redmine_download_attachment` only to safe local paths.
- Structure: issue relation, parent/child, project category, project membership,
project, and user tools are available through MCP.
## Troubleshooting
- If `redmcp-server.php` fails immediately, check that `.env` contains
`REDMINE_URL` and `REDMINE_API_KEY`.
- If PHP autoloading fails, run `composer install` in the `redMCP` install
directory, or install from a package that includes `vendor/`.
- If HTTP transport is used, `MCP_SERVER_TOKEN` is required and clients must send
`Authorization: Bearer <token>`.
- Debug logging can include customer text and issue notes. Enable it only for
local troubleshooting and store logs somewhere private.
@@ -0,0 +1,12 @@
interface:
display_name: "Redmine Communicator"
short_description: "Connect agents to Redmine through redMCP"
default_prompt: "Use $redmine-communicator to connect to Redmine, inspect issues, and make safe Helpdesk-aware updates."
dependencies:
tools:
- type: "mcp"
value: "redmcp"
description: "Local redMCP server exposing Redmine and Helpdesk-aware tools."
transport: "stdio"
policy:
allow_implicit_invocation: true
@@ -0,0 +1,125 @@
# redMCP Tool Reference
Use this reference after the `redmine-communicator` skill triggers and the task
requires specific tool selection or setup details.
## Runtime
Required environment:
```text
REDMINE_URL=http://redmine.example.test
REDMINE_API_KEY=...
```
For Streamable HTTP MCP:
```text
MCP_SERVER_TOKEN=...
```
Stdio server:
```sh
redMCP/bin/redmcp-server.php
```
HTTP server:
```sh
MCP_SERVER_TOKEN=... redMCP/bin/redmcp-http-server.php --host 0.0.0.0 --port 8765
```
HTTP endpoint defaults to `/mcp` and requires `Authorization: Bearer <token>`.
## Read Tools
- `redmine_list_projects`: list projects.
- `redmine_get_project`: fetch one project by id or identifier.
- `redmine_list_project_memberships`: users/groups and roles for a project.
- `redmine_list_users`, `redmine_get_user`: user discovery.
- `redmine_list_issues`: structured issue filters with friendly fields like
`project_id`, `status`, `updated`, `created`, `sort`, `limit`, and `page`.
- `redmine_search`, `redmine_search_issues`: Redmine native text search.
- `redmine_get_issue`: plain issue read.
- `redmine_issue_with_helpdesk`: issue plus Helpdesk ticket/contact/messages.
- `redmine_list_project_issue_categories`, `redmine_get_issue_category`.
- `redmine_get_attachment`.
## Write Tools
- `redmine_create_issue`: create an issue.
- `redmine_update_issue`: update fields or add an internal note. Helpdesk email
is opt-in with `options.send_helpdesk_email=true`.
- `redmine_send_helpdesk_response`: send a customer-visible Helpdesk email.
- `redmine_create_issue_relation`, `redmine_remove_issue_relation`.
- `redmine_set_issue_parent`, `redmine_clear_issue_parent`.
- `redmine_create_issue_category`, `redmine_update_issue_category`.
- `redmine_upload_attachment`, `redmine_download_attachment`,
`redmine_update_attachment`.
## Safety Notes
- Customer-visible email requires explicit intent. Prefer internal notes unless
the user asks to email the customer.
- Deletion tools for issues, projects, users, categories, and attachments are
intentionally not exposed. Relation removal only unlinks the relationship.
- For Helpdesk workflows, read with `redmine_issue_with_helpdesk` before
replying so the agent sees customer/contact context.
- For file uploads, use `redmine_upload_attachment` with a path, base64 content,
data URL, or file envelope. Use data/file inputs for PDFs and non-image files.
- `redmine_download_attachment` requires an explicit path under `/tmp` or the
repository tree and limits optional base64 response size.
## Example MCP Client Config
```json
{
"mcpServers": {
"redmcp": {
"command": "/path/to/redMCP/bin/redmcp-server.php"
}
}
}
```
## Example Calls
Read Helpdesk-aware issue context:
```json
{
"name": "redmine_issue_with_helpdesk",
"arguments": {
"issue_id": 39858,
"include": ["journals", "attachments"],
"message_limit": 100
}
}
```
Internal note:
```json
{
"name": "redmine_update_issue",
"arguments": {
"issue_id": 39858,
"fields": {
"notes": "Internal follow-up note."
}
}
}
```
Customer-visible Helpdesk reply:
```json
{
"name": "redmine_send_helpdesk_response",
"arguments": {
"issue_id": 39858,
"content": "Customer-visible response text."
}
}
```
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Install or stage redMCP for another agent's MCP client."""
import argparse
import json
import os
import shutil
import stat
import subprocess
import sys
from pathlib import Path
DEFAULT_INSTALL_DIR = Path.home() / ".local" / "share" / "redmcp"
def main():
parser = argparse.ArgumentParser(description="Install redMCP and print MCP client configuration.")
parser.add_argument("--source-redmcp", type=Path, default=repo_root() / "redMCP")
parser.add_argument("--install-dir", type=Path, default=DEFAULT_INSTALL_DIR)
parser.add_argument("--redmine-url", required=True)
parser.add_argument("--redmine-api-key", required=True)
parser.add_argument("--transport", choices=["stdio", "http"], default="stdio")
parser.add_argument("--mcp-server-token", default="")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8765)
parser.add_argument("--apply", action="store_true", help="Copy files and write .env. Default is dry-run.")
args = parser.parse_args()
if args.transport == "http" and not args.mcp_server_token:
print("error: --mcp-server-token is required for --transport http", file=sys.stderr)
return 2
if not args.source_redmcp.is_dir():
print("error: source redMCP directory not found: {0}".format(args.source_redmcp), file=sys.stderr)
return 1
print("mode={0}".format("apply" if args.apply else "dry-run"))
print("source_redmcp={0}".format(args.source_redmcp))
print("install_dir={0}".format(args.install_dir))
if args.apply:
copy_redmcp(args.source_redmcp, args.install_dir)
write_env(args.install_dir / ".env", args)
ensure_executable(args.install_dir / "bin" / "redmcp-server.php")
ensure_executable(args.install_dir / "bin" / "redmcp-http-server.php")
else:
print("would copy redMCP files")
print("would write {0} with REDMINE_URL and REDMINE_API_KEY".format(args.install_dir / ".env"))
print_runtime_notes(args)
print_mcp_config(args)
return 0
def repo_root():
return Path(__file__).resolve().parents[3]
def copy_redmcp(source, target):
if target.exists():
shutil.rmtree(str(target))
ignore = shutil.ignore_patterns(".env", ".cache", "__pycache__", "*.pyc", "*.log")
shutil.copytree(str(source), str(target), ignore=ignore)
def write_env(path, args):
lines = [
"REDMINE_URL={0}".format(args.redmine_url.rstrip("/")),
"REDMINE_API_KEY={0}".format(args.redmine_api_key),
]
if args.transport == "http":
lines.append("MCP_SERVER_TOKEN={0}".format(args.mcp_server_token))
path.write_text("\n".join(lines) + "\n")
os.chmod(str(path), 0o600)
def ensure_executable(path):
if path.exists():
mode = path.stat().st_mode
path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
def print_runtime_notes(args):
composer = shutil.which("composer")
vendor = args.install_dir / "vendor" / "autoload.php"
if args.apply and not vendor.exists():
if composer:
print("vendor/autoload.php missing; run: cd {0} && composer install".format(args.install_dir))
else:
print("vendor/autoload.php missing and composer was not found on PATH")
elif not args.apply:
print("after apply, ensure vendor/autoload.php exists or run composer install in the install dir")
def print_mcp_config(args):
if args.transport == "stdio":
config = {
"mcpServers": {
"redmcp": {
"command": str(args.install_dir / "bin" / "redmcp-server.php")
}
}
}
print(json.dumps(config, indent=2))
return
command = "{0} --host {1} --port {2}".format(
args.install_dir / "bin" / "redmcp-http-server.php",
args.host,
args.port,
)
print("start_http_server={0}".format(command))
print("mcp_url=http://{0}:{1}/mcp".format(args.host, args.port))
print("authorization=Bearer <MCP_SERVER_TOKEN>")
if __name__ == "__main__":
raise SystemExit(main())