171 lines
5.5 KiB
Markdown
171 lines
5.5 KiB
Markdown
This project is for creating a simple library and MCP server for handling Redmine, particularly for when Redmine is being used for customer service/support.
|
|
|
|
This is a private project for now, as it pertains to an installation of Redmine 3.4.4-stable used by LDR, which also uses some outdated plugins. Notably, the outdated pluggins in use are both from Redmine Up (contacts, helpdesk/crm).
|
|
|
|
This is about creating the basic tools that an agent would need in order to interact with and automate LDR's redmine communications. Some of the functionality will extend beyond Redmine.
|
|
|
|
## Notable issues to be aware of
|
|
|
|
Projects which have the modules "contacts" or "contacts_helpdesk" enabled have the Helpdesk plugins enabled. That means a few things:
|
|
|
|
The regular API call to fetch an issue will not necessarily return the helpdesk information. Instead, it may claim that the issue's author or journals were created by "anonymous". When getting these issues, we need to use a different way to handle it.
|
|
|
|
The project in /home/iadnah/redmine/ is related to this problem.
|
|
|
|
## Client shape
|
|
|
|
`RedMCP\RedmineClient` wraps the normal Redmine REST client and composes
|
|
Helpdesk context in application logic instead of modifying Redmine's core issue
|
|
API.
|
|
|
|
```php
|
|
$client = RedMCP\RedmineClient::fromCredentials('http://192.168.50.170', $apiKey);
|
|
$context = $client->issueWithHelpdesk(39858);
|
|
```
|
|
|
|
The returned array has:
|
|
|
|
- `issue`: the normal `/issues/:id.json` response
|
|
- `helpdesk.ticket`: metadata from `/helpdesk_search/issues/:id/ticket`, or
|
|
`null`
|
|
- `helpdesk.journal_messages`: metadata from
|
|
`/helpdesk_search/issues/:id/messages`
|
|
|
|
Basic issue CRUD is exposed on the same wrapper:
|
|
|
|
```php
|
|
$issues = $client->issues(['project_id' => 'customer-service', 'status_id' => 'open', 'limit' => 10]);
|
|
$filtered = $client->filterIssues(['query_id' => 12, 'limit' => 25]);
|
|
$issue = $client->issue(39858);
|
|
|
|
$created = $client->createIssue([
|
|
'project_id' => 78,
|
|
'subject' => 'Example issue from redMCP',
|
|
'description' => 'Created through the Redmine REST API wrapper.',
|
|
]);
|
|
|
|
$client->updateIssue((int) $created['id'], ['notes' => 'Follow-up note']);
|
|
$client->deleteIssue((int) $created['id']);
|
|
```
|
|
|
|
Native Redmine search is exposed separately from issue filtering. Use
|
|
`filterIssues()` or `issues()` when you already know the structured filters.
|
|
Use `search()` or `searchIssues()` when you want Redmine's built-in text search:
|
|
|
|
```php
|
|
$results = $client->search('power supply', [
|
|
'all_words' => '1',
|
|
'limit' => 10,
|
|
]);
|
|
|
|
$issueResults = $client->searchIssues('power supply', [
|
|
'project_id' => 'customer-service',
|
|
'open_issues' => '1',
|
|
'limit' => 10,
|
|
]);
|
|
```
|
|
|
|
`updateIssue()` is intentionally safe by default: on Helpdesk-backed issues, a
|
|
normal Redmine note does **not** send an email to the customer. To send through
|
|
the Helpdesk plugin, opt in explicitly:
|
|
|
|
```php
|
|
$client->updateIssue(39858, [
|
|
'notes' => 'Customer-visible response.',
|
|
], [
|
|
'send_helpdesk_email' => true,
|
|
]);
|
|
|
|
// Equivalent explicit form:
|
|
$client->sendHelpdeskIssueResponse(39858, 'Customer-visible response.');
|
|
```
|
|
|
|
Use the default non-email update for internal notes, status/category/assignee
|
|
changes, and automation cleanup. Use the Helpdesk email path only when the
|
|
caller deliberately wants the customer to receive mail.
|
|
|
|
## MCP server
|
|
|
|
`redMCP` can run as either a stdio MCP server or a network MCP server. It reads
|
|
Redmine credentials from environment variables or `redMCP/.env`.
|
|
|
|
```sh
|
|
redMCP/bin/redmcp-server.php
|
|
```
|
|
|
|
For local network testing, run the Streamable HTTP server:
|
|
|
|
```sh
|
|
MCP_SERVER_TOKEN=test-token redMCP/bin/redmcp-http-server.php --host 0.0.0.0 --port 8765
|
|
```
|
|
|
|
Generate a bearer token with:
|
|
|
|
```sh
|
|
redMCP/bin/generate-bearer-token.php --env-line
|
|
```
|
|
|
|
The network endpoint defaults to `/mcp` and requires:
|
|
|
|
```text
|
|
Authorization: Bearer <MCP_SERVER_TOKEN>
|
|
```
|
|
|
|
Example Streamable HTTP request:
|
|
|
|
```sh
|
|
curl -sS \
|
|
-H 'Authorization: Bearer test-token' \
|
|
-H 'Content-Type: application/json' \
|
|
http://127.0.0.1:8765/mcp \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
|
```
|
|
|
|
HTTP server process helpers:
|
|
|
|
```sh
|
|
redMCP/bin/redmcp-http-server.php --status
|
|
redMCP/bin/redmcp-http-server.php --stop
|
|
redMCP/bin/redmcp-http-server.php --pid-file /tmp/redmcp-http-server.pid --status
|
|
```
|
|
|
|
The default PID file is `/tmp/redmcp-http-server.pid`. A second server start
|
|
fails if the PID file points to a live process. Use `--force` only to replace a
|
|
stale PID file.
|
|
|
|
Debug logging is disabled by default. To record full MCP params/tool arguments
|
|
as JSONL during local testing:
|
|
|
|
```sh
|
|
MCP_SERVER_TOKEN=test-token redMCP/bin/redmcp-http-server.php \
|
|
--debug-log /tmp/redmcp-mcp.log
|
|
```
|
|
|
|
Debug logs may include customer text, issue notes, search terms, email content,
|
|
and IDs. Authorization headers, bearer tokens, and Redmine API keys are not
|
|
logged.
|
|
|
|
Example stdio client configuration:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"redmcp": {
|
|
"command": "/home/iadnah/redmine/redMCP/bin/redmcp-server.php"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Both transports expose tools for native Redmine project listing/detail,
|
|
filtering/search, issue CRUD, Helpdesk-aware issue reads, and explicit Helpdesk
|
|
email responses. Tools that can send customer-visible mail require an explicit
|
|
tool call such as `redmine_send_helpdesk_response` or `redmine_update_issue`
|
|
with `send_helpdesk_email=true`.
|
|
|
|
## Test instance
|
|
|
|
A working test copy of Redmine is available on the LAN at `192.168.50.170`.
|
|
The related Redmine plugin forks, helper scripts, and operational docs live in
|
|
the parent repository.
|