#!/usr/bin/env node
/**
* Cozi MCP Server
*
* Model Context Protocol server for Cozi Family Organizer
* Enables Claude to manage shopping lists and todo lists through Cozi API
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { CoziApiClient } from "@brandcast_app/cozi-api-client";
import type {
CoziList,
CoziItem,
} from "@brandcast_app/cozi-api-client";
// Initialize Cozi client
const client = new CoziApiClient({
debug: process.env.DEBUG === "true",
});
// Session state
let isAuthenticated = false;
// Authentication helper
async function ensureAuthenticated(): Promise<void> {
if (isAuthenticated) return;
const username = process.env.COZI_USERNAME;
const password = process.env.COZI_PASSWORD;
if (!username || !password) {
throw new Error(
"COZI_USERNAME and COZI_PASSWORD environment variables are required"
);
}
await client.authenticate(username, password);
isAuthenticated = true;
}
// Create MCP server
const server = new Server(
{
name: "cozi-mcp-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// Tool definitions
const tools: Tool[] = [
{
name: "cozi_get_lists",
description:
"Get all Cozi lists (shopping and todo lists) with their items. Returns complete list of all lists including item counts and contents.",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "cozi_get_list",
description:
"Get a specific Cozi list by ID with all its items. Use this to see the full details of a single list.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list to retrieve",
},
},
required: ["listId"],
},
},
{
name: "cozi_create_list",
description:
"Create a new Cozi list. Can be either a shopping list or a todo list.",
inputSchema: {
type: "object",
properties: {
title: {
type: "string",
description: "The title/name of the new list",
},
type: {
type: "string",
enum: ["shopping", "todo"],
description: "The type of list to create",
},
},
required: ["title", "type"],
},
},
{
name: "cozi_delete_list",
description:
"Delete a Cozi list by ID. This permanently removes the list and all its items.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list to delete",
},
},
required: ["listId"],
},
},
{
name: "cozi_add_item",
description:
"Add a new item to a Cozi list. The item will be added to the specified list.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list to add the item to",
},
text: {
type: "string",
description: "The text/description of the item",
},
},
required: ["listId", "text"],
},
},
{
name: "cozi_edit_item",
description:
"Edit the text of an existing item in a Cozi list.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list containing the item",
},
itemId: {
type: "string",
description: "The ID of the item to edit",
},
text: {
type: "string",
description: "The new text for the item",
},
},
required: ["listId", "itemId", "text"],
},
},
{
name: "cozi_mark_item_complete",
description:
"Mark an item as complete/done in a Cozi list. This checks off the item.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list containing the item",
},
itemId: {
type: "string",
description: "The ID of the item to mark complete",
},
},
required: ["listId", "itemId"],
},
},
{
name: "cozi_mark_item_incomplete",
description:
"Mark an item as incomplete/not done in a Cozi list. This unchecks the item.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list containing the item",
},
itemId: {
type: "string",
description: "The ID of the item to mark incomplete",
},
},
required: ["listId", "itemId"],
},
},
{
name: "cozi_remove_item",
description:
"Remove/delete an item from a Cozi list. This permanently deletes the item.",
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "The ID of the list containing the item",
},
itemId: {
type: "string",
description: "The ID of the item to remove",
},
},
required: ["listId", "itemId"],
},
},
];
// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error("No arguments provided");
}
try {
await ensureAuthenticated();
switch (name) {
case "cozi_get_lists": {
const lists = await client.getLists();
return {
content: [
{
type: "text",
text: JSON.stringify(lists, null, 2),
},
],
};
}
case "cozi_get_list": {
const list = await client.getList(args.listId as string);
return {
content: [
{
type: "text",
text: JSON.stringify(list, null, 2),
},
],
};
}
case "cozi_create_list": {
const listId = await client.addList({
title: args.title as string,
type: args.type as "shopping" | "todo",
});
return {
content: [
{
type: "text",
text: `Successfully created list with ID: ${listId}`,
},
],
};
}
case "cozi_delete_list": {
await client.removeList(args.listId as string);
return {
content: [
{
type: "text",
text: `Successfully deleted list ${args.listId}`,
},
],
};
}
case "cozi_add_item": {
await client.addItem({
listId: args.listId as string,
text: args.text as string,
});
return {
content: [
{
type: "text",
text: `Successfully added item "${args.text}" to list ${args.listId}`,
},
],
};
}
case "cozi_edit_item": {
await client.editItem({
listId: args.listId as string,
itemId: args.itemId as string,
text: args.text as string,
});
return {
content: [
{
type: "text",
text: `Successfully updated item ${args.itemId} to "${args.text}"`,
},
],
};
}
case "cozi_mark_item_complete": {
await client.markItem({
listId: args.listId as string,
itemId: args.itemId as string,
completed: true,
});
return {
content: [
{
type: "text",
text: `Successfully marked item ${args.itemId} as complete`,
},
],
};
}
case "cozi_mark_item_incomplete": {
await client.markItem({
listId: args.listId as string,
itemId: args.itemId as string,
completed: false,
});
return {
content: [
{
type: "text",
text: `Successfully marked item ${args.itemId} as incomplete`,
},
],
};
}
case "cozi_remove_item": {
await client.removeItem({
listId: args.listId as string,
itemId: args.itemId as string,
});
return {
content: [
{
type: "text",
text: `Successfully removed item ${args.itemId} from list ${args.listId}`,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Cozi MCP Server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});