Developer docs/Transports
MCP
An MCP server for Concat is planned and prototyped, not shipped. This page says what exists, what it should look like, and how to connect an agent today.
Status: not in this tree.
On this page: What exists · The shape it should take · Bridging today · Guidance for agents
What exists
| Thing | Where | State |
|---|---|---|
| The API an MCP server would expose | concat-api | Shipped. Every method in Methods |
| A socket server with a token and event fan-out | concat-server | Shipped. JSON-RPC, gRPC |
| A prototype: the window serving MCP over Streamable HTTP with 17 tools, plus a stdio Python bridge | PR #69 | Closed, not merged. It re-implemented the transport instead of reusing concat-server |
| The request | issue #95 “MCP support” | Closed on 2026-09-19 with a pointer to the documentation issue #123 |
The design direction, from the September 2026 audit: build the MCP server
on the official Rust SDK (rmcp) over concat-server’s Hub, so it is a
third transport beside JSON-RPC and gRPC and not a second API.
The shape it should take
The API is already close to a tool set. The intended mapping:
| MCP concept | Concat |
|---|---|
| A tool | One API method. project.open, edit.apply, export.run, … Names, params and results as in Methods |
| A tool’s input schema | The method’s params. For edit.apply, the command union in Edit commands |
| A tool result | The reply payload as JSON; a preview.frame reply as an image content block |
| A notification / progress | The job events: export.progress, export.done, export.failed, cutout.progress |
| A resource | concat://project/<path> for project.get; the catalogue as concat://catalogue |
| Auth | The same token concat-server mints, as a bearer |
Principles that carry over from the other transports:
- No new meaning in the transport. A tool calls a method; it does not interpret the edit.
- Refusals are the window’s sentences. An agent that hits
refusedcan show or reason about the message as it is. - Discover, don’t assume.
version→capabilities, andcatalogue.list→ what effects exist, before building a chain.
Bridging today
Until the server ships, an agent reaches Concat through a small MCP server that wraps the JSON-RPC socket. Three moving parts:
concat-cli serve(or the window’s Remote page), listening on127.0.0.1:7420with a token.- A stdio MCP server, spawned by the agent’s host, that opens one socket
connection and turns tool calls into
call(method, params). - Tool definitions that mirror the methods.
A sketch in Python, using the Concat class from
JSON-RPC → Python client and the
mcp package:
# concat_mcp.py — run with: python concat_mcp.py (the host spawns it over stdio)
import os
from mcp.server.fastmcp import FastMCP
from concat_client import Concat # the class from the JSON-RPC page
api = Concat(port=int(os.environ.get("CONCAT_PORT", 7420)), token=os.environ["CONCAT_API_TOKEN"])
mcp = FastMCP("concat")
@mcp.tool()
def version() -> dict:
"""What this Concat build serves."""
return api.call("version")
@mcp.tool()
def project_open(path: str) -> dict:
"""Open a project folder. Returns the editor view."""
return api.call("project.open", path=path)
@mcp.tool()
def project_create(location: str, name: str) -> dict:
"""Create a project folder under `location` and open it."""
return api.call("project.create", location=location, name=name)
@mcp.tool()
def media_import(path: str, file: str) -> dict:
"""Probe a file and add it to the project's bin."""
return api.call("media.import", path=path, file=file)
@mcp.tool()
def edit_apply(path: str, command: dict) -> dict:
"""Apply one edit command ({"op": ..., ...}). See docs/api/edits.md."""
return api.call("edit.apply", path=path, command=command)
@mcp.tool()
def catalogue_list(kind: str | None = None) -> list:
"""Effect packages and their parameters."""
return api.call("catalogue.list", **({"kind": kind} if kind else {}))
@mcp.tool()
def export_run(path: str, output: str) -> dict:
"""Render the timeline to a file and wait for it to finish."""
started = api.call("export.run", path=path, output=output)
return api.wait_for_job(started["job"])["params"]
if __name__ == "__main__":
mcp.run()
Register it with the host as a stdio server with CONCAT_API_TOKEN in
its environment. The exact registration is the host’s; the server above
speaks standard MCP over stdio.
Guidance for agents
If you are an agent reading this because you have been pointed at Concat:
- Call
version. CheckapiVersionstarts with0.2and readcapabilities. - Open or create a project. Keep its folder path; every call needs it.
- Import media with
media.import. The reply’screatedIdis the media id. - Place clips with
addClipAtFirstFreeoraddClip. Read the returnedEditorViewfor clip ids; do not guess them. - Prefer one
batchfor a sequence of edits that belong together. - Export with
export.run, then wait forexport.doneorexport.failedfor that job. Do not start a second export meanwhile; it is refused withbusy. - Treat a
refusedmessage as the reason, in plain words. Nothing changed. - Save with
project.savebeforeproject.close, or passsave: true.
Recipes has complete scripts for each of these.