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.
This commit is contained in:
Markov 2026-02-27 09:37:21 +01:00
parent fc86592a73
commit 1322a0b480
4 changed files with 39 additions and 39 deletions

View File

@ -3,35 +3,35 @@ import type { ToolDefinition } from '@mariozechner/pi-coding-agent';
import type { ToolContext } from './types.js'; import type { ToolContext } from './types.js';
const ListFilesParams = Type.Object({ 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' })), search: Type.Optional(Type.String({ description: 'Search by filename' })),
}); });
const GetFileParams = Type.Object({ 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' }), file_id: Type.String({ description: 'File UUID' }),
}); });
const DownloadFileParams = Type.Object({ 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' }), file_id: Type.String({ description: 'File UUID' }),
}); });
const UploadFileParams = Type.Object({ 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")' }), filename: Type.String({ description: 'File name (e.g. "spec.md")' }),
content: Type.String({ description: 'File content as base64 string' }), content: Type.String({ description: 'File content as base64 string' }),
description: Type.Optional(Type.String({ description: 'File description' })), description: Type.Optional(Type.String({ description: 'File description' })),
}); });
const UpdateFileParams = Type.Object({ 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' }), file_id: Type.String({ description: 'File UUID' }),
description: Type.String({ description: 'New description' }), description: Type.String({ description: 'New description' }),
}); });
const DeleteFileParams = Type.Object({ 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' }), file_id: Type.String({ description: 'File UUID' }),
}); });
@ -47,7 +47,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition<any>[] {
description: 'List files in a project. Optionally search by filename.', description: 'List files in a project. Optionally search by filename.',
parameters: ListFilesParams, parameters: ListFilesParams,
async execute(_id: string, params: any) { 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.'); 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'); 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}`); return ok(`${files.length} file(s):\n${list}`);
@ -59,7 +59,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition<any>[] {
description: 'Get metadata of a project file by ID.', description: 'Get metadata of a project file by ID.',
parameters: GetFileParams, parameters: GetFileParams,
async execute(_id: string, params: any) { 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)); return ok(JSON.stringify(file, null, 2));
}, },
}, },
@ -69,7 +69,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition<any>[] {
description: 'Download the content of a project file (returns text content).', description: 'Download the content of a project file (returns text content).',
parameters: DownloadFileParams, parameters: DownloadFileParams,
async execute(_id: string, params: any) { 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); return ok(content);
}, },
}, },
@ -80,7 +80,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition<any>[] {
parameters: UploadFileParams, parameters: UploadFileParams,
async execute(_id: string, params: any) { async execute(_id: string, params: any) {
const result = await ctx.trackerClient.uploadProjectFile( const result = await ctx.trackerClient.uploadProjectFile(
params.project_slug, params.project_id,
params.filename, params.filename,
params.content, params.content,
params.description, params.description,
@ -94,7 +94,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition<any>[] {
description: 'Update the description of a project file.', description: 'Update the description of a project file.',
parameters: UpdateFileParams, parameters: UpdateFileParams,
async execute(_id: string, params: any) { 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.'); return ok('Description updated.');
}, },
}, },
@ -104,7 +104,7 @@ export function createFileTools(ctx: ToolContext): ToolDefinition<any>[] {
description: 'Delete a project file from disk and database.', description: 'Delete a project file from disk and database.',
parameters: DeleteFileParams, parameters: DeleteFileParams,
async execute(_id: string, params: any) { 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.'); return ok('File deleted.');
}, },
}, },

View File

@ -3,7 +3,7 @@ import type { ToolDefinition } from '@mariozechner/pi-coding-agent';
import type { ToolContext } from './types.js'; import type { ToolContext } from './types.js';
const GetProjectParams = Type.Object({ const GetProjectParams = Type.Object({
slug: Type.String({ description: 'Project slug' }), project_id: Type.String({ description: 'Project ID (UUID)' }),
}); });
function ok(text: string) { function ok(text: string) {
@ -15,7 +15,7 @@ export function createProjectTools(ctx: ToolContext): ToolDefinition<any>[] {
{ {
name: 'list_projects', name: 'list_projects',
label: '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({}), parameters: Type.Object({}),
async execute() { async execute() {
const projects = await ctx.trackerClient.listProjects(); const projects = await ctx.trackerClient.listProjects();
@ -25,10 +25,10 @@ export function createProjectTools(ctx: ToolContext): ToolDefinition<any>[] {
{ {
name: 'get_project', name: 'get_project',
label: '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, parameters: GetProjectParams,
async execute(_id: string, params: any) { 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)); return ok(JSON.stringify(project, null, 2));
}, },
}, },

View File

@ -3,7 +3,7 @@ import type { ToolDefinition } from '@mariozechner/pi-coding-agent';
import type { ToolContext } from './types.js'; import type { ToolContext } from './types.js';
const ListTasksParams = Type.Object({ 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' })), 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)' })), assignee_id: Type.Optional(Type.String({ description: 'Filter by assignee ID (UUID)' })),
}); });
@ -13,7 +13,7 @@ const GetTaskParams = Type.Object({
}); });
const CreateTaskParams = 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' }), title: Type.String({ description: 'Task title' }),
description: Type.Optional(Type.String({ description: 'Task description (markdown)' })), description: Type.Optional(Type.String({ description: 'Task description (markdown)' })),
priority: Type.Optional(Type.String({ description: 'Priority: low|medium|high|critical' })), priority: Type.Optional(Type.String({ description: 'Priority: low|medium|high|critical' })),
@ -51,11 +51,11 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition<any>[] {
{ {
name: 'list_tasks', name: 'list_tasks',
label: '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, parameters: ListTasksParams,
async execute(_id: string, params: any) { async execute(_id: string, params: any) {
const query: Record<string, string> = {}; const query: Record<string, string> = {};
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.status) query.status = params.status;
if (params.assignee_id) query.assignee_id = params.assignee_id; if (params.assignee_id) query.assignee_id = params.assignee_id;
const tasks = await ctx.trackerClient.listTasks(query); const tasks = await ctx.trackerClient.listTasks(query);
@ -78,8 +78,8 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition<any>[] {
description: 'Create a new task in a project. Returns the created task with generated key (e.g. TE-1).', description: 'Create a new task in a project. Returns the created task with generated key (e.g. TE-1).',
parameters: CreateTaskParams, parameters: CreateTaskParams,
async execute(_id: string, params: any) { async execute(_id: string, params: any) {
const { project_slug, ...taskData } = params; const { project_id, ...taskData } = params;
const task = await ctx.trackerClient.createTask(project_slug, taskData); const task = await ctx.trackerClient.createTask(project_id, taskData);
return ok(JSON.stringify(task, null, 2)); return ok(JSON.stringify(task, null, 2));
}, },
}, },
@ -102,7 +102,7 @@ export function createTaskTools(ctx: ToolContext): ToolDefinition<any>[] {
async execute(_id: string, params: any) { async execute(_id: string, params: any) {
ctx.selfAssignedTasks.add(params.task_id); ctx.selfAssignedTasks.add(params.task_id);
await ctx.trackerClient.takeTask(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`);
}, },
}, },
{ {

View File

@ -58,8 +58,8 @@ export class TrackerClient {
return this.request('GET', `/api/v1/tasks/${taskId}`); return this.request('GET', `/api/v1/tasks/${taskId}`);
} }
async createTask(projectSlug: string, task: Record<string, unknown>): Promise<Record<string, unknown>> { async createTask(projectId: string, task: Record<string, unknown>): Promise<Record<string, unknown>> {
return this.request('POST', `/api/v1/tasks?project_slug=${encodeURIComponent(projectSlug)}`, task); return this.request('POST', `/api/v1/tasks?project_id=${encodeURIComponent(projectId)}`, task);
} }
async updateTask(taskId: string, fields: Record<string, unknown>): Promise<void> { async updateTask(taskId: string, fields: Record<string, unknown>): Promise<void> {
@ -109,17 +109,17 @@ export class TrackerClient {
// --- Project Files --- // --- Project Files ---
async listProjectFiles(projectSlug: string, search?: string): Promise<Record<string, unknown>[]> { async listProjectFiles(projectId: string, search?: string): Promise<Record<string, unknown>[]> {
const qs = search ? `?search=${encodeURIComponent(search)}` : ''; 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<Record<string, unknown>> { async getProjectFile(projectId: string, fileId: string): Promise<Record<string, unknown>> {
return this.request('GET', `/api/v1/projects/${projectSlug}/files/${fileId}`); return this.request('GET', `/api/v1/projects/${projectId}/files/${fileId}`);
} }
async downloadProjectFile(projectSlug: string, fileId: string): Promise<string> { async downloadProjectFile(projectId: string, fileId: string): Promise<string> {
const url = `${this.baseUrl}/api/v1/projects/${projectSlug}/files/${fileId}/download`; const url = `${this.baseUrl}/api/v1/projects/${projectId}/files/${fileId}/download`;
const res = await fetch(url, { const res = await fetch(url, {
headers: { 'Authorization': `Bearer ${this.token}` }, headers: { 'Authorization': `Bearer ${this.token}` },
}); });
@ -127,8 +127,8 @@ export class TrackerClient {
return res.text(); return res.text();
} }
async uploadProjectFile(projectSlug: string, filename: string, content: string, description?: string): Promise<Record<string, unknown>> { async uploadProjectFile(projectId: string, filename: string, content: string, description?: string): Promise<Record<string, unknown>> {
const url = `${this.baseUrl}/api/v1/projects/${projectSlug}/files`; const url = `${this.baseUrl}/api/v1/projects/${projectId}/files`;
const formData = new FormData(); const formData = new FormData();
formData.append('file', new Blob([Buffer.from(content, 'base64')]), filename); formData.append('file', new Blob([Buffer.from(content, 'base64')]), filename);
if (description) formData.append('description', description); if (description) formData.append('description', description);
@ -145,12 +145,12 @@ export class TrackerClient {
return res.json(); return res.json();
} }
async updateProjectFile(projectSlug: string, fileId: string, data: { description?: string }): Promise<Record<string, unknown>> { async updateProjectFile(projectId: string, fileId: string, data: { description?: string }): Promise<Record<string, unknown>> {
return this.request('PATCH', `/api/v1/projects/${projectSlug}/files/${fileId}`, data); return this.request('PATCH', `/api/v1/projects/${projectId}/files/${fileId}`, data);
} }
async deleteProjectFile(projectSlug: string, fileId: string): Promise<void> { async deleteProjectFile(projectId: string, fileId: string): Promise<void> {
await this.request('DELETE', `/api/v1/projects/${projectSlug}/files/${fileId}`); await this.request('DELETE', `/api/v1/projects/${projectId}/files/${fileId}`);
} }
// --- Projects --- // --- Projects ---
@ -159,8 +159,8 @@ export class TrackerClient {
return this.request('GET', '/api/v1/projects'); return this.request('GET', '/api/v1/projects');
} }
async getProject(slug: string): Promise<Record<string, unknown>> { async getProject(projectId: string): Promise<Record<string, unknown>> {
return this.request('GET', `/api/v1/projects/${slug}`); return this.request('GET', `/api/v1/projects/${projectId}`);
} }
// --- Members --- // --- Members ---