import { McpContext, Resolver, Tool } from '@nestjs-mcp/server'; import { z } from 'zod'; import { TaskStreamApi } from '../api/taskstream-api'; import { limit } from './params'; import { toolResult } from './tool-result'; @Resolver() export class NotificationsResolver { constructor(private readonly api: TaskStreamApi) {} @Tool({ name: 'my_notifications', description: "The user's notifications, newest first: assignments, status changes, failed QA, comments, mentions, followed tickets, reminders.", paramsSchema: z.object({ unreadOnly: z.boolean().default(true), limit: limit(100, 30), }), annotations: { readOnlyHint: true, openWorldHint: false }, }) mine( { unreadOnly, limit }: { unreadOnly: boolean; limit: number }, ctx: McpContext, ) { return toolResult(async () => { const page = await this.api.data( this.api.as(ctx).GET('/api/me/notifications', { params: { query: { unreadOnly, limit } }, }), ); return { unread: page.unread, items: page.items.map((n) => ({ id: n.id, kind: n.kind, project: n.projectKey, issue: n.issueKey, who: n.actor?.name ?? null, summary: n.summary, read: n.readAt !== null, at: n.createdAt, })), }; }); } @Tool({ name: 'mark_notifications_read', description: 'Mark notifications as read: these ids, or all of them.', paramsSchema: z.object({ ids: z .array(z.string()) .optional() .describe('Notification ids; absent: all'), }), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }) markRead({ ids }: { ids?: string[] }, ctx: McpContext) { return toolResult(() => this.api.data( this.api.as(ctx).POST('/api/me/notifications/read', { body: { ids } }), ), ); } }