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';
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<any>[] {
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<any>[] {
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<any>[] {
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<any>[] {
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<any>[] {
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<any>[] {
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.');
},
},

View File

@ -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<any>[] {
{
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<any>[] {
{
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));
},
},

View File

@ -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<any>[] {
{
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<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.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<any>[] {
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<any>[] {
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`);
},
},
{

View File

@ -58,8 +58,8 @@ export class TrackerClient {
return this.request('GET', `/api/v1/tasks/${taskId}`);
}
async createTask(projectSlug: string, task: Record<string, unknown>): Promise<Record<string, unknown>> {
return this.request('POST', `/api/v1/tasks?project_slug=${encodeURIComponent(projectSlug)}`, task);
async createTask(projectId: string, task: Record<string, unknown>): Promise<Record<string, unknown>> {
return this.request('POST', `/api/v1/tasks?project_id=${encodeURIComponent(projectId)}`, task);
}
async updateTask(taskId: string, fields: Record<string, unknown>): Promise<void> {
@ -109,17 +109,17 @@ export class TrackerClient {
// --- 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)}` : '';
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>> {
return this.request('GET', `/api/v1/projects/${projectSlug}/files/${fileId}`);
async getProjectFile(projectId: string, fileId: string): Promise<Record<string, unknown>> {
return this.request('GET', `/api/v1/projects/${projectId}/files/${fileId}`);
}
async downloadProjectFile(projectSlug: string, fileId: string): Promise<string> {
const url = `${this.baseUrl}/api/v1/projects/${projectSlug}/files/${fileId}/download`;
async downloadProjectFile(projectId: string, fileId: string): Promise<string> {
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<Record<string, unknown>> {
const url = `${this.baseUrl}/api/v1/projects/${projectSlug}/files`;
async uploadProjectFile(projectId: string, filename: string, content: string, description?: string): Promise<Record<string, unknown>> {
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<Record<string, unknown>> {
return this.request('PATCH', `/api/v1/projects/${projectSlug}/files/${fileId}`, data);
async updateProjectFile(projectId: string, fileId: string, data: { description?: string }): Promise<Record<string, unknown>> {
return this.request('PATCH', `/api/v1/projects/${projectId}/files/${fileId}`, data);
}
async deleteProjectFile(projectSlug: string, fileId: string): Promise<void> {
await this.request('DELETE', `/api/v1/projects/${projectSlug}/files/${fileId}`);
async deleteProjectFile(projectId: string, fileId: string): Promise<void> {
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<Record<string, unknown>> {
return this.request('GET', `/api/v1/projects/${slug}`);
async getProject(projectId: string): Promise<Record<string, unknown>> {
return this.request('GET', `/api/v1/projects/${projectId}`);
}
// --- Members ---