From 1322a0b480e3c112475144044f456dd841f3f829 Mon Sep 17 00:00:00 2001 From: Markov Date: Fri, 27 Feb 2026 09:37:21 +0100 Subject: [PATCH] UUID everywhere: MCP tools use project_id instead of project_slug All tools (tasks, files, projects) now accept project_id (UUID). TrackerClient methods renamed accordingly. --- src/tools/files.ts | 24 ++++++++++++------------ src/tools/projects.ts | 8 ++++---- src/tools/tasks.ts | 14 +++++++------- src/tracker/client.ts | 32 ++++++++++++++++---------------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/tools/files.ts b/src/tools/files.ts index 080941a..47e8601 100644 --- a/src/tools/files.ts +++ b/src/tools/files.ts @@ -3,35 +3,35 @@ import type { ToolDefinition } from '@mariozechner/pi-coding-agent'; import type { ToolContext } from './types.js'; const ListFilesParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), search: Type.Optional(Type.String({ description: 'Search by filename' })), }); const GetFileParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), file_id: Type.String({ description: 'File UUID' }), }); const DownloadFileParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), file_id: Type.String({ description: 'File UUID' }), }); const UploadFileParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), filename: Type.String({ description: 'File name (e.g. "spec.md")' }), content: Type.String({ description: 'File content as base64 string' }), description: Type.Optional(Type.String({ description: 'File description' })), }); const UpdateFileParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), file_id: Type.String({ description: 'File UUID' }), description: Type.String({ description: 'New description' }), }); const DeleteFileParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), file_id: Type.String({ description: 'File UUID' }), }); @@ -47,7 +47,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition[] { description: 'List files in a project. Optionally search by filename.', parameters: ListFilesParams, async execute(_id: string, params: any) { - const files = await ctx.trackerClient.listProjectFiles(params.project_slug, params.search); + const files = await ctx.trackerClient.listProjectFiles(params.project_id, params.search); if (files.length === 0) return ok('No files found.'); const list = files.map((f: any) => `- ${f.filename} (${f.size} bytes) ${f.description ? `— ${f.description}` : ''} [id: ${f.id}]`).join('\n'); return ok(`${files.length} file(s):\n${list}`); @@ -59,7 +59,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition[] { description: 'Get metadata of a project file by ID.', parameters: GetFileParams, async execute(_id: string, params: any) { - const file = await ctx.trackerClient.getProjectFile(params.project_slug, params.file_id); + const file = await ctx.trackerClient.getProjectFile(params.project_id, params.file_id); return ok(JSON.stringify(file, null, 2)); }, }, @@ -69,7 +69,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition[] { description: 'Download the content of a project file (returns text content).', parameters: DownloadFileParams, async execute(_id: string, params: any) { - const content = await ctx.trackerClient.downloadProjectFile(params.project_slug, params.file_id); + const content = await ctx.trackerClient.downloadProjectFile(params.project_id, params.file_id); return ok(content); }, }, @@ -80,7 +80,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition[] { parameters: UploadFileParams, async execute(_id: string, params: any) { const result = await ctx.trackerClient.uploadProjectFile( - params.project_slug, + params.project_id, params.filename, params.content, params.description, @@ -94,7 +94,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition[] { description: 'Update the description of a project file.', parameters: UpdateFileParams, async execute(_id: string, params: any) { - await ctx.trackerClient.updateProjectFile(params.project_slug, params.file_id, { description: params.description }); + await ctx.trackerClient.updateProjectFile(params.project_id, params.file_id, { description: params.description }); return ok('Description updated.'); }, }, @@ -104,7 +104,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition[] { description: 'Delete a project file from disk and database.', parameters: DeleteFileParams, async execute(_id: string, params: any) { - await ctx.trackerClient.deleteProjectFile(params.project_slug, params.file_id); + await ctx.trackerClient.deleteProjectFile(params.project_id, params.file_id); return ok('File deleted.'); }, }, diff --git a/src/tools/projects.ts b/src/tools/projects.ts index ba6c1f6..a61b71b 100644 --- a/src/tools/projects.ts +++ b/src/tools/projects.ts @@ -3,7 +3,7 @@ import type { ToolDefinition } from '@mariozechner/pi-coding-agent'; import type { ToolContext } from './types.js'; const GetProjectParams = Type.Object({ - slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), }); function ok(text: string) { @@ -15,7 +15,7 @@ export function createProjectTools(ctx: ToolContext): ToolDefinition[] { { name: 'list_projects', label: 'List Projects', - description: 'List all projects the agent has access to.', + description: 'List all projects the agent has access to. Returns id, slug, name, chat_id for each.', parameters: Type.Object({}), async execute() { const projects = await ctx.trackerClient.listProjects(); @@ -25,10 +25,10 @@ export function createProjectTools(ctx: ToolContext): ToolDefinition[] { { name: 'get_project', label: 'Get Project', - description: 'Get project details by slug, including chat_id.', + description: 'Get project details by ID (UUID), including chat_id.', parameters: GetProjectParams, async execute(_id: string, params: any) { - const project = await ctx.trackerClient.getProject(params.slug); + const project = await ctx.trackerClient.getProject(params.project_id); return ok(JSON.stringify(project, null, 2)); }, }, diff --git a/src/tools/tasks.ts b/src/tools/tasks.ts index 58731dc..c9eb0f0 100644 --- a/src/tools/tasks.ts +++ b/src/tools/tasks.ts @@ -3,7 +3,7 @@ import type { ToolDefinition } from '@mariozechner/pi-coding-agent'; import type { ToolContext } from './types.js'; const ListTasksParams = Type.Object({ - project_slug: Type.Optional(Type.String({ description: 'Filter by project slug' })), + project_id: Type.Optional(Type.String({ description: 'Filter by project ID (UUID)' })), status: Type.Optional(Type.String({ description: 'Filter by status: backlog|todo|in_progress|in_review|done' })), assignee_id: Type.Optional(Type.String({ description: 'Filter by assignee ID (UUID)' })), }); @@ -13,7 +13,7 @@ const GetTaskParams = Type.Object({ }); const CreateTaskParams = Type.Object({ - project_slug: Type.String({ description: 'Project slug' }), + project_id: Type.String({ description: 'Project ID (UUID)' }), title: Type.String({ description: 'Task title' }), description: Type.Optional(Type.String({ description: 'Task description (markdown)' })), priority: Type.Optional(Type.String({ description: 'Priority: low|medium|high|critical' })), @@ -51,11 +51,11 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition[] { { name: 'list_tasks', label: 'List Tasks', - description: 'List tasks with optional filters (project_slug, status, assignee_id). Returns array of task objects.', + description: 'List tasks with optional filters (project_id, status, assignee_id). Returns array of task objects.', parameters: ListTasksParams, async execute(_id: string, params: any) { const query: Record = {}; - if (params.project_slug) query.project_slug = params.project_slug; + if (params.project_id) query.project_id = params.project_id; if (params.status) query.status = params.status; if (params.assignee_id) query.assignee_id = params.assignee_id; const tasks = await ctx.trackerClient.listTasks(query); @@ -78,8 +78,8 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition[] { description: 'Create a new task in a project. Returns the created task with generated key (e.g. TE-1).', parameters: CreateTaskParams, async execute(_id: string, params: any) { - const { project_slug, ...taskData } = params; - const task = await ctx.trackerClient.createTask(project_slug, taskData); + const { project_id, ...taskData } = params; + const task = await ctx.trackerClient.createTask(project_id, taskData); return ok(JSON.stringify(task, null, 2)); }, }, @@ -102,7 +102,7 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition[] { async execute(_id: string, params: any) { ctx.selfAssignedTasks.add(params.task_id); await ctx.trackerClient.takeTask(params.task_id); - return ok(`Task ${params.task_id} taken by ${ctx.agentSlug}`); + return ok(`Task ${params.task_id} taken`); }, }, { diff --git a/src/tracker/client.ts b/src/tracker/client.ts index a5e8148..169b972 100644 --- a/src/tracker/client.ts +++ b/src/tracker/client.ts @@ -58,8 +58,8 @@ export class TrackerClient { return this.request('GET', `/api/v1/tasks/${taskId}`); } - async createTask(projectSlug: string, task: Record): Promise> { - return this.request('POST', `/api/v1/tasks?project_slug=${encodeURIComponent(projectSlug)}`, task); + async createTask(projectId: string, task: Record): Promise> { + return this.request('POST', `/api/v1/tasks?project_id=${encodeURIComponent(projectId)}`, task); } async updateTask(taskId: string, fields: Record): Promise { @@ -109,17 +109,17 @@ export class TrackerClient { // --- Project Files --- - async listProjectFiles(projectSlug: string, search?: string): Promise[]> { + async listProjectFiles(projectId: string, search?: string): Promise[]> { const qs = search ? `?search=${encodeURIComponent(search)}` : ''; - return this.request('GET', `/api/v1/projects/${projectSlug}/files${qs}`); + return this.request('GET', `/api/v1/projects/${projectId}/files${qs}`); } - async getProjectFile(projectSlug: string, fileId: string): Promise> { - return this.request('GET', `/api/v1/projects/${projectSlug}/files/${fileId}`); + async getProjectFile(projectId: string, fileId: string): Promise> { + return this.request('GET', `/api/v1/projects/${projectId}/files/${fileId}`); } - async downloadProjectFile(projectSlug: string, fileId: string): Promise { - const url = `${this.baseUrl}/api/v1/projects/${projectSlug}/files/${fileId}/download`; + async downloadProjectFile(projectId: string, fileId: string): Promise { + const url = `${this.baseUrl}/api/v1/projects/${projectId}/files/${fileId}/download`; const res = await fetch(url, { headers: { 'Authorization': `Bearer ${this.token}` }, }); @@ -127,8 +127,8 @@ export class TrackerClient { return res.text(); } - async uploadProjectFile(projectSlug: string, filename: string, content: string, description?: string): Promise> { - const url = `${this.baseUrl}/api/v1/projects/${projectSlug}/files`; + async uploadProjectFile(projectId: string, filename: string, content: string, description?: string): Promise> { + const url = `${this.baseUrl}/api/v1/projects/${projectId}/files`; const formData = new FormData(); formData.append('file', new Blob([Buffer.from(content, 'base64')]), filename); if (description) formData.append('description', description); @@ -145,12 +145,12 @@ export class TrackerClient { return res.json(); } - async updateProjectFile(projectSlug: string, fileId: string, data: { description?: string }): Promise> { - return this.request('PATCH', `/api/v1/projects/${projectSlug}/files/${fileId}`, data); + async updateProjectFile(projectId: string, fileId: string, data: { description?: string }): Promise> { + return this.request('PATCH', `/api/v1/projects/${projectId}/files/${fileId}`, data); } - async deleteProjectFile(projectSlug: string, fileId: string): Promise { - await this.request('DELETE', `/api/v1/projects/${projectSlug}/files/${fileId}`); + async deleteProjectFile(projectId: string, fileId: string): Promise { + await this.request('DELETE', `/api/v1/projects/${projectId}/files/${fileId}`); } // --- Projects --- @@ -159,8 +159,8 @@ export class TrackerClient { return this.request('GET', '/api/v1/projects'); } - async getProject(slug: string): Promise> { - return this.request('GET', `/api/v1/projects/${slug}`); + async getProject(projectId: string): Promise> { + return this.request('GET', `/api/v1/projects/${projectId}`); } // --- Members ---