Skip to main content
Glama

get_recent_changes

Retrieve nodes created or modified within a specified time period in Dynalist documents to track updates and changes.

Instructions

Get nodes created or modified within a time period. WARNING: Long time periods with active documents can return many words.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlNoDynalist URL
file_idNoDocument ID (alternative to URL)
sinceYesStart date - ISO string (e.g. '2024-01-15') or timestamp in milliseconds
untilNoEnd date - ISO string or timestamp (default: now)
typeNoFilter by change typemodified
parent_levelsNoHow many parent levels to include for context
sortNoSort order by timestampnewest_first
bypass_warningNoONLY use after receiving a size warning. Do NOT set true on first request.

Implementation Reference

  • The core handler function executing the tool logic: parses Dynalist URL or file_id, validates timestamps, reads the document, filters nodes by creation/modification time within the range, determines change type, optionally includes parent nodes for context, sorts results, performs size check with bypass option, and returns formatted JSON or error/content.
    async ({ url, file_id, since, until, type, parent_levels, sort, bypass_warning }) => { let documentId = file_id; if (url) { const parsed = parseDynalistUrl(url); documentId = parsed.documentId; } if (!documentId) { return { content: [{ type: "text", text: "Error: Either 'url' or 'file_id' must be provided" }], isError: true, }; } // Parse timestamps const parseTimestamp = (val: string | number, endOfDay: boolean = false): number => { if (typeof val === "number") return val; const date = new Date(val); // If it's a date-only string (no time component) and endOfDay is true, use end of day if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(val)) { date.setUTCHours(23, 59, 59, 999); } return date.getTime(); }; const sinceTs = parseTimestamp(since, false); const untilTs = until ? parseTimestamp(until, true) : Date.now(); if (isNaN(sinceTs)) { return { content: [{ type: "text", text: "Error: Invalid 'since' date format" }], isError: true, }; } const doc = await client.readDocument(documentId); // Filter nodes by time range and type const matches = doc.nodes .filter((node) => { const createdInRange = node.created >= sinceTs && node.created <= untilTs; const modifiedInRange = node.modified >= sinceTs && node.modified <= untilTs; if (type === "created") return createdInRange; if (type === "modified") return modifiedInRange && !createdInRange; // Modified but not newly created // "both" - either created or modified in range return createdInRange || modifiedInRange; }) .map((node) => { const createdInRange = node.created >= sinceTs && node.created <= untilTs; const result: { id: string; content: string; created: number; modified: number; url: string; change_type: string; parents?: { id: string; content: string }[]; } = { id: node.id, content: node.content, created: node.created, modified: node.modified, url: buildDynalistUrl(documentId!, node.id), change_type: createdInRange ? "created" : "modified", }; // Add parents if requested if (parent_levels > 0) { const parents = getAncestors(doc.nodes, node.id, parent_levels); if (parents.length > 0) { result.parents = parents; } } return result; }); // Sort matches.sort((a, b) => { const aTime = a.change_type === "created" ? a.created : a.modified; const bTime = b.change_type === "created" ? b.created : b.modified; return sort === "newest_first" ? bTime - aTime : aTime - bTime; }); const resultText = matches.length > 0 ? JSON.stringify(matches, null, 2) : `No changes found in the specified time period`; // Check content size const sizeCheck = checkContentSize(resultText, bypass_warning || false, [ "Use a shorter time period (narrower since/until range)", "Use parent_levels: 0 to exclude parent context", "Filter by type: 'created' or 'modified' instead of 'both'", ]); if (sizeCheck) { return { content: [{ type: "text", text: sizeCheck.warning }], }; } return { content: [ { type: "text", text: resultText, }, ], }; }
  • Zod schema defining the input parameters for the get_recent_changes tool, including optional URL/file_id, required since timestamp, optional until/type/parent_levels/sort, and bypass_warning.
    { url: z.string().optional().describe("Dynalist URL"), file_id: z.string().optional().describe("Document ID (alternative to URL)"), since: z.union([z.string(), z.number()]).describe("Start date - ISO string (e.g. '2024-01-15') or timestamp in milliseconds"), until: z.union([z.string(), z.number()]).optional().describe("End date - ISO string or timestamp (default: now)"), type: z.enum(["created", "modified", "both"]).optional().default("modified").describe("Filter by change type"), parent_levels: z.number().optional().default(1).describe("How many parent levels to include for context"), sort: z.enum(["newest_first", "oldest_first"]).optional().default("newest_first").describe("Sort order by timestamp"), bypass_warning: z.boolean().optional().default(false).describe("ONLY use after receiving a size warning. Do NOT set true on first request."), },
  • The server.tool registration call that registers the get_recent_changes tool with its description, input schema, and handler function.
    server.tool( "get_recent_changes", "Get nodes created or modified within a time period. WARNING: Long time periods with active documents can return many words.", { url: z.string().optional().describe("Dynalist URL"), file_id: z.string().optional().describe("Document ID (alternative to URL)"), since: z.union([z.string(), z.number()]).describe("Start date - ISO string (e.g. '2024-01-15') or timestamp in milliseconds"), until: z.union([z.string(), z.number()]).optional().describe("End date - ISO string or timestamp (default: now)"), type: z.enum(["created", "modified", "both"]).optional().default("modified").describe("Filter by change type"), parent_levels: z.number().optional().default(1).describe("How many parent levels to include for context"), sort: z.enum(["newest_first", "oldest_first"]).optional().default("newest_first").describe("Sort order by timestamp"), bypass_warning: z.boolean().optional().default(false).describe("ONLY use after receiving a size warning. Do NOT set true on first request."), }, async ({ url, file_id, since, until, type, parent_levels, sort, bypass_warning }) => { let documentId = file_id; if (url) { const parsed = parseDynalistUrl(url); documentId = parsed.documentId; } if (!documentId) { return { content: [{ type: "text", text: "Error: Either 'url' or 'file_id' must be provided" }], isError: true, }; } // Parse timestamps const parseTimestamp = (val: string | number, endOfDay: boolean = false): number => { if (typeof val === "number") return val; const date = new Date(val); // If it's a date-only string (no time component) and endOfDay is true, use end of day if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(val)) { date.setUTCHours(23, 59, 59, 999); } return date.getTime(); }; const sinceTs = parseTimestamp(since, false); const untilTs = until ? parseTimestamp(until, true) : Date.now(); if (isNaN(sinceTs)) { return { content: [{ type: "text", text: "Error: Invalid 'since' date format" }], isError: true, }; } const doc = await client.readDocument(documentId); // Filter nodes by time range and type const matches = doc.nodes .filter((node) => { const createdInRange = node.created >= sinceTs && node.created <= untilTs; const modifiedInRange = node.modified >= sinceTs && node.modified <= untilTs; if (type === "created") return createdInRange; if (type === "modified") return modifiedInRange && !createdInRange; // Modified but not newly created // "both" - either created or modified in range return createdInRange || modifiedInRange; }) .map((node) => { const createdInRange = node.created >= sinceTs && node.created <= untilTs; const result: { id: string; content: string; created: number; modified: number; url: string; change_type: string; parents?: { id: string; content: string }[]; } = { id: node.id, content: node.content, created: node.created, modified: node.modified, url: buildDynalistUrl(documentId!, node.id), change_type: createdInRange ? "created" : "modified", }; // Add parents if requested if (parent_levels > 0) { const parents = getAncestors(doc.nodes, node.id, parent_levels); if (parents.length > 0) { result.parents = parents; } } return result; }); // Sort matches.sort((a, b) => { const aTime = a.change_type === "created" ? a.created : a.modified; const bTime = b.change_type === "created" ? b.created : b.modified; return sort === "newest_first" ? bTime - aTime : aTime - bTime; }); const resultText = matches.length > 0 ? JSON.stringify(matches, null, 2) : `No changes found in the specified time period`; // Check content size const sizeCheck = checkContentSize(resultText, bypass_warning || false, [ "Use a shorter time period (narrower since/until range)", "Use parent_levels: 0 to exclude parent context", "Filter by type: 'created' or 'modified' instead of 'both'", ]); if (sizeCheck) { return { content: [{ type: "text", text: sizeCheck.warning }], }; } return { content: [ { type: "text", text: resultText, }, ], }; } );

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/cristip73/dynalist-mcp'

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