add_relationship
Link two MantisBT issues with relationships like duplicate_of, related_to, depends_on, or blocks to track dependencies and connections between bugs.
Instructions
Add a relationship between two MantisBT issues.
Relationship types — use either type_id (numeric) or type_name (string):
0 / "duplicate_of" — this issue is a duplicate of target
1 / "related_to" — this issue is related to target
2 / "parent_of" — this issue depends on target (target must be done first); alias: "depends_on"
3 / "child_of" — this issue blocks target (target can't proceed until this is done); alias: "blocks"
4 / "has_duplicate" — this issue has target as a duplicate
Directionality note: "A child_of B" means A blocks B. "A parent_of B" means A depends on B.
Dash variants (e.g. "related-to") are also accepted for type_name.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| issue_id | Yes | The source issue ID (the one the relationship is added to) | |
| target_id | Yes | The target issue ID | |
| type_id | No | Relationship type ID: 0=duplicate_of, 1=related_to, 2=parent_of (depends on), 3=child_of (blocks), 4=has_duplicate. Use either type_id or type_name. | |
| type_name | No | Relationship type name as alternative to type_id. Accepted: "duplicate_of", "related_to", "parent_of" (or "depends_on"), "child_of" (or "blocks"), "has_duplicate". Dash variants (e.g. "related-to") also work. |
Implementation Reference
- src/tools/relationships.ts:21-84 (registration)The tool 'add_relationship' is registered using server.registerTool, defining its title, description, and input schema.
server.registerTool( 'add_relationship', { title: 'Add Issue Relationship', description: `Add a relationship between two MantisBT issues. Relationship types — use either type_id (numeric) or type_name (string): - ${RELATIONSHIP_TYPES.DUPLICATE_OF} / "duplicate_of" — this issue is a duplicate of target - ${RELATIONSHIP_TYPES.RELATED_TO} / "related_to" — this issue is related to target - ${RELATIONSHIP_TYPES.PARENT_OF} / "parent_of" — this issue depends on target (target must be done first); alias: "depends_on" - ${RELATIONSHIP_TYPES.CHILD_OF} / "child_of" — this issue blocks target (target can't proceed until this is done); alias: "blocks" - ${RELATIONSHIP_TYPES.HAS_DUPLICATE} / "has_duplicate" — this issue has target as a duplicate Directionality note: "A child_of B" means A blocks B. "A parent_of B" means A depends on B. Dash variants (e.g. "related-to") are also accepted for type_name.`, inputSchema: z.object({ issue_id: z.coerce.number().int().positive().describe('The source issue ID (the one the relationship is added to)'), target_id: z.coerce.number().int().positive().describe('The target issue ID'), type_id: z.coerce.number().int().min(0).max(4).optional().describe( 'Relationship type ID: 0=duplicate_of, 1=related_to, 2=parent_of (depends on), 3=child_of (blocks), 4=has_duplicate. Use either type_id or type_name.' ), type_name: z.string().optional().describe( 'Relationship type name as alternative to type_id. Accepted: "duplicate_of", "related_to", "parent_of" (or "depends_on"), "child_of" (or "blocks"), "has_duplicate". Dash variants (e.g. "related-to") also work.' ), }), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, }, }, async ({ issue_id, target_id, type_id, type_name }) => { // Resolve type_id from type_name when type_id is not provided let resolvedTypeId = type_id; if (resolvedTypeId === undefined) { if (type_name === undefined) { return { content: [{ type: 'text', text: errorText('Either type_id or type_name must be provided') }], isError: true }; } const normalized = type_name.toLowerCase().replace(/-/g, '_'); resolvedTypeId = RELATIONSHIP_NAME_TO_ID[normalized]; if (resolvedTypeId === undefined) { return { content: [{ type: 'text', text: errorText(`Unknown relationship type name: "${type_name}". Valid values: duplicate_of, related_to, parent_of, child_of, has_duplicate`) }], isError: true, }; } } try { const body = { issue: { id: target_id }, type: { id: resolvedTypeId }, }; const result = await client.post<unknown>(`issues/${issue_id}/relationships`, body); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: errorText(msg) }], isError: true }; } } ); - src/tools/relationships.ts:53-83 (handler)The tool's implementation function, which resolves the relationship type ID and makes a POST request to the MantisBT API to create the relationship.
async ({ issue_id, target_id, type_id, type_name }) => { // Resolve type_id from type_name when type_id is not provided let resolvedTypeId = type_id; if (resolvedTypeId === undefined) { if (type_name === undefined) { return { content: [{ type: 'text', text: errorText('Either type_id or type_name must be provided') }], isError: true }; } const normalized = type_name.toLowerCase().replace(/-/g, '_'); resolvedTypeId = RELATIONSHIP_NAME_TO_ID[normalized]; if (resolvedTypeId === undefined) { return { content: [{ type: 'text', text: errorText(`Unknown relationship type name: "${type_name}". Valid values: duplicate_of, related_to, parent_of, child_of, has_duplicate`) }], isError: true, }; } } try { const body = { issue: { id: target_id }, type: { id: resolvedTypeId }, }; const result = await client.post<unknown>(`issues/${issue_id}/relationships`, body); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: errorText(msg) }], isError: true }; } } - src/tools/relationships.ts:37-46 (schema)The input schema for 'add_relationship', validating issue_id, target_id, type_id, and type_name.
inputSchema: z.object({ issue_id: z.coerce.number().int().positive().describe('The source issue ID (the one the relationship is added to)'), target_id: z.coerce.number().int().positive().describe('The target issue ID'), type_id: z.coerce.number().int().min(0).max(4).optional().describe( 'Relationship type ID: 0=duplicate_of, 1=related_to, 2=parent_of (depends on), 3=child_of (blocks), 4=has_duplicate. Use either type_id or type_name.' ), type_name: z.string().optional().describe( 'Relationship type name as alternative to type_id. Accepted: "duplicate_of", "related_to", "parent_of" (or "depends_on"), "child_of" (or "blocks"), "has_duplicate". Dash variants (e.g. "related-to") also work.' ), }),