@vidofy/mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/backend.d.ts +133 -0
- package/dist/backend.js +455 -0
- package/dist/config.d.ts +85 -0
- package/dist/config.js +207 -0
- package/dist/heartbeat.d.ts +11 -0
- package/dist/heartbeat.js +70 -0
- package/dist/http.d.ts +44 -0
- package/dist/http.js +885 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +661 -0
- package/dist/log.d.ts +19 -0
- package/dist/log.js +22 -0
- package/dist/map/b2c.d.ts +139 -0
- package/dist/map/b2c.js +236 -0
- package/dist/media-duration.d.ts +33 -0
- package/dist/media-duration.js +137 -0
- package/dist/oauth/authorize.d.ts +61 -0
- package/dist/oauth/authorize.js +275 -0
- package/dist/oauth/clients.d.ts +137 -0
- package/dist/oauth/clients.js +621 -0
- package/dist/oauth/ratelimit.d.ts +92 -0
- package/dist/oauth/ratelimit.js +116 -0
- package/dist/oauth/store.d.ts +135 -0
- package/dist/oauth/store.js +277 -0
- package/dist/oauth/token.d.ts +29 -0
- package/dist/oauth/token.js +165 -0
- package/dist/schema.d.ts +147 -0
- package/dist/schema.js +629 -0
- package/dist/tools/account.d.ts +28 -0
- package/dist/tools/account.js +130 -0
- package/dist/tools/generation.d.ts +102 -0
- package/dist/tools/generation.js +1194 -0
- package/dist/tools/info.d.ts +50 -0
- package/dist/tools/info.js +95 -0
- package/dist/ui/generation-card.html +869 -0
- package/package.json +79 -0
- package/server.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vidofy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @vidofy/mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vidofy/mcp)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://modelcontextprotocol.io)
|
|
7
|
+
|
|
8
|
+
**MCP server for [Vidofy](https://vidofy.ai)** — generate images, video, audio and speech from
|
|
9
|
+
Claude Desktop, Cursor, or any MCP client, **billed to your own Vidofy account**, at the same
|
|
10
|
+
prices the website charges.
|
|
11
|
+
|
|
12
|
+
Over 570 models, including **Veo 3.1**, **Kling 3.0**, **Flux 2**, **Seedance 2.5**, **Wan 2.7**,
|
|
13
|
+
**Hailuo 2.3**, **Runway**, **Luma Ray 2**, **Qwen Image 3.0**, **Vidu Q3** and **LTX 2** —
|
|
14
|
+
text-to-video, image-to-video, text-to-image, image editing, video and photo effects, lipsync,
|
|
15
|
+
text-to-speech and voice cloning. The agent browses the catalogue, prices a generation before
|
|
16
|
+
running it, and follows one to its result.
|
|
17
|
+
|
|
18
|
+
> **Status: v0.1.0, the first public release.**
|
|
19
|
+
>
|
|
20
|
+
> **This server is for personal Vidofy accounts.** It takes one credential,
|
|
21
|
+
> `VIDOFY_TOKEN`, and spends **your own coins** — the same balance the website
|
|
22
|
+
> spends, at the same prices. There is no other billing mode: `VIDOFY_API_KEY` is
|
|
23
|
+
> refused at startup, and that is a decision, not a feature waiting on a release.
|
|
24
|
+
|
|
25
|
+
## Tools
|
|
26
|
+
|
|
27
|
+
| Tool | What it does | Spends |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `list_modes` | What Vidofy can generate: text-to-image, image-to-video, lipsync, speech… | no |
|
|
30
|
+
| `list_models` | The models in one mode, with each one's credit cost and rough duration | no |
|
|
31
|
+
| `get_model` | One model's full input contract: a JSON Schema, its file slots and their limits | no |
|
|
32
|
+
| `estimate_cost` | What a generation will cost, before running it | no |
|
|
33
|
+
| **`generate`** | **Runs it. The only tool that spends the balance.** | **yes** |
|
|
34
|
+
| `get_status` | Whether a generation has finished | no |
|
|
35
|
+
| `get_result` | The finished media | no |
|
|
36
|
+
| `get_balance` | Coins left, and how many expire with the subscription | no |
|
|
37
|
+
| `get_usage` | Recent generations and what they cost | no |
|
|
38
|
+
|
|
39
|
+
The usual order is `list_modes` → `list_models` → `get_model` → `estimate_cost` → `generate`
|
|
40
|
+
→ `get_status` → `get_result`.
|
|
41
|
+
|
|
42
|
+
`generate` is the only tool without `readOnlyHint`, which is what tells a client to ask the user
|
|
43
|
+
before running it. It charges at **submit**, not on success, and returns immediately with an id —
|
|
44
|
+
a generation takes from ~30 seconds to several minutes, so the agent polls `get_status` rather
|
|
45
|
+
than holding the call open. Output is **private by default**; pass `public: true` only when the
|
|
46
|
+
user asked for a permanent public link.
|
|
47
|
+
|
|
48
|
+
File inputs take a **path on the machine running the server**. The package reads the user's own
|
|
49
|
+
file and streams it with the submit — it never makes a temporary copy — and checks the extension
|
|
50
|
+
and size against that model's own limits first, so a file the server would reject never leaves
|
|
51
|
+
the disk.
|
|
52
|
+
|
|
53
|
+
**Not exposed, deliberately:** checkout, auto top-up, purchases, referrals, the daily reward.
|
|
54
|
+
Nothing in this package can buy coins or change a plan, however it is prompted.
|
|
55
|
+
|
|
56
|
+
## Setup
|
|
57
|
+
|
|
58
|
+
There are two ways in. **Take the first one** unless your client cannot do it.
|
|
59
|
+
|
|
60
|
+
### 1. Remote connector — nothing to install
|
|
61
|
+
|
|
62
|
+
Give your client this URL:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
https://vidofy.ai/mcp-app
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You sign in **in your browser** and approve once. No token to copy, nothing to keep in a
|
|
69
|
+
config file, and nothing to update when this package changes.
|
|
70
|
+
|
|
71
|
+
| Client | How |
|
|
72
|
+
|---|---|
|
|
73
|
+
| **Claude.ai** · **Claude Desktop** | Settings → Connectors → *Add custom connector* → paste the URL → **Connect**, then approve the sign-in. They share one list: add it in either and it appears in both. Available on every plan, including Free — where you get one connector. |
|
|
74
|
+
| **ChatGPT** | Settings → Connectors → add a custom connector (no such option? turn on Developer Mode in Settings first) → paste the URL → **Connect**, then approve. On a Business or Enterprise workspace an administrator adds it for everyone. |
|
|
75
|
+
| **Claude Code** · **Codex** · **Cursor** | Each accepts a remote MCP server URL. Follow that client's own MCP documentation and give it the URL above. |
|
|
76
|
+
|
|
77
|
+
Then ask it: *"list Vidofy modes"* to confirm the connection, and
|
|
78
|
+
*"make me a 5-second clip of a red bicycle"* — it prices the generation before running it.
|
|
79
|
+
|
|
80
|
+
### 2. Local stdio server — for a client that only speaks stdio
|
|
81
|
+
|
|
82
|
+
Create a personal MCP token at **vidofy.ai → Studio → Account → MCP Access**. It is shown once.
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
// claude_desktop_config.json (Cursor: .cursor/mcp.json — same shape)
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"vidofy": {
|
|
89
|
+
"command": "npx",
|
|
90
|
+
"args": ["-y", "@vidofy/mcp"],
|
|
91
|
+
"env": { "VIDOFY_TOKEN": "vmt_..." }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`npx` fetches it on first run. Prefer a pinned copy? `npm i -g @vidofy/mcp`, then:
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
{ "command": "vidofy-mcp", "env": { "VIDOFY_TOKEN": "vmt_..." } }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Both paths reach the same account, the same models and the same balance. The difference is
|
|
104
|
+
only where the process runs and how you prove who you are.
|
|
105
|
+
|
|
106
|
+
### Environment
|
|
107
|
+
|
|
108
|
+
| Variable | Required | What it does |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `VIDOFY_TOKEN` | **yes** | Personal MCP token (`vmt_…`). Spends **your own Vidofy coins**, exactly as the studio does. |
|
|
111
|
+
| `VIDOFY_API_BASE` | no | Override the origin the server talks to — an **origin only**, no path. Defaults to `https://vidofy.ai`, which is what you want. |
|
|
112
|
+
|
|
113
|
+
`VIDOFY_API_KEY` is recognised only in order to be **refused**: a `vky_…` key bills a
|
|
114
|
+
different balance, which this server does not serve. Setting it stops startup with a message
|
|
115
|
+
naming the token to use instead — and setting *both* is refused too, since the two bill
|
|
116
|
+
different balances and no precedence rule is worth having to remember.
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm install
|
|
122
|
+
npm run build
|
|
123
|
+
npm run inspect # MCP Inspector — spends nothing
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`VIDOFY_API_BASE` points it at a different origin, if you are running one.
|
|
127
|
+
|
|
128
|
+
**Nothing here writes to stdout.** With stdio transport, stdout *is* the protocol channel — a
|
|
129
|
+
single stray `console.log()` puts a non-JSON line in the stream and the client drops the
|
|
130
|
+
connection with an error that explains nothing. Diagnostics go to stderr via the `log()` helper
|
|
131
|
+
in `src/index.ts`.
|
|
132
|
+
|
|
133
|
+
## Layout
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
src/config.ts credential + mode + base URL, validated at startup
|
|
137
|
+
src/backend.ts the only place that talks HTTP: auth, retries, multipart, errors
|
|
138
|
+
src/schema.ts one model's m_options → a JSON Schema the agent can fill in
|
|
139
|
+
src/map/b2c.ts both response shapes → one; strips the provider cost
|
|
140
|
+
src/tools/info.ts list_modes, list_models, get_model
|
|
141
|
+
src/tools/generation.ts estimate_cost, generate, get_status, get_result
|
|
142
|
+
src/tools/account.ts get_balance, get_usage
|
|
143
|
+
src/index.ts the server: stdio transport, tool registration, annotations
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
server.json MCP registry manifest (name must match package.json "mcpName")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Licence
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place that talks to Vidofy over HTTP.
|
|
3
|
+
*
|
|
4
|
+
* Everything else in this package builds a request description and hands it
|
|
5
|
+
* here, so door selection, authentication, retries, timeouts and error shape
|
|
6
|
+
* are decided once instead of in nine tools.
|
|
7
|
+
*
|
|
8
|
+
* WHAT THIS FILE IS RESPONSIBLE FOR
|
|
9
|
+
* · picking /app/v1 or /api/v1 from the configured mode
|
|
10
|
+
* · attaching the credential and the User-Agent on every call
|
|
11
|
+
* · multipart bodies — one form field per m_* key, exactly as the studio posts
|
|
12
|
+
* · carrying an Idempotency-Key when the caller supplies one
|
|
13
|
+
* · retrying the things that are worth retrying, and nothing else
|
|
14
|
+
* · turning both transport failures and API error envelopes into one error type
|
|
15
|
+
*/
|
|
16
|
+
import { type Config } from './config.js';
|
|
17
|
+
/**
|
|
18
|
+
* Anything that went wrong, from either side of the wire.
|
|
19
|
+
*
|
|
20
|
+
* `code` is the machine-readable one — the API's own `error` field where there
|
|
21
|
+
* was a response, or a transport pseudo-code where there was not. `message` is
|
|
22
|
+
* what a person (or a model) should read.
|
|
23
|
+
*/
|
|
24
|
+
export declare class VidofyError extends Error {
|
|
25
|
+
readonly code: string;
|
|
26
|
+
readonly httpStatus: number | null;
|
|
27
|
+
/** Extra fields the API returned, e.g. `allowed` on INVALID_MODE. */
|
|
28
|
+
readonly details: Record<string, unknown>;
|
|
29
|
+
constructor(code: string, message: string, httpStatus?: number | null,
|
|
30
|
+
/** Extra fields the API returned, e.g. `allowed` on INVALID_MODE. */
|
|
31
|
+
details?: Record<string, unknown>);
|
|
32
|
+
}
|
|
33
|
+
/** Exported for get_status, which holds its call open rather than letting the
|
|
34
|
+
* agent poll in a tight loop — one definition, not a second one that drifts. */
|
|
35
|
+
export declare const sleep: (ms: number) => Promise<void>;
|
|
36
|
+
/** A local file to attach to a multipart request. */
|
|
37
|
+
export interface FileField {
|
|
38
|
+
/** The form field name — m_image, m_first_frame, m_multi_image_file_0, … */
|
|
39
|
+
field: string;
|
|
40
|
+
/** Absolute or relative path on the machine running this server. */
|
|
41
|
+
path: string;
|
|
42
|
+
/**
|
|
43
|
+
* Extensions this slot accepts, with or without the dot. Comes from the
|
|
44
|
+
* model's own m_upload_*_settings via get_model, already defaulted the way
|
|
45
|
+
* the server defaults them. REQUIRED in practice: an empty or missing list
|
|
46
|
+
* is refused, not waved through — see the check in buildBody.
|
|
47
|
+
*/
|
|
48
|
+
accept?: readonly string[];
|
|
49
|
+
/** The slot's max_size in megabytes, from the same place. */
|
|
50
|
+
maxSizeMb?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The slot's max_duration in seconds, from the same place. 0/absent = no
|
|
53
|
+
* cap. Enforced only when the file's own header states its length exactly
|
|
54
|
+
* — see media-duration.ts for which formats those are, and why a guess is
|
|
55
|
+
* not allowed to refuse an upload.
|
|
56
|
+
*/
|
|
57
|
+
maxDurationSec?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface RequestOptions {
|
|
60
|
+
method: 'GET' | 'POST';
|
|
61
|
+
/** Path AFTER the version prefix, e.g. 'info/modes'. No leading slash. */
|
|
62
|
+
path: string;
|
|
63
|
+
query?: Record<string, string | number | undefined>;
|
|
64
|
+
/**
|
|
65
|
+
* Form fields. Sent as multipart when `files` is present, otherwise as
|
|
66
|
+
* application/x-www-form-urlencoded — the two shapes the submit handler
|
|
67
|
+
* reads. JSON is deliberately NOT used on the account door: measured in
|
|
68
|
+
* early testing, a JSON body from a session caller is not decoded at all by
|
|
69
|
+
* the submit handler, so it would silently lose every field.
|
|
70
|
+
*/
|
|
71
|
+
form?: Record<string, string | number | boolean | undefined>;
|
|
72
|
+
files?: FileField[];
|
|
73
|
+
/**
|
|
74
|
+
* Same value across every retry of one logical call — that is what makes
|
|
75
|
+
* retrying a POST safe rather than a way to pay twice. Generated once by
|
|
76
|
+
* the caller, never here.
|
|
77
|
+
*/
|
|
78
|
+
idempotencyKey?: string;
|
|
79
|
+
timeoutMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Return the body instead of throwing when a 200 carries `success: false`.
|
|
82
|
+
*
|
|
83
|
+
* For the two endpoints that report on a JOB: a generation that failed is
|
|
84
|
+
* a successful call whose answer happens to be bad news, and the caller's
|
|
85
|
+
* mapper is what turns it into `{done:true, status:'failed'}`. Everywhere
|
|
86
|
+
* else an envelope error is still an error.
|
|
87
|
+
*/
|
|
88
|
+
allowEnvelopeError?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Cap the attempts for this call. Defaults to MAX_ATTEMPTS.
|
|
91
|
+
*
|
|
92
|
+
* `generate` sets 1. Retrying it is safe on the SERVER — the
|
|
93
|
+
* Idempotency-Key dedupes — but the whole budget still has to finish
|
|
94
|
+
* inside the client's 60s tool-call timeout, or the client gives up while
|
|
95
|
+
* the charge lands and the agent retries with a new key. One attempt, well
|
|
96
|
+
* inside the window, beats four that outlive the caller.
|
|
97
|
+
*/
|
|
98
|
+
maxAttempts?: number;
|
|
99
|
+
}
|
|
100
|
+
/** A fresh idempotency key. Callers make ONE per logical operation. */
|
|
101
|
+
export declare function newIdempotencyKey(): string;
|
|
102
|
+
/**
|
|
103
|
+
* An idempotency key derived from WHAT IS BEING ASKED FOR, inside a window.
|
|
104
|
+
*
|
|
105
|
+
* The server dedupes on (user, origin, m_client_request_id) and this package
|
|
106
|
+
* sent a randomUUID() per invocation, so that tuple could never repeat and
|
|
107
|
+
* the dedupe could never fire: two identical generate calls were two
|
|
108
|
+
* generations and two charges. The mechanism existed on both sides and was
|
|
109
|
+
* joined by a value guaranteed to differ.
|
|
110
|
+
*
|
|
111
|
+
* NO TIME COMPONENT IN THE KEY, deliberately — that was tried and is wrong.
|
|
112
|
+
* The expiry a derived key needs (the same prompt next week must generate
|
|
113
|
+
* again, not replay) is a property of the LOOKUP, and it now lives there:
|
|
114
|
+
* The server's idempotent-lookup takes a max age and the submit
|
|
115
|
+
* handler passes 60 seconds for an MCP caller. Hashing a time bucket into the
|
|
116
|
+
* key instead makes a tumbling grid, so two calls seconds apart miss each
|
|
117
|
+
* other whenever a boundary falls between them — measured on the first live
|
|
118
|
+
* pair tried: 11 seconds apart, two jobs, two charges. A bound on the query
|
|
119
|
+
* is a real "within 60s of each other" at any alignment.
|
|
120
|
+
*
|
|
121
|
+
* Files are identified by path AND by size+mtime, so editing an image and
|
|
122
|
+
* re-running inside the window is a different request rather than a cached
|
|
123
|
+
* answer. A path that cannot be stat'd contributes its raw path and the real
|
|
124
|
+
* complaint arrives later, from the code whose job that is.
|
|
125
|
+
*/
|
|
126
|
+
export declare function contentIdempotencyKey(parts: Record<string, unknown>, filePaths?: readonly string[]): string;
|
|
127
|
+
/**
|
|
128
|
+
* Perform one request, with retries.
|
|
129
|
+
*
|
|
130
|
+
* Returns the parsed JSON body on success. Throws VidofyError on anything else,
|
|
131
|
+
* including a 200 whose envelope says `success: false`.
|
|
132
|
+
*/
|
|
133
|
+
export declare function request<T = unknown>(cfg: Config, opts: RequestOptions): Promise<T>;
|