Skip to main content
Glama

backlog_create

Create new tasks, epics, or other backlog items with titles, descriptions, and organizational links to manage project work effectively.

Instructions

Create a new item in the backlog.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
titleYesTask title
descriptionNoTask description in markdown
source_pathNoLocal file path to read as description. Mutually exclusive with description — provide one or the other. Server reads the file directly.
typeNoType: task (default) or epic
epic_idNoParent epic ID to link this task to
parent_idNoParent ID (any entity). Supports subtasks (task→task), epic membership, folder organization, milestone grouping.
referencesNoReference links. Formats: external URLs (https://...), task refs (mcp://backlog/tasks/TASK-XXXX.md), resources (mcp://backlog/resources/{path}). Local files must include extension (file:///path/to/file.md)

Implementation Reference

  • Main handler function that processes backlog_create requests: resolves source_path if provided, generates entity ID, creates the task entity using createTask factory, persists to storage, and returns the created entity ID.
    async ({ title, description, source_path, type, epic_id, parent_id, references }) => {
      let resolvedDescription = description;
      if (source_path) {
        try {
          resolvedDescription = resolveSourcePath(source_path);
        } catch (error) {
          return { content: [{ type: 'text' as const, text: `Error reading source_path: ${error instanceof Error ? error.message : String(error)}` }] };
        }
      }
    
      // parent_id takes precedence; epic_id is alias for backward compat
      const resolvedParent = parent_id ?? epic_id;
      const id = nextEntityId(storage.getMaxId(type), type);
      const task = createTask({ id, title, description: resolvedDescription, type, parent_id: resolvedParent, references });
      // Write epic_id too for backward compat when caller used epic_id
      if (epic_id && !parent_id) task.epic_id = epic_id;
      storage.add(task);
      return { content: [{ type: 'text', text: `Created ${task.id}` }] };
    }
  • Zod input schema defining the tool's parameters: title (required), description (optional markdown), source_path (optional file path), type (task/epic), epic_id/parent_id for hierarchy, and references array. Includes validation that description and source_path are mutually exclusive.
    inputSchema: z.object({
      title: z.string().describe('Task title'),
      description: z.string().optional().describe('Task description in markdown'),
      source_path: z.string().optional().describe('Local file path to read as description. Mutually exclusive with description — provide one or the other. Server reads the file directly.'),
      type: z.enum(ENTITY_TYPES).optional().describe('Type: task (default) or epic'),
      epic_id: z.string().optional().describe('Parent epic ID to link this task to'),
      parent_id: z.string().optional().describe('Parent ID (any entity). Supports subtasks (task→task), epic membership, folder organization, milestone grouping.'),
      references: z.array(z.object({ url: z.string(), title: z.string().optional() })).optional().describe('Reference links. Formats: external URLs (https://...), task refs (mcp://backlog/tasks/TASK-XXXX.md), resources (mcp://backlog/resources/{path}). Local files must include extension (file:///path/to/file.md)'),
    }).refine(
      (data) => !(data.description && data.source_path),
      { message: 'Cannot provide both description and source_path — use one or the other' },
    ),
  • Registration function that registers the backlog_create tool with the MCP server, including its description, input schema validation, and the async handler implementation.
    export function registerBacklogCreateTool(server: McpServer) {
      server.registerTool(
        'backlog_create',
        {
          description: 'Create a new item in the backlog.',
          inputSchema: z.object({
            title: z.string().describe('Task title'),
            description: z.string().optional().describe('Task description in markdown'),
            source_path: z.string().optional().describe('Local file path to read as description. Mutually exclusive with description — provide one or the other. Server reads the file directly.'),
            type: z.enum(ENTITY_TYPES).optional().describe('Type: task (default) or epic'),
            epic_id: z.string().optional().describe('Parent epic ID to link this task to'),
            parent_id: z.string().optional().describe('Parent ID (any entity). Supports subtasks (task→task), epic membership, folder organization, milestone grouping.'),
            references: z.array(z.object({ url: z.string(), title: z.string().optional() })).optional().describe('Reference links. Formats: external URLs (https://...), task refs (mcp://backlog/tasks/TASK-XXXX.md), resources (mcp://backlog/resources/{path}). Local files must include extension (file:///path/to/file.md)'),
          }).refine(
            (data) => !(data.description && data.source_path),
            { message: 'Cannot provide both description and source_path — use one or the other' },
          ),
        },
        async ({ title, description, source_path, type, epic_id, parent_id, references }) => {
          let resolvedDescription = description;
          if (source_path) {
            try {
              resolvedDescription = resolveSourcePath(source_path);
            } catch (error) {
              return { content: [{ type: 'text' as const, text: `Error reading source_path: ${error instanceof Error ? error.message : String(error)}` }] };
            }
          }
    
          // parent_id takes precedence; epic_id is alias for backward compat
          const resolvedParent = parent_id ?? epic_id;
          const id = nextEntityId(storage.getMaxId(type), type);
          const task = createTask({ id, title, description: resolvedDescription, type, parent_id: resolvedParent, references });
          // Write epic_id too for backward compat when caller used epic_id
          if (epic_id && !parent_id) task.epic_id = epic_id;
          storage.add(task);
          return { content: [{ type: 'text', text: `Created ${task.id}` }] };
        }
      );
  • Helper function resolveSourcePath that expands ~ to home directory, resolves the file path, validates it exists and is a file, then reads and returns the file contents as a UTF-8 string.
    export function resolveSourcePath(sourcePath: string): string {
      const expanded = sourcePath.startsWith('~') ? sourcePath.replace('~', homedir()) : sourcePath;
      const resolved = resolve(expanded);
      const stat = statSync(resolved, { throwIfNoEntry: false });
      if (!stat) throw new Error(`File not found: ${sourcePath}`);
      if (!stat.isFile()) throw new Error(`Not a file: ${sourcePath}`);
      return readFileSync(resolved, 'utf-8');
    }
  • Factory function createTask that constructs an Entity object with proper defaults (status='open', created_at/updated_at timestamps) and conditionally sets optional fields like description, type, parent_id, epic_id, references, due_date, content_type, and path.
    export function createTask(input: CreateTaskInput): Entity {
      const now = new Date().toISOString();
      const task: Entity = {
        id: input.id,
        title: input.title,
        status: 'open',
        created_at: now,
        updated_at: now,
      };
      if (input.description) task.description = input.description;
      if (input.type) task.type = input.type;
      if (input.epic_id) task.epic_id = input.epic_id;
      if (input.parent_id) task.parent_id = input.parent_id;
      if (input.references?.length) task.references = input.references;
      if (input.due_date) task.due_date = input.due_date;
      if (input.content_type) task.content_type = input.content_type;
      if (input.path) task.path = input.path;
      return task;
    }

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/gkoreli/backlog-mcp'

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