import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import {
pickFile,
pickFolder,
pickFiles,
saveDialog,
clipboardRead,
clipboardWrite,
notify,
openUrl,
getSystemInfo,
revealInFinder,
openWithDefault,
getFinderSelection,
takeScreenshot,
screenshotToClipboard,
getScreenResolution,
getImageInfo,
resizeImage,
convertImage,
getPdfPageCount,
mergePdfs,
quickLook,
createNote,
listNoteFolders,
listNotes,
getNoteContent,
} from "./lib/macos.js";
export function registerTools(server: McpServer): void {
// ============ FILE DIALOGS ============
// 1. macos_pick_file
server.registerTool(
"macos_pick_file",
{
description:
"Open a file picker dialog and return the selected file path. Returns null if cancelled.",
inputSchema: {
prompt: z.string().optional().describe("Dialog prompt text"),
defaultLocation: z
.string()
.optional()
.describe("Default directory to open in"),
fileTypes: z
.array(z.string())
.optional()
.describe("Allowed file extensions (e.g., ['txt', 'md', 'pdf'])"),
},
},
async ({ prompt, defaultLocation, fileTypes }) => {
try {
const result = await pickFile({ prompt, defaultLocation, fileTypes });
return {
content: [
{
type: "text",
text: JSON.stringify({ selected: result, cancelled: result === null }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 2. macos_pick_folder
server.registerTool(
"macos_pick_folder",
{
description:
"Open a folder picker dialog and return the selected folder path. Returns null if cancelled.",
inputSchema: {
prompt: z.string().optional().describe("Dialog prompt text"),
defaultLocation: z
.string()
.optional()
.describe("Default directory to open in"),
},
},
async ({ prompt, defaultLocation }) => {
try {
const result = await pickFolder({ prompt, defaultLocation });
return {
content: [
{
type: "text",
text: JSON.stringify({ selected: result, cancelled: result === null }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 3. macos_pick_files
server.registerTool(
"macos_pick_files",
{
description:
"Open a file picker dialog allowing multiple selection. Returns array of paths or null if cancelled.",
inputSchema: {
prompt: z.string().optional().describe("Dialog prompt text"),
defaultLocation: z
.string()
.optional()
.describe("Default directory to open in"),
fileTypes: z
.array(z.string())
.optional()
.describe("Allowed file extensions (e.g., ['txt', 'md', 'pdf'])"),
},
},
async ({ prompt, defaultLocation, fileTypes }) => {
try {
const result = await pickFiles({ prompt, defaultLocation, fileTypes });
return {
content: [
{
type: "text",
text: JSON.stringify(
{ selected: result, count: result?.length ?? 0, cancelled: result === null },
null,
2
),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 4. macos_save_dialog
server.registerTool(
"macos_save_dialog",
{
description:
"Open a save file dialog and return the chosen path. Returns null if cancelled.",
inputSchema: {
prompt: z.string().optional().describe("Dialog prompt text"),
defaultName: z.string().optional().describe("Default file name"),
defaultLocation: z
.string()
.optional()
.describe("Default directory to save in"),
},
},
async ({ prompt, defaultName, defaultLocation }) => {
try {
const result = await saveDialog({ prompt, defaultName, defaultLocation });
return {
content: [
{
type: "text",
text: JSON.stringify({ selected: result, cancelled: result === null }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ CLIPBOARD ============
// 5. macos_clipboard_read
server.registerTool(
"macos_clipboard_read",
{
description: "Read the current text content from the system clipboard.",
inputSchema: {},
},
async () => {
try {
const text = await clipboardRead();
return {
content: [
{
type: "text",
text: JSON.stringify({ content: text, length: text.length }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 6. macos_clipboard_write
server.registerTool(
"macos_clipboard_write",
{
description: "Write text content to the system clipboard.",
inputSchema: {
text: z.string().describe("Text to copy to clipboard"),
},
},
async ({ text }) => {
try {
await clipboardWrite(text);
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, length: text.length }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ SYSTEM ============
// 7. macos_notify
server.registerTool(
"macos_notify",
{
description: "Display a macOS system notification.",
inputSchema: {
title: z.string().describe("Notification title"),
message: z.string().describe("Notification message body"),
subtitle: z.string().optional().describe("Optional subtitle"),
sound: z
.string()
.optional()
.describe("Optional sound name (e.g., 'Basso', 'Blow', 'Bottle', 'Frog', 'Funk', 'Glass', 'Hero', 'Morse', 'Ping', 'Pop', 'Purr', 'Sosumi', 'Submarine', 'Tink')"),
},
},
async ({ title, message, subtitle, sound }) => {
try {
await notify({ title, message, subtitle, sound });
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, title, message }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 8. macos_open_url
server.registerTool(
"macos_open_url",
{
description: "Open a URL in the default web browser.",
inputSchema: {
url: z.string().describe("URL to open"),
},
},
async ({ url }) => {
try {
await openUrl(url);
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, url }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 9. macos_system_info
server.registerTool(
"macos_system_info",
{
description:
"Get macOS system information: computer name, username, home directory, OS version.",
inputSchema: {},
},
async () => {
try {
const info = await getSystemInfo();
return {
content: [
{
type: "text",
text: JSON.stringify(info, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ FINDER ============
// 10. macos_reveal_in_finder
server.registerTool(
"macos_reveal_in_finder",
{
description: "Reveal a file or folder in Finder and bring Finder to front.",
inputSchema: {
path: z.string().describe("Path to reveal in Finder"),
},
},
async ({ path }) => {
try {
await revealInFinder(path);
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, path }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 11. macos_open_with_default
server.registerTool(
"macos_open_with_default",
{
description: "Open a file with its default application.",
inputSchema: {
path: z.string().describe("Path to file to open"),
},
},
async ({ path }) => {
try {
await openWithDefault(path);
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, path }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 12. macos_get_finder_selection
server.registerTool(
"macos_get_finder_selection",
{
description:
"Get the currently selected files/folders in the frontmost Finder window.",
inputSchema: {},
},
async () => {
try {
const selection = await getFinderSelection();
return {
content: [
{
type: "text",
text: JSON.stringify({ selected: selection, count: selection.length }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ SCREENSHOT ============
// 13. macos_screenshot
server.registerTool(
"macos_screenshot",
{
description:
"Take a screenshot and save to file. Can capture full screen, a region, or specific window.",
inputSchema: {
path: z.string().describe("Path to save the screenshot (png)"),
fullScreen: z
.boolean()
.optional()
.describe("Capture entire screen (default: interactive window selection)"),
region: z
.object({
x: z.number(),
y: z.number(),
width: z.number(),
height: z.number(),
})
.optional()
.describe("Capture specific region { x, y, width, height }"),
},
},
async ({ path, fullScreen, region }) => {
try {
const result = await takeScreenshot({ path, fullScreen, region });
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, path: result }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 14. macos_screenshot_clipboard
server.registerTool(
"macos_screenshot_clipboard",
{
description: "Take a screenshot and copy to clipboard (interactive selection).",
inputSchema: {},
},
async () => {
try {
await screenshotToClipboard();
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, destination: "clipboard" }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 15. macos_screen_resolution
server.registerTool(
"macos_screen_resolution",
{
description: "Get the current screen resolution and scale factor.",
inputSchema: {},
},
async () => {
try {
const resolution = await getScreenResolution();
return {
content: [
{
type: "text",
text: JSON.stringify(resolution, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ IMAGE TOOLS ============
// 16. macos_image_info
server.registerTool(
"macos_image_info",
{
description:
"Get image metadata: dimensions, format, color space, bit depth.",
inputSchema: {
path: z.string().describe("Path to image file"),
},
},
async ({ path }) => {
try {
const info = await getImageInfo(path);
return {
content: [
{
type: "text",
text: JSON.stringify(info, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 17. macos_image_resize
server.registerTool(
"macos_image_resize",
{
description:
"Resize an image. Specify width, height, or maxSize to maintain aspect ratio.",
inputSchema: {
inputPath: z.string().describe("Path to input image"),
outputPath: z.string().describe("Path for output image"),
width: z.number().optional().describe("Target width in pixels"),
height: z.number().optional().describe("Target height in pixels"),
maxSize: z
.number()
.optional()
.describe("Maximum dimension (maintains aspect ratio)"),
},
},
async ({ inputPath, outputPath, width, height, maxSize }) => {
try {
const result = await resizeImage({ inputPath, outputPath, width, height, maxSize });
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, outputPath: result }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 18. macos_image_convert
server.registerTool(
"macos_image_convert",
{
description: "Convert image to another format (jpeg, png, gif, tiff, bmp, heic).",
inputSchema: {
inputPath: z.string().describe("Path to input image"),
outputPath: z.string().describe("Path for output image"),
format: z
.enum(["jpeg", "png", "gif", "tiff", "bmp", "heic"])
.describe("Target format"),
},
},
async ({ inputPath, outputPath, format }) => {
try {
const result = await convertImage({ inputPath, outputPath, format });
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, outputPath: result, format }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ PDF TOOLS ============
// 19. macos_pdf_page_count
server.registerTool(
"macos_pdf_page_count",
{
description: "Get the number of pages in a PDF file.",
inputSchema: {
path: z.string().describe("Path to PDF file"),
},
},
async ({ path }) => {
try {
const count = await getPdfPageCount(path);
return {
content: [
{
type: "text",
text: JSON.stringify({ path, pageCount: count }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 20. macos_pdf_merge
server.registerTool(
"macos_pdf_merge",
{
description: "Merge multiple PDF files into one.",
inputSchema: {
inputPaths: z.array(z.string()).describe("Array of PDF file paths to merge"),
outputPath: z.string().describe("Path for merged output PDF"),
},
},
async ({ inputPaths, outputPath }) => {
try {
const result = await mergePdfs({ inputPaths, outputPath });
return {
content: [
{
type: "text",
text: JSON.stringify(
{ success: true, outputPath: result, mergedCount: inputPaths.length },
null,
2
),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ QUICK LOOK ============
// 21. macos_quick_look
server.registerTool(
"macos_quick_look",
{
description: "Open a file with Quick Look preview.",
inputSchema: {
path: z.string().describe("Path to file to preview"),
},
},
async ({ path }) => {
try {
await quickLook(path);
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, path }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// ============ NOTES ============
// 22. macos_note_create
server.registerTool(
"macos_note_create",
{
description: "Create a new note in the Notes app.",
inputSchema: {
name: z.string().describe("Note title"),
body: z.string().describe("Note content"),
folder: z
.string()
.optional()
.describe("Folder name (defaults to 'Notes')"),
},
},
async ({ name, body, folder }) => {
try {
await createNote({ name, body, folder });
return {
content: [
{
type: "text",
text: JSON.stringify({ success: true, name, folder: folder || "Notes" }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 23. macos_note_folders
server.registerTool(
"macos_note_folders",
{
description: "List all folders in the Notes app.",
inputSchema: {},
},
async () => {
try {
const folders = await listNoteFolders();
return {
content: [
{
type: "text",
text: JSON.stringify({ folders, count: folders.length }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 24. macos_note_list
server.registerTool(
"macos_note_list",
{
description: "List all notes in a folder.",
inputSchema: {
folder: z
.string()
.optional()
.describe("Folder name (defaults to 'Notes')"),
},
},
async ({ folder }) => {
try {
const notes = await listNotes(folder);
return {
content: [
{
type: "text",
text: JSON.stringify({ notes, count: notes.length }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
// 25. macos_note_read
server.registerTool(
"macos_note_read",
{
description: "Read the content of a note by its ID.",
inputSchema: {
noteId: z.string().describe("Note ID (from macos_note_list)"),
},
},
async ({ noteId }) => {
try {
const content = await getNoteContent(noteId);
return {
content: [
{
type: "text",
text: JSON.stringify({ noteId, content }, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true,
};
}
}
);
}