Skip to main content
Glama

Check Seedance 2.0 task status

seedance_check_task
Read-onlyIdempotent

Query the status of a Seedance 2.0 video generation task by task ID. Returns running, succeeded, or failed, with download URLs on success.

Instructions

Query the status of a Seedance 2.0 task by task_id. Returns running / succeeded / failed / other. On success returns video_url (and last_frame_url if return_last_frame was true). Generated URLs expire within ~24h - download promptly.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
task_idYesSeedance task id returned by seedance_create_task (e.g. cgt-...).

Implementation Reference

  • src/server.ts:92-174 (registration)
    Registration of the seedance_check_task tool with the MCP server, binding the tool name to its schema and handler
    server.registerTool(
      "seedance_check_task",
      {
        title: "Check Seedance 2.0 task status",
        description:
          "Query the status of a Seedance 2.0 task by task_id. Returns running / succeeded / failed / other. On success returns video_url (and last_frame_url if return_last_frame was true). Generated URLs expire within ~24h - download promptly.",
        inputSchema: checkTaskInputShape,
        annotations: {
          readOnlyHint: true,
          idempotentHint: true,
          openWorldHint: true,
        },
      },
      async (input: CheckTaskInput): Promise<CallToolResult> => {
        try {
          const result = await checkSeedanceTask(input.task_id);
          const status = result.status.toLowerCase();
          let summary: string;
    
          switch (status) {
            case "succeeded": {
              const lines = [
                `Task ${result.id} succeeded.`,
                result.videoUrl
                  ? `video_url: ${result.videoUrl}`
                  : "WARNING: succeeded but no video_url was returned by ARK.",
              ];
              if (result.lastFrameUrl) {
                lines.push(`last_frame_url: ${result.lastFrameUrl}`);
              }
              lines.push("");
              lines.push(
                "Reminder: ARK signed URLs typically expire within 24 hours. Download the video promptly.",
              );
              summary = lines.join("\n");
              break;
            }
            case "failed": {
              summary = `Task ${result.id} failed.${
                result.failReason ? ` Reason: ${result.failReason}` : ""
              }`;
              break;
            }
            case "running":
            case "queued":
            case "in_queue":
            case "pending": {
              summary = [
                `Task ${result.id} is still ${status}.`,
                "Wait another 30-90 seconds, then call seedance_check_task again.",
              ].join("\n");
              break;
            }
            case "cancelled":
            case "canceled": {
              summary = `Task ${result.id} was cancelled.`;
              break;
            }
            case "expired": {
              summary = `Task ${result.id} has expired. Submit a new task with seedance_create_task.`;
              break;
            }
            default: {
              summary = `Task ${result.id} is in status "${result.status}". This is not one of the standard states (running/succeeded/failed/cancelled/expired); see the structured response for details.`;
            }
          }
    
          return {
            content: [{ type: "text", text: summary }],
            structuredContent: {
              task_id: result.id,
              status: result.status,
              video_url: result.videoUrl,
              last_frame_url: result.lastFrameUrl,
              fail_reason: result.failReason,
              raw: result.raw,
            },
          };
        } catch (err) {
          return errorResult(formatApiError(err));
        }
      },
    );
  • Handler function for seedance_check_task — receives CheckTaskInput, calls checkSeedanceTask, and formats the response based on status (succeeded/failed/running/etc.)
    async (input: CheckTaskInput): Promise<CallToolResult> => {
      try {
        const result = await checkSeedanceTask(input.task_id);
        const status = result.status.toLowerCase();
        let summary: string;
    
        switch (status) {
          case "succeeded": {
            const lines = [
              `Task ${result.id} succeeded.`,
              result.videoUrl
                ? `video_url: ${result.videoUrl}`
                : "WARNING: succeeded but no video_url was returned by ARK.",
            ];
            if (result.lastFrameUrl) {
              lines.push(`last_frame_url: ${result.lastFrameUrl}`);
            }
            lines.push("");
            lines.push(
              "Reminder: ARK signed URLs typically expire within 24 hours. Download the video promptly.",
            );
            summary = lines.join("\n");
            break;
          }
          case "failed": {
            summary = `Task ${result.id} failed.${
              result.failReason ? ` Reason: ${result.failReason}` : ""
            }`;
            break;
          }
          case "running":
          case "queued":
          case "in_queue":
          case "pending": {
            summary = [
              `Task ${result.id} is still ${status}.`,
              "Wait another 30-90 seconds, then call seedance_check_task again.",
            ].join("\n");
            break;
          }
          case "cancelled":
          case "canceled": {
            summary = `Task ${result.id} was cancelled.`;
            break;
          }
          case "expired": {
            summary = `Task ${result.id} has expired. Submit a new task with seedance_create_task.`;
            break;
          }
          default: {
            summary = `Task ${result.id} is in status "${result.status}". This is not one of the standard states (running/succeeded/failed/cancelled/expired); see the structured response for details.`;
          }
        }
    
        return {
          content: [{ type: "text", text: summary }],
          structuredContent: {
            task_id: result.id,
            status: result.status,
            video_url: result.videoUrl,
            last_frame_url: result.lastFrameUrl,
            fail_reason: result.failReason,
            raw: result.raw,
          },
        };
      } catch (err) {
        return errorResult(formatApiError(err));
      }
    },
  • Core API helper that performs a GET request to the ARK API to check the status of a video generation task by task_id
    export async function checkSeedanceTask(
      taskId: string,
    ): Promise<CheckTaskResponse> {
      if (!taskId || taskId.trim().length === 0) {
        throw new Error("task_id is required");
      }
      const apiKey = getApiKey();
      const baseUrl = getBaseUrl();
    
      const res = await fetch(
        `${baseUrl}/contents/generations/tasks/${encodeURIComponent(taskId.trim())}`,
        {
          method: "GET",
          headers: {
            Authorization: `Bearer ${apiKey}`,
          },
        },
      );
    
      const parsed = await parseJsonSafe(res);
    
      if (!res.ok) {
        throw new SeedanceApiError(
          extractApiErrorMessage(parsed, res.status),
          res.status,
          parsed,
        );
      }
    
      if (!parsed || typeof parsed !== "object") {
        throw new SeedanceApiError(
          "ARK API returned an unexpected non-JSON response",
          res.status,
          parsed,
        );
      }
    
      const obj = parsed as Record<string, unknown>;
      const id = typeof obj.id === "string" ? obj.id : taskId;
      const status = typeof obj.status === "string" ? obj.status : "unknown";
    
      // Different SDK versions surface results in slightly different places; be
      // lenient and probe the most common shapes.
      const content =
        (obj.content as Record<string, unknown> | undefined) ??
        (obj.result as Record<string, unknown> | undefined) ??
        {};
    
      const videoUrl = pickString(
        content.video_url,
        content.videoUrl,
        obj.video_url,
        obj.videoUrl,
      );
    
      const lastFrameUrl = pickString(
        content.last_frame_url,
        content.lastFrameUrl,
        obj.last_frame_url,
        obj.lastFrameUrl,
      );
    
      const failReason = pickString(
        obj.fail_reason,
        obj.failReason,
        obj.error_message,
        (obj.error as Record<string, unknown> | undefined)?.message,
      );
    
      return {
        id,
        status,
        videoUrl,
        lastFrameUrl,
        failReason,
        raw: obj,
      };
    }
  • Schema definition for the check_task input — requires a non-empty task_id string
    export const checkTaskInputShape = {
      task_id: z
        .string()
        .min(1, "task_id cannot be empty")
        .describe("Seedance task id returned by seedance_create_task (e.g. cgt-...)."),
    };
    
    export const checkTaskInputSchema = z.object(checkTaskInputShape);
    export type CheckTaskInput = z.infer<typeof checkTaskInputSchema>;
Behavior4/5

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

Annotations indicate readOnly and idempotent. Description adds URL expiration (~24h) and return format, which isn't in annotations. No contradiction.

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?

Three sentences, front-loaded with purpose, no redundant or extraneous content. Efficient and clear.

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?

Describes statuses and URL expiry without output schema. Adequate for a status check tool; could mention error handling but not required.

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

Parameters4/5

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

Schema has 100% coverage with description for task_id. Description adds context that it's the ID from seedance_create_task, improving usability.

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?

Clearly states it queries status of a Seedance 2.0 task by task_id, lists possible statuses and success returns. Distinguishes from sibling tools (create and guide).

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?

Implies usage after task creation but does not explicitly state when to use vs alternatives or mention polling patterns. No when-not or alternatives provided.

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/leonaiuv/seedance-2-mcp'

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