# invideo for agents

invideo is a cloud video editor. Agents and people work on the same projects.

Think of it as Figma and GitHub for video. Like Figma, a project lives in the browser and several people can work in it at once; anyone with the link can watch the cut and edit the timeline. Like GitHub, a project has a main timeline, branches, and a full edit history, and changes can be proposed for review before they reach main.

> invideo exposes its projects as a remote MCP server. Connected, your agent opens a project by its URL, brings media in, edits timelines, and the person watches every edit land live in the real editor, where they can also edit by hand. The tools work in the person's invideo account; connecting asks them to sign in through your client, or see "No browser".

- **MCP server URL:** https://mcp.invideo.io/mcp
- **Transport:** Streamable HTTP (JSON-RPC 2.0 over POST)
- **Auth:** OAuth sign-in when connecting, or device sign-in ("No browser")

Find your client below. If it is not listed, use "Any MCP client" or "By hand over HTTP".

If you are the agent reading this: run the setup below yourself. Give the person a command to run only as a last resort, when nothing you can do works. Adding the server changes the person's client settings, so ask once ("Should I add invideo to your Claude Code?") and do it on a yes. The person's part is only what a client cannot do from your shell, as each client's section says. Once invideo's tools are available, ask the person what video you're making.

## Claude Code

```bash
claude mcp add --transport http --scope user invideo https://mcp.invideo.io/mcp
```

Claude Code loads a newly added server only when it starts, and only `/mcp` can sign the person in; `claude mcp login` needs an interactive terminal, which your shell is not, so do not run it. After adding the server, tell the person in one message: exit Claude Code and run `claude --continue` to come back to this conversation, then run `/mcp`, choose invideo and Authenticate, which opens their browser to sign in. Once they are back, carry on. If the sign-in ever lapses, the same `/mcp` → invideo → Authenticate is their one step.

## Codex

```bash
codex mcp add invideo --url https://mcp.invideo.io/mcp
```

Run it yourself: adding the server starts the sign-in and opens the person's browser, where they sign in. Codex loads a newly added server only in a new session, so then tell the person in one message to start a new Codex session to load the tools.

## Cursor

Add to `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "invideo": { "url": "https://mcp.invideo.io/mcp" }
  }
}
```

## VS Code

```bash
code --add-mcp '{"name":"invideo","type":"http","url":"https://mcp.invideo.io/mcp"}'
```

## Claude (claude.ai web or desktop)

Settings → Connectors → Add custom connector. Name `invideo`, URL `https://mcp.invideo.io/mcp`, with OAuth sign-in; connecting opens invideo's sign-in.

## ChatGPT

Custom connectors need Developer mode (Settings → Connectors → Advanced). Create one: name `invideo`, MCP server URL `https://mcp.invideo.io/mcp`. ChatGPT agents that run in the cloud can't use connectors; they go "By hand over HTTP".

## Any MCP client

Add a remote Streamable HTTP MCP server at `https://mcp.invideo.io/mcp` with your client's usual mechanism. Connecting answers an OAuth sign-in challenge, which the client runs; a client that can't run OAuth sign-in uses "No browser".

## By hand over HTTP

The server is plain JSON-RPC over POST, so any agent that can make HTTP requests can drive it without a client at all. Send `content-type: application/json`. Both protocol eras work: a client that opens with `initialize` gets a session id to send back as `mcp-session-id`; a client that names itself in `_meta` on every request needs no handshake at all.

```bash
# list the tools, with an access token from "No browser"
curl -s https://mcp.invideo.io/mcp -H 'content-type: application/json' -H "authorization: Bearer $TOKEN" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-agent","version":"1"}}}}'

# list the person's projects
curl -s https://mcp.invideo.io/mcp -H 'content-type: application/json' -H "authorization: Bearer $TOKEN" -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_projects","arguments":{},"_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-agent","version":"1"}}}}'
```

Every request but `ping` needs the person's invideo account: without it the server answers a sign-in challenge. Get a token with "No browser" and send it as `authorization: Bearer <token>` on every request.

## No browser

Device sign-in, for when the person isn't at a browser you control (say you run in the cloud, like a ChatGPT agent). They approve on their own device; you get a token. Sign in once, then stay signed in with the refresh token (step 6).

**1. Endpoints.** Fetch the protected-resource document, which a sign-in challenge also names as `resource_metadata`:

```bash
MCP_URL=https://mcp.invideo.io/mcp
ORIGIN=$(echo "$MCP_URL" | cut -d/ -f1-3)
curl -s "$ORIGIN/.well-known/oauth-protected-resource${MCP_URL#"$ORIGIN"}"
```

`invideo_device_authorization` gives `client_id` and `device_authorization_endpoint`; the token endpoint is `/oauth/token` on that host. If the field is absent, use `authorization_servers[0]` + `/.well-known/oauth-authorization-server`, and take `client_id` from the sign-in challenge's text.

**2. Get a code.** `curl -s -X POST "$DEVICE_ENDPOINT" -d "client_id=$CLIENT_ID"` returns `device_code`, `user_code`, `verification_uri_complete`, `expires_in` and `interval`. On `unauthorized_client`, `error_description` names the right `client_id`. One sign-in at a time: while a code is pending, don't request another.

**3. Tell the person, in one message:** "Open <verification_uri_complete> and check the code is <user_code>. It expires in <expires_in / 60> minutes." They choose a workspace there; the token works in it. Send one message per code. Never ask for a password, an email code, or the code back.

**4. Poll** that same `device_code` every `interval` seconds until it is approved, denied, or `expires_in` runs out:

```bash
curl -s -X POST "$TOKEN_ENDPOINT" -d grant_type=urn:ietf:params:oauth:grant-type:device_code \
  -d "device_code=$DEVICE_CODE" -d "client_id=$CLIENT_ID"
```

`authorization_pending`: wait. The person may be away for a while; don't request a new code because they haven't approved yet. `slow_down`: add 5 seconds to the interval. `access_denied`: stop and tell the person. `expired_token`: start one new flow with one new message; if that expires too, stop. `invalid_grant`: stop; restart only if the person asks.

**5. Use the token.** Send `authorization: Bearer <access_token>` on every request. It lasts `expires_in` seconds, about a day; read `expires_in` rather than assuming.

**6. Stay signed in.** Keep the refresh token somewhere that survives between your tasks and sessions: your persistent storage or secret store, not chat or files the person sees. When a call returns 401, or the access token's `expires_in` has passed, refresh first:

```bash
curl -s -X POST "$TOKEN_ENDPOINT" -d grant_type=refresh_token \
  -d "refresh_token=$REFRESH_TOKEN" -d "client_id=$CLIENT_ID"
```

Each refresh returns a new access token and a new refresh token. Store the new refresh token every time; they rotate. Start device sign-in again (step 2) only if you have no refresh token or the refresh fails. Keep tokens and `device_code` out of chat, visible files and logs.

## Once connected

Call `get_help` for how to work with invideo: projects and their URLs, the notebook, timelines, media, exports. `get_help` with a `topic` gives one section.
