Skip to main content
Glama

move-api-mcp

A local MCP server for the Move AI UGC GraphQL API (https://api.move.ai/ugc/graphql). It gives Claude Code, Claude Desktop, Cursor and any other MCP client typed tools for the markerless mocap pipeline — upload footage, create takes, run single- and multi-camera jobs, poll progress and pull the output files down.


Install

git clone <this repo> && cd move-api-mcp

# with a venv (no extra tooling needed)
python -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/move-api-mcp --version

# or with uv, if you have it
uv sync
uv run move-api-mcp --version

Requires Python 3.11+. Whichever you pick, note the absolute path of the launcher — .venv/bin/move-api-mcp or uv — because MCP clients need it below.

Related MCP server: Magic Hour MCP Server

Configure

Copy .env.example and set one credential:

Variable

Default

Purpose

MOVE_API_KEY

—

API key from dev.move.ai. Sent verbatim: Authorization: mv_key_…

MOVE_API_JWT

—

JWT / OAuth access token. Sent as Authorization: Bearer …

MOVE_API_TOKEN

—

Alias for either; the scheme is auto-detected from the token shape

MOVE_API_ENDPOINT

https://api.move.ai/ugc/graphql

Use https://api-test.move.ai/ugc/graphql for the test stack

MOVE_API_AUTH_SCHEME

auto

auto | bearer | raw — force the Authorization format

MOVE_API_ALLOW_WRITES

false

Must be true before any mutating tool will run

MOVE_API_TIMEOUT

30

HTTP timeout in seconds

MOVE_API_MAX_UPLOAD_BYTES

5368709120 (5 GiB)

Upload size ceiling

MOVE_API_DOWNLOAD_DIR

unset

When set, downloads may only be written inside this directory

Precedence for the credential is MOVE_API_JWT → MOVE_API_TOKEN → MOVE_API_KEY. auto sends Bearer for JWT-shaped tokens (three dot-separated segments) and the raw value for Move API keys, which the API expects without a scheme prefix.

CLI flags override the environment: --endpoint, --auth-scheme, --allow-writes, --read-only.

Register with an MCP client

Claude Code

Substitute your own checkout path for /path/to/move-api-mcp — claude mcp add will happily register the placeholder, and the server then fails to start.

# venv install
claude mcp add move-api \
  --env MOVE_API_KEY=mv_key_… \
  -- /path/to/move-api-mcp/.venv/bin/move-api-mcp

# uv install
claude mcp add move-api \
  --env MOVE_API_KEY=mv_key_… \
  -- uv --directory /path/to/move-api-mcp run move-api-mcp

Re-registering under a name that already exists is refused, so change it with claude mcp remove move-api followed by a fresh add. claude mcp get move-api shows what is currently registered.

Claude Desktop / Cursor (claude_desktop_config.json, .cursor/mcp.json)

{
  "mcpServers": {
    "move-api": {
      "command": "/path/to/move-api-mcp/.venv/bin/move-api-mcp",
      "env": {
        "MOVE_API_KEY": "mv_key_…",
        "MOVE_API_ALLOW_WRITES": "false"
      }
    }
  }
}

Enable writes only when you intend to spend processing credits:

"env": { "MOVE_API_KEY": "mv_key_…", "MOVE_API_ALLOW_WRITES": "true" }

Check it works by asking the client to call move_client_info — it returns the account the credential belongs to, the endpoint, the auth scheme in use and whether writes are on.

Tools

Read-only (always available):

Tool

What it does

move_client_info

Current account + effective server config

move_get_file

File details and a fresh presigned URL

move_download_file

Stream a file to local disk

move_get_take / move_list_takes

Takes, with expand for sources, volume, files

move_get_job / move_list_jobs

Jobs, state and progress.percentageComplete

move_download_job_outputs

Pull every (or selected) output of a finished job

move_get_volume / move_list_volumes

Multicam calibration volumes

move_list_rigs

Available skeleton rigs

move_list_camera_settings

Supported camera lens ids

Mutating (need MOVE_API_ALLOW_WRITES=true):

Tool

What it does

move_create_file

Reserve a file and get a presigned PUT URL

move_upload_file

Create and upload a local video in one step

move_update_file / move_update_take / move_update_job

Rename / set metadata

move_create_single_cam_take / move_create_multi_cam_take

Group footage into a take

move_create_single_cam_job / move_create_multi_cam_job

Start mocap processing (billable)

move_create_volume_with_human

Calibrate a multicam rig from footage of a person (billable)

move_generate_share_code

Shareable link for an output file

move_update_client

Set account metadata

move_upsert_webhook_endpoint

Create/update a webhook subscription

Escape hatch: move_graphql runs an arbitrary document for fields the typed tools do not cover. Mutations detected in it are gated the same way.

expand arguments mirror move-ugc-python's: pass ["outputs"], ["take", "inputs"], ["sources", "volume"] and so on to pull nested objects in a single round trip.

Workflows

Single camera

  1. move_upload_file — the .mp4 (plus the matching .move file for Move One footage; both share a device_label)

  2. move_create_single_cam_take with those file ids

  3. move_create_single_cam_job with the take id

  4. Poll move_get_job until state is FINISHED (NOT_STARTED → STARTED → RUNNING → FINISHED / FAILED)

  5. move_download_job_outputs

Multi camera

  1. move_list_camera_settings — find each camera's lens id

  2. move_upload_file per calibration clip → move_create_volume_with_human (needs the performer's height in metres)

  3. Poll move_get_volume until FINISHED

  4. move_upload_file per action clip → move_create_multi_cam_take with the volume id, re-using the same device_labels and lenses

  5. move_create_multi_cam_job → poll → download

Design notes

  • AWSJSON — the API's metadata fields carry JSON as a string. Tools take a normal JSON object and encode/decode it for you; move_graphql does not (pass strings there).

  • Pagination — list tools return {items, count, next_cursor}; feed next_cursor back as after.

  • Presigned URLs — short-lived, and the same field is an upload target on input files and a download link on outputs. Fetch them at the point of use.

  • Uploads stream in 8 MiB chunks with an explicit Content-Length, so large takes don't sit in memory and S3 doesn't reject chunked encoding.

  • Errors — GraphQL errors are surfaced with the Move error code (MV_010_010_0001) and any suggestions attached; a 401/403 says which header format was sent.

  • Downloads are confined to MOVE_API_DOWNLOAD_DIR when it is set, and never overwrite without overwrite=true.

Development

pip install -e '.[dev]'
pre-commit install          # mandatory before committing

pytest                      # unit tests
pytest --cov                # with coverage (fails under 90%)
ruff check . && ruff format --check .
mypy src

# stdio handshake against the real MCP client SDK — no Move API call is made
MOVE_API_KEY=mv_key_dummy python scripts/smoke_stdio.py

The transport is exercised through httpx.MockTransport; no test touches the real API.

src/move_api_mcp/
  config.py     env -> Settings, credential + auth-scheme resolution
  client.py     GraphQL/HTTP transport, AWSJSON encoding, upload + download
  errors.py     Move error codes and suggestions
  graphql.py    documents and expandable selection sets
  models.py     typed tool inputs (Source, SyncMethod, JobOptions, ClipWindow)
  server.py     MCPServer assembly, instructions, lifespan
  tools/        one module per resource: catalog, files, takes, jobs, volumes,
                webhooks, raw

Built against the MCP Python SDK 2.x (MCPServer).

Security

  • The credential is read from the environment only — never committed, never logged (describe() redacts it, and status output goes to stderr, not the MCP stdout channel).

  • Writes are off by default so an agent cannot start billable jobs by accident.

  • secret on move_upsert_webhook_endpoint is a credential: treat tool output as sensitive.

  • Presigned URLs in responses grant temporary access to the underlying object — avoid pasting them into shared transcripts.

  • However you register the server, the key is stored in plaintext in the client's config (~/.claude.json for Claude Code), so treat that file as a secret. Passing it as -e MOVE_API_KEY="$MOVE_API_KEY" from an exported variable keeps it out of your shell history, which an inline literal does not. Rotate any key that has been pasted into a terminal or a chat.

Available Tools

26 tools
move_client_infoA
Read-only

Return the Move API client (account) the current credential belongs to.

Use this first to confirm the server is authenticated against the expected account and endpoint.

Inputs: no named parameters. Returns: object

move_create_fileA

Reserve a file slot and get a presigned PUT URL to upload the bytes to.

Prefer move_upload_file when the video is on this machine — it does the create and the upload in one step.

Inputs: file_type (required), name, metadata. Returns: object

move_create_multi_cam_jobA

Start a multi-camera mocap job. This consumes processing credits.

Inputs: take_id (required), name, expand, options, +4 more. Returns: object

move_create_multi_cam_takeA

Create a multi-camera take against an existing calibration volume.

Inputs: sources (required), volume_id (required), name, expand, +2 more. Returns: object

move_create_single_cam_jobA

Start a single-camera mocap job. This consumes processing credits.

Inputs: take_id (required), name, expand, options, +3 more. Returns: object

move_create_single_cam_takeA

Create a single-camera take from already-uploaded files.

Inputs: sources (required), name, expand, metadata. Returns: object

move_create_volume_with_humanA

Calibrate a multicam setup from footage of a person of known height.

This is step one of the multicam workflow and consumes processing credits. Poll move_get_volume until state is FINISHED, then create a multicam take with the same device_labels and lenses.

Inputs: sources (required), human_height (required), name, expand, +4 more. Returns: object

move_download_fileC
Idempotent

Download a Move file (typically a job output) to this machine.

Inputs: file_id (required), dest_path, overwrite. Returns: object

move_download_job_outputsA
Idempotent

Download every output file of a FINISHED job to this machine.

Inputs: job_id (required), keys, dest_dir, overwrite. Returns: object

move_generate_share_codeB

Generate a shareable code and URL for a job output file.

Inputs: file_id (required). Returns: object

move_get_fileA
Read-only

Fetch a file, including a fresh presigned URL.

For input files the presigned URL is an HTTP PUT upload target; for job output files it is a time-limited download URL.

Inputs: file_id (required). Returns: object

move_get_jobA
Read-only

Fetch a job, including its state and percentage progress.

States: NOT_STARTED, STARTED, RUNNING, FINISHED, FAILED. Poll this rather than blocking; processing typically takes minutes.

Inputs: job_id (required), expand. Returns: object

move_get_takeA
Read-only

Fetch a take by id.

Inputs: take_id (required), expand. Returns: object

move_get_volumeA
Read-only

Fetch a calibration volume and its processing state.

A volume must reach state FINISHED before it can back a multicam take.

Inputs: volume_id (required), expand. Returns: object

move_graphqlA

Run an arbitrary GraphQL document against the Move API.

Use the typed tools first; this is for fields they do not expose. Mutations still require writes to be enabled. Schema reference: https://move-ai.github.io/move-ugc-api/

Inputs: query (required), variables. Returns: object

move_list_camera_settingsA
Read-only

List supported camera lens ids for Source.camera_settings.lens.

Multicam accuracy depends on declaring the lens each camera used.

Inputs: no named parameters. Returns: object

move_list_jobsC
Read-only

List jobs, optionally filtered to one take.

Inputs: after, first, expand, take_id, +1 more. Returns: object

move_list_rigsA
Read-only

List the skeleton rigs available to this account.

Inputs: no named parameters. Returns: object

move_list_takesB
Read-only

List takes, newest first when sort_direction=DESC.

Inputs: after, first, expand, sort_direction. Returns: object

move_list_volumesC
Read-only

List calibration volumes.

Inputs: after, first, expand, sort_direction. Returns: object

move_update_clientA
Idempotent

Replace the metadata object stored on the client (account).

Inputs: metadata (required). Returns: object

move_update_fileB
Idempotent

Update a file's name and/or metadata.

Inputs: file_id (required), name, metadata. Returns: object

move_update_jobA
Idempotent

Update a job's name and/or metadata.

Inputs: job_id (required), name, expand, metadata. Returns: object

move_update_takeC
Idempotent

Update a take's name and/or metadata.

Inputs: take_id (required), name, expand, metadata. Returns: object

move_upload_fileA

Create a Move file and upload a local video to it in one step.

Returns the file id to pass as Source.file_id when creating a take or volume.

Inputs: local_path (required), name, metadata, file_type. Returns: object

move_upsert_webhook_endpointA
Idempotent

Create or update a webhook endpoint for this client.

Inputs: uid (required), url (required), events, secret, +2 more. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 26 tools

    Disambiguation4/5

    Tools are mostly organized by resource and action, making takes, jobs, volumes, and files easy to distinguish. The only real confusable pairs are create_file/upload_file and get_file/download_file, though their descriptions clarify the intended use.

    Naming Consistency4/5

    Nearly every tool follows a clear move_<verb>_<noun> pattern with consistent snake_case. Minor deviations like move_client_info and move_graphql break the pattern slightly, but the overall style is predictable.

    Tool Count3/5

    With 26 tools, the server is on the heavy side, and the many file/job variants make the surface feel crowded. However, the tools map to a genuinely broad API spanning clients, files, takes, jobs, volumes, and webhooks, so the count is not an extreme mismatch.

    Completeness3/5

    The core file-to-take-to-job-to-output workflow is well covered, including multicam volume calibration and webhook setup. Notable gaps remain: there is no list-files tool, no delete/cancel operations for takes or jobs, and volume update/delete coverage is absent.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers