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:
parent
fc86592a73
commit
1322a0b480
@ -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.');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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 ---
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user