Skip to main content
Glama
claus-92

Super Productivity MCP Server

by claus-92

update_task

Update an existing task by modifying any of its fields including title, notes, project, due date, tags, or completion status using the task ID.

Instructions

Updates one or more fields of an existing task.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesTask ID
titleNo
notesNo
projectIdNo
projectNameNo
isDoneNo
timeEstimateMinsNoNew estimate in minutes
dueDateISONoNew due date in ISO 8601 format
tagIdsNo
tagNamesNo

Implementation Reference

  • The MCP tool handler for 'update_task'. Registers the tool with a Zod schema for inputs (id, title, notes, projectId, projectName, isDone, timeEstimateMins, dueDateISO, tagIds, tagNames) and an async handler that resolves project/tag name references, then calls SpClient.updateTask to PATCH the task.
    // ── Update task ─────────────────────────────────────────────────────────
    server.tool(
      "update_task",
      "Updates one or more fields of an existing task.",
      {
        id: nonEmptyString.describe("Task ID"),
        title: nonEmptyString.optional(),
        notes: z.string().optional(),
        projectId: nonEmptyString.optional(),
        projectName: nonEmptyString.optional(),
        isDone: z.boolean().optional(),
        timeEstimateMins: z.number().optional().describe("New estimate in minutes"),
        dueDateISO: nonEmptyString.optional().describe("New due date in ISO 8601 format"),
        tagIds: z.array(nonEmptyString).optional(),
        tagNames: z.array(nonEmptyString).optional(),
      },
      async ({ id, title, notes, projectId, projectName, isDone, timeEstimateMins, dueDateISO, tagIds, tagNames }) => {
        const resolved = await resolveTaskWriteIds({ projectId, projectName, tagIds, tagNames });
        const task = await SpClient.updateTask(id, {
          title,
          notes,
          projectId: resolved.projectId,
          isDone,
          timeEstimate: timeEstimateMins ? timeEstimateMins * 60_000 : undefined,
          dueDate: parseIsoDateToTimestamp(dueDateISO),
          tagIds: resolved.tagIds,
        });
        return ok(task);
      }
    );
  • Zod input schema for the update_task tool, defining all optional fields: id, title, notes, projectId, projectName, isDone, timeEstimateMins, dueDateISO, tagIds, tagNames.
    server.tool(
      "update_task",
      "Updates one or more fields of an existing task.",
      {
        id: nonEmptyString.describe("Task ID"),
        title: nonEmptyString.optional(),
        notes: z.string().optional(),
        projectId: nonEmptyString.optional(),
        projectName: nonEmptyString.optional(),
        isDone: z.boolean().optional(),
        timeEstimateMins: z.number().optional().describe("New estimate in minutes"),
        dueDateISO: nonEmptyString.optional().describe("New due date in ISO 8601 format"),
        tagIds: z.array(nonEmptyString).optional(),
        tagNames: z.array(nonEmptyString).optional(),
      },
  • UpdateTaskPayload interface defining the shape of the payload sent to the Super Productivity local API: title, notes, projectId, isDone, timeEstimate, dueDate, tagIds.
    export interface UpdateTaskPayload {
      title?: string;
      notes?: string;
      projectId?: string;
      isDone?: boolean;
      timeEstimate?: number;
      dueDate?: number;
      tagIds?: string[];
    }
  • SpClient.updateTask() - the low-level API call that sends a PATCH request to /tasks/{id} with the UpdateTaskPayload body.
    updateTask(id: string, payload: UpdateTaskPayload): Promise<Task> {
      return request(`/tasks/${id}`, TaskSchema, {
        method: "PATCH",
        body: JSON.stringify(payload),
      });
    },
  • registerTaskTools() function that registers all task-related tools (including update_task) on the McpServer instance. Called from src/index.ts line 16.
    export function registerTaskTools(server: McpServer) {
      server.tool(
        "health_check",
        "Checks whether the Super Productivity local REST API is reachable and the renderer is ready.",
        {},
        async () => {
          const health = await SpClient.health();
          return ok(health);
        }
      );
    
      // ── List all tasks ──────────────────────────────────────────────────────
      server.tool(
        "list_tasks",
        "Returns tasks from Super Productivity with optional filters.",
        {
          query: nonEmptyString.optional().describe("Filter by title text (case-insensitive contains)"),
          projectId: nonEmptyString.optional().describe("Filter by project ID"),
          projectName: nonEmptyString.optional().describe("Filter by project name"),
          tagId: nonEmptyString.optional().describe("Filter by tag ID"),
          tagName: nonEmptyString.optional().describe("Filter by tag name"),
          includeDone: z.boolean().optional().describe("Include completed tasks"),
          source: z.enum(["active", "archived", "all"]).optional().describe("Which task source to query"),
        },
        async ({ query, projectId, projectName, tagId, tagName, includeDone, source }) => {
          const resolved = await resolveTaskFilterIds({ projectId, projectName, tagId, tagName });
          const tasks = await SpClient.getTasks({
            query,
            projectId: resolved.projectId,
            tagId: resolved.tagId,
            includeDone,
            source,
          });
          return ok(tasks);
        }
      );
    
      server.tool(
        "search_tasks",
        "Searches tasks by title text and optional filters.",
        {
          query: nonEmptyString.describe("Text to search for in the task title"),
          projectId: nonEmptyString.optional().describe("Filter by project ID"),
          projectName: nonEmptyString.optional().describe("Filter by project name"),
          tagId: nonEmptyString.optional().describe("Filter by tag ID"),
          tagName: nonEmptyString.optional().describe("Filter by tag name"),
          includeDone: z.boolean().optional().describe("Include completed tasks"),
          source: z.enum(["active", "archived", "all"]).optional().describe("Which task source to query"),
        },
        async ({ query, projectId, projectName, tagId, tagName, includeDone, source }) => {
          const resolved = await resolveTaskFilterIds({ projectId, projectName, tagId, tagName });
          const tasks = await SpClient.getTasks({
            query,
            projectId: resolved.projectId,
            tagId: resolved.tagId,
            includeDone,
            source,
          });
          return ok(tasks);
        }
      );
    
      // ── Get single task ─────────────────────────────────────────────────────
      server.tool(
        "get_task",
        "Returns details of a specific task by ID.",
        { id: z.string().describe("Task ID") },
        async ({ id }) => {
          const task = await SpClient.getTask(id);
          return ok(task);
        }
      );
    
      server.tool(
        "get_status",
        "Returns the current task and task-count status.",
        {},
        async () => {
          const status = await SpClient.getStatus();
          return ok(status);
        }
      );
    
      server.tool(
        "get_current_task",
        "Returns the currently active task, if any.",
        {},
        async () => {
          const task = await SpClient.getCurrentTask();
          return ok(task);
        }
      );
    
      // ── Create task ─────────────────────────────────────────────────────────
      server.tool(
        "create_task",
        "Creates a new task in Super Productivity. Extra fields are passed through to the local API if supported by your app version.",
        {
          title: nonEmptyString.describe("Task title"),
          notes: z.string().optional().describe("Additional notes / description"),
          projectId: nonEmptyString.optional().describe("Project ID to assign the task to"),
          projectName: nonEmptyString.optional().describe("Project name to assign the task to"),
          tagIds: z.array(nonEmptyString).optional().describe("List of tag IDs"),
          tagNames: z.array(nonEmptyString).optional().describe("List of tag names"),
          timeEstimateMins: z.number().optional().describe("Estimated duration in minutes"),
          dueDateISO: nonEmptyString.optional().describe("Due date in ISO 8601 format (e.g. 2026-04-23)"),
        },
        async ({ title, notes, projectId, projectName, tagIds, tagNames, timeEstimateMins, dueDateISO }) => {
          const resolved = await resolveTaskWriteIds({ projectId, projectName, tagIds, tagNames });
          const task = await SpClient.createTask({
            title,
            notes,
            projectId: resolved.projectId,
            tagIds: resolved.tagIds,
            timeEstimate: timeEstimateMins ? timeEstimateMins * 60_000 : undefined,
            dueDate: parseIsoDateToTimestamp(dueDateISO),
          });
          return ok(task);
        }
      );
    
      // ── Update task ─────────────────────────────────────────────────────────
      server.tool(
        "update_task",
        "Updates one or more fields of an existing task.",
        {
          id: nonEmptyString.describe("Task ID"),
          title: nonEmptyString.optional(),
          notes: z.string().optional(),
          projectId: nonEmptyString.optional(),
          projectName: nonEmptyString.optional(),
          isDone: z.boolean().optional(),
          timeEstimateMins: z.number().optional().describe("New estimate in minutes"),
          dueDateISO: nonEmptyString.optional().describe("New due date in ISO 8601 format"),
          tagIds: z.array(nonEmptyString).optional(),
          tagNames: z.array(nonEmptyString).optional(),
        },
        async ({ id, title, notes, projectId, projectName, isDone, timeEstimateMins, dueDateISO, tagIds, tagNames }) => {
          const resolved = await resolveTaskWriteIds({ projectId, projectName, tagIds, tagNames });
          const task = await SpClient.updateTask(id, {
            title,
            notes,
            projectId: resolved.projectId,
            isDone,
            timeEstimate: timeEstimateMins ? timeEstimateMins * 60_000 : undefined,
            dueDate: parseIsoDateToTimestamp(dueDateISO),
            tagIds: resolved.tagIds,
          });
          return ok(task);
        }
      );
    
      // ── Complete task ───────────────────────────────────────────────────────
      server.tool(
        "complete_task",
        "Marks a task as done.",
        { id: z.string().describe("Task ID") },
        async ({ id }) => {
          const task = await SpClient.completeTask(id);
          return ok(task);
        }
      );
    
      server.tool(
        "start_task",
        "Starts the specified task and makes it the current task.",
        { id: z.string().describe("Task ID") },
        async ({ id }) => {
          const task = await SpClient.startTask(id);
          return ok(task);
        }
      );
    
      server.tool(
        "set_current_task",
        "Sets the current task explicitly, or clears it when id is null.",
        { id: z.string().nullable().describe("Task ID, or null to clear the current task") },
        async ({ id }) => {
          const task = await SpClient.setCurrentTask(id);
          return ok(task);
        }
      );
    
      server.tool(
        "stop_current_task",
        "Stops tracking the current task.",
        {},
        async () => {
          const result = await SpClient.stopCurrentTask();
          return ok(result);
        }
      );
    
      server.tool(
        "archive_task",
        "Archives a task.",
        { id: z.string().describe("Task ID") },
        async ({ id }) => {
          const task = await SpClient.archiveTask(id);
          return ok(task);
        }
      );
    
      server.tool(
        "restore_task",
        "Restores an archived task.",
        { id: z.string().describe("Task ID") },
        async ({ id }) => {
          const task = await SpClient.restoreTask(id);
          return ok(task);
        }
      );
    
      // ── Delete task ─────────────────────────────────────────────────────────
      server.tool(
        "delete_task",
        "Permanently deletes a task.",
        { id: z.string().describe("Task ID") },
        async ({ id }) => {
          await SpClient.deleteTask(id);
          return ok({ deleted: id });
        }
      );
    }
Behavior2/5

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

No annotations are provided, so the description carries full burden. It only states 'updates' but does not disclose any side effects, permissions required, idempotency, or error cases (e.g., invalid fields, missing task).

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

Conciseness4/5

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

The description is a single sentence of 8 words with no redundancy. It is front-loaded and efficient, though it could be slightly expanded to add context without harming conciseness.

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

Completeness2/5

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

Given 10 parameters, no output schema, and no annotations, the description is inadequate. It does not explain that only provided fields are updated (partial update), the return value, or how to handle optional fields. Sibling tools like complete_task suggest alternate methods.

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

Parameters2/5

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

Schema description coverage is low (30%). The description adds no parameter-specific information beyond repeating 'fields'. It does not explain which fields are commonly updated or provide usage hints, leaving agents to rely on the sparse 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 'Updates one or more fields of an existing task', which specifies the verb 'updates' and the resource 'existing task'. This distinguishes it from sibling tools like create_task, delete_task, and archive_task.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool vs alternatives (e.g., complete_task to mark done, or archive_task to move). No when-not or prerequisites are mentioned.

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/claus-92/super-productivity-mcp'

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