import { Tool } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { UpworkClient } from "../../services/upwork-client.js"; // Schema for the unread rooms tool export const GetUnreadRoomsArgsSchema = z.object({ sort_by: z.enum(["unread_count", "last_activity", "room_name"]) .default("unread_count") .describe("How to sort the unread rooms"), include_context: z.boolean() .default(true) .describe("Include contract info and participant details") }); export type GetUnreadRoomsArgs = z.infer; // Tool definition export const getUnreadRoomsToolDefinition: Tool = { name: "upwork_get_unread_rooms", description: "Get only rooms with unread messages, sorted by importance. Perfect for quick attention checks.", inputSchema: { type: "object", properties: { sort_by: { type: "string", enum: ["unread_count", "last_activity", "room_name"], default: "unread_count", description: "How to sort the unread rooms" }, include_context: { type: "boolean", default: true, description: "Include contract info and participant details" } } } }; // Handler implementation export async function getUnreadRoomsHandler( args: unknown, upworkClient: UpworkClient ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedArgs = GetUnreadRoomsArgsSchema.parse(args); try { // GraphQL query to get rooms with unread messages const roomListQuery = ` query GetUnreadRooms { roomList { totalCount edges { node { id roomName roomType topic numUnread lastVisitedDateTime lastReadDateTime contractId latestStory { id message createdDateTime user { id name } } } } } } `; const response = await upworkClient.graphqlQuery(roomListQuery, {}); if (!response.data?.roomList) { throw new Error('Failed to fetch rooms from Upwork'); } // Filter for unread rooms only const allRooms = response.data.roomList.edges.map((edge: any) => edge.node); const unreadRooms = allRooms.filter((room: any) => room.numUnread > 0); // Sort based on user preference unreadRooms.sort((a: any, b: any) => { switch (validatedArgs.sort_by) { case "unread_count": return b.numUnread - a.numUnread; // Most unread first case "last_activity": if (!a.latestStory?.createdDateTime || !b.latestStory?.createdDateTime) return 0; return new Date(b.latestStory.createdDateTime).getTime() - new Date(a.latestStory.createdDateTime).getTime(); case "room_name": return (a.roomName || '').localeCompare(b.roomName || ''); default: return 0; } }); // Calculate summary stats const totalUnreadMessages = unreadRooms.reduce((sum: number, room: any) => sum + room.numUnread, 0); const interviewRooms = unreadRooms.filter((room: any) => room.roomType === 'INTERVIEW'); const contractRooms = unreadRooms.filter((room: any) => room.contractId); // Format the response let textResponse = `๐Ÿ“ฌ **Unread Upwork Messages**\n\n`; if (unreadRooms.length === 0) { textResponse = `โœ… **All Caught Up!**\n\n` + `No unread messages across all ${allRooms.length} rooms.\n` + `You're at inbox zero! ๐ŸŽ‰`; } else { // Summary section textResponse += `๐Ÿ“Š **Summary**\n`; textResponse += `โ€ข **${unreadRooms.length}** rooms with unread messages\n`; textResponse += `โ€ข **${totalUnreadMessages}** total unread messages\n`; if (interviewRooms.length > 0) { textResponse += `โ€ข ๐Ÿ”ด **${interviewRooms.length}** interview room${interviewRooms.length > 1 ? 's' : ''}\n`; } if (contractRooms.length > 0) { textResponse += `โ€ข ๐Ÿ’ผ **${contractRooms.length}** active contract discussion${contractRooms.length > 1 ? 's' : ''}\n`; } textResponse += `\n`; // Priority grouping const highPriorityRooms = unreadRooms.filter((room: any) => room.roomType === 'INTERVIEW' || room.numUnread >= 5 ); const normalRooms = unreadRooms.filter((room: any) => room.roomType !== 'INTERVIEW' && room.numUnread < 5 ); // High priority section if (highPriorityRooms.length > 0) { textResponse += `๐Ÿ”ด **HIGH PRIORITY**\n`; for (const room of highPriorityRooms) { textResponse += formatUnreadRoom(room, validatedArgs.include_context); } } // Normal priority section if (normalRooms.length > 0) { textResponse += `\n๐ŸŸก **NORMAL PRIORITY**\n`; for (const room of normalRooms) { textResponse += formatUnreadRoom(room, validatedArgs.include_context); } } // Quick action hint textResponse += `\n๐Ÿ’ก _Tip: Use 'upwork_send_message' to quickly reply to any room_`; } return { content: [{ type: "text", text: textResponse }] }; } catch (error) { throw upworkClient.handleError(error); } } // Helper function to format a room with unread messages function formatUnreadRoom(room: any, includeContext: boolean): string { let text = `\n๐Ÿ’ฌ **${room.roomName || 'Unnamed Room'}**\n`; // Unread count with emphasis const unreadEmoji = room.numUnread >= 5 ? '๐Ÿ”ฅ' : room.numUnread >= 3 ? 'โš ๏ธ' : '๐Ÿ“Œ'; text += `${unreadEmoji} **${room.numUnread} unread** message${room.numUnread > 1 ? 's' : ''}\n`; // Room type indicator if (room.roomType === 'INTERVIEW') { text += `๐ŸŽฏ Type: **Interview Room**\n`; } else if (room.contractId) { text += `๐Ÿ“„ Contract ID: ${room.contractId}\n`; } // Latest message preview if (room.latestStory) { const messageTime = new Date(room.latestStory.createdDateTime); const timeAgo = getTimeAgo(messageTime); text += `โฐ Last message: ${timeAgo} from ${room.latestStory.user?.name || 'Unknown'}\n`; if (includeContext && room.latestStory.message) { const preview = room.latestStory.message.length > 80 ? room.latestStory.message.substring(0, 77) + '...' : room.latestStory.message; text += `๐Ÿ’ญ _"${preview}"_\n`; } } // Contract topic if available if (includeContext && room.topic) { text += `๐Ÿ“‹ Topic: _${room.topic}_\n`; } // Room URL text += `๐Ÿ”— [Open Room](https://www.upwork.com/ab/messages/rooms/${room.id})\n`; return text; } // Helper function to get relative time function getTimeAgo(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffDays > 0) { return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; } else if (diffHours > 0) { return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; } else if (diffMins > 0) { return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`; } else { return 'just now'; } } // Export the tool export const getUnreadRoomsTool = { definition: getUnreadRoomsToolDefinition, handler: getUnreadRoomsHandler, schema: GetUnreadRoomsArgsSchema }; // Test harness - run this file directly to test async function testUnreadRooms() { console.log("๐Ÿงช Testing upwork_get_unread_rooms..."); try { // Import the UpworkClient const { UpworkClient } = await import("../../services/upwork-client.js"); // Create client instance const client = new UpworkClient(); // Test with default parameters const testArgs = { sort_by: "unread_count" as const, include_context: true }; console.log("๐Ÿ“ก Making GraphQL call to Upwork..."); const result = await getUnreadRoomsHandler(testArgs, client); console.log("โœ… SUCCESS! Response:"); console.log(result.content[0].text); } catch (error) { console.error("โŒ ERROR:", error); process.exit(1); } } // Run test if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { testUnreadRooms(); }