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