Skip to main content
Glama

whats_new

List files created or modified since a given timestamp or relative duration. Use at session start to identify what changed since your last checkpoint.

Instructions

List files created or modified since a checkpoint. since accepts ISO-8601 (2025-01-15T00:00:00Z) or relative durations (1h, 7d, 2w); invalid formats throw. Read-only; no side effects, auth, or rate limits. Returns annotated rows plus aggregate total_est_tokens so you can decide what to read next. CAVEAT: hard-deleted files are NOT surfaced — only mtime-driven changes. Defaults: include_tags=true, limit=200. project_id: null = KB only; omit = everything. Use at session start to catch up.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sinceYesISO 8601 timestamp or relative duration (e.g. "1h", "7d", "2w").
project_idNoFilter to a single project. Pass null for knowledge-base-only files. Omit for all.
include_tagsNoAttach tags[] to each file. Default true.
limitNoMax files returned. Default 200.

Implementation Reference

  • Core handler function that queries the files table for records with updated_at >= since, classifies each as 'created' or 'modified', optionally attaches tags, and returns the result with resolved timestamps.
    export function whatsNew(opts: WhatsNewOptions): WhatsNewResult {
      const now = new Date();
      const cutoff = resolveSince(opts.since, now);
      const limit = opts.limit ?? 200;
      const includeTags = opts.include_tags !== false;
    
      const db = getDatabase();
      let sql = `
        SELECT *,
          CASE WHEN created_at >= ? THEN 'created' ELSE 'modified' END AS change
        FROM files
        WHERE updated_at >= ?
      `;
      const params: unknown[] = [cutoff, cutoff];
    
      if (opts.project_id !== undefined) {
        if (opts.project_id === null) {
          sql += " AND project_id IS NULL";
        } else {
          sql += " AND project_id = ?";
          params.push(opts.project_id);
        }
      }
    
      sql += " ORDER BY updated_at DESC LIMIT ?";
      params.push(limit);
    
      const rows = db.prepare(sql).all(...params) as (FileRecord & { change: ChangeKind })[];
    
      let entries: WhatsNewEntry[] = rows;
      if (includeTags && rows.length > 0) {
        const tagMap = getTagsForFiles(rows.map((r) => r.id));
        entries = rows.map((r) => ({ ...r, tags: tagMap.get(r.id) ?? [] }));
      }
    
      return {
        since: new Date(cutoff.replace(" ", "T") + "Z").toISOString(),
        until: now.toISOString(),
        count: entries.length,
        files: entries,
      };
    }
  • Type definitions for the whats_new tool: input options (since, project_id, include_tags, limit), entry shape with change kind and optional tags, and result structure with resolved timestamps and count.
    export interface WhatsNewOptions {
      /**
       * Cutoff. Either:
       *   - ISO 8601 timestamp ("2026-04-30T12:00:00Z")
       *   - Relative duration ("30s", "15m", "2h", "1d", "7d", "1w")
       * Records with updated_at >= since are returned.
       */
      since: string;
      project_id?: number | null;
      /** If true, fetch and attach tags[] for each file. Default true. */
      include_tags?: boolean;
      /** Max rows to return. Default 200. */
      limit?: number;
    }
    
    export interface WhatsNewEntry extends FileRecord {
      change: ChangeKind;
      tags?: string[];
    }
    
    export interface WhatsNewResult {
      /** Resolved cutoff as ISO timestamp. */
      since: string;
      /** Server "now" at query time, ISO. */
      until: string;
      count: number;
      files: WhatsNewEntry[];
    }
  • MCP server tool registration for 'whats_new' — defines the Zod schema for input parameters, calls the core whatsNew() handler, annotates results with token estimates, and wraps in try/catch for error responses.
    server.tool(
      "whats_new",
      "List files created or modified since a checkpoint. `since` accepts ISO-8601 (`2025-01-15T00:00:00Z`) or relative durations (`1h`, `7d`, `2w`); invalid formats throw. Read-only; no side effects, auth, or rate limits. Returns annotated rows plus aggregate `total_est_tokens` so you can decide what to read next. CAVEAT: hard-deleted files are NOT surfaced — only mtime-driven changes. Defaults: include_tags=true, limit=200. `project_id: null` = KB only; omit = everything. Use at session start to catch up.",
      {
        since: z.string().describe("ISO 8601 timestamp or relative duration (e.g. \"1h\", \"7d\", \"2w\")."),
        project_id: z.number().nullable().optional().describe("Filter to a single project. Pass null for knowledge-base-only files. Omit for all."),
        include_tags: z.boolean().optional().describe("Attach tags[] to each file. Default true."),
        limit: z.number().optional().describe("Max files returned. Default 200."),
      },
      async ({ since, project_id, include_tags, limit }) => {
        try {
          const opts: any = { since };
          if (project_id !== undefined) opts.project_id = project_id;
          if (include_tags !== undefined) opts.include_tags = include_tags;
          if (limit !== undefined) opts.limit = limit;
          const result = whatsNew(opts);
          const annotated = result.files.map(annotateTokens);
          const total_est_tokens = annotated.reduce((s, f) => s + (f.est_tokens ?? 0), 0);
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(
                  {
                    since: result.since,
                    until: result.until,
                    count: result.count,
                    total_est_tokens,
                    files: annotated,
                  },
                  null,
                  2
                ),
              },
            ],
          };
        } catch (e) {
          return {
            isError: true,
            content: [{ type: "text", text: JSON.stringify({ error: (e as Error).message }, null, 2) }],
          };
        }
      }
    );
  • Helper that parses the 'since' parameter — accepts ISO 8601 timestamps or relative durations (s/m/h/d/w) and converts to a SQLite-comparable UTC datetime string. Also validates the cutoff is not in the future.
    /**
     * Resolve `since` (ISO timestamp OR relative duration) to a sqlite-comparable
     * UTC datetime string ("YYYY-MM-DD HH:MM:SS"). Throws RangeError if unparseable
     * or if the resulting time is in the future.
     */
    export function resolveSince(since: string, now: Date = new Date()): string {
      const trimmed = since.trim();
      if (!trimmed) throw new RangeError("`since` is required");
    
      const dur = DURATION_RE.exec(trimmed);
      let cutoff: Date;
      if (dur) {
        const n = parseInt(dur[1], 10);
        const unit = dur[2].toLowerCase();
        cutoff = new Date(now.getTime() - n * UNIT_MS[unit]);
      } else {
        const t = Date.parse(trimmed);
        if (Number.isNaN(t)) {
          throw new RangeError(
            `Cannot parse \`since\`: ${JSON.stringify(since)}. Expected ISO 8601 timestamp or relative duration like "1h", "7d", "2w".`
          );
        }
        cutoff = new Date(t);
      }
    
      if (cutoff.getTime() > now.getTime()) {
        throw new RangeError(`\`since\` is in the future: ${cutoff.toISOString()}`);
      }
      return toSqliteTime(cutoff);
    }
    
    function toSqliteTime(d: Date): string {
      // sqlite stores datetime('now') as "YYYY-MM-DD HH:MM:SS" in UTC.
      // Comparisons in our tables expect that exact shape.
      return d.toISOString().replace("T", " ").slice(0, 19);
    }
  • Helper used by the whatsNew handler to bulk-fetch tags for all returned files in a single query, avoiding N+1 round-trips.
    /**
     * Bulk-fetch tag names for many files in a single query.
     * Returns a Map keyed by file_id; missing keys = file has no tags.
     * Used by list_files / search / whats_new / project_map to inline tags
     * without an N+1 round-trip per file.
     */
    export function getTagsForFiles(fileIds: number[]): Map<number, string[]> {
      const out = new Map<number, string[]>();
      if (fileIds.length === 0) return out;
      const db = getDatabase();
      const placeholders = fileIds.map(() => "?").join(",");
      const rows = db
        .prepare(
          `SELECT ft.file_id AS file_id, t.name AS name
           FROM file_tags ft
           JOIN tags t ON t.id = ft.tag_id
           WHERE ft.file_id IN (${placeholders})
           ORDER BY t.name ASC`
        )
        .all(...fileIds) as { file_id: number; name: string }[];
      for (const r of rows) {
        const arr = out.get(r.file_id);
        if (arr) arr.push(r.name);
        else out.set(r.file_id, [r.name]);
      }
      return out;
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully discloses behavior: it states 'Read-only; no side effects, auth, or rate limits.' It also reveals the caveat about hard-deleted files not being surfaced and mentions the return type (annotated rows plus `total_est_tokens`). Defaults for `include_tags` and `limit` are provided.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured paragraph. It begins with the core purpose, follows with parameter details, adds a caveat, specifies defaults, and ends with a usage recommendation. Every sentence is informative and necessary, with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description adequately explains what the tool returns ('annotated rows plus aggregate `total_est_tokens`'). It covers all input parameters, their behaviors, defaults, caveats, and a specific use case. For a tool with 4 parameters, this is highly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description adds significant value by explaining accepted formats for `since` (ISO-8601 and relative durations), the meaning of `project_id` values (null vs omit), and defaults for `include_tags` and `limit`. This enriches understanding beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'List files created or modified since a checkpoint', specifying the action and resource. It mentions the unique checkpoint-based filtering, distinguishing it from sibling list tools like list_files. The phrase 'Use at session start to catch up' further clarifies its specific purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit usage guidance: 'Use at session start to catch up.' It also details the `since` parameter formats and caveats about hard-deleted files, implicitly guiding when not to use. However, it does not explicitly name alternative tools for other scenarios.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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/safiyu/ctxnest'

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