Add redmine-communicator skill docs and setup tooling
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user