#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import tools from "./tools.js"; import messageModule from "./utils/message.js"; console.error("Starting mac-messages-mcp server..."); // Main server object const server = new Server( { name: "Mac Messages MCP", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) { throw new Error("No arguments provided"); } switch (name) { case "messages_send": { if (!isMessagesSendArgs(args)) { throw new Error("Invalid arguments for messages_send tool"); } try { await messageModule.sendMessage(args.phoneNumber, args.message); return { content: [{ type: "text", text: `Message sent to ${args.phoneNumber}` }], isError: false }; } catch (error) { return { content: [{ type: "text", text: `Error sending message: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } case "messages_read": { if (!isMessagesReadArgs(args)) { throw new Error("Invalid arguments for messages_read tool"); } try { const messages = await messageModule.readMessages(args.phoneNumber, args.limit); return { content: [{ type: "text", text: messages.length > 0 ? messages.map(msg => `[${new Date(msg.date).toLocaleString()}] ${msg.is_from_me ? 'Me' : msg.sender}: ${msg.content}` ).join("\n") : "No messages found" }], isError: false }; } catch (error) { return { content: [{ type: "text", text: `Error reading messages: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } case "messages_schedule": { if (!isMessagesScheduleArgs(args)) { throw new Error("Invalid arguments for messages_schedule tool"); } try { const scheduledMsg = await messageModule.scheduleMessage( args.phoneNumber, args.message, new Date(args.scheduledTime) ); return { content: [{ type: "text", text: `Message scheduled to be sent to ${args.phoneNumber} at ${scheduledMsg.scheduledTime}` }], isError: false }; } catch (error) { return { content: [{ type: "text", text: `Error scheduling message: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } case "messages_unread": { if (!isMessagesUnreadArgs(args)) { throw new Error("Invalid arguments for messages_unread tool"); } try { const messages = await messageModule.getUnreadMessages(args.limit); return { content: [{ type: "text", text: messages.length > 0 ? `Found ${messages.length} unread message(s):\n` + messages.map(msg => `[${new Date(msg.date).toLocaleString()}] From ${msg.sender}:\n${msg.content}` ).join("\n\n") : "No unread messages found" }], isError: false }; } catch (error) { return { content: [{ type: "text", text: `Error getting unread messages: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } default: return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true, }; } } catch (error) { return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Start the server transport console.error("Setting up MCP server transport..."); (async () => { try { console.error("Initializing transport..."); const transport = new StdioServerTransport(); // Ensure stdout is only used for JSON messages console.error("Setting up stdout filter..."); const originalStdoutWrite = process.stdout.write.bind(process.stdout); process.stdout.write = (chunk: any, encoding?: any, callback?: any) => { // Only allow JSON messages to pass through if (typeof chunk === "string" && !chunk.startsWith("{")) { console.error("Filtering non-JSON stdout message"); return true; // Silently skip non-JSON messages } return originalStdoutWrite(chunk, encoding, callback); }; console.error("Connecting transport to server..."); await server.connect(transport); console.error("Server connected successfully!"); } catch (error) { console.error("Failed to initialize MCP server:", error); process.exit(1); } })(); // Helper functions for argument type checking function isMessagesSendArgs(args: unknown): args is { phoneNumber: string; message: string; } { if (typeof args !== "object" || args === null) return false; const { phoneNumber, message } = args as any; if (typeof phoneNumber !== "string" || typeof message !== "string") { return false; } return true; } function isMessagesReadArgs(args: unknown): args is { phoneNumber: string; limit?: number; } { if (typeof args !== "object" || args === null) return false; const { phoneNumber, limit } = args as any; if (typeof phoneNumber !== "string") { return false; } if (limit !== undefined && typeof limit !== "number") { return false; } return true; } function isMessagesScheduleArgs(args: unknown): args is { phoneNumber: string; message: string; scheduledTime: string; } { if (typeof args !== "object" || args === null) return false; const { phoneNumber, message, scheduledTime } = args as any; if (typeof phoneNumber !== "string" || typeof message !== "string" || typeof scheduledTime !== "string") { return false; } return true; } function isMessagesUnreadArgs(args: unknown): args is { limit?: number; } { if (typeof args !== "object" || args === null) return false; const { limit } = args as any; if (limit !== undefined && typeof limit !== "number") { return false; } return true; }