Skip to main content
Glama

motion_custom_fields

Manage custom fields for tasks and projects in Motion to track additional data beyond standard fields. Create, list, delete fields, and assign them to specific projects or tasks.

Instructions

Manage custom fields for tasks and projects. Required params per operation: list: workspaceId or workspaceName. create: workspaceId/workspaceName + name + field (type); options[] also required for select/multiSelect. delete: workspaceId/workspaceName + fieldId. add_to_project: projectId + fieldId. remove_from_project: projectId + valueId. add_to_task: taskId + fieldId. remove_from_task: taskId + valueId.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
operationYesOperation to perform
fieldIdNoCustom field definition ID. Required for: delete, add_to_project, add_to_task. For remove operations, use valueId instead.
valueIdNoCustom field value assignment ID (not the field definition ID). Required for: remove_from_project, remove_from_task.
workspaceIdNoWorkspace ID. Required for: list, create, delete.
workspaceNameNoWorkspace name (alternative to workspaceId). Required for: list, create, delete.
nameNoField name. Required for: create.
fieldNoField type. Required for: create. Also needed for add_to_project/add_to_task when providing a non-null value.
optionsNoOption labels. Required for: create when field is select or multiSelect.
requiredNoWhether field is required on tasks/projects.
projectIdNoProject ID. Required for: add_to_project, remove_from_project.
taskIdNoTask ID. Required for: add_to_task, remove_from_task.
valueNoField value to set. Optional for add_to_project/add_to_task. When provided and non-null, the field param (type) is also required.

Implementation Reference

  • Main handler class implementing the motion_custom_fields tool. Supports 7 operations: list, create, delete, add_to_project, remove_from_project, add_to_task, remove_from_task. Each operation has a dedicated private method with validation and error handling.
    export class CustomFieldHandler extends BaseHandler {
      async handle(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        try {
          const { operation } = args;
    
          switch(operation) {
            case 'list':
              return await this.handleList(args);
            case 'create':
              return await this.handleCreate(args);
            case 'delete':
              return await this.handleDelete(args);
            case 'add_to_project':
              return await this.handleAddToProject(args);
            case 'remove_from_project':
              return await this.handleRemoveFromProject(args);
            case 'add_to_task':
              return await this.handleAddToTask(args);
            case 'remove_from_task':
              return await this.handleRemoveFromTask(args);
            default:
              return this.handleUnknownOperation(operation);
          }
        } catch (error: unknown) {
          return this.handleError(error);
        }
      }
    
      private async handleList(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if (!args.workspaceId && !args.workspaceName) {
          return this.handleError(new Error('Workspace ID or workspace name is required for list operation'));
        }
        const workspace = await this.workspaceResolver.resolveWorkspace({
          workspaceId: args.workspaceId,
          workspaceName: args.workspaceName
        });
        const fields = await this.motionService.getCustomFields(workspace.id);
        return formatCustomFieldList(fields);
      }
    
      private async handleCreate(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if ((!args.workspaceId && !args.workspaceName) || !args.name || !args.field) {
          return this.handleError(new Error('Workspace ID/name, name, and field are required for create operation'));
        }
    
        if (args.name.length > LIMITS.CUSTOM_FIELD_NAME_MAX_LENGTH) {
          return this.handleError(new Error(`Field name exceeds ${LIMITS.CUSTOM_FIELD_NAME_MAX_LENGTH} characters`));
        }
    
        const isSelectType = ['select', 'multiSelect'].includes(args.field);
        if (!isSelectType && args.options) {
          return this.handleError(new Error('Options parameter is only allowed for select/multiSelect field types'));
        }
        if (isSelectType && !args.options) {
          return this.handleError(new Error('Options parameter is required for select/multiSelect field types'));
        }
    
        const workspace = await this.workspaceResolver.resolveWorkspace({
          workspaceId: args.workspaceId,
          workspaceName: args.workspaceName
        });
    
        const fieldData: CreateCustomFieldData = {
          name: args.name,
          field: args.field,
          ...(args.required !== undefined && { required: args.required }),
          ...(args.options && { metadata: { options: args.options } })
        };
    
        const newField = await this.motionService.createCustomField(workspace.id, fieldData);
        return formatCustomFieldDetail(newField);
      }
    
      private async handleDelete(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if ((!args.workspaceId && !args.workspaceName) || !args.fieldId) {
          return this.handleError(new Error('Workspace ID/name and field ID are required for delete operation'));
        }
    
        const workspace = await this.workspaceResolver.resolveWorkspace({
          workspaceId: args.workspaceId,
          workspaceName: args.workspaceName
        });
    
        await this.motionService.deleteCustomField(workspace.id, args.fieldId);
        return formatCustomFieldSuccess('deleted');
      }
    
      private async handleAddToProject(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if (!args.projectId || !args.fieldId) {
          return this.handleError(new Error('Project ID and field ID are required for add_to_project operation'));
        }
        if (args.value !== undefined && args.value !== null && !args.field) {
          return this.handleError(new Error('Field type (field) is required when providing a value. Use "text", "number", "multiSelect", etc.'));
        }
    
        const result = await this.motionService.addCustomFieldToProject(args.projectId, args.fieldId, args.value, args.field);
        return formatCustomFieldSuccess('added', 'project', args.projectId, result);
      }
    
      private async handleRemoveFromProject(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if (!args.projectId || !args.valueId) {
          return this.handleError(new Error('Project ID and valueId (custom field value ID) are required for remove_from_project operation'));
        }
    
        await this.motionService.removeCustomFieldFromProject(args.projectId, args.valueId);
        return formatCustomFieldSuccess('removed', 'project', args.projectId);
      }
    
      private async handleAddToTask(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if (!args.taskId || !args.fieldId) {
          return this.handleError(new Error('Task ID and field ID are required for add_to_task operation'));
        }
        if (args.value !== undefined && args.value !== null && !args.field) {
          return this.handleError(new Error('Field type (field) is required when providing a value. Use "text", "number", "multiSelect", etc.'));
        }
    
        const result = await this.motionService.addCustomFieldToTask(args.taskId, args.fieldId, args.value, args.field);
        return formatCustomFieldSuccess('added', 'task', args.taskId, result);
      }
    
      private async handleRemoveFromTask(args: MotionCustomFieldsArgs): Promise<McpToolResponse> {
        if (!args.taskId || !args.valueId) {
          return this.handleError(new Error('Task ID and valueId (custom field value ID) are required for remove_from_task operation'));
        }
    
        await this.motionService.removeCustomFieldFromTask(args.taskId, args.valueId);
        return formatCustomFieldSuccess('removed', 'task', args.taskId);
      }
    }
  • Tool definition for motion_custom_fields with name, description, and complete input schema including all 7 operations (list, create, delete, add_to_project, remove_from_project, add_to_task, remove_to_task) and their required/optional parameters.
    export const customFieldsToolDefinition: McpToolDefinition = {
      name: TOOL_NAMES.CUSTOM_FIELDS,
      description: "Manage custom fields for tasks and projects. Required params per operation: list: workspaceId or workspaceName. create: workspaceId/workspaceName + name + field (type); options[] also required for select/multiSelect. delete: workspaceId/workspaceName + fieldId. add_to_project: projectId + fieldId. remove_from_project: projectId + valueId. add_to_task: taskId + fieldId. remove_from_task: taskId + valueId.",
      inputSchema: {
        type: "object",
        properties: {
          operation: {
            type: "string",
            enum: ["list", "create", "delete", "add_to_project", "remove_from_project", "add_to_task", "remove_from_task"],
            description: "Operation to perform"
          },
          fieldId: {
            type: "string",
            description: "Custom field definition ID. Required for: delete, add_to_project, add_to_task. For remove operations, use valueId instead."
          },
          valueId: {
            type: "string",
            description: "Custom field value assignment ID (not the field definition ID). Required for: remove_from_project, remove_from_task."
          },
          workspaceId: {
            type: "string",
            description: "Workspace ID. Required for: list, create, delete."
          },
          workspaceName: {
            type: "string",
            description: "Workspace name (alternative to workspaceId). Required for: list, create, delete."
          },
          name: {
            type: "string",
            description: "Field name. Required for: create."
          },
          field: {
            type: "string",
            enum: ["text", "url", "date", "person", "multiPerson", "phone", "select", "multiSelect", "number", "email", "checkbox", "relatedTo"],
            description: "Field type. Required for: create. Also needed for add_to_project/add_to_task when providing a non-null value."
          },
          options: {
            type: "array",
            items: { type: "string" },
            description: "Option labels. Required for: create when field is select or multiSelect."
          },
          required: {
            type: "boolean",
            description: "Whether field is required on tasks/projects."
          },
          projectId: {
            type: "string",
            description: "Project ID. Required for: add_to_project, remove_from_project."
          },
          taskId: {
            type: "string",
            description: "Task ID. Required for: add_to_task, remove_from_task."
          },
          value: {
            oneOf: [
              { type: "string" },
              { type: "number" },
              { type: "boolean" },
              { type: "array", items: { type: "string" } },
              { type: "null" }
            ],
            description: "Field value to set. Optional for add_to_project/add_to_task. When provided and non-null, the field param (type) is also required."
          }
        },
        required: ["operation"]
      }
    };
  • TypeScript interface defining MotionCustomFieldsArgs with all possible parameters for the 7 operations, including operation type, fieldId, valueId, workspaceId/workspaceName, name, field type, options, required flag, projectId, taskId, and value.
    export type CustomFieldsOperation = 'list' | 'create' | 'delete' | 'add_to_project' | 'remove_from_project' | 'add_to_task' | 'remove_from_task';
    
    export interface MotionCustomFieldsArgs {
      operation: CustomFieldsOperation;
      fieldId?: string;
      valueId?: string;
      workspaceId?: string;
      workspaceName?: string;
      name?: string;
      field?: 'text' | 'url' | 'date' | 'person' | 'multiPerson' | 'phone' | 'select' | 'multiSelect' | 'number' | 'email' | 'checkbox' | 'relatedTo';
      options?: string[];
      required?: boolean;
      projectId?: string;
      taskId?: string;
      value?: string | number | boolean | string[] | null;
    }
  • Handler factory registration mapping the TOOL_NAMES.CUSTOM_FIELDS constant to the CustomFieldHandler class, enabling dynamic handler instantiation based on tool name.
    private registerHandlers(): void {
      this.handlers.set(TOOL_NAMES.TASKS, TaskHandler);
      this.handlers.set(TOOL_NAMES.PROJECTS, ProjectHandler);
      this.handlers.set(TOOL_NAMES.WORKSPACES, WorkspaceHandler);
      this.handlers.set(TOOL_NAMES.USERS, UserHandler);
      this.handlers.set(TOOL_NAMES.SEARCH, SearchHandler);
      this.handlers.set(TOOL_NAMES.COMMENTS, CommentHandler);
      this.handlers.set(TOOL_NAMES.CUSTOM_FIELDS, CustomFieldHandler);
      this.handlers.set(TOOL_NAMES.RECURRING_TASKS, RecurringTaskHandler);
      this.handlers.set(TOOL_NAMES.SCHEDULES, ScheduleHandler);
      this.handlers.set(TOOL_NAMES.STATUSES, StatusHandler);
    }
  • Helper functions for formatting custom field responses: formatCustomFieldList for listing multiple fields, formatCustomFieldDetail for single field details, and formatCustomFieldSuccess for operation success messages with valueId tracking.
    export function formatCustomFieldList(fields: MotionCustomField[]): CallToolResult {
      if (fields.length === 0) {
        return formatMcpSuccess("No custom fields found.");
      }
      
      const fieldFormatter = (field: MotionCustomField) => {
        const name = field.name ? `${field.name} ` : '';
        return `- ${name}ID: ${field.id} [Type: ${field.field}]`;
      };
      
      return formatListResponse(fields, `Found ${fields.length} custom field${fields.length === 1 ? '' : 's'}`, fieldFormatter);
    }
    
    /**
     * Format single custom field response
     */
    export function formatCustomFieldDetail(field: MotionCustomField): CallToolResult {
      const details = [
        `Custom field created successfully:`,
        field.name ? `- Name: ${field.name}` : null,
        `- ID: ${field.id}`,
        `- Type: ${field.field}`
      ].filter(Boolean).join('\n');
      
      return formatMcpSuccess(details);
    }
    
    /**
     * Format success message for custom field operations
     */
    export function formatCustomFieldSuccess(operation: string, entityType?: string, entityId?: string, apiResponse?: MotionCustomFieldValue): CallToolResult {
      let message = `Custom field ${operation} successfully`;
      if (entityType && entityId) {
        message += ` for ${entityType} ${entityId}`;
      }
      // For add operations, include the API response so users can obtain the valueId for later removal
      if (apiResponse) {
        if (apiResponse.id) {
          message += `\nValue ID: ${apiResponse.id}`;
          if (entityType) {
            message += `\nTip: Use this valueId with remove_from_${entityType} to remove this custom field assignment.`;
          }
        } else {
          message += `\nNote: The API did not return a valueId. To find the valueId, use motion_tasks (operation: get) or inspect the task/project's customFieldValues.`;
        }
        if (apiResponse.type) {
          message += `\nType: ${apiResponse.type}`;
        }
        if (apiResponse.value !== undefined) {
          message += `\nValue: ${JSON.stringify(apiResponse.value)}`;
        }
      }
      return formatMcpSuccess(message);
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes the required parameters per operation, which gives some behavioral context, but fails to disclose critical traits like whether operations are read-only or destructive, authentication needs, rate limits, error handling, or what the tool returns. For a tool with 7 operations including create/delete, this is a significant gap.

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

Conciseness3/5

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

The description is appropriately sized but poorly structured as a single run-on sentence with comma-separated operation lists. It's front-loaded with the core purpose, but the parameter requirements could be better organized (e.g., bullet points or clearer separation). Some redundancy exists with schema information.

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 the tool's complexity (12 parameters, 7 operations, no annotations, no output schema), the description is incomplete. It covers parameter requirements but misses behavioral context, return values, error conditions, and operational constraints. For a multi-operation tool managing custom fields, this leaves significant gaps for an AI agent.

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?

The schema description coverage is 100%, so the schema already documents all 12 parameters thoroughly. The description adds minimal value by listing required parameters per operation, but doesn't provide additional semantic context beyond what's in the schema descriptions (e.g., explaining what 'field' types mean in practice or how 'valueId' differs from 'fieldId'). Baseline 3 is appropriate when the schema does the heavy lifting.

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

Purpose4/5

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

The description clearly states the tool's purpose as 'Manage custom fields for tasks and projects' with a specific verb ('manage') and resource ('custom fields'), distinguishing it from sibling tools like motion_tasks or motion_projects. However, it doesn't explicitly differentiate from all siblings (e.g., motion_statuses could also involve field management).

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

Usage Guidelines3/5

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

The description provides implied usage guidance through the enumeration of required parameters per operation, which helps understand when to use each operation. However, it lacks explicit when-to-use or when-not-to-use statements, and doesn't mention alternatives among sibling tools (e.g., when to use this vs. motion_tasks for task-related operations).

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/sergiolopez94/motion-mcp-server'

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