---
title: "join_clips: Join clips into one film"
description: "Concatenate 2-20 images or clips, in the order given, into a single MP4 in the library."
canonical_url: "https://meetkuro.com/agents/tools/join_clips/"
updated: 2026-11-03
locale: "en"
tool: "join_clips"
scopes: ["generation.write"]
---

# join_clips

**Join clips into one film**

Concatenate 2-20 images or clips, in the order given, into a single MP4 in the library. A single clip is accepted too when it carries a music bed or narration — that is how a voice-over is laid on one clip. This is how a run of shots becomes one file: generate each clip, carry the last frame into the next with extract_video_frame, then join them here. Images hold for image_duration_seconds (three seconds by default). Every clip keeps its own audio; a music bed is looped to the cut's length and ducked under any voice-over, and each narration line is laid at the second you name. Costs no credits. The cut may not run past 10 minutes, and one account joins one set at a time.

## Scope

- `generation.write` — Makes, edits, imports, shares and deletes media in the account.

## Behaviour

- **Writes**: Creates or changes items in the caller's own account.
- **Adds or updates**: Adds or updates; nothing the caller already has is deleted or overwritten.
- **Stays in the account**: Stays inside the caller's private Kuro account: any media provider it calls acts on Kuro's account, and the result is visible to no one else.

## Credits

What the tool's description says about credits, verbatim:

- Costs no credits.

## Inputs

| Name | Type | Required | Details |
| --- | --- | --- | --- |
| `sources` | array of object (type: "upload") or object (type: "generation") | yes | at least 1 item; at most 20 items |
| `aspect_ratio` | string | no | one of "9:16", "1:1", "16:9" |
| `image_duration_seconds` | number | no | ≥ 0.2; ≤ 600; default 3 |
| `music` | object | no | fields: source, gain_db |
| `narration` | array of object | no | at most 20 items |

## Example call

A `tools/call` request with the required arguments only, each a placeholder read off the schema. Replace them before sending.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "join_clips",
    "arguments": {
      "sources": [
        {
          "type": "upload",
          "upload_id": "00000000-0000-4000-8000-000000000000"
        }
      ]
    }
  }
}
```

## Input schema

As the server sends it in `tools/list`:

```json
{
  "type": "object",
  "properties": {
    "sources": {
      "type": "array",
      "items": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "upload"
              },
              "upload_id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "type",
              "upload_id"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "generation"
              },
              "generation_id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "type",
              "generation_id"
            ],
            "additionalProperties": false
          }
        ]
      },
      "minItems": 1,
      "maxItems": 20
    },
    "aspect_ratio": {
      "type": "string",
      "enum": [
        "9:16",
        "1:1",
        "16:9"
      ]
    },
    "image_duration_seconds": {
      "type": "number",
      "minimum": 0.2,
      "maximum": 600,
      "default": 3
    },
    "music": {
      "type": "object",
      "properties": {
        "source": {
          "$ref": "#/properties/sources/items"
        },
        "gain_db": {
          "type": "integer",
          "minimum": -40,
          "maximum": 0
        }
      },
      "required": [
        "source"
      ],
      "additionalProperties": false
    },
    "narration": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "source": {
            "$ref": "#/properties/sources/items"
          },
          "start_seconds": {
            "type": "number",
            "minimum": 0
          },
          "duration_seconds": {
            "type": "number",
            "exclusiveMinimum": 0
          }
        },
        "required": [
          "source",
          "start_seconds"
        ],
        "additionalProperties": false
      },
      "maxItems": 20
    }
  },
  "required": [
    "sources"
  ],
  "additionalProperties": false
}
```

## Related tools

- [`extract_video_frame`](https://meetkuro.com/agents/tools/extract_video_frame.md): Save the first or last decoded frame of an owned video as a new image generation.
- [`generate_clip`](https://meetkuro.com/agents/tools/generate_clip.md): Start a short video clip generation.
- [`export_storyboard_video`](https://meetkuro.com/agents/tools/export_storyboard_video.md): Copy a finished film into the library as an ordinary clip, and get the file's link.

---

## About Kuro

Kuro is an AI picture studio: prompt-driven images, clips, voice-overs and music, and storyboard-backed films with consistent characters across every scene.

Agents connect over MCP and can generate directly:

```
claude mcp add kuro https://api.meetkuro.com/mcp --transport http
```

The app is at https://app.meetkuro.com and requires an account.

Each guide, model page, comparison and free tool page on this site (not a tool's variants, and not the index pages) has a Markdown twin at its own URL plus `.md`, and so do /benchmarks/, the English /agents/ page (as /agents.md) and the MCP tool reference. Every tool: https://meetkuro.com/agents/tools/
