@rendobar/mcp 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,39 @@
1
- # @rendobar/mcp
2
-
3
- > Rendobar — serverless media processing for AI agents.
1
+ <p align="center">
2
+ <a href="https://rendobar.com">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.rendobar.com/assets/brand/logo-mark.svg">
5
+ <img alt="Rendobar" src="https://cdn.rendobar.com/assets/brand/logo-mark-black.svg" width="80">
6
+ </picture>
7
+ </a>
8
+ </p>
9
+
10
+ <h1 align="center">@rendobar/mcp</h1>
11
+
12
+ <p align="center">
13
+ <strong>Serverless media processing for AI agents.</strong><br>
14
+ The official Model Context Protocol server for Rendobar.
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://rendobar.com">Website</a> &nbsp;·&nbsp;
19
+ <a href="https://rendobar.com/docs/mcp/">MCP docs</a> &nbsp;·&nbsp;
20
+ <a href="https://www.npmjs.com/package/@rendobar/mcp">npm</a> &nbsp;·&nbsp;
21
+ <a href="https://discord.gg/kAGqjBzx8N">Discord</a>
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="https://www.npmjs.com/package/@rendobar/mcp"><img src="https://img.shields.io/npm/v/@rendobar/mcp?style=flat-square&color=059669&label=npm" alt="npm version"></a>
26
+ <a href="https://www.npmjs.com/package/@rendobar/mcp"><img src="https://img.shields.io/npm/dm/@rendobar/mcp?style=flat-square&color=059669" alt="npm downloads"></a>
27
+ <img src="https://img.shields.io/npm/l/@rendobar/mcp?style=flat-square&color=059669" alt="MIT license">
28
+ <img src="https://img.shields.io/node/v/@rendobar/mcp?style=flat-square&color=059669" alt="Node version">
29
+ </p>
30
+
31
+ ---
4
32
 
5
33
  `@rendobar/mcp` is the official Model Context Protocol server for [Rendobar](https://rendobar.com). It lets AI agents in Claude Desktop, Cursor, Cline, Windsurf, Zed, VS Code, Claude Code, and Continue submit Rendobar jobs and upload local files in a single tool call.
6
34
 
35
+ The difference from the hosted MCP at `api.rendobar.com`: this server runs locally, so it can read and upload files straight from your machine. An agent can take a video on your disk, run an FFmpeg job on it, and hand back the result without you touching a browser.
36
+
7
37
  ## Install
8
38
 
9
39
  You don't install it. Configure your MCP client to spawn it via `npx`.
@@ -107,13 +137,22 @@ env:
107
137
  | `cancel_job` | Cancel a waiting/dispatched job. |
108
138
  | `get_account` | Check balance, plan limits, active job count. |
109
139
 
140
+ ## Local vs hosted MCP
141
+
142
+ | | `@rendobar/mcp` (this package) | Hosted MCP (`api.rendobar.com`) |
143
+ |---|---|---|
144
+ | Transport | stdio, spawned by your client | Streamable HTTP |
145
+ | Local file upload | Yes, the whole point | No, server has no disk |
146
+ | Setup | `npx` line in a config file | Bearer API key over HTTP |
147
+ | Best for | Claude Desktop, Cursor, Cline, Zed, local agents | claude.ai web, ChatGPT, hosted gateways |
148
+
110
149
  ## Authentication
111
150
 
112
151
  Three sources, first match wins:
113
152
 
114
153
  1. `--api-key=<key>` flag
115
154
  2. `RENDOBAR_API_KEY` environment variable
116
- 3. `~/.config/rendobar/credentials.json` (Unix) / `%APPDATA%\rendobar\credentials.json` (Windows) written by Rendobar CLI's `rb login` (CLI v1.1+)
155
+ 3. `~/.config/rendobar/credentials.json` (Unix) / `%APPDATA%\rendobar\credentials.json` (Windows), written by Rendobar CLI's `rb login` (CLI v1.1+)
117
156
 
118
157
  ## Troubleshooting
119
158
 
@@ -131,11 +170,11 @@ Use `"command": "npx.cmd"` instead of `"command": "npx"` if your client doesn't
131
170
 
132
171
  ### Server fails to start
133
172
 
134
- Check logs in your client's output panel. Server writes JSON lines to stderr look for entries with `level: "error"`.
173
+ Check logs in your client's output panel. The server writes JSON lines to stderr. Look for entries with `level: "error"`.
135
174
 
136
175
  ## Contributing
137
176
 
138
- See [CONTRIBUTING.md](./CONTRIBUTING.md). For AI-assisted development, see [CLAUDE.md](./CLAUDE.md).
177
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). For AI-assisted development, see [AGENTS.md](./AGENTS.md) and [CLAUDE.md](./CLAUDE.md).
139
178
 
140
179
  ## License
141
180
 
package/dist/bin.js CHANGED
@@ -29,7 +29,7 @@ Active job types:
29
29
  ${o.map(e=>` ${e.type} \u2014 ${e.summary}`).join(`
30
30
  `)}`:"";return{name:"submit_job",title:"Submit Rendobar Job",description:V+r+`
31
31
 
32
- For local files, call upload_file first to get a downloadUrl, then use it as inputs.source.`,inputSchema:W,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(e,t)=>{let n=await t.sdk.jobs.create({type:e.type,inputs:e.inputs,params:e.params,idempotencyKey:e.idempotencyKey});return{jobId:n.id,status:n.status}}}}var Y={name:"cancel_job",title:"Cancel Rendobar Job",description:"Cancel a job that has not started running. Only jobs in status 'waiting' or 'dispatched' can be cancelled. Use when the user changes their mind, or when you submitted the wrong job. Running, completed, failed, or already-cancelled jobs cannot be cancelled.",inputSchema:{jobId:a.string().describe("Job ID to cancel (e.g. 'job_abc123')")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0},execute:async(o,r)=>{let e=await r.sdk.jobs.cancel(o.jobId);return{id:e.id,status:e.status}}},g=o=>o;async function k(o){let r=[];try{r=await o.jobs.types()}catch{}return[g(Z),g(G),g(q(r)),g(Y)]}import{z as b}from"zod";import{promises as O}from"fs";import y from"path";import{promises as F}from"fs";import P from"path";async function E(o,r){let e=P.resolve(r.cwd,o),t=await F.realpath(e);if(r.roots!==void 0&&r.roots.length>0&&!(await Promise.all(r.roots.map(s=>F.realpath(s).catch(()=>null)))).some(s=>s!==null&&(t===s||t.startsWith(s+P.sep))))throw new Error(`Path is outside the allowed MCP roots: ${t}`);return t}async function X(o){if(o.cachedMaxFileSize!==null)return o.cachedMaxFileSize;let r=await o.sdk.billing.state();return o.cachedMaxFileSize=r.plan.limits.maxInputFileSize,o.cachedMaxFileSize}var Q={name:"upload_file",title:"Upload Local File to Rendobar",description:"Read a local file and upload it to Rendobar. Returns a downloadUrl to use as input in submit_job. If the file is already at a public HTTPS URL, skip this and pass the URL directly to submit_job.",inputSchema:{path:b.string().describe("Absolute or working-dir-relative path to the file"),filename:b.string().optional().describe("Filename hint sent to Rendobar (defaults to basename of path)")},outputSchema:{downloadUrl:b.string().url(),sizeBytes:b.number()},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(o,r,e)=>{let t=await E(o.path,{cwd:process.cwd()}),n=await O.stat(t);if(!n.isFile())throw new Error(`Path is not a regular file: ${y.basename(t)}`);let i=n.size,s=await X(r);if(i>s)throw new Error(`File size (${i} bytes) exceeds plan limit (${s} bytes). Upgrade your plan for larger uploads.`);r.logger.debug({msg:"upload_start",basename:y.basename(t),sizeBytes:i});let l=await O.readFile(t),d=new Blob([l]),h=await r.sdk.uploads.upload(d,{filename:o.filename??y.basename(t),signal:e.signal});return r.logger.info({msg:"upload_complete",basename:y.basename(t),sizeBytes:i}),{downloadUrl:h.downloadUrl,sizeBytes:i}}},ee=o=>o;function L(){return[ee(Q)]}async function D(o,r){for(let e of I())f(o,r,e);for(let e of await k(r.sdk))f(o,r,e);for(let e of L())f(o,r,e)}var te="1.0.1";async function U(o){let r=_(o.config,o.logger),e=new oe({name:"rendobar",version:te},{capabilities:{tools:{},logging:{}},instructions:j});return await D(e,r),{server:e,cleanup:async()=>{}}}var R="1.0.1",ie=`@rendobar/mcp v${R}
32
+ For local files, call upload_file first to get a downloadUrl, then use it as inputs.source.`,inputSchema:W,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(e,t)=>{let n=await t.sdk.jobs.create({type:e.type,inputs:e.inputs,params:e.params,idempotencyKey:e.idempotencyKey});return{jobId:n.id,status:n.status}}}}var Y={name:"cancel_job",title:"Cancel Rendobar Job",description:"Cancel a job that has not started running. Only jobs in status 'waiting' or 'dispatched' can be cancelled. Use when the user changes their mind, or when you submitted the wrong job. Running, completed, failed, or already-cancelled jobs cannot be cancelled.",inputSchema:{jobId:a.string().describe("Job ID to cancel (e.g. 'job_abc123')")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0},execute:async(o,r)=>{let e=await r.sdk.jobs.cancel(o.jobId);return{id:e.id,status:e.status}}},g=o=>o;async function k(o){let r=[];try{r=await o.jobs.types()}catch{}return[g(Z),g(G),g(q(r)),g(Y)]}import{z as b}from"zod";import{promises as O}from"fs";import y from"path";import{promises as F}from"fs";import P from"path";async function E(o,r){let e=P.resolve(r.cwd,o),t=await F.realpath(e);if(r.roots!==void 0&&r.roots.length>0&&!(await Promise.all(r.roots.map(s=>F.realpath(s).catch(()=>null)))).some(s=>s!==null&&(t===s||t.startsWith(s+P.sep))))throw new Error(`Path is outside the allowed MCP roots: ${t}`);return t}async function X(o){if(o.cachedMaxFileSize!==null)return o.cachedMaxFileSize;let r=await o.sdk.billing.state();return o.cachedMaxFileSize=r.plan.limits.maxInputFileSize,o.cachedMaxFileSize}var Q={name:"upload_file",title:"Upload Local File to Rendobar",description:"Read a local file and upload it to Rendobar. Returns a downloadUrl to use as input in submit_job. If the file is already at a public HTTPS URL, skip this and pass the URL directly to submit_job.",inputSchema:{path:b.string().describe("Absolute or working-dir-relative path to the file"),filename:b.string().optional().describe("Filename hint sent to Rendobar (defaults to basename of path)")},outputSchema:{downloadUrl:b.string().url(),sizeBytes:b.number()},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(o,r,e)=>{let t=await E(o.path,{cwd:process.cwd()}),n=await O.stat(t);if(!n.isFile())throw new Error(`Path is not a regular file: ${y.basename(t)}`);let i=n.size,s=await X(r);if(i>s)throw new Error(`File size (${i} bytes) exceeds plan limit (${s} bytes). Upgrade your plan for larger uploads.`);r.logger.debug({msg:"upload_start",basename:y.basename(t),sizeBytes:i});let l=await O.readFile(t),d=new Blob([l]),h=await r.sdk.uploads.upload(d,{filename:o.filename??y.basename(t),signal:e.signal});return r.logger.info({msg:"upload_complete",basename:y.basename(t),sizeBytes:i}),{downloadUrl:h.downloadUrl,sizeBytes:i}}},ee=o=>o;function L(){return[ee(Q)]}async function D(o,r){for(let e of I())f(o,r,e);for(let e of await k(r.sdk))f(o,r,e);for(let e of L())f(o,r,e)}var te="1.0.3";async function U(o){let r=_(o.config,o.logger),e=new oe({name:"rendobar",version:te},{capabilities:{tools:{},logging:{}},instructions:j});return await D(e,r),{server:e,cleanup:async()=>{}}}var R="1.0.3",ie=`@rendobar/mcp v${R}
33
33
  Local stdio Model Context Protocol server for Rendobar.
34
34
 
35
35
  Usage:
package/dist/index.js CHANGED
@@ -23,4 +23,4 @@ Active job types:
23
23
  ${t.map(e=>` ${e.type} \u2014 ${e.summary}`).join(`
24
24
  `)}`:"";return{name:"submit_job",title:"Submit Rendobar Job",description:M+o+`
25
25
 
26
- For local files, call upload_file first to get a downloadUrl, then use it as inputs.source.`,inputSchema:U,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(e,n)=>{let r=await n.sdk.jobs.create({type:e.type,inputs:e.inputs,params:e.params,idempotencyKey:e.idempotencyKey});return{jobId:r.id,status:r.status}}}}var P={name:"cancel_job",title:"Cancel Rendobar Job",description:"Cancel a job that has not started running. Only jobs in status 'waiting' or 'dispatched' can be cancelled. Use when the user changes their mind, or when you submitted the wrong job. Running, completed, failed, or already-cancelled jobs cannot be cancelled.",inputSchema:{jobId:s.string().describe("Job ID to cancel (e.g. 'job_abc123')")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0},execute:async(t,o)=>{let e=await o.sdk.jobs.cancel(t.jobId);return{id:e.id,status:e.status}}},c=t=>t;async function R(t){let o=[];try{o=await t.jobs.types()}catch{}return[c(O),c(z),c(D(o)),c(P)]}import{z as d}from"zod";import{promises as T}from"fs";import u from"path";import{promises as w}from"fs";import x from"path";async function S(t,o){let e=x.resolve(o.cwd,t),n=await w.realpath(e);if(o.roots!==void 0&&o.roots.length>0&&!(await Promise.all(o.roots.map(a=>w.realpath(a).catch(()=>null)))).some(a=>a!==null&&(n===a||n.startsWith(a+x.sep))))throw new Error(`Path is outside the allowed MCP roots: ${n}`);return n}async function H(t){if(t.cachedMaxFileSize!==null)return t.cachedMaxFileSize;let o=await t.sdk.billing.state();return t.cachedMaxFileSize=o.plan.limits.maxInputFileSize,t.cachedMaxFileSize}var E={name:"upload_file",title:"Upload Local File to Rendobar",description:"Read a local file and upload it to Rendobar. Returns a downloadUrl to use as input in submit_job. If the file is already at a public HTTPS URL, skip this and pass the URL directly to submit_job.",inputSchema:{path:d.string().describe("Absolute or working-dir-relative path to the file"),filename:d.string().optional().describe("Filename hint sent to Rendobar (defaults to basename of path)")},outputSchema:{downloadUrl:d.string().url(),sizeBytes:d.number()},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(t,o,e)=>{let n=await S(t.path,{cwd:process.cwd()}),r=await T.stat(n);if(!r.isFile())throw new Error(`Path is not a regular file: ${u.basename(n)}`);let i=r.size,a=await H(o);if(i>a)throw new Error(`File size (${i} bytes) exceeds plan limit (${a} bytes). Upgrade your plan for larger uploads.`);o.logger.debug({msg:"upload_start",basename:u.basename(n),sizeBytes:i});let m=await T.readFile(n),F=new Blob([m]),_=await o.sdk.uploads.upload(F,{filename:t.filename??u.basename(n),signal:e.signal});return o.logger.info({msg:"upload_complete",basename:u.basename(n),sizeBytes:i}),{downloadUrl:_.downloadUrl,sizeBytes:i}}},J=t=>t;function j(){return[J(E)]}async function v(t,o){for(let e of h())p(t,o,e);for(let e of await R(o.sdk))p(t,o,e);for(let e of j())p(t,o,e)}var L="1.0.1";async function N(t){let o=f(t.config,t.logger),e=new Z({name:"rendobar",version:L},{capabilities:{tools:{},logging:{}},instructions:b});return await v(e,o),{server:e,cleanup:async()=>{}}}export{N as createRendobarMcpServer};
26
+ For local files, call upload_file first to get a downloadUrl, then use it as inputs.source.`,inputSchema:U,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(e,n)=>{let r=await n.sdk.jobs.create({type:e.type,inputs:e.inputs,params:e.params,idempotencyKey:e.idempotencyKey});return{jobId:r.id,status:r.status}}}}var P={name:"cancel_job",title:"Cancel Rendobar Job",description:"Cancel a job that has not started running. Only jobs in status 'waiting' or 'dispatched' can be cancelled. Use when the user changes their mind, or when you submitted the wrong job. Running, completed, failed, or already-cancelled jobs cannot be cancelled.",inputSchema:{jobId:s.string().describe("Job ID to cancel (e.g. 'job_abc123')")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0},execute:async(t,o)=>{let e=await o.sdk.jobs.cancel(t.jobId);return{id:e.id,status:e.status}}},c=t=>t;async function R(t){let o=[];try{o=await t.jobs.types()}catch{}return[c(O),c(z),c(D(o)),c(P)]}import{z as d}from"zod";import{promises as T}from"fs";import u from"path";import{promises as w}from"fs";import x from"path";async function S(t,o){let e=x.resolve(o.cwd,t),n=await w.realpath(e);if(o.roots!==void 0&&o.roots.length>0&&!(await Promise.all(o.roots.map(a=>w.realpath(a).catch(()=>null)))).some(a=>a!==null&&(n===a||n.startsWith(a+x.sep))))throw new Error(`Path is outside the allowed MCP roots: ${n}`);return n}async function H(t){if(t.cachedMaxFileSize!==null)return t.cachedMaxFileSize;let o=await t.sdk.billing.state();return t.cachedMaxFileSize=o.plan.limits.maxInputFileSize,t.cachedMaxFileSize}var E={name:"upload_file",title:"Upload Local File to Rendobar",description:"Read a local file and upload it to Rendobar. Returns a downloadUrl to use as input in submit_job. If the file is already at a public HTTPS URL, skip this and pass the URL directly to submit_job.",inputSchema:{path:d.string().describe("Absolute or working-dir-relative path to the file"),filename:d.string().optional().describe("Filename hint sent to Rendobar (defaults to basename of path)")},outputSchema:{downloadUrl:d.string().url(),sizeBytes:d.number()},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},execute:async(t,o,e)=>{let n=await S(t.path,{cwd:process.cwd()}),r=await T.stat(n);if(!r.isFile())throw new Error(`Path is not a regular file: ${u.basename(n)}`);let i=r.size,a=await H(o);if(i>a)throw new Error(`File size (${i} bytes) exceeds plan limit (${a} bytes). Upgrade your plan for larger uploads.`);o.logger.debug({msg:"upload_start",basename:u.basename(n),sizeBytes:i});let m=await T.readFile(n),F=new Blob([m]),_=await o.sdk.uploads.upload(F,{filename:t.filename??u.basename(n),signal:e.signal});return o.logger.info({msg:"upload_complete",basename:u.basename(n),sizeBytes:i}),{downloadUrl:_.downloadUrl,sizeBytes:i}}},J=t=>t;function j(){return[J(E)]}async function v(t,o){for(let e of h())p(t,o,e);for(let e of await R(o.sdk))p(t,o,e);for(let e of j())p(t,o,e)}var L="1.0.3";async function N(t){let o=f(t.config,t.logger),e=new Z({name:"rendobar",version:L},{capabilities:{tools:{},logging:{}},instructions:b});return await v(e,o),{server:e,cleanup:async()=>{}}}export{N as createRendobarMcpServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rendobar/mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Rendobar — serverless media processing for AI agents",