MCP: ticket links and similar-ticket search (#113)
Lint / PHP (phpcs PSR-12) (push) Successful in 48s
Lint / JS (eslint) (push) Successful in 17s
Lint / PHP requirements (version + extensions) (push) Successful in 49s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 3m15s
Lint / Deploy (push) Successful in 4s

- get_ticket now returns `links` (blocks / blocked_by / relates_to /
  duplicates / duplicated_by, phrased from this ticket's side, limited to
  linked tickets the user can see) and `blocked` (any open blocked_by).
- find_similar_tickets (tickets:read): the possible-duplicates finder, by
  title or by an existing ticket (which is excluded from the results).
- link_tickets / unlink_tickets (tickets:write). Marking a duplicate only
  records the link. unlink also finds a link stored from the other side
  ("B blocked_by A" for "A blocks B").

api/ticket_dependencies.php's list/add/remove logic moves to
services/DependencyService.php, used by both (same checks and messages).
DependencyModel's remove methods now return rows removed, so removing a
link that is already gone no longer writes a "deleted" audit row.

Also includes the port in ToolScopeMiddleware's resource_metadata URL
(matches the 401's; no effect on prod, which has no port).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
2026-09-26 17:08:04 -04:00
co-authored by Claude Opus 5.5
parent a9bc72fdcc
commit c490d4f387
8 changed files with 418 additions and 141 deletions
+4 -4
View File
@@ -219,14 +219,14 @@ class DependencyModel
* Remove a dependency
*
* @param int $dependencyId Dependency ID
* @return bool Success status
* @return int|false Rows removed (0 if it no longer existed), or false on failure
*/
public function removeDependency($dependencyId)
{
$sql = "DELETE FROM ticket_dependencies WHERE dependency_id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $dependencyId);
$result = $stmt->execute();
$result = $stmt->execute() ? $stmt->affected_rows : false;
$stmt->close();
return $result;
}
@@ -237,7 +237,7 @@ class DependencyModel
* @param string $ticketId Source ticket ID
* @param string $dependsOnId Target ticket ID
* @param string $type Dependency type
* @return bool Success status
* @return int|false Rows removed (0 if no such link), or false on failure
*/
public function removeDependencyByTickets($ticketId, $dependsOnId, $type)
{
@@ -245,7 +245,7 @@ class DependencyModel
WHERE ticket_id = ? AND depends_on_id = ? AND dependency_type = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("sss", $ticketId, $dependsOnId, $type);
$result = $stmt->execute();
$result = $stmt->execute() ? $stmt->affected_rows : false;
$stmt->close();
return $result;
}