diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..e7298cc --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,45 @@ +import { z } from 'zod' +import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js' +import { PermissionDeniedError } from './auth.js' + +export function formatZodError(err: z.ZodError): string { + return err.issues + .map((issue) => { + const path = issue.path.length ? issue.path.join('.') : '(root)' + return `${path}: ${issue.message}` + }) + .join('; ') +} + +export function toolError(message: string): CallToolResult { + return { + content: [{ type: 'text', text: message }], + isError: true, + } +} + +export function toolJson(value: unknown): CallToolResult { + return { + content: [{ type: 'text', text: JSON.stringify(value, null, 2) }], + structuredContent: value as Record, + } +} + +export async function withToolErrors( + fn: () => Promise, +): Promise { + try { + return await fn() + } catch (err) { + if (err instanceof PermissionDeniedError) { + return toolError(`PERMISSION_DENIED: ${err.message}`) + } + if (err instanceof z.ZodError) { + return toolError(`VALIDATION_ERROR: ${formatZodError(err)}`) + } + if (err instanceof Error) { + return toolError(err.message) + } + return toolError(String(err)) + } +} diff --git a/src/status.ts b/src/status.ts new file mode 100644 index 0000000..74e2e52 --- /dev/null +++ b/src/status.ts @@ -0,0 +1,49 @@ +import type { TaskStatus, StoryStatus } from '@prisma/client' + +const TASK_DB_TO_API = { + TO_DO: 'todo', + IN_PROGRESS: 'in_progress', + REVIEW: 'review', + DONE: 'done', +} as const satisfies Record + +const TASK_API_TO_DB: Record = { + todo: 'TO_DO', + in_progress: 'IN_PROGRESS', + review: 'REVIEW', + done: 'DONE', +} + +const STORY_DB_TO_API = { + OPEN: 'open', + IN_SPRINT: 'in_sprint', + DONE: 'done', +} as const satisfies Record + +const STORY_API_TO_DB: Record = { + open: 'OPEN', + in_sprint: 'IN_SPRINT', + done: 'DONE', +} + +export type TaskStatusApi = (typeof TASK_DB_TO_API)[TaskStatus] +export type StoryStatusApi = (typeof STORY_DB_TO_API)[StoryStatus] + +export function taskStatusToApi(s: TaskStatus): TaskStatusApi { + return TASK_DB_TO_API[s] +} + +export function taskStatusFromApi(s: string): TaskStatus | null { + return TASK_API_TO_DB[s.toLowerCase()] ?? null +} + +export function storyStatusToApi(s: StoryStatus): StoryStatusApi { + return STORY_DB_TO_API[s] +} + +export function storyStatusFromApi(s: string): StoryStatus | null { + return STORY_API_TO_DB[s.toLowerCase()] ?? null +} + +export const TASK_STATUS_API_VALUES = Object.values(TASK_DB_TO_API) +export const STORY_STATUS_API_VALUES = Object.values(STORY_DB_TO_API)