MCP: ticket links (dependencies/duplicates) and similar-ticket search #113

Closed
opened 2026-09-26 17:07:52 -04:00 by jared · 1 comment
Owner

Follow-up to #111: let MCP clients use the Dependencies tab fully, including marking duplicates.

Tools

  • get_ticket returns links (blocks / blocked_by / relates_to / duplicates / duplicated_by, from this ticket's side, only linked tickets the user can see) and blocked (any open blocked_by).
  • find_similar_tickets (read): the ticket page's possible-duplicates finder, by title or by existing ticket.
  • link_tickets / unlink_tickets (write). Marking a duplicate only adds the link, per decision; it never closes the duplicate.

Web and MCP share services/DependencyService.php and services/SimilarTicketService.php.

Bugs found in the existing duplicate finder (api/check_duplicates.php, the "Mark duplicate" list on the ticket page)

  • Non-admins never saw any matches. The query had no t alias, but the visibility filter uses t.*, so the query failed and the error was swallowed.
  • Word-overlap scoring could never fire, because candidates were only whole-title substring or SOUNDEX matches.
  • SOUNDEX compared PHP's 4-char code (effectively the first word) to MariaDB's full-length code. The SQL side almost never matched, and the scorer marked any two titles sharing a first word as "sounds alike" (every hwmonDaemon title starts with [proxmox-cluster]).
Follow-up to #111: let MCP clients use the Dependencies tab fully, including marking duplicates. **Tools** - `get_ticket` returns `links` (blocks / blocked_by / relates_to / duplicates / duplicated_by, from this ticket's side, only linked tickets the user can see) and `blocked` (any open blocked_by). - `find_similar_tickets` (read): the ticket page's possible-duplicates finder, by title or by existing ticket. - `link_tickets` / `unlink_tickets` (write). Marking a duplicate **only adds the link**, per decision; it never closes the duplicate. Web and MCP share `services/DependencyService.php` and `services/SimilarTicketService.php`. **Bugs found in the existing duplicate finder** (`api/check_duplicates.php`, the "Mark duplicate" list on the ticket page) - Non-admins never saw any matches. The query had no `t` alias, but the visibility filter uses `t.*`, so the query failed and the error was swallowed. - Word-overlap scoring could never fire, because candidates were only whole-title substring or SOUNDEX matches. - SOUNDEX compared PHP's 4-char code (effectively the first word) to MariaDB's full-length code. The SQL side almost never matched, and the scorer marked any two titles sharing a first word as "sounds alike" (every hwmonDaemon title starts with `[proxmox-cluster]`).
Author
Owner

Done in a9bc72f (duplicate finder) and c490d4f (MCP tools), merged in cc160fe and deployed to beta and prod.

Verification (local MariaDB, real HTTP):

  • Web endpoints: the same 34-request script ran against the old (HEAD) and new code on identically seeded DBs, as an admin and a non-admin. ticket_dependencies.php responses were identical and the resulting links matched. Differences, all intended: check_duplicates.php now returns matches for non-admins and finds word-overlap matches, and removing an already-removed link no longer writes a "deleted" audit row.
  • MCP: the real mcp/server.php against a local stand-in issuer (RS256 JWTs, JWKS discovery), so token validation, scope gating and identity mapping all ran. 27 tool calls covered:
    • visibility: a confidential ticket reads as "not found" for a non-admin, both as a link target and in get_ticket links;
    • rejections: duplicate, inverse and circular links, bad relation, self-link;
    • removal: unlink_tickets finding an inverse-stored link;
    • blocked set and cleared;
    • a read-only token gets 403 insufficient_scope on both write tools;
    • audit rows.
  • Prod/beta smoke test: files lint on PHP 8.4, /mcp returns 401 without a token, and there are no new errors in the logs.
Done in a9bc72f (duplicate finder) and c490d4f (MCP tools), merged in cc160fe and deployed to beta and prod. **Verification** (local MariaDB, real HTTP): - **Web endpoints:** the same 34-request script ran against the old (HEAD) and new code on identically seeded DBs, as an admin and a non-admin. `ticket_dependencies.php` responses were identical and the resulting links matched. Differences, all intended: `check_duplicates.php` now returns matches for non-admins and finds word-overlap matches, and removing an already-removed link no longer writes a "deleted" audit row. - **MCP:** the real `mcp/server.php` against a local stand-in issuer (RS256 JWTs, JWKS discovery), so token validation, scope gating and identity mapping all ran. 27 tool calls covered: - visibility: a confidential ticket reads as "not found" for a non-admin, both as a link target and in `get_ticket` links; - rejections: duplicate, inverse and circular links, bad relation, self-link; - removal: `unlink_tickets` finding an inverse-stored link; - `blocked` set and cleared; - a read-only token gets 403 `insufficient_scope` on both write tools; - audit rows. - **Prod/beta smoke test:** files lint on PHP 8.4, `/mcp` returns 401 without a token, and there are no new errors in the logs.
jared closed this issue 2026-09-26 17:18:48 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/tinker_tickets#113