Skip to main content
Glama

timeline_list_scheduled_events

Retrieve scheduled social media events with filters for track, status, platform, and date range to manage content automation workflows.

Instructions

List scheduled events with optional filtering by track, status, platform, and date range.

STATUS FILTERING:

  • 'all': Returns all events regardless of status

  • 'pending': Returns events that haven't been generated yet (contentGenerated = false)

  • 'generated': Returns events that have been generated but not posted yet (contentGenerated = true, posted = false)

  • 'posted': Returns events that have been posted (posted = true)

DATE FILTERING:

  • startDate/endDate use DATE ONLY comparison (time is ignored)

  • Format: YYYY-MM-DD or ISO 8601 datetime string

  • Example: startDate="2025-10-14" matches ALL events scheduled on Oct 14, regardless of time

  • To get events for a single day, use the same date for both startDate and endDate

EXAMPLES:

  • Get all posted events from today: { "startDate": "2025-10-14", "endDate": "2025-10-14", "status": "posted" }

  • Get all Reddit events this week: { "platform": "reddit", "startDate": "2025-10-14", "endDate": "2025-10-20" }

  • Get pending events in a specific track: { "trackId": "track-uuid", "status": "pending" }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
trackIdNoFilter by track ID
statusNoFilter by event statusall
platformNoFilter by platform (x, reddit, linkedin, instagram, tiktok, youtube)
startDateNoStart date for filtering (YYYY-MM-DD). Date-only comparison - time is ignored.
endDateNoEnd date for filtering (YYYY-MM-DD). Date-only comparison - time is ignored.
limitNoMaximum number of events to return
offsetNoOffset for pagination

Implementation Reference

  • The main handler function for timeline_list_scheduled_events. Queries the database for scheduled events, applies filters for track, platform, status, and date range, parses results, and returns formatted JSON response.
    execute: async (params) => {
      const db = await getDb();
      
      let whereConditions = [eq(events.eventType, 'scheduled')];
      
      if (params.trackId) {
        whereConditions.push(eq(events.trackId, params.trackId));
      }
      
      if (params.platform) {
        whereConditions.push(eq(events.platform, params.platform));
      }
      
      const results = await db.select({
        event: events,
        track: tracks
      })
      .from(events)
      .innerJoin(tracks, eq(events.trackId, tracks.id))
      .where(and(...whereConditions))
      .orderBy(asc(events.scheduledTime))
      .limit(params.limit)
      .offset(params.offset);
      
      // Apply additional filters
      const filtered = results.filter(({ event }) => {
        // Status filter
        if (params.status !== 'all') {
          if (params.status === 'posted' && !event.posted) return false;
          if (params.status === 'generated' && (!event.contentGenerated || event.posted)) return false;
          if (params.status === 'pending' && event.contentGenerated) return false;
        }
    
        // Date filters - compare dates only, not times
        if (params.startDate) {
          const eventDate = new Date(event.scheduledTime);
          eventDate.setHours(0, 0, 0, 0);
    
          // Parse the filter date in local timezone by extracting YYYY-MM-DD
          const dateMatch = params.startDate.match(/^(\d{4})-(\d{2})-(\d{2})/);
          if (!dateMatch) return false;
          const startDate = new Date(parseInt(dateMatch[1]), parseInt(dateMatch[2]) - 1, parseInt(dateMatch[3]), 0, 0, 0, 0);
    
          if (eventDate < startDate) return false;
        }
    
        if (params.endDate) {
          const eventDate = new Date(event.scheduledTime);
          eventDate.setHours(0, 0, 0, 0);
    
          // Parse the filter date in local timezone by extracting YYYY-MM-DD
          const dateMatch = params.endDate.match(/^(\d{4})-(\d{2})-(\d{2})/);
          if (!dateMatch) return false;
          const endDate = new Date(parseInt(dateMatch[1]), parseInt(dateMatch[2]) - 1, parseInt(dateMatch[3]), 0, 0, 0, 0);
    
          if (eventDate > endDate) return false;
        }
    
        return true;
      });
      
      const response = {
        events: filtered.map(({ event, track }) => {
          const parsedEvent = parseEventFromDb(event);
          return eventResponseSchema.parse({
            id: parsedEvent.id,
            trackId: parsedEvent.trackId,
            trackName: track.name,
            name: parsedEvent.name,
            prompt: parsedEvent.prompt, // Now using prompt field
            platform: parsedEvent.platform,
            scheduledTime: parsedEvent.scheduledTime?.toISOString(),
            generationTime: parsedEvent.generationTime?.toISOString(),
            status: parsedEvent.posted ? 'posted' : (parsedEvent.contentGenerated ? 'generated' : 'pending'),
            mediaPath: parsedEvent.mediaPath,
            metadata: parsedEvent.metadata ? JSON.parse(parsedEvent.metadata) : undefined,
            generationSessionId: parsedEvent.generationSessionId,
            postingSessionId: parsedEvent.postingSessionId,
            generationStartedAt: parsedEvent.generationStartedAt?.toISOString(),
            approvalRequestedAt: parsedEvent.approvalRequestedAt?.toISOString(),
            error: parsedEvent.error,
            postedUrl: parsedEvent.postedUrl
          });
        }),
        pagination: {
          limit: params.limit,
          offset: params.offset,
          total: filtered.length
        }
      };
      
      return JSON.stringify(response, null, 2);
    }
  • Zod input schema defining parameters for filtering scheduled events by track ID, status, platform, date range, with pagination.
    parameters: z.object({
      trackId: z.string().uuid().optional().describe('Filter by track ID'),
      status: z.enum(['all', 'pending', 'generated', 'posted']).optional().default('all').describe('Filter by event status'),
      platform: platformSchema.optional().describe('Filter by platform (x, reddit, linkedin, instagram, tiktok, youtube)'),
      startDate: isoDateTimeSchema.optional().describe('Start date for filtering (YYYY-MM-DD). Date-only comparison - time is ignored.'),
      endDate: isoDateTimeSchema.optional().describe('End date for filtering (YYYY-MM-DD). Date-only comparison - time is ignored.'),
      limit: z.number().int().positive().max(100).optional().default(50).describe('Maximum number of events to return'),
      offset: z.number().int().nonnegative().optional().default(0).describe('Offset for pagination')
    }),
  • Registration of the timeline_list_scheduled_events tool with FastMCP, including name, description, parameters schema, and handler.
    mcp.addTool({
      name: 'timeline_list_scheduled_events',
      description: `List scheduled events with optional filtering by track, status, platform, and date range.
    
    STATUS FILTERING:
    - 'all': Returns all events regardless of status
    - 'pending': Returns events that haven't been generated yet (contentGenerated = false)
    - 'generated': Returns events that have been generated but not posted yet (contentGenerated = true, posted = false)
    - 'posted': Returns events that have been posted (posted = true)
    
    DATE FILTERING:
    - startDate/endDate use DATE ONLY comparison (time is ignored)
    - Format: YYYY-MM-DD or ISO 8601 datetime string
    - Example: startDate="2025-10-14" matches ALL events scheduled on Oct 14, regardless of time
    - To get events for a single day, use the same date for both startDate and endDate
    
    EXAMPLES:
    - Get all posted events from today: { "startDate": "2025-10-14", "endDate": "2025-10-14", "status": "posted" }
    - Get all Reddit events this week: { "platform": "reddit", "startDate": "2025-10-14", "endDate": "2025-10-20" }
    - Get pending events in a specific track: { "trackId": "track-uuid", "status": "pending" }`,
      parameters: z.object({
        trackId: z.string().uuid().optional().describe('Filter by track ID'),
        status: z.enum(['all', 'pending', 'generated', 'posted']).optional().default('all').describe('Filter by event status'),
        platform: platformSchema.optional().describe('Filter by platform (x, reddit, linkedin, instagram, tiktok, youtube)'),
        startDate: isoDateTimeSchema.optional().describe('Start date for filtering (YYYY-MM-DD). Date-only comparison - time is ignored.'),
        endDate: isoDateTimeSchema.optional().describe('End date for filtering (YYYY-MM-DD). Date-only comparison - time is ignored.'),
        limit: z.number().int().positive().max(100).optional().default(50).describe('Maximum number of events to return'),
        offset: z.number().int().nonnegative().optional().default(0).describe('Offset for pagination')
      }),
      execute: async (params) => {
        const db = await getDb();
        
        let whereConditions = [eq(events.eventType, 'scheduled')];
        
        if (params.trackId) {
          whereConditions.push(eq(events.trackId, params.trackId));
        }
        
        if (params.platform) {
          whereConditions.push(eq(events.platform, params.platform));
        }
        
        const results = await db.select({
          event: events,
          track: tracks
        })
        .from(events)
        .innerJoin(tracks, eq(events.trackId, tracks.id))
        .where(and(...whereConditions))
        .orderBy(asc(events.scheduledTime))
        .limit(params.limit)
        .offset(params.offset);
        
        // Apply additional filters
        const filtered = results.filter(({ event }) => {
          // Status filter
          if (params.status !== 'all') {
            if (params.status === 'posted' && !event.posted) return false;
            if (params.status === 'generated' && (!event.contentGenerated || event.posted)) return false;
            if (params.status === 'pending' && event.contentGenerated) return false;
          }
    
          // Date filters - compare dates only, not times
          if (params.startDate) {
            const eventDate = new Date(event.scheduledTime);
            eventDate.setHours(0, 0, 0, 0);
    
            // Parse the filter date in local timezone by extracting YYYY-MM-DD
            const dateMatch = params.startDate.match(/^(\d{4})-(\d{2})-(\d{2})/);
            if (!dateMatch) return false;
            const startDate = new Date(parseInt(dateMatch[1]), parseInt(dateMatch[2]) - 1, parseInt(dateMatch[3]), 0, 0, 0, 0);
    
            if (eventDate < startDate) return false;
          }
    
          if (params.endDate) {
            const eventDate = new Date(event.scheduledTime);
            eventDate.setHours(0, 0, 0, 0);
    
            // Parse the filter date in local timezone by extracting YYYY-MM-DD
            const dateMatch = params.endDate.match(/^(\d{4})-(\d{2})-(\d{2})/);
            if (!dateMatch) return false;
            const endDate = new Date(parseInt(dateMatch[1]), parseInt(dateMatch[2]) - 1, parseInt(dateMatch[3]), 0, 0, 0, 0);
    
            if (eventDate > endDate) return false;
          }
    
          return true;
        });
        
        const response = {
          events: filtered.map(({ event, track }) => {
            const parsedEvent = parseEventFromDb(event);
            return eventResponseSchema.parse({
              id: parsedEvent.id,
              trackId: parsedEvent.trackId,
              trackName: track.name,
              name: parsedEvent.name,
              prompt: parsedEvent.prompt, // Now using prompt field
              platform: parsedEvent.platform,
              scheduledTime: parsedEvent.scheduledTime?.toISOString(),
              generationTime: parsedEvent.generationTime?.toISOString(),
              status: parsedEvent.posted ? 'posted' : (parsedEvent.contentGenerated ? 'generated' : 'pending'),
              mediaPath: parsedEvent.mediaPath,
              metadata: parsedEvent.metadata ? JSON.parse(parsedEvent.metadata) : undefined,
              generationSessionId: parsedEvent.generationSessionId,
              postingSessionId: parsedEvent.postingSessionId,
              generationStartedAt: parsedEvent.generationStartedAt?.toISOString(),
              approvalRequestedAt: parsedEvent.approvalRequestedAt?.toISOString(),
              error: parsedEvent.error,
              postedUrl: parsedEvent.postedUrl
            });
          }),
          pagination: {
            limit: params.limit,
            offset: params.offset,
            total: filtered.length
          }
        };
        
        return JSON.stringify(response, null, 2);
      }
    });
Behavior4/5

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

With no annotations provided, the description carries full burden and does well by explaining key behavioral aspects: date filtering uses DATE ONLY comparison with time ignored, format requirements (YYYY-MM-DD or ISO 8601), and how to get single-day events. It also clarifies status filtering semantics with contentGenerated/posted conditions. It doesn't mention pagination behavior or rate limits, preventing a perfect score.

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?

Perfectly structured with clear sections (STATUS FILTERING, DATE FILTERING, EXAMPLES) and zero wasted sentences. Every sentence provides essential information. The description is appropriately sized for a complex filtering tool with 7 parameters and no annotations.

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

Completeness4/5

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

For a listing tool with 7 parameters, 100% schema coverage, but no annotations or output schema, the description provides excellent context about filtering behavior and usage. The examples are particularly helpful. It doesn't describe the return format or structure, which would be needed for a perfect score given no output schema exists.

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

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description adds value by explaining status filtering semantics (mapping status values to contentGenerated/posted states) and date filtering behavior (date-only comparison, single-day technique). However, it doesn't add significant meaning beyond what the schema already documents for most parameters.

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 the verb 'List' and resource 'scheduled events' with specific filtering capabilities. It distinguishes this tool from siblings like timeline_add_scheduled_event (create) and timeline_update_scheduled_event (modify) by focusing on retrieval only. The opening sentence establishes a precise 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 clear context for when to use this tool through detailed filtering options and examples. It implicitly positions this as the primary listing tool for scheduled events. However, it doesn't explicitly mention when NOT to use it or name specific alternatives among siblings, keeping it at a 4 rather than a 5.

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/derekalia/timeline-mcp'

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