Skip to main content
Glama

things_summary

Create summaries of your Things database with customizable filters for areas, projects, and tags. Export results as Markdown for readability or JSON for further processing.

Instructions

Generate a summary of your Things database with filtering options. Returns formatted Markdown or structured JSON data for tasks, projects, areas, and tags.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
areasNoFilter to show only specific areas by name (e.g., ["Work", "Personal"]). If not provided, shows all areas
formatNoOutput format for the summary. Use "markdown" for readable formatted summary (default) or "json" for structured data that can be processed by other toolsmarkdown
includeCompletedNoInclude completed tasks and projects in the summary (default: false). When true, shows recently completed items for reference
projectsNoFilter to show only specific projects by name. If not provided, shows all projects
tagsNoFilter to show only tasks/projects with specific tags (e.g., ["urgent", "review"]). If not provided, shows all items

Implementation Reference

  • The main handler function for the 'things_summary' tool. It checks for macOS platform, logs the request, fetches summary data using getThingsSummary, and returns either JSON or Markdown formatted response.
    async (params) => { try { // Validate macOS platform if (process.platform !== 'darwin') { throw new Error('Things database access is only available on macOS'); } logger.info('Generating Things summary', { format: params.format, filters: { areas: params.areas?.length || 0, tags: params.tags?.length || 0, projects: params.projects?.length || 0, includeCompleted: params.includeCompleted } }); const data = getThingsSummary(params); if (params.format === 'json') { return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else { const markdown = generateMarkdownSummary(data); return { content: [{ type: "text", text: markdown }] }; } } catch (error) { logger.error('Failed to generate Things summary', { error: error instanceof Error ? error.message : error }); throw error; } }
  • Zod input schema for the things_summary tool parameters: format (markdown/json), includeCompleted, filters for areas, tags, projects.
    const summarySchema = z.object({ format: z.enum(['markdown', 'json']) .optional() .default('markdown') .describe('Output format for the summary. Use "markdown" for readable formatted summary (default) or "json" for structured data that can be processed by other tools'), includeCompleted: z.boolean() .optional() .default(false) .describe('Include completed tasks and projects in the summary (default: false). When true, shows recently completed items for reference'), areas: z.array(z.string()) .optional() .describe('Filter to show only specific areas by name (e.g., ["Work", "Personal"]). If not provided, shows all areas'), tags: z.array(z.string()) .optional() .describe('Filter to show only tasks/projects with specific tags (e.g., ["urgent", "review"]). If not provided, shows all items'), projects: z.array(z.string()) .optional() .describe('Filter to show only specific projects by name. If not provided, shows all projects'), });
  • The registration function that calls server.tool('things_summary', ...) to register the tool with MCP server, providing name, description, schema, and handler.
    export function registerThingsSummaryTool(server: McpServer): void { server.tool( 'things_summary', 'Generate a summary of your Things database with filtering options. Returns formatted Markdown or structured JSON data for tasks, projects, areas, and tags.', summarySchema.shape, async (params) => { try { // Validate macOS platform if (process.platform !== 'darwin') { throw new Error('Things database access is only available on macOS'); } logger.info('Generating Things summary', { format: params.format, filters: { areas: params.areas?.length || 0, tags: params.tags?.length || 0, projects: params.projects?.length || 0, includeCompleted: params.includeCompleted } }); const data = getThingsSummary(params); if (params.format === 'json') { return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else { const markdown = generateMarkdownSummary(data); return { content: [{ type: "text", text: markdown }] }; } } catch (error) { logger.error('Failed to generate Things summary', { error: error instanceof Error ? error.message : error }); throw error; } } ); }
  • Core helper function that locates Things SQLite database, executes SQL queries to fetch tasks/projects/areas/tags, applies filters, structures data into ThingsSummary interface, and compresses empty fields.
    function getThingsSummary(params: any): ThingsSummary { const dbPath = findThingsDatabase(); logger.info('Found Things database', { path: dbPath }); // Build status filter let statusFilter = 'status = 0 AND trashed = 0'; if (params.includeCompleted) { statusFilter = 'trashed = 0'; } // Get all areas const areasData = executeSqlQuery(dbPath, "SELECT uuid, title, visible FROM TMArea"); const areas: ThingsArea[] = areasData.map(row => { const area: any = { id: row[0], name: row[1] || 'Unnamed Area', thingsUrl: generateThingsUrl('show', row[0]) }; if (row[2] === '1') area.visible = true; return area; }); // Filter areas if specified let filteredAreas = areas; if (params.areas && params.areas.length > 0) { filteredAreas = areas.filter(area => params.areas.includes(area.name)); } // Get all tags const tagsData = executeSqlQuery(dbPath, "SELECT uuid, title, shortcut FROM TMTag"); const tags: ThingsTag[] = tagsData.map(row => ({ id: row[0], name: row[1] || 'Unnamed Tag', shortcut: row[2] || undefined, taskCount: 0, thingsUrl: generateThingsUrl('show', undefined, { filter: row[1] }) })); // Get open tasks and projects const tasksData = executeSqlQuery(dbPath, `SELECT uuid, title, notes, type, creationDate, startDate, deadline, area, project, checklistItemsCount, openChecklistItemsCount FROM TMTask WHERE ${statusFilter}` ); // Create a map for quick lookup of project names const projectMap = new Map(); tasksData.forEach(row => { if (row[3] === '1') { projectMap.set(row[0], row[1]); } }); const allTasks: ThingsTask[] = tasksData.map(row => { const taskType = row[3] === '0' ? 'task' : row[3] === '1' ? 'project' : 'heading'; const areaInfo = areas.find(area => area.id === row[7]); const projectName = row[8] ? projectMap.get(row[8]) : null; const task: any = { id: row[0], title: row[1] || 'Untitled', type: taskType, thingsUrl: generateThingsUrl('show', row[0]) }; if (row[2]) task.notes = row[2]; const creationDate = formatDate(row[4]); if (creationDate) task.creationDate = creationDate; const startDate = formatDate(row[5], true); if (startDate) task.startDate = startDate; const deadline = formatDate(row[6], true); if (deadline) task.deadline = deadline; if (areaInfo) task.area = { id: areaInfo.id, name: areaInfo.name }; if (projectName) task.project = { id: row[8], name: projectName }; const checklistTotal = parseInt(row[9]) || 0; const checklistOpen = parseInt(row[10]) || 0; if (checklistTotal > 0 || checklistOpen > 0) { task.checklistItems = { total: checklistTotal, open: checklistOpen }; } return task; }); // Get task-tag relationships const taskTagData = executeSqlQuery(dbPath, "SELECT tasks, tags FROM TMTaskTag"); taskTagData.forEach(row => { const task = allTasks.find(t => t.id === row[0]); const tag = tags.find(t => t.id === row[1]); if (task && tag) { if (!task.tags) task.tags = []; task.tags.push({ id: tag.id, name: tag.name }); tag.taskCount++; } }); // Apply tag filtering let filteredTasks = allTasks; if (params.tags && params.tags.length > 0) { filteredTasks = allTasks.filter(task => task.tags && task.tags.some(tag => params.tags.includes(tag.name)) ); } // Apply project filtering if (params.projects && params.projects.length > 0) { filteredTasks = filteredTasks.filter(task => (task.type === 'project' && params.projects.includes(task.title)) || (task.project && params.projects.includes(task.project.name)) ); } // Separate tasks by type const projects = filteredTasks.filter(task => task.type === 'project'); const tasks = filteredTasks.filter(task => task.type === 'task'); const inboxTasks = tasks.filter(task => !task.area && !task.project); const todayTasks = tasks.filter(task => { const today = new Date().toISOString().split('T')[0]; return task.startDate === today; }); // Organize tasks by area and add project tasks filteredAreas.forEach(area => { const areaProjects = projects.filter(project => project.area?.id === area.id); const areaTasks = tasks.filter(task => task.area?.id === area.id && !task.project); if (areaProjects.length > 0) area.projects = areaProjects; if (areaTasks.length > 0) area.tasks = areaTasks; }); // Add tasks to their respective projects projects.forEach(project => { const projectTasks = tasks.filter(task => task.project?.id === project.id); if (projectTasks.length > 0) { (project as any).tasks = projectTasks; } }); // Filter areas and tags to show only active ones const activeAreas = filteredAreas.filter(area => (area.projects && area.projects.length > 0) || (area.tasks && area.tasks.length > 0) ); const activeTags = tags.filter(tag => tag.taskCount > 0); const summary: any = { summary: { totalOpenTasks: tasks.length, totalActiveProjects: projects.length, totalAreas: activeAreas.length, totalTags: activeTags.length, lastUpdated: new Date().toISOString() }, urls: { showToday: generateThingsUrl('show', undefined, { list: 'today' }), showInbox: generateThingsUrl('show', undefined, { list: 'inbox' }), showProjects: generateThingsUrl('show', undefined, { list: 'projects' }), showAreas: generateThingsUrl('show', undefined, { list: 'areas' }) } }; // Only add sections that have content if (activeAreas.length > 0) summary.areas = activeAreas; if (inboxTasks.length > 0) summary.inboxTasks = inboxTasks; if (todayTasks.length > 0) summary.todayTasks = todayTasks; if (projects.length > 0) summary.projects = projects; if (activeTags.length > 0) summary.tags = activeTags; return compressObject(summary); }
  • Import and invocation of registerThingsSummaryTool in the main server entrypoint to register the tool with the MCP server instance.
    import { registerThingsSummaryTool } from './tools/things-summary.js'; import { registerExportJsonTool } from './tools/export-json.js'; const server = new McpServer({ name: 'things-mcp', version: '1.0.0' }); // Register all tools registerAddTodoTool(server); registerAddProjectTool(server); registerUpdateTodoTool(server); registerUpdateProjectTool(server); registerThingsSummaryTool(server);

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wbopan/things-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server