Add redmine-communicator skill docs and setup tooling
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
## Cleanup Notes ~ May 4, 2026
|
||||||
|
|
||||||
|
This repository currently mixes multiple partially finished workstreams. The
|
||||||
|
goal is to recover to a clean, reviewable git state with focused commits so
|
||||||
|
normal development can continue.
|
||||||
|
|
||||||
|
## Scope and constraints
|
||||||
|
|
||||||
|
- `TODO.md` is long-horizon context and is out of scope for this cleanup pass.
|
||||||
|
- `redMCP/` is actively used on this machine; do not delete files in that tree
|
||||||
|
and do not stop running `redMCP` processes during cleanup.
|
||||||
|
- `redMCP/startProd.sh` is a local convenience script and is intentionally not a
|
||||||
|
project artifact for this cleanup. Ignore it.
|
||||||
|
- Use `plugins/redmine_contacts_helpdesk/LOCAL_CHANGELOG.md` as a primary anchor
|
||||||
|
for reconstructing intent and grouping related changes.
|
||||||
|
|
||||||
|
## Recovered change groups
|
||||||
|
|
||||||
|
The current dirty tree appears to contain these distinct units:
|
||||||
|
|
||||||
|
1. Helpdesk issue API `include=helpdesk` patch and docs/manifest.
|
||||||
|
2. Post-import automation and validator/worker hardening.
|
||||||
|
3. Semantic index service, deployment assets, tests, and runbooks.
|
||||||
|
4. redMCP feature expansion (HTTP handler/server, client/dispatcher updates,
|
||||||
|
tests, docs).
|
||||||
|
5. Skill metadata/docs under `skills/redmine-communicator/`.
|
||||||
|
|
||||||
|
## Working checklist
|
||||||
|
|
||||||
|
- [x] Inventory all modified and untracked files.
|
||||||
|
- [x] Identify likely project groupings for clean commits.
|
||||||
|
- [x] Confirm `LOCAL_CHANGELOG.md` aligns with Helpdesk API patch files.
|
||||||
|
- [ ] Stage and commit Helpdesk API patch as a focused unit.
|
||||||
|
- [ ] Stage and commit post-import automation as a focused unit.
|
||||||
|
- [ ] Stage and commit semantic index files as a focused unit.
|
||||||
|
- [ ] Stage and commit redMCP feature updates as a focused unit.
|
||||||
|
- [ ] Stage and commit redmine-communicator skill files (optional split).
|
||||||
|
- [ ] Run targeted syntax/tests for each committed unit.
|
||||||
|
- [ ] Confirm final worktree state and note any intentionally uncommitted files.
|
||||||
|
|
||||||
|
## Notes to keep in mind
|
||||||
|
|
||||||
|
- Do not commit secrets (`.env`, tokens, credentials).
|
||||||
|
- `semantic_index/search.sh.md` looks like conversational scratch text; treat as
|
||||||
|
optional/non-essential unless deliberately kept.
|
||||||
|
- If a file belongs to multiple units, prefer smallest safe unit first and
|
||||||
|
document rationale in commit messages.
|
||||||
@@ -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
@@ -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())
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SKILL = ROOT / "skills" / "redmine-communicator"
|
||||||
|
SETUP = SKILL / "scripts" / "setup_redmcp.py"
|
||||||
|
|
||||||
|
|
||||||
|
class RedmineCommunicatorSkillTest(unittest.TestCase):
|
||||||
|
def test_skill_files_exist_and_reference_redmcp_safety_rules(self):
|
||||||
|
skill_md = (SKILL / "SKILL.md").read_text()
|
||||||
|
reference = (SKILL / "references" / "redmcp-tools.md").read_text()
|
||||||
|
|
||||||
|
self.assertIn("redmine-communicator", skill_md)
|
||||||
|
self.assertIn("redMCP", skill_md)
|
||||||
|
self.assertIn("send_helpdesk_email=true", skill_md)
|
||||||
|
self.assertIn("redmine_send_helpdesk_response", reference)
|
||||||
|
self.assertIn("customer-visible", reference)
|
||||||
|
|
||||||
|
def test_setup_script_dry_run_prints_stdio_config(self):
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(SETUP),
|
||||||
|
"--redmine-url",
|
||||||
|
"http://redmine.example.test",
|
||||||
|
"--redmine-api-key",
|
||||||
|
"secret-key",
|
||||||
|
"--transport",
|
||||||
|
"stdio",
|
||||||
|
],
|
||||||
|
cwd=ROOT,
|
||||||
|
universal_newlines=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(0, result.returncode, result.stderr)
|
||||||
|
self.assertIn("mode=dry-run", result.stdout)
|
||||||
|
self.assertIn("redmcp-server.php", result.stdout)
|
||||||
|
self.assertNotIn("secret-key", result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user