vugola-mcp 1.0.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 +108 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +57 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +51 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/rate-limit.d.ts +14 -0
- package/dist/rate-limit.js +22 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/sanitize.d.ts +4 -0
- package/dist/sanitize.js +18 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/tools/clip-video.d.ts +44 -0
- package/dist/tools/clip-video.js +75 -0
- package/dist/tools/clip-video.js.map +1 -0
- package/dist/tools/get-clip-status.d.ts +32 -0
- package/dist/tools/get-clip-status.js +95 -0
- package/dist/tools/get-clip-status.js.map +1 -0
- package/dist/tools/get-usage.d.ts +20 -0
- package/dist/tools/get-usage.js +51 -0
- package/dist/tools/get-usage.js.map +1 -0
- package/dist/tools/schedule-post.d.ts +278 -0
- package/dist/tools/schedule-post.js +122 -0
- package/dist/tools/schedule-post.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vadim Strizheus
|
|
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,108 @@
|
|
|
1
|
+
# vugola-mcp
|
|
2
|
+
|
|
3
|
+
Official MCP server for [Vugola](https://www.vugolaai.com) — the AI video clipping tool.
|
|
4
|
+
|
|
5
|
+
Let Claude (or any MCP-capable agent) clip videos, check your credits, and schedule posts on your Vugola account.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Requires
|
|
10
|
+
|
|
11
|
+
- Node.js 20 or higher.
|
|
12
|
+
- A paid Vugola account. Generate a key at [vugolaai.com/dashboard/api-key](https://www.vugolaai.com/dashboard/api-key).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Drop one block into your agent's MCP config. Every block below pins to version `1.0.0` on purpose — always pin a specific version, never install "latest."
|
|
19
|
+
|
|
20
|
+
### Claude Desktop
|
|
21
|
+
|
|
22
|
+
Open `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) and add:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"vugola": {
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": ["-y", "vugola-mcp@1.0.0"],
|
|
30
|
+
"env": { "VUGOLA_API_KEY": "vug_sk_your_key_here" }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Restart Claude Desktop.
|
|
37
|
+
|
|
38
|
+
### Claude Code
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
claude mcp add vugola -- npx -y vugola-mcp@1.0.0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then export your key in your shell or `.env`:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export VUGOLA_API_KEY=vug_sk_your_key_here
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Cursor / Cline
|
|
51
|
+
|
|
52
|
+
Same JSON shape as Claude Desktop. Paste the block above into each client's MCP config file.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
### `clip_video`
|
|
59
|
+
|
|
60
|
+
Start a clipping job. Takes 20–40 minutes. Vugola emails you when it's done.
|
|
61
|
+
|
|
62
|
+
Inputs: `video_url`, `aspect_ratio` (`9:16` | `16:9` | `1:1`), `caption_style` (`none` | `highlighted` | `scale` | `minimalist` | `box`).
|
|
63
|
+
|
|
64
|
+
### `get_clip_status`
|
|
65
|
+
|
|
66
|
+
Check a running job. Agent calls this when you ask "is that clip done?"
|
|
67
|
+
|
|
68
|
+
Inputs: `job_id`.
|
|
69
|
+
|
|
70
|
+
### `get_usage`
|
|
71
|
+
|
|
72
|
+
Show credits remaining, monthly usage, and plan.
|
|
73
|
+
|
|
74
|
+
No inputs.
|
|
75
|
+
|
|
76
|
+
### `schedule_post`
|
|
77
|
+
|
|
78
|
+
Schedule one or more social posts. Supports x, instagram, tiktok, youtube, facebook, linkedin, threads, bluesky. Instagram carousels supported with 2–10 items.
|
|
79
|
+
|
|
80
|
+
Inputs: `posts[]` (max 25 per call). See the tool description for full fields.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Security
|
|
85
|
+
|
|
86
|
+
- **Never commit your `VUGOLA_API_KEY`.** Your agent's config file contains a secret. Add it to `.gitignore`, or use a secrets manager (1Password CLI, direnv) instead of inline env vars.
|
|
87
|
+
- **Always pin the version** (`vugola-mcp@1.0.0`) in your install. Don't install "latest."
|
|
88
|
+
- If you accidentally leak your key, regenerate it at [vugolaai.com/dashboard/api-key](https://www.vugolaai.com/dashboard/api-key) immediately.
|
|
89
|
+
- Download URLs returned by `get_clip_status` require the same `Authorization: Bearer <key>` header and expire in ~1 hour. Save clips promptly or re-fetch the status before downloading.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Pricing
|
|
94
|
+
|
|
95
|
+
This MCP requires a paid Vugola account (Creator plan or above). See [pricing](https://www.vugolaai.com/pricing).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Links
|
|
100
|
+
|
|
101
|
+
- Vugola: https://www.vugolaai.com
|
|
102
|
+
- Dashboard: https://www.vugolaai.com/dashboard/api-key
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT © 2026 Vadim Strizheus
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ClientConfig {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
fetch?: typeof fetch;
|
|
5
|
+
}
|
|
6
|
+
export interface RequestOptions {
|
|
7
|
+
method: "GET" | "POST" | "DELETE";
|
|
8
|
+
body?: unknown;
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
retryIdempotent: boolean;
|
|
11
|
+
retryDelayMs?: number;
|
|
12
|
+
}
|
|
13
|
+
export type RequestResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
httpStatus: number;
|
|
16
|
+
body: unknown;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
httpStatus: number;
|
|
20
|
+
body: unknown;
|
|
21
|
+
};
|
|
22
|
+
export interface Client {
|
|
23
|
+
request(path: string, opts: RequestOptions): Promise<RequestResult>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createClient(cfg: ClientConfig): Client;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function createClient(cfg) {
|
|
2
|
+
const fetchFn = cfg.fetch ?? fetch;
|
|
3
|
+
async function doRequest(path, opts) {
|
|
4
|
+
if (!cfg.apiKey) {
|
|
5
|
+
throw new Error("VUGOLA_API_KEY not set");
|
|
6
|
+
}
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
9
|
+
try {
|
|
10
|
+
const headers = {
|
|
11
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
12
|
+
};
|
|
13
|
+
const init = {
|
|
14
|
+
method: opts.method,
|
|
15
|
+
headers,
|
|
16
|
+
signal: controller.signal,
|
|
17
|
+
};
|
|
18
|
+
if (opts.body !== undefined) {
|
|
19
|
+
headers["content-type"] = "application/json";
|
|
20
|
+
init.body = JSON.stringify(opts.body);
|
|
21
|
+
}
|
|
22
|
+
const res = await fetchFn(cfg.baseUrl + path, init);
|
|
23
|
+
const text = await res.text();
|
|
24
|
+
let body = {};
|
|
25
|
+
if (text.length > 0) {
|
|
26
|
+
try {
|
|
27
|
+
body = JSON.parse(text);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
body = { raw: text.slice(0, 500) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
ok: res.ok,
|
|
35
|
+
httpStatus: res.status,
|
|
36
|
+
body,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
async request(path, opts) {
|
|
45
|
+
const first = await doRequest(path, opts);
|
|
46
|
+
if (first.ok)
|
|
47
|
+
return first;
|
|
48
|
+
const isRetryable5xx = first.httpStatus >= 500 && first.httpStatus < 600;
|
|
49
|
+
if (opts.retryIdempotent && opts.method === "GET" && isRetryable5xx) {
|
|
50
|
+
await new Promise((r) => setTimeout(r, opts.retryDelayMs ?? 3000));
|
|
51
|
+
return doRequest(path, opts);
|
|
52
|
+
}
|
|
53
|
+
return first;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC;IAEnC,KAAK,UAAU,SAAS,CACtB,IAAY,EACZ,IAAoB;QAEpB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,CAAC;YACH,MAAM,OAAO,GAA2B;gBACtC,aAAa,EAAE,UAAU,GAAG,CAAC,MAAM,EAAE;aACtC,CAAC;YACF,MAAM,IAAI,GAAgB;gBACxB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO;gBACP,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC;YACF,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC5B,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;gBAC7C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,IAAI,GAAY,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;gBACrC,CAAC;YACH,CAAC;YACD,OAAO;gBACL,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,UAAU,EAAE,GAAG,CAAC,MAAM;gBACtB,IAAI;aACL,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI;YACtB,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC1C,IAAI,KAAK,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC;YAC3B,MAAM,cAAc,GAAG,KAAK,CAAC,UAAU,IAAI,GAAG,IAAI,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC;YACzE,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,cAAc,EAAE,CAAC;gBACpE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC;gBACnE,OAAO,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC/B,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const DASHBOARD = "https://www.vugolaai.com/dashboard/api-key";
|
|
2
|
+
const PRICING = "https://www.vugolaai.com/pricing";
|
|
3
|
+
const KNOWN_400_CODES = {
|
|
4
|
+
video_too_short: "Videos must be at least 2 minutes long.",
|
|
5
|
+
video_too_long: "Videos must be 3 hours or shorter.",
|
|
6
|
+
invalid_url: "That video URL isn't supported.",
|
|
7
|
+
missing_fields: "Vugola rejected the request. A required field was missing.",
|
|
8
|
+
invalid_aspect_ratio: "aspect_ratio must be one of: 9:16, 16:9, 1:1.",
|
|
9
|
+
invalid_caption_style: "caption_style must be one of: none, highlighted, scale, minimalist, box.",
|
|
10
|
+
};
|
|
11
|
+
export function translateHttpError(status, body) {
|
|
12
|
+
const errObj = (body && typeof body === "object" ? body : {});
|
|
13
|
+
const code = typeof errObj.error === "string" ? errObj.error : "";
|
|
14
|
+
switch (status) {
|
|
15
|
+
case 401:
|
|
16
|
+
return `Your API key was rejected. Check or regenerate it at ${DASHBOARD}`;
|
|
17
|
+
case 402:
|
|
18
|
+
return `Out of credits. They may have been used by another session. Upgrade or top up at ${PRICING}`;
|
|
19
|
+
case 403:
|
|
20
|
+
return `Your Vugola plan doesn't include this feature. See ${PRICING} for plans.`;
|
|
21
|
+
case 404:
|
|
22
|
+
return "Job or post not found. The ID may be wrong or the job may have been deleted.";
|
|
23
|
+
case 408:
|
|
24
|
+
return "Vugola took too long to respond. Try again shortly.";
|
|
25
|
+
case 429:
|
|
26
|
+
return "Vugola rate limit hit. Try again in about a minute.";
|
|
27
|
+
case 400: {
|
|
28
|
+
const canned = KNOWN_400_CODES[code];
|
|
29
|
+
if (canned)
|
|
30
|
+
return canned;
|
|
31
|
+
return "Vugola rejected the request. Check the input and try again.";
|
|
32
|
+
}
|
|
33
|
+
case 500:
|
|
34
|
+
case 502:
|
|
35
|
+
case 503:
|
|
36
|
+
case 504:
|
|
37
|
+
return "Vugola is having a temporary problem. Try again in a few minutes.";
|
|
38
|
+
default:
|
|
39
|
+
return `Vugola returned an unexpected response (status ${status}). Try again shortly.`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function translateNetworkError(err) {
|
|
43
|
+
if (err &&
|
|
44
|
+
typeof err === "object" &&
|
|
45
|
+
"name" in err &&
|
|
46
|
+
err.name === "AbortError") {
|
|
47
|
+
return "Vugola took too long to respond. Try again shortly.";
|
|
48
|
+
}
|
|
49
|
+
return "Couldn't reach Vugola. Check your internet connection.";
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,4CAA4C,CAAC;AAC/D,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAEnD,MAAM,eAAe,GAA2B;IAC9C,eAAe,EAAE,yCAAyC;IAC1D,cAAc,EAAE,oCAAoC;IACpD,WAAW,EAAE,iCAAiC;IAC9C,cAAc,EAAE,4DAA4D;IAC5E,oBAAoB,EAAE,+CAA+C;IACrE,qBAAqB,EACnB,0EAA0E;CAC7E,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,IAAa;IAC9D,MAAM,MAAM,GAAG,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAE3D,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAElE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,GAAG;YACN,OAAO,wDAAwD,SAAS,EAAE,CAAC;QAC7E,KAAK,GAAG;YACN,OAAO,oFAAoF,OAAO,EAAE,CAAC;QACvG,KAAK,GAAG;YACN,OAAO,sDAAsD,OAAO,aAAa,CAAC;QACpF,KAAK,GAAG;YACN,OAAO,8EAA8E,CAAC;QACxF,KAAK,GAAG;YACN,OAAO,qDAAqD,CAAC;QAC/D,KAAK,GAAG;YACN,OAAO,qDAAqD,CAAC;QAC/D,KAAK,GAAG,CAAC,CAAC,CAAC;YACT,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1B,OAAO,6DAA6D,CAAC;QACvE,CAAC;QACD,KAAK,GAAG,CAAC;QACT,KAAK,GAAG,CAAC;QACT,KAAK,GAAG,CAAC;QACT,KAAK,GAAG;YACN,OAAO,mEAAmE,CAAC;QAC7E;YACE,OAAO,kDAAkD,MAAM,uBAAuB,CAAC;IAC3F,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,IACE,GAAG;QACH,OAAO,GAAG,KAAK,QAAQ;QACvB,MAAM,IAAI,GAAG;QACZ,GAAyB,CAAC,IAAI,KAAK,YAAY,EAChD,CAAC;QACD,OAAO,qDAAqD,CAAC;IAC/D,CAAC;IACD,OAAO,wDAAwD,CAAC;AAClE,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
6
|
+
import { createClient } from "./client.js";
|
|
7
|
+
import { createRateLimiter } from "./rate-limit.js";
|
|
8
|
+
import { createClipVideoTool } from "./tools/clip-video.js";
|
|
9
|
+
import { createGetClipStatusTool } from "./tools/get-clip-status.js";
|
|
10
|
+
import { createGetUsageTool } from "./tools/get-usage.js";
|
|
11
|
+
import { createSchedulePostTool } from "./tools/schedule-post.js";
|
|
12
|
+
const MISSING_KEY_MSG = "Set VUGOLA_API_KEY in your MCP config. Get one at https://www.vugolaai.com/dashboard/api-key";
|
|
13
|
+
function lastFour(s) {
|
|
14
|
+
return s.slice(-4);
|
|
15
|
+
}
|
|
16
|
+
async function main() {
|
|
17
|
+
const apiKey = process.env["VUGOLA_API_KEY"] ?? "";
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
process.stderr.write("[vugola-mcp] VUGOLA_API_KEY not set — tools will return a setup message.\n");
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
process.stderr.write(`[vugola-mcp] ready — key loaded (last 4: ...${lastFour(apiKey)})\n`);
|
|
23
|
+
}
|
|
24
|
+
const client = createClient({
|
|
25
|
+
baseUrl: "https://api.vugolaai.com",
|
|
26
|
+
apiKey,
|
|
27
|
+
});
|
|
28
|
+
const rateLimiter = createRateLimiter({
|
|
29
|
+
clip_video: { max: 5, windowMs: 60_000 },
|
|
30
|
+
schedule_post: { max: 10, windowMs: 60_000 },
|
|
31
|
+
get_clip_status: { max: 30, windowMs: 60_000 },
|
|
32
|
+
get_usage: { max: 30, windowMs: 60_000 },
|
|
33
|
+
});
|
|
34
|
+
const tools = [
|
|
35
|
+
createClipVideoTool({ client, rateLimiter }),
|
|
36
|
+
createGetClipStatusTool({ client, rateLimiter }),
|
|
37
|
+
createGetUsageTool({ client, rateLimiter }),
|
|
38
|
+
createSchedulePostTool({ client, rateLimiter }),
|
|
39
|
+
];
|
|
40
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
41
|
+
const server = new Server({ name: "vugola-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
42
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
43
|
+
tools: tools.map((t) => ({
|
|
44
|
+
name: t.name,
|
|
45
|
+
description: t.description,
|
|
46
|
+
inputSchema: zodToJsonSchema(t.inputSchema),
|
|
47
|
+
})),
|
|
48
|
+
}));
|
|
49
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
50
|
+
if (!apiKey) {
|
|
51
|
+
return { content: [{ type: "text", text: MISSING_KEY_MSG }] };
|
|
52
|
+
}
|
|
53
|
+
const tool = byName.get(req.params.name);
|
|
54
|
+
if (!tool) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `Unknown tool: ${req.params.name}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return await tool.handler((req.params.arguments ?? {}));
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const msg = err instanceof Error ? err.message : "Invalid arguments for this tool.";
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: `Invalid input: ${msg}`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const transport = new StdioServerTransport();
|
|
80
|
+
await server.connect(transport);
|
|
81
|
+
}
|
|
82
|
+
main().catch((err) => {
|
|
83
|
+
process.stderr.write(`[vugola-mcp] fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BucketConfig {
|
|
2
|
+
max: number;
|
|
3
|
+
windowMs: number;
|
|
4
|
+
}
|
|
5
|
+
export type RateLimitResult = {
|
|
6
|
+
allowed: true;
|
|
7
|
+
} | {
|
|
8
|
+
allowed: false;
|
|
9
|
+
retryAfterMs: number;
|
|
10
|
+
};
|
|
11
|
+
export interface RateLimiter {
|
|
12
|
+
check(tool: string): RateLimitResult;
|
|
13
|
+
}
|
|
14
|
+
export declare function createRateLimiter(config: Record<string, BucketConfig>): RateLimiter;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function createRateLimiter(config) {
|
|
2
|
+
const history = {};
|
|
3
|
+
return {
|
|
4
|
+
check(tool) {
|
|
5
|
+
const cfg = config[tool];
|
|
6
|
+
if (!cfg)
|
|
7
|
+
return { allowed: true };
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const cutoff = now - cfg.windowMs;
|
|
10
|
+
const calls = (history[tool] ??= []);
|
|
11
|
+
while (calls.length > 0 && calls[0] < cutoff)
|
|
12
|
+
calls.shift();
|
|
13
|
+
if (calls.length >= cfg.max) {
|
|
14
|
+
const oldest = calls[0];
|
|
15
|
+
return { allowed: false, retryAfterMs: oldest + cfg.windowMs - now };
|
|
16
|
+
}
|
|
17
|
+
calls.push(now);
|
|
18
|
+
return { allowed: true };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,iBAAiB,CAC/B,MAAoC;IAEpC,MAAM,OAAO,GAA6B,EAAE,CAAC;IAE7C,OAAO;QACL,KAAK,CAAC,IAAY;YAChB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,CAAC,GAAG;gBAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC;YAClC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YACrC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,GAAG,MAAM;gBAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7D,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;gBACzB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YACvE,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const INJECTION_PATTERN = /^(ignore|forget|system:|assistant:|user:|new instructions?)/im;
|
|
2
|
+
export function sanitize(input, opts) {
|
|
3
|
+
if (input === null || input === undefined)
|
|
4
|
+
return "";
|
|
5
|
+
const str = typeof input === "string" ? input : String(input);
|
|
6
|
+
if (INJECTION_PATTERN.test(str)) {
|
|
7
|
+
return "<content-suppressed: possible injection>";
|
|
8
|
+
}
|
|
9
|
+
const escaped = str
|
|
10
|
+
.replace(/&/g, "&")
|
|
11
|
+
.replace(/</g, "<")
|
|
12
|
+
.replace(/>/g, ">");
|
|
13
|
+
if (escaped.length > opts.maxLength) {
|
|
14
|
+
return escaped.slice(0, opts.maxLength) + "…";
|
|
15
|
+
}
|
|
16
|
+
return escaped;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA,MAAM,iBAAiB,GAAG,+DAA+D,CAAC;AAM1F,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,IAAqB;IAC5D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE9D,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,0CAA0C,CAAC;IACpD,CAAC;IAED,MAAM,OAAO,GAAG,GAAG;SAChB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEzB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QACpC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;IAChD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Client } from "../client.js";
|
|
3
|
+
import type { RateLimiter } from "../rate-limit.js";
|
|
4
|
+
export interface ToolDeps {
|
|
5
|
+
client: Client;
|
|
6
|
+
rateLimiter: RateLimiter;
|
|
7
|
+
}
|
|
8
|
+
declare const InputSchema: z.ZodObject<{
|
|
9
|
+
video_url: z.ZodString;
|
|
10
|
+
aspect_ratio: z.ZodEnum<["9:16", "16:9", "1:1"]>;
|
|
11
|
+
caption_style: z.ZodEnum<["none", "highlighted", "scale", "minimalist", "box"]>;
|
|
12
|
+
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
video_url: string;
|
|
14
|
+
aspect_ratio: "9:16" | "16:9" | "1:1";
|
|
15
|
+
caption_style: "none" | "highlighted" | "scale" | "minimalist" | "box";
|
|
16
|
+
}, {
|
|
17
|
+
video_url: string;
|
|
18
|
+
aspect_ratio: "9:16" | "16:9" | "1:1";
|
|
19
|
+
caption_style: "none" | "highlighted" | "scale" | "minimalist" | "box";
|
|
20
|
+
}>;
|
|
21
|
+
export declare function createClipVideoTool(deps: ToolDeps): {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
inputSchema: z.ZodObject<{
|
|
25
|
+
video_url: z.ZodString;
|
|
26
|
+
aspect_ratio: z.ZodEnum<["9:16", "16:9", "1:1"]>;
|
|
27
|
+
caption_style: z.ZodEnum<["none", "highlighted", "scale", "minimalist", "box"]>;
|
|
28
|
+
}, "strict", z.ZodTypeAny, {
|
|
29
|
+
video_url: string;
|
|
30
|
+
aspect_ratio: "9:16" | "16:9" | "1:1";
|
|
31
|
+
caption_style: "none" | "highlighted" | "scale" | "minimalist" | "box";
|
|
32
|
+
}, {
|
|
33
|
+
video_url: string;
|
|
34
|
+
aspect_ratio: "9:16" | "16:9" | "1:1";
|
|
35
|
+
caption_style: "none" | "highlighted" | "scale" | "minimalist" | "box";
|
|
36
|
+
}>;
|
|
37
|
+
handler(input: z.infer<typeof InputSchema>): Promise<{
|
|
38
|
+
content: {
|
|
39
|
+
type: "text";
|
|
40
|
+
text: string;
|
|
41
|
+
}[];
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sanitize } from "../sanitize.js";
|
|
3
|
+
import { translateHttpError, translateNetworkError } from "../errors.js";
|
|
4
|
+
const InputSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
video_url: z.string().min(1).max(2048),
|
|
7
|
+
aspect_ratio: z.enum(["9:16", "16:9", "1:1"]),
|
|
8
|
+
caption_style: z.enum([
|
|
9
|
+
"none",
|
|
10
|
+
"highlighted",
|
|
11
|
+
"scale",
|
|
12
|
+
"minimalist",
|
|
13
|
+
"box",
|
|
14
|
+
]),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
const RATE_LIMIT_MSG = "You're calling Vugola too quickly. Wait a moment and try again.";
|
|
18
|
+
export function createClipVideoTool(deps) {
|
|
19
|
+
return {
|
|
20
|
+
name: "clip_video",
|
|
21
|
+
description: "Start a video-clipping job. Ask the user for aspect_ratio and caption_style if they're not given. If the user says 'just pick,' default to aspect_ratio '9:16' and caption_style 'minimalist'. Videos must be 2–180 minutes long. Jobs take 20–40 minutes; Vugola will email the user when done, and the agent can check status via get_clip_status.",
|
|
22
|
+
inputSchema: InputSchema,
|
|
23
|
+
async handler(input) {
|
|
24
|
+
InputSchema.parse(input);
|
|
25
|
+
const rl = deps.rateLimiter.check("clip_video");
|
|
26
|
+
if (!rl.allowed) {
|
|
27
|
+
return { content: [{ type: "text", text: RATE_LIMIT_MSG }] };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const res = await deps.client.request("/clip", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: {
|
|
33
|
+
video_url: input.video_url,
|
|
34
|
+
aspect_ratio: input.aspect_ratio,
|
|
35
|
+
caption_style: input.caption_style,
|
|
36
|
+
},
|
|
37
|
+
timeoutMs: 15_000,
|
|
38
|
+
retryIdempotent: false, // explicit: POST /clip never retries
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: translateHttpError(res.httpStatus, res.body),
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const body = res.body;
|
|
51
|
+
const jobId = sanitize(body.job_id, { maxLength: 64 });
|
|
52
|
+
const payload = {
|
|
53
|
+
job_id: jobId,
|
|
54
|
+
status: "processing",
|
|
55
|
+
estimated_minutes: 30,
|
|
56
|
+
notification: {
|
|
57
|
+
channel: "email",
|
|
58
|
+
expected_within_minutes: 40,
|
|
59
|
+
},
|
|
60
|
+
message: "Job started. Vugola will email you when the clips are ready (usually 20-40 minutes). You can also ask me to check the status.",
|
|
61
|
+
next_action_hint: `To check progress, call get_clip_status with job_id "${jobId}".`,
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: translateNetworkError(err) }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=clip-video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip-video.js","sourceRoot":"","sources":["../../src/tools/clip-video.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Client } from "../client.js";
|
|
3
|
+
import type { RateLimiter } from "../rate-limit.js";
|
|
4
|
+
export interface ToolDeps {
|
|
5
|
+
client: Client;
|
|
6
|
+
rateLimiter: RateLimiter;
|
|
7
|
+
}
|
|
8
|
+
declare const InputSchema: z.ZodObject<{
|
|
9
|
+
job_id: z.ZodString;
|
|
10
|
+
}, "strict", z.ZodTypeAny, {
|
|
11
|
+
job_id: string;
|
|
12
|
+
}, {
|
|
13
|
+
job_id: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function createGetClipStatusTool(deps: ToolDeps): {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
inputSchema: z.ZodObject<{
|
|
19
|
+
job_id: z.ZodString;
|
|
20
|
+
}, "strict", z.ZodTypeAny, {
|
|
21
|
+
job_id: string;
|
|
22
|
+
}, {
|
|
23
|
+
job_id: string;
|
|
24
|
+
}>;
|
|
25
|
+
handler(input: z.infer<typeof InputSchema>): Promise<{
|
|
26
|
+
content: {
|
|
27
|
+
type: "text";
|
|
28
|
+
text: string;
|
|
29
|
+
}[];
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sanitize } from "../sanitize.js";
|
|
3
|
+
import { translateHttpError, translateNetworkError } from "../errors.js";
|
|
4
|
+
const InputSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
job_id: z.string().min(1).max(64),
|
|
7
|
+
})
|
|
8
|
+
.strict();
|
|
9
|
+
const RATE_LIMIT_MSG = "You're calling Vugola too quickly. Wait a moment and try again.";
|
|
10
|
+
const DOWNLOAD_NOTE = "Download URLs require the same Authorization: Bearer header as other calls. They're valid for approximately 1 hour after completion — save clips promptly.";
|
|
11
|
+
export function createGetClipStatusTool(deps) {
|
|
12
|
+
return {
|
|
13
|
+
name: "get_clip_status",
|
|
14
|
+
description: "Check whether a clipping job is done. Call this when the user asks about a job they've already started.",
|
|
15
|
+
inputSchema: InputSchema,
|
|
16
|
+
async handler(input) {
|
|
17
|
+
InputSchema.parse(input);
|
|
18
|
+
const rl = deps.rateLimiter.check("get_clip_status");
|
|
19
|
+
if (!rl.allowed) {
|
|
20
|
+
return { content: [{ type: "text", text: RATE_LIMIT_MSG }] };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const res = await deps.client.request(`/clip/${encodeURIComponent(input.job_id)}`, {
|
|
24
|
+
method: "GET",
|
|
25
|
+
timeoutMs: 10_000,
|
|
26
|
+
retryIdempotent: true,
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: translateHttpError(res.httpStatus, res.body),
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const body = res.body;
|
|
39
|
+
const status = body.status;
|
|
40
|
+
if (status === "processing") {
|
|
41
|
+
const out = {
|
|
42
|
+
job_id: sanitize(body.job_id, { maxLength: 64 }),
|
|
43
|
+
status: "processing",
|
|
44
|
+
progress_percent: typeof body.progress === "number" ? body.progress : 0,
|
|
45
|
+
clips_ready: typeof body.clips_ready === "number" ? body.clips_ready : 0,
|
|
46
|
+
clips_total: typeof body.clips_total === "number" ? body.clips_total : 0,
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: JSON.stringify(out) }],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (status === "complete") {
|
|
53
|
+
const rawClips = Array.isArray(body.clips) ? body.clips : [];
|
|
54
|
+
const clips = rawClips.map((c) => {
|
|
55
|
+
const clip = c;
|
|
56
|
+
return {
|
|
57
|
+
clip_id: sanitize(clip.clip_id, { maxLength: 64 }),
|
|
58
|
+
title: sanitize(clip.title, { maxLength: 200 }),
|
|
59
|
+
duration_seconds: typeof clip.duration === "number" ? clip.duration : 0,
|
|
60
|
+
virality_score: typeof clip.virality_score === "number"
|
|
61
|
+
? clip.virality_score
|
|
62
|
+
: 0,
|
|
63
|
+
download_url: sanitize(clip.download_url, { maxLength: 2048 }),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const out = {
|
|
67
|
+
job_id: sanitize(body.job_id, { maxLength: 64 }),
|
|
68
|
+
status: "complete",
|
|
69
|
+
credits_used: typeof body.credits_used === "number" ? body.credits_used : 0,
|
|
70
|
+
clips,
|
|
71
|
+
download_note: DOWNLOAD_NOTE,
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: JSON.stringify(out) }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// failed or unexpected
|
|
78
|
+
const out = {
|
|
79
|
+
job_id: sanitize(body.job_id, { maxLength: 64 }),
|
|
80
|
+
status: "failed",
|
|
81
|
+
error: sanitize(body.error, { maxLength: 500 }),
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: JSON.stringify(out) }],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: "text", text: translateNetworkError(err) }],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=get-clip-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-clip-status.js","sourceRoot":"","sources":["../../src/tools/get-clip-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Client } from "../client.js";
|
|
3
|
+
import type { RateLimiter } from "../rate-limit.js";
|
|
4
|
+
export interface ToolDeps {
|
|
5
|
+
client: Client;
|
|
6
|
+
rateLimiter: RateLimiter;
|
|
7
|
+
}
|
|
8
|
+
declare const InputSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
9
|
+
export declare function createGetUsageTool(deps: ToolDeps): {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
13
|
+
handler(_input: z.infer<typeof InputSchema>): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { translateHttpError, translateNetworkError } from "../errors.js";
|
|
3
|
+
const InputSchema = z.object({}).strict();
|
|
4
|
+
const RATE_LIMIT_MSG = "You're calling Vugola too quickly. Wait a moment and try again.";
|
|
5
|
+
export function createGetUsageTool(deps) {
|
|
6
|
+
return {
|
|
7
|
+
name: "get_usage",
|
|
8
|
+
description: "Return how many credits the user has, their plan, and how many they've used this month.",
|
|
9
|
+
inputSchema: InputSchema,
|
|
10
|
+
async handler(_input) {
|
|
11
|
+
const rl = deps.rateLimiter.check("get_usage");
|
|
12
|
+
if (!rl.allowed) {
|
|
13
|
+
return { content: [{ type: "text", text: RATE_LIMIT_MSG }] };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const res = await deps.client.request("/status", {
|
|
17
|
+
method: "GET",
|
|
18
|
+
timeoutMs: 8_000,
|
|
19
|
+
retryIdempotent: true,
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: translateHttpError(res.httpStatus, res.body),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const body = res.body;
|
|
32
|
+
const payload = {
|
|
33
|
+
credits_remaining: body.credits_remaining ?? 0,
|
|
34
|
+
credits_total: body.credits_total ?? 0,
|
|
35
|
+
credits_used_this_month: body.credits_used_this_month ?? 0,
|
|
36
|
+
plan: body.plan ?? "unknown",
|
|
37
|
+
as_of: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: translateNetworkError(err) }],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=get-usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-usage.js","sourceRoot":"","sources":["../../src/tools/get-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAOzE,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAE1C,MAAM,cAAc,GAClB,iEAAiE,CAAC;AAEpE,MAAM,UAAU,kBAAkB,CAAC,IAAc;IAC/C,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,WAAW,EACT,yFAAyF;QAC3F,WAAW,EAAE,WAAW;QACxB,KAAK,CAAC,OAAO,CAAC,MAAmC;YAC/C,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC/C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;gBAChB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;YACxE,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE;oBAC/C,MAAM,EAAE,KAAK;oBACb,SAAS,EAAE,KAAK;oBAChB,eAAe,EAAE,IAAI;iBACtB,CAAC,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACZ,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAe;gCACrB,IAAI,EAAE,kBAAkB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC;6BACnD;yBACF;qBACF,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAKhB,CAAC;gBACF,MAAM,OAAO,GAAG;oBACd,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,IAAI,CAAC;oBAC9C,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,CAAC;oBACtC,uBAAuB,EAAE,IAAI,CAAC,uBAAuB,IAAI,CAAC;oBAC1D,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS;oBAC5B,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBAChC,CAAC;gBACF,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;iBACpE,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;iBACvE,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Client } from "../client.js";
|
|
3
|
+
import type { RateLimiter } from "../rate-limit.js";
|
|
4
|
+
export interface ToolDeps {
|
|
5
|
+
client: Client;
|
|
6
|
+
rateLimiter: RateLimiter;
|
|
7
|
+
}
|
|
8
|
+
declare const InputSchema: z.ZodObject<{
|
|
9
|
+
posts: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
10
|
+
platform: z.ZodEnum<["x", "instagram", "tiktok", "youtube", "facebook", "linkedin", "threads", "bluesky"]>;
|
|
11
|
+
post_type: z.ZodEnum<["single", "carousel", "text"]>;
|
|
12
|
+
caption: z.ZodString;
|
|
13
|
+
title: z.ZodOptional<z.ZodString>;
|
|
14
|
+
scheduled_at: z.ZodString;
|
|
15
|
+
media_url: z.ZodOptional<z.ZodString>;
|
|
16
|
+
asset_id: z.ZodOptional<z.ZodString>;
|
|
17
|
+
carousel_items: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
18
|
+
media_url: z.ZodOptional<z.ZodString>;
|
|
19
|
+
asset_id: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
media_url?: string | undefined;
|
|
22
|
+
asset_id?: string | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
media_url?: string | undefined;
|
|
25
|
+
asset_id?: string | undefined;
|
|
26
|
+
}>, "many">>;
|
|
27
|
+
platform_settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
28
|
+
}, "strict", z.ZodTypeAny, {
|
|
29
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
30
|
+
post_type: "text" | "single" | "carousel";
|
|
31
|
+
caption: string;
|
|
32
|
+
scheduled_at: string;
|
|
33
|
+
title?: string | undefined;
|
|
34
|
+
media_url?: string | undefined;
|
|
35
|
+
asset_id?: string | undefined;
|
|
36
|
+
carousel_items?: {
|
|
37
|
+
media_url?: string | undefined;
|
|
38
|
+
asset_id?: string | undefined;
|
|
39
|
+
}[] | undefined;
|
|
40
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
43
|
+
post_type: "text" | "single" | "carousel";
|
|
44
|
+
caption: string;
|
|
45
|
+
scheduled_at: string;
|
|
46
|
+
title?: string | undefined;
|
|
47
|
+
media_url?: string | undefined;
|
|
48
|
+
asset_id?: string | undefined;
|
|
49
|
+
carousel_items?: {
|
|
50
|
+
media_url?: string | undefined;
|
|
51
|
+
asset_id?: string | undefined;
|
|
52
|
+
}[] | undefined;
|
|
53
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
54
|
+
}>, {
|
|
55
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
56
|
+
post_type: "text" | "single" | "carousel";
|
|
57
|
+
caption: string;
|
|
58
|
+
scheduled_at: string;
|
|
59
|
+
title?: string | undefined;
|
|
60
|
+
media_url?: string | undefined;
|
|
61
|
+
asset_id?: string | undefined;
|
|
62
|
+
carousel_items?: {
|
|
63
|
+
media_url?: string | undefined;
|
|
64
|
+
asset_id?: string | undefined;
|
|
65
|
+
}[] | undefined;
|
|
66
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
69
|
+
post_type: "text" | "single" | "carousel";
|
|
70
|
+
caption: string;
|
|
71
|
+
scheduled_at: string;
|
|
72
|
+
title?: string | undefined;
|
|
73
|
+
media_url?: string | undefined;
|
|
74
|
+
asset_id?: string | undefined;
|
|
75
|
+
carousel_items?: {
|
|
76
|
+
media_url?: string | undefined;
|
|
77
|
+
asset_id?: string | undefined;
|
|
78
|
+
}[] | undefined;
|
|
79
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
80
|
+
}>, {
|
|
81
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
82
|
+
post_type: "text" | "single" | "carousel";
|
|
83
|
+
caption: string;
|
|
84
|
+
scheduled_at: string;
|
|
85
|
+
title?: string | undefined;
|
|
86
|
+
media_url?: string | undefined;
|
|
87
|
+
asset_id?: string | undefined;
|
|
88
|
+
carousel_items?: {
|
|
89
|
+
media_url?: string | undefined;
|
|
90
|
+
asset_id?: string | undefined;
|
|
91
|
+
}[] | undefined;
|
|
92
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
93
|
+
}, {
|
|
94
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
95
|
+
post_type: "text" | "single" | "carousel";
|
|
96
|
+
caption: string;
|
|
97
|
+
scheduled_at: string;
|
|
98
|
+
title?: string | undefined;
|
|
99
|
+
media_url?: string | undefined;
|
|
100
|
+
asset_id?: string | undefined;
|
|
101
|
+
carousel_items?: {
|
|
102
|
+
media_url?: string | undefined;
|
|
103
|
+
asset_id?: string | undefined;
|
|
104
|
+
}[] | undefined;
|
|
105
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
106
|
+
}>, "many">;
|
|
107
|
+
}, "strict", z.ZodTypeAny, {
|
|
108
|
+
posts: {
|
|
109
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
110
|
+
post_type: "text" | "single" | "carousel";
|
|
111
|
+
caption: string;
|
|
112
|
+
scheduled_at: string;
|
|
113
|
+
title?: string | undefined;
|
|
114
|
+
media_url?: string | undefined;
|
|
115
|
+
asset_id?: string | undefined;
|
|
116
|
+
carousel_items?: {
|
|
117
|
+
media_url?: string | undefined;
|
|
118
|
+
asset_id?: string | undefined;
|
|
119
|
+
}[] | undefined;
|
|
120
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
121
|
+
}[];
|
|
122
|
+
}, {
|
|
123
|
+
posts: {
|
|
124
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
125
|
+
post_type: "text" | "single" | "carousel";
|
|
126
|
+
caption: string;
|
|
127
|
+
scheduled_at: string;
|
|
128
|
+
title?: string | undefined;
|
|
129
|
+
media_url?: string | undefined;
|
|
130
|
+
asset_id?: string | undefined;
|
|
131
|
+
carousel_items?: {
|
|
132
|
+
media_url?: string | undefined;
|
|
133
|
+
asset_id?: string | undefined;
|
|
134
|
+
}[] | undefined;
|
|
135
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
136
|
+
}[];
|
|
137
|
+
}>;
|
|
138
|
+
export declare function createSchedulePostTool(deps: ToolDeps): {
|
|
139
|
+
name: string;
|
|
140
|
+
description: string;
|
|
141
|
+
inputSchema: z.ZodObject<{
|
|
142
|
+
posts: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
143
|
+
platform: z.ZodEnum<["x", "instagram", "tiktok", "youtube", "facebook", "linkedin", "threads", "bluesky"]>;
|
|
144
|
+
post_type: z.ZodEnum<["single", "carousel", "text"]>;
|
|
145
|
+
caption: z.ZodString;
|
|
146
|
+
title: z.ZodOptional<z.ZodString>;
|
|
147
|
+
scheduled_at: z.ZodString;
|
|
148
|
+
media_url: z.ZodOptional<z.ZodString>;
|
|
149
|
+
asset_id: z.ZodOptional<z.ZodString>;
|
|
150
|
+
carousel_items: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
151
|
+
media_url: z.ZodOptional<z.ZodString>;
|
|
152
|
+
asset_id: z.ZodOptional<z.ZodString>;
|
|
153
|
+
}, "strip", z.ZodTypeAny, {
|
|
154
|
+
media_url?: string | undefined;
|
|
155
|
+
asset_id?: string | undefined;
|
|
156
|
+
}, {
|
|
157
|
+
media_url?: string | undefined;
|
|
158
|
+
asset_id?: string | undefined;
|
|
159
|
+
}>, "many">>;
|
|
160
|
+
platform_settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
161
|
+
}, "strict", z.ZodTypeAny, {
|
|
162
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
163
|
+
post_type: "text" | "single" | "carousel";
|
|
164
|
+
caption: string;
|
|
165
|
+
scheduled_at: string;
|
|
166
|
+
title?: string | undefined;
|
|
167
|
+
media_url?: string | undefined;
|
|
168
|
+
asset_id?: string | undefined;
|
|
169
|
+
carousel_items?: {
|
|
170
|
+
media_url?: string | undefined;
|
|
171
|
+
asset_id?: string | undefined;
|
|
172
|
+
}[] | undefined;
|
|
173
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
174
|
+
}, {
|
|
175
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
176
|
+
post_type: "text" | "single" | "carousel";
|
|
177
|
+
caption: string;
|
|
178
|
+
scheduled_at: string;
|
|
179
|
+
title?: string | undefined;
|
|
180
|
+
media_url?: string | undefined;
|
|
181
|
+
asset_id?: string | undefined;
|
|
182
|
+
carousel_items?: {
|
|
183
|
+
media_url?: string | undefined;
|
|
184
|
+
asset_id?: string | undefined;
|
|
185
|
+
}[] | undefined;
|
|
186
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
187
|
+
}>, {
|
|
188
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
189
|
+
post_type: "text" | "single" | "carousel";
|
|
190
|
+
caption: string;
|
|
191
|
+
scheduled_at: string;
|
|
192
|
+
title?: string | undefined;
|
|
193
|
+
media_url?: string | undefined;
|
|
194
|
+
asset_id?: string | undefined;
|
|
195
|
+
carousel_items?: {
|
|
196
|
+
media_url?: string | undefined;
|
|
197
|
+
asset_id?: string | undefined;
|
|
198
|
+
}[] | undefined;
|
|
199
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
200
|
+
}, {
|
|
201
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
202
|
+
post_type: "text" | "single" | "carousel";
|
|
203
|
+
caption: string;
|
|
204
|
+
scheduled_at: string;
|
|
205
|
+
title?: string | undefined;
|
|
206
|
+
media_url?: string | undefined;
|
|
207
|
+
asset_id?: string | undefined;
|
|
208
|
+
carousel_items?: {
|
|
209
|
+
media_url?: string | undefined;
|
|
210
|
+
asset_id?: string | undefined;
|
|
211
|
+
}[] | undefined;
|
|
212
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
213
|
+
}>, {
|
|
214
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
215
|
+
post_type: "text" | "single" | "carousel";
|
|
216
|
+
caption: string;
|
|
217
|
+
scheduled_at: string;
|
|
218
|
+
title?: string | undefined;
|
|
219
|
+
media_url?: string | undefined;
|
|
220
|
+
asset_id?: string | undefined;
|
|
221
|
+
carousel_items?: {
|
|
222
|
+
media_url?: string | undefined;
|
|
223
|
+
asset_id?: string | undefined;
|
|
224
|
+
}[] | undefined;
|
|
225
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
226
|
+
}, {
|
|
227
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
228
|
+
post_type: "text" | "single" | "carousel";
|
|
229
|
+
caption: string;
|
|
230
|
+
scheduled_at: string;
|
|
231
|
+
title?: string | undefined;
|
|
232
|
+
media_url?: string | undefined;
|
|
233
|
+
asset_id?: string | undefined;
|
|
234
|
+
carousel_items?: {
|
|
235
|
+
media_url?: string | undefined;
|
|
236
|
+
asset_id?: string | undefined;
|
|
237
|
+
}[] | undefined;
|
|
238
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
239
|
+
}>, "many">;
|
|
240
|
+
}, "strict", z.ZodTypeAny, {
|
|
241
|
+
posts: {
|
|
242
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
243
|
+
post_type: "text" | "single" | "carousel";
|
|
244
|
+
caption: string;
|
|
245
|
+
scheduled_at: string;
|
|
246
|
+
title?: string | undefined;
|
|
247
|
+
media_url?: string | undefined;
|
|
248
|
+
asset_id?: string | undefined;
|
|
249
|
+
carousel_items?: {
|
|
250
|
+
media_url?: string | undefined;
|
|
251
|
+
asset_id?: string | undefined;
|
|
252
|
+
}[] | undefined;
|
|
253
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
254
|
+
}[];
|
|
255
|
+
}, {
|
|
256
|
+
posts: {
|
|
257
|
+
platform: "x" | "instagram" | "tiktok" | "youtube" | "facebook" | "linkedin" | "threads" | "bluesky";
|
|
258
|
+
post_type: "text" | "single" | "carousel";
|
|
259
|
+
caption: string;
|
|
260
|
+
scheduled_at: string;
|
|
261
|
+
title?: string | undefined;
|
|
262
|
+
media_url?: string | undefined;
|
|
263
|
+
asset_id?: string | undefined;
|
|
264
|
+
carousel_items?: {
|
|
265
|
+
media_url?: string | undefined;
|
|
266
|
+
asset_id?: string | undefined;
|
|
267
|
+
}[] | undefined;
|
|
268
|
+
platform_settings?: Record<string, unknown> | undefined;
|
|
269
|
+
}[];
|
|
270
|
+
}>;
|
|
271
|
+
handler(input: z.infer<typeof InputSchema>): Promise<{
|
|
272
|
+
content: {
|
|
273
|
+
type: "text";
|
|
274
|
+
text: string;
|
|
275
|
+
}[];
|
|
276
|
+
}>;
|
|
277
|
+
};
|
|
278
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sanitize } from "../sanitize.js";
|
|
3
|
+
import { translateHttpError, translateNetworkError } from "../errors.js";
|
|
4
|
+
const PlatformEnum = z.enum([
|
|
5
|
+
"x",
|
|
6
|
+
"instagram",
|
|
7
|
+
"tiktok",
|
|
8
|
+
"youtube",
|
|
9
|
+
"facebook",
|
|
10
|
+
"linkedin",
|
|
11
|
+
"threads",
|
|
12
|
+
"bluesky",
|
|
13
|
+
]);
|
|
14
|
+
const PostSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
platform: PlatformEnum,
|
|
17
|
+
post_type: z.enum(["single", "carousel", "text"]),
|
|
18
|
+
caption: z.string().max(2200),
|
|
19
|
+
title: z.string().max(200).optional(),
|
|
20
|
+
scheduled_at: z.string().min(1).max(64),
|
|
21
|
+
media_url: z.string().max(2048).optional(),
|
|
22
|
+
asset_id: z.string().max(64).optional(),
|
|
23
|
+
carousel_items: z
|
|
24
|
+
.array(z.object({
|
|
25
|
+
media_url: z.string().max(2048).optional(),
|
|
26
|
+
asset_id: z.string().max(64).optional(),
|
|
27
|
+
}))
|
|
28
|
+
.min(2)
|
|
29
|
+
.max(10)
|
|
30
|
+
.optional(),
|
|
31
|
+
platform_settings: z.record(z.unknown()).optional(),
|
|
32
|
+
})
|
|
33
|
+
.strict()
|
|
34
|
+
.refine((p) => p.post_type !== "carousel" || p.platform === "instagram", { message: "carousel post_type is only supported on instagram" })
|
|
35
|
+
.refine((p) => p.post_type !== "carousel" ||
|
|
36
|
+
(p.carousel_items && p.carousel_items.length >= 2), { message: "carousel requires 2-10 carousel_items" });
|
|
37
|
+
const InputSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
posts: z.array(PostSchema).min(1).max(25),
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
const RATE_LIMIT_MSG = "You're calling Vugola too quickly. Wait a moment and try again.";
|
|
43
|
+
export function createSchedulePostTool(deps) {
|
|
44
|
+
return {
|
|
45
|
+
name: "schedule_post",
|
|
46
|
+
description: "Schedule clips or media to post on supported social platforms. Ask the user for platform, post_type, caption, and scheduled_at if missing. Instagram carousels need 2-10 items. YouTube, TikTok, and Instagram single posts require media.",
|
|
47
|
+
inputSchema: InputSchema,
|
|
48
|
+
async handler(input) {
|
|
49
|
+
InputSchema.parse(input);
|
|
50
|
+
const rl = deps.rateLimiter.check("schedule_post");
|
|
51
|
+
if (!rl.allowed) {
|
|
52
|
+
return { content: [{ type: "text", text: RATE_LIMIT_MSG }] };
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const res = await deps.client.request("/schedule", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: { posts: input.posts },
|
|
58
|
+
timeoutMs: 15_000,
|
|
59
|
+
retryIdempotent: false,
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: translateHttpError(res.httpStatus, res.body),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const body = res.body;
|
|
72
|
+
const posts = Array.isArray(body.posts) ? body.posts : [];
|
|
73
|
+
const scheduled = [];
|
|
74
|
+
const failed = [];
|
|
75
|
+
for (const p of posts) {
|
|
76
|
+
const status = p.status;
|
|
77
|
+
const platform = sanitize(p.platform, { maxLength: 32 });
|
|
78
|
+
if (status === "scheduled" || status === "processing") {
|
|
79
|
+
scheduled.push({
|
|
80
|
+
id: sanitize(p.id, { maxLength: 64 }),
|
|
81
|
+
platform,
|
|
82
|
+
scheduled_at: sanitize(p.scheduled_at, { maxLength: 64 }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
failed.push({
|
|
87
|
+
platform,
|
|
88
|
+
error: sanitize(p.failure_reason ?? p.error ?? "unknown error", { maxLength: 500 }),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const total = scheduled.length + failed.length;
|
|
93
|
+
const overall_status = failed.length === 0
|
|
94
|
+
? "all_scheduled"
|
|
95
|
+
: scheduled.length === 0
|
|
96
|
+
? "all_failed"
|
|
97
|
+
: "partial_failure";
|
|
98
|
+
const failedSummary = failed.length === 0
|
|
99
|
+
? ""
|
|
100
|
+
: ` Failed: ${failed
|
|
101
|
+
.map((f) => `${f.platform} (${f.error})`)
|
|
102
|
+
.join(", ")}.`;
|
|
103
|
+
const summary = `Scheduled ${scheduled.length} of ${total} posts.${failedSummary}`;
|
|
104
|
+
const payload = {
|
|
105
|
+
overall_status,
|
|
106
|
+
summary,
|
|
107
|
+
scheduled,
|
|
108
|
+
failed,
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: translateNetworkError(err) }],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=schedule-post.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule-post.js","sourceRoot":"","sources":["../../src/tools/schedule-post.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vugola-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official MCP server for Vugola — AI video clipping for agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vugola-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"smoke": "tsx scripts/test-client.ts",
|
|
21
|
+
"inject-probe": "tsx scripts/inject-probe.ts",
|
|
22
|
+
"prepublishOnly": "npm run build && npm test"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.0.0"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
|
+
"zod": "^3.23.0",
|
|
30
|
+
"zod-to-json-schema": "^3.25.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.11.0",
|
|
34
|
+
"tsx": "^4.7.0",
|
|
35
|
+
"typescript": "^5.4.0",
|
|
36
|
+
"vitest": "^1.4.0"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"mcp",
|
|
40
|
+
"vugola",
|
|
41
|
+
"video",
|
|
42
|
+
"clipping",
|
|
43
|
+
"ai",
|
|
44
|
+
"agents"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/VCoder25/vugola-mcp.git"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT"
|
|
51
|
+
}
|