OverviewAuthenticationScopesEndpointsVideo previewErrors & limitsPluginsBuild a platform
Documentation

Plugins

Extend what every Vidmoat agent can do. You host an MCP server; we proxy to it, namespace its tools, and expose them over both MCP and REST.

A plugin is an MCP server you run. Vidmoat never executes your code — it forwards a tools/call to your endpoint and hands the result back to whoever asked. That means you keep your runtime, your dependencies and your deploy cycle, and we keep the ability to cut a bad integration off without touching anything else.

Submitting: paste the URL

You do not write a manifest. Give us your endpoint and Vidmoat calls tools/list and initialize and fills in the rest from what your server reports — so what is registered can never disagree with what you actually serve. That mismatch is otherwise the most common reason a submission is held.

WE READFROM
Tool names, descriptions, input schemastools/list
Display nameinitializeserverInfo.title (or .name)
Descriptioninitializeinstructions
Suggested slugderived from the name — you can change it

The one thing MCP cannot express is producesMedia, because it is a Vidmoat concept with no place in the spec. Declare it per tool and your plugin imports with nothing to fill in at all:

Making your server fully self-describing
{
  "name": "make_thumbnail",
  "description": "Takes a title and an optional face crop and returns a 1280x720 thumbnail.",
  "inputSchema": { "type": "object", "properties": { "title": { "type": "string" } }, "required": ["title"] },
  "_meta": { "vidmoat": { "producesMedia": "image" } }
}

Otherwise you tick it in the form. Everything else — slug, name, description — is editable there too; what you cannot override is the tool list, which is re-read from your server at the moment you submit.

The shape of a call

Once a user installs your plugin, your tools appear in their tools/list alongside ours, and are reachable over REST. Both go through the same code path, so they behave identically.

Two doors, one plugin
# MCP — for agents
{"method":"tools/call","params":{"name":"my_plugin__do_the_thing","arguments":{...}}}

# REST — for everything else
POST https://api.vidmoat.com/v1/plugins/my-plugin/do_the_thing
Authorization: Bearer vm_live_...
Content-Type: application/json

{ "arguments": { "subject": "..." }, "projectId": "optional" }

Namespacing, and why your tool names are safe

Your tools are exposed as slug__tool. Agents pick tools by name, so if a plugin could register render_project, an agent asked to render a video would reach a stranger's server holding a real project id. Slugs are unique across Vidmoat and a manifest claiming one of our tool names is refused at submission — which also means nobody else can shadow yours.

What we send you

The request your endpoint receives
POST /mcp
X-Vidmoat-Request-Id: 0f0d…            # unique per call
X-Vidmoat-Timestamp: 1756...            # unix seconds
X-Vidmoat-Signature: <hex>              # HMAC-SHA256 of "timestamp.body"
X-Your-Header: <your secret>            # only if you configured one

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "do_the_thing",             # YOUR name, not the namespaced one
    "arguments": { ... },
    "_meta": { "vidmoat": {
      "requestId": "0f0d…",
      "user": "a1b2…",                  # opaque, stable, per-plugin
      "projectId": "cm…" | null
    }}
  }
}
You never receive the caller's Vidmoat credentials.Not their API key, not their session, not their scopes — there is no mechanism by which you could. Your plugin acts on its own behalf. If it needs data from the user's Vidmoat account, ask them for their own API key through your own flow.

The user field is an HMAC of the real id with your plugin id. It is stable, so you can cache per-user work against it — and it is useless to any other plugin, so nobody can correlate one person across the catalogue.

Limits, and what happens when you hit them

LIMITVALUEWHAT THE CALLER SEES
Response time20sTimeout, counted as a failure
Response body1 MBbody_too_large — return a URL, not inline data
Returned media200 MBThe call fails; nothing is added to the project
RedirectsNot followedredirect — respond directly
Consecutive failures8Plugin auto-disabled until you fix and re-enable it
Your endpoint must resolve to a public address on every call, not just at registration — we re-check DNS each time. Development tunnels (ngrok and friends) are refused at review: fine for building, gone the moment you close your laptop.

Returning media

Declare producesMediaon a tool and return a URL. Vidmoat downloads it, checks the content type against what you declared, and copies it into the user's own library before handing back our URL.

This is not a formality. A plugin's URL written into a saved project is a clip that goes black the day you change hosts, and neither the render worker nor the user would be able to tell that from a deliberately black shot. Copying it is what makes plugin output a real asset — garbage-collected, proxied and rendered exactly like an upload.

A media tool
{
  "name": "rig_character",
  "description": "Rigs a front-facing character illustration and returns a looping idle animation.",
  "inputSchema": { "type": "object", "properties": { "imageUrl": { "type": "string" } }, "required": ["imageUrl"] },
  "producesMedia": "video"
}

// Your response — any of these shapes is understood:
{ "content": [{ "type": "text", "text": "{\"url\":\"https://you.dev/out.webm\"}" }] }
{ "content": { "url": "https://you.dev/out.webm" } }

Writing tool descriptions

The description is the whole interface. An agent reads it and nothing else when deciding whether to call your tool — it never sees your README, your site, or your intent. Say what the tool does, what it needs, and what it returns, in a sentence someone could act on. Submissions where the descriptions say nothing are held for review for exactly this reason.

Private plugins

A plugin does not have to be for anybody else. Register it as private and it is yours alone: it never appears in the marketplace, nobody else can install it, and it skips review entirely — there is no third party for a reviewer to be protecting. Its tools show up in your agents' tools/list and are callable over REST with your own key, exactly like a published one.

VISIBILITYWHO CAN INSTALL ITREVIEWEDIN THE MARKETPLACE
privateNobody — only you can call itNoNo
unlistedAnyone you give the slug toYesNo
publicAnyoneYesYes

Unlisted is reviewed like public on purpose. Being hard to find is not a safety property — a link gets forwarded — so the moment anybody other than you can install it, somebody has looked at it.

Private skips review, and only review. Everything mechanical still applies: the namespace rules, the reserved slugs, the collision check against our own tool names, the SSRF gate re-run on every single call, the request signature, the response caps and the failure breaker. Those protect our infrastructure and your account, not the public, and a private plugin pointed somewhere it should not be is exactly as much of a problem as a published one.

Your slug is taken globally either way, including against plugins you cannot see. It is the namespace your tools are exposed under and that namespace is shared by everything on the platform, so my_thing__run has to mean one thing whoever is asking.

Changing privatepublic or unlisted re-runs the whole submission triage at that moment — we re-probe your endpoint and re-check your tools — so registering privately and flipping the switch later is not a way around review, it is just review at the point it starts mattering. Going back to private is free and immediate; anyone who had installed it keeps the install but loses the tools, and gets them back if you publish again.

Review

You can call your own plugin the moment it is registered — building before talking to anyone is the point. Review gates other people installing it. Most clean submissions publish automatically; a hold always comes with the reason, and the ones that hold are mechanical: an endpoint that does not answer, one that is not an MCP server, or a manifest declaring tools the endpoint does not advertise.

A useful consequence: a plugin behind a development tunnel is refused for publication (it stops answering the moment you close your laptop) but registers privately without argument. The objection to a tunnel is that it disappears on other people, and a private plugin has no other people.

Ready?
Paste your endpoint URL at Plugins. We read your server and show you exactly what we got before anything is created — so if it is not answering, or a tool name clashes with one of ours, you find out in that moment rather than after a reviewer gets to it.
Versioning. Additive changes ship into v1 — new fields, new endpoints, new enum values. Ignore unknown fields; a client that rejects them will break on a routine release. Breaking changes get a /v2, and v1 is supported for at least 12 months after one exists. Send Vidmoat-Version: 2026-08-01 to pin date-based behaviour within v1.