Expand redMCP safe issue operations and HTTP handling
This commit is contained in:
+127
-7
@@ -45,7 +45,6 @@ $created = $client->createIssue([
|
||||
]);
|
||||
|
||||
$client->updateIssue((int) $created['id'], ['notes' => 'Follow-up note']);
|
||||
$client->deleteIssue((int) $created['id']);
|
||||
```
|
||||
|
||||
Native Redmine search is exposed separately from issue filtering. Use
|
||||
@@ -101,6 +100,33 @@ $users = $client->users(['status' => 1, 'limit' => 25]);
|
||||
$user = $client->user(1, ['include' => 'memberships,groups']);
|
||||
```
|
||||
|
||||
MCP clients that do not know the exact Redmine project identifier should call
|
||||
`redmine_find_project` first. Redmine identifiers are often slug-like strings
|
||||
and are not always the same as the display name.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "redmine_find_project",
|
||||
"arguments": {
|
||||
"query": "Quality Tracker"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `matches[0].project_id_to_use` or `recommended_project_id` when it is
|
||||
non-null in later calls:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "redmine_create_issue",
|
||||
"arguments": {
|
||||
"project_id": "quality-tracker",
|
||||
"subject": "Front warehouse deadbolt key gets stuck in lock",
|
||||
"description": "Problem: The deadbolt on the front door is sticking.\n\n~HermesBot"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`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:
|
||||
@@ -120,6 +146,58 @@ 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.
|
||||
|
||||
Issue structure operations are exposed explicitly. Issue create/update preserve
|
||||
Redmine structure fields such as `parent_issue_id`, `parent_id`,
|
||||
`category_id`, and `uploads`, so callers can create subtasks, categorize issues,
|
||||
and attach previously uploaded files without falling through the bundled API
|
||||
client's sanitized XML helpers.
|
||||
|
||||
```php
|
||||
$upload = $client->uploadAttachment([
|
||||
'path' => '/tmp/redmine-note.txt',
|
||||
'content_type' => 'text/plain',
|
||||
]);
|
||||
|
||||
$pdfUpload = $client->uploadAttachment([
|
||||
'data_url' => 'data:application/pdf;base64,...',
|
||||
]);
|
||||
|
||||
$fileEnvelopeUpload = $client->uploadAttachment([
|
||||
'file' => [
|
||||
'name' => 'quote.pdf',
|
||||
'mime_type' => 'application/pdf',
|
||||
'data' => 'JVBERi0xLjQK...',
|
||||
],
|
||||
]);
|
||||
|
||||
$parent = $client->createIssue([
|
||||
'project_id' => 'fud-nohelpdesk',
|
||||
'subject' => 'Parent example',
|
||||
]);
|
||||
|
||||
$child = $client->createIssue([
|
||||
'project_id' => 'fud-nohelpdesk',
|
||||
'subject' => 'Child example',
|
||||
'parent_issue_id' => (int) $parent['id'],
|
||||
'uploads' => [$upload],
|
||||
]);
|
||||
|
||||
$client->createIssueRelation((int) $parent['id'], [
|
||||
'issue_to_id' => (int) $child['id'],
|
||||
]);
|
||||
```
|
||||
|
||||
The MCP server exposes explicit tools for issue relations, children/parents,
|
||||
project issue categories, and attachments. It intentionally does not expose
|
||||
tools for deleting issues, projects, users, categories, or attachments. The only
|
||||
removal tool is `redmine_remove_issue_relation`, which unlinks the relationship
|
||||
only and does not delete either issue.
|
||||
|
||||
For MCP attachment uploads, prefer `redmine_upload_attachment` with `path`,
|
||||
`base64_content`, `data_url`, or a `file` envelope. PDFs and other non-image
|
||||
files should be passed as file/data URL inputs such as
|
||||
`data:application/pdf;base64,...`, not as `image_url`.
|
||||
|
||||
## MCP server
|
||||
|
||||
`redMCP` can run as either a stdio MCP server or a network MCP server. It reads
|
||||
@@ -129,12 +207,16 @@ Redmine credentials from environment variables or `redMCP/.env`.
|
||||
redMCP/bin/redmcp-server.php
|
||||
```
|
||||
|
||||
For local network testing, run the Streamable HTTP server:
|
||||
For local 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
|
||||
MCP_SERVER_TOKEN=test-token redMCP/bin/redmcp-http-server.php --port 8765
|
||||
```
|
||||
|
||||
For LAN testing, pass `--host 0.0.0.0` deliberately. Browser-origin requests
|
||||
from non-localhost origins require `MCP_ALLOWED_ORIGINS` as a comma-separated
|
||||
allowlist.
|
||||
|
||||
Generate a bearer token with:
|
||||
|
||||
```sh
|
||||
@@ -153,10 +235,44 @@ Example Streamable HTTP request:
|
||||
curl -sS \
|
||||
-H 'Authorization: Bearer test-token' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
http://127.0.0.1:8765/mcp \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
||||
```
|
||||
|
||||
When the request `Accept` header includes `text/event-stream`, redMCP returns a
|
||||
short SSE response with one `message` event per JSON-RPC response. Clients that
|
||||
send only `Accept: application/json` receive the traditional JSON response.
|
||||
`GET /mcp` returns `405 Method Not Allowed` with `Allow: POST`; redMCP does not
|
||||
currently expose standalone server-to-client notification streams.
|
||||
|
||||
Issue create and update tools accept either canonical nested `fields` or common
|
||||
issue fields at the top level. These two create calls are equivalent:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "redmine_create_issue",
|
||||
"arguments": {
|
||||
"fields": {
|
||||
"project_id": "quality-tracker",
|
||||
"subject": "Front warehouse deadbolt key gets stuck in lock",
|
||||
"description": "Problem: The deadbolt on the front door is sticking.\n\n~HermesBot"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "redmine_create_issue",
|
||||
"arguments": {
|
||||
"project_id": "quality-tracker",
|
||||
"subject": "Front warehouse deadbolt key gets stuck in lock",
|
||||
"description": "Problem: The deadbolt on the front door is sticking.\n\n~HermesBot"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
HTTP server process helpers:
|
||||
|
||||
```sh
|
||||
@@ -195,15 +311,19 @@ Example stdio client configuration:
|
||||
```
|
||||
|
||||
Both transports expose tools for native Redmine project listing/detail, project
|
||||
memberships, users, 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`.
|
||||
memberships, users, filtering/search, issue create/update, issue relations,
|
||||
subtasks/parents, project issue categories, attachments, 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`.
|
||||
|
||||
Run the local no-network query normalizer checks with:
|
||||
|
||||
```sh
|
||||
php redMCP/bin/test-query-normalizer.php
|
||||
php redMCP/bin/test-redmine-structure.php
|
||||
php redMCP/bin/test-mcp-http-handler.php
|
||||
```
|
||||
|
||||
## Test instance
|
||||
|
||||
Reference in New Issue
Block a user