// src/tools/todosets.ts
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { bcRequest } from "../lib/basecamp.js";
async function getTodosetFromDock(
project_id: number
): Promise<{ id: number; url: string } | null> {
const project = await bcRequest<any>("GET", `/projects/${project_id}.json`);
const dock = Array.isArray(project?.dock) ? project.dock : [];
const todoset = dock.find((d: any) => (d?.name || d?.app_name) === "todoset");
if (!todoset) return null;
const href: string =
todoset.url || todoset.href || todoset.api_url || todoset.app_url || "";
const m = href.match(/\/todosets\/(\d+)/);
return m ? { id: Number(m[1]), url: href } : null;
}
export function registerTodosetTools(server: McpServer) {
// Get the to-do set (and optionally lists) for a project
server.registerTool(
"get_todoset",
{
title: "Get a project's to-do set (via dock)",
description:
"Resolves the project's to-do set by reading the dock; can include lists.",
inputSchema: {
project_id: z.number().int(),
include_lists: z.boolean().optional(),
},
},
async ({ project_id, include_lists }) => {
const resolved = await getTodosetFromDock(project_id);
if (!resolved) {
return {
content: [
{ type: "text", text: "No to-do set found in the project's dock." },
],
};
}
if (!include_lists) {
return {
content: [{ type: "text", text: JSON.stringify(resolved, null, 2) }],
};
}
const lists = await bcRequest<any[]>(
"GET",
`/buckets/${project_id}/todosets/${resolved.id}/todolists.json`
);
return {
content: [
{
type: "text",
text: JSON.stringify({ todoset: resolved, lists }, null, 2),
},
],
};
}
);
// List to-do lists for a project (via dock)
server.registerTool(
"list_todolists",
{
title: "List to-do lists for a project",
description:
"Resolves the project's to-do set from dock, then lists its to-do lists.",
inputSchema: { project_id: z.number().int() },
},
async ({ project_id }) => {
const resolved = await getTodosetFromDock(project_id);
if (!resolved) {
return {
content: [
{ type: "text", text: "No to-do set found in the project's dock." },
],
};
}
const lists = await bcRequest<any[]>(
"GET",
`/buckets/${project_id}/todosets/${resolved.id}/todolists.json`
);
const text =
Array.isArray(lists) && lists.length
? lists.map((t: any) => `${t.id}: ${t.name}`).join("\n")
: "No to-do lists found.";
return {
content: [{ type: "text", text: `To-do set ${resolved.id}\n${text}` }],
};
}
);
}