From 7d7fe6df42af5b0cd3b358ade54d3d23cd71a92c Mon Sep 17 00:00:00 2001 From: Markov Date: Fri, 27 Feb 2026 23:32:32 +0100 Subject: [PATCH] Task link MCP tools + anti-loop agent prompts Tools: create_task_link, list_task_links, delete_task_link (23 total) TrackerClient.request() made public for generic API calls Agent prompts: anti-loop rules for agent-to-agent communication --- src/tools/index.ts | 2 ++ src/tools/tasks.ts | 55 +++++++++++++++++++++++++++++++++++++++++++ src/tracker/client.ts | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index ca4e212..518b26a 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -6,6 +6,7 @@ import { createMessageTools } from './messages.js'; import { createProjectTools } from './projects.js'; import { createMemberTools } from './members.js'; import { createFileTools } from './files.js'; +import { taskLinkTools } from './tasks.js'; /** * Create all Team Board tracker tools for the agent. @@ -19,6 +20,7 @@ export function createTrackerTools(ctx: ToolContext): ToolDefinition[] { ...createProjectTools(ctx), ...createMemberTools(ctx), ...createFileTools(ctx), + ...taskLinkTools(ctx), ]; } diff --git a/src/tools/tasks.ts b/src/tools/tasks.ts index c9eb0f0..5827be8 100644 --- a/src/tools/tasks.ts +++ b/src/tools/tasks.ts @@ -127,3 +127,58 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition[] { }, ]; } + +// --- Task Links --- + +const CreateTaskLinkParams = Type.Object({ + task_id: Type.String({ description: 'Source task ID (UUID)' }), + target_id: Type.String({ description: 'Target task ID (UUID)' }), + link_type: Type.String({ description: 'Link type: blocks | depends_on | relates_to' }), +}); + +const ListTaskLinksParams = Type.Object({ + task_id: Type.String({ description: 'Task ID (UUID)' }), +}); + +const DeleteTaskLinkParams = Type.Object({ + task_id: Type.String({ description: 'Task ID (UUID)' }), + link_id: Type.String({ description: 'Link ID (UUID)' }), +}); + +export function taskLinkTools(ctx: ToolContext): ToolDefinition[] { + return [ + { + name: 'create_task_link', + label: 'Create Task Link', + description: 'Create a dependency/link between tasks. Types: blocks (source blocks target), depends_on (source depends on target), relates_to (informational).', + parameters: CreateTaskLinkParams, + async execute(_id: string, params: any) { + const { task_id, target_id, link_type } = params as { task_id: string; target_id: string; link_type: string }; + const resp = await ctx.trackerClient.request('POST', `/api/v1/tasks/${task_id}/links`, { target_id, link_type }); + return ok(JSON.stringify(resp)); + }, + }, + { + name: 'list_task_links', + label: 'List Task Links', + description: 'List all dependencies/links for a task. Shows both outgoing (this task blocks/depends_on) and incoming (blocked_by/required_by) links.', + parameters: ListTaskLinksParams, + async execute(_id: string, params: any) { + const { task_id } = params as { task_id: string }; + const resp = await ctx.trackerClient.request('GET', `/api/v1/tasks/${task_id}/links`); + return ok(JSON.stringify(resp)); + }, + }, + { + name: 'delete_task_link', + label: 'Delete Task Link', + description: 'Remove a dependency/link between tasks.', + parameters: DeleteTaskLinkParams, + async execute(_id: string, params: any) { + const { task_id, link_id } = params as { task_id: string; link_id: string }; + const resp = await ctx.trackerClient.request('DELETE', `/api/v1/tasks/${task_id}/links/${link_id}`); + return ok(JSON.stringify(resp)); + }, + }, + ]; +} diff --git a/src/tracker/client.ts b/src/tracker/client.ts index 169b972..b70e8cf 100644 --- a/src/tracker/client.ts +++ b/src/tracker/client.ts @@ -17,7 +17,7 @@ export class TrackerClient { private token: string, ) {} - private async request(method: string, path: string, body?: unknown): Promise { + async request(method: string, path: string, body?: unknown): Promise { const url = `${this.baseUrl}${path}`; this.log.info(' REST %s %s', method, path); if (body) this.log.info(' body: %s', JSON.stringify(body).slice(0, 300));