@clipwright/mcp-server 0.2.0 → 0.3.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/SKILL.md +40 -15
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/server.js +10 -29
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/SKILL.md
CHANGED
|
@@ -38,8 +38,8 @@ Requirements: Node ≥24, pnpm (`packageManager: pnpm@11.13.1` in the root
|
|
|
38
38
|
`package.json` — with corepack available: `corepack enable`).
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
git clone <repo-url>
|
|
42
|
-
cd
|
|
41
|
+
git clone <repo-url> clipwright # or use a checkout you already have
|
|
42
|
+
cd clipwright
|
|
43
43
|
pnpm install
|
|
44
44
|
pnpm build # pnpm -r build — builds every package in topological order
|
|
45
45
|
```
|
|
@@ -95,7 +95,7 @@ at the end of this section; it differs only in `command`/`args`.
|
|
|
95
95
|
"clipwright": {
|
|
96
96
|
"type": "stdio",
|
|
97
97
|
"command": "node",
|
|
98
|
-
"args": ["/absolute/path/to/
|
|
98
|
+
"args": ["/absolute/path/to/clipwright/packages/mcp-server/dist/index.js"],
|
|
99
99
|
"env": {
|
|
100
100
|
"CLIPWRIGHT_API_KEY": "cw_...",
|
|
101
101
|
"CLIPWRIGHT_API_URL": "https://api-production-7e69.up.railway.app",
|
|
@@ -113,7 +113,7 @@ claude mcp add clipwright \
|
|
|
113
113
|
-e CLIPWRIGHT_API_KEY=cw_... \
|
|
114
114
|
-e CLIPWRIGHT_API_URL=https://api-production-7e69.up.railway.app \
|
|
115
115
|
-e CLIPWRIGHT_CLIENT_ID="$(node -e "console.log(crypto.randomUUID().replaceAll('-',''))")" \
|
|
116
|
-
-- node /absolute/path/to/
|
|
116
|
+
-- node /absolute/path/to/clipwright/packages/mcp-server/dist/index.js
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
**Variant C — from npm, without the repository.** Same thing, but the command is
|
|
@@ -346,10 +346,16 @@ returns a `run_id` immediately:
|
|
|
346
346
|
"run_id": "run_...",
|
|
347
347
|
"state": "queued",
|
|
348
348
|
"video_url": null,
|
|
349
|
-
"
|
|
349
|
+
"warnings": [],
|
|
350
|
+
"next_action": "Video is NOT ready. Call get_run with run_id=run_... again in ~5 seconds. Repeat until state is 'succeeded' or 'failed'. Do NOT tell the user the video is done until you have a video_url."
|
|
350
351
|
}
|
|
351
352
|
```
|
|
352
353
|
|
|
354
|
+
The shape is the one `get_run` returns: `make_ugc` calls the same formatter, so
|
|
355
|
+
`warnings[]` is declared HERE, on the very first response, and not one poll later
|
|
356
|
+
— by then the paid run is already going. Read it: this is where a format snap or
|
|
357
|
+
an ignored field is announced.
|
|
358
|
+
|
|
353
359
|
After that you MUST poll `get_run` with that `run_id` every ~5–10 seconds until it
|
|
354
360
|
reaches a terminal state. Do not tell the user the video is ready until you have a
|
|
355
361
|
`video_url`.
|
|
@@ -415,7 +421,8 @@ in `state`, with `status` always `"IN_PROGRESS"`:
|
|
|
415
421
|
|
|
416
422
|
**`warnings[]` in `get_run`:** every branch of the response (`IN_PROGRESS`,
|
|
417
423
|
`SUCCEEDED`, `FAILED`) carries the run's `warnings` — parameters that were not
|
|
418
|
-
honoured are always declared, never dropped silently.
|
|
424
|
+
honoured are always declared, never dropped silently. `make_ugc` shares the
|
|
425
|
+
formatter, so its first response carries them too. Read it on the terminal
|
|
419
426
|
response without fail: for instance, missing captions in the prototype are
|
|
420
427
|
announced exactly there.
|
|
421
428
|
|
|
@@ -529,15 +536,33 @@ the balance is not what blocks the run.
|
|
|
529
536
|
**THE SAME call with the same arguments**. Do not bump `attempt`: this is not a
|
|
530
537
|
failed run but a refused request, and a new `attempt` would mean a second paid
|
|
531
538
|
render (section 5).
|
|
532
|
-
- `retryable: false` (`insufficient_credits`, `debt_outstanding`,
|
|
533
|
-
**stop and tell the human**. A repeat
|
|
534
|
-
the rate limit. For money refusals,
|
|
535
|
-
credits, this needs 8" — without them the
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
`
|
|
539
|
-
|
|
540
|
-
the
|
|
539
|
+
- `retryable: false` (`insufficient_credits`, `debt_outstanding`,
|
|
540
|
+
`account_not_admitted`, other 4xx) — **stop and tell the human**. A repeat
|
|
541
|
+
gives the same answer while eating into the rate limit. For money refusals,
|
|
542
|
+
quote the NUMBERS: "the account has 3 credits, this needs 8" — without them the
|
|
543
|
+
human cannot tell how much to buy.
|
|
544
|
+
|
|
545
|
+
`account_not_admitted` (403) is not a money refusal and carries no numbers: the
|
|
546
|
+
account has no beta access. Buying credits will not lift it — access is granted
|
|
547
|
+
by the operator. Say that, and do not offer to top up.
|
|
548
|
+
|
|
549
|
+
Do NOT promise a position in line or an upcoming batch. The refusal says one
|
|
550
|
+
thing only: this account has no access right now. An account whose access was
|
|
551
|
+
REVOKED gets the same 403, and promising it a turn would be a lie at the worst
|
|
552
|
+
possible moment.
|
|
553
|
+
|
|
554
|
+
The two money codes are cured the same way — buy credits — but they must not be
|
|
555
|
+
conflated, because they describe different situations. `insufficient_credits`
|
|
556
|
+
means the balance is too small for this run. `debt_outstanding` means the account
|
|
557
|
+
owes credits, and it can owe them while still holding a balance, so telling the
|
|
558
|
+
human "you do not have enough credits" would be false. Credits bought while a
|
|
559
|
+
debt stands clear the debt first and only then reach the balance. Clearing the
|
|
560
|
+
debt lifts this particular refusal and nothing else: the run still needs enough
|
|
561
|
+
credits of its own, so a purchase equal to the debt clears it and adds nothing to
|
|
562
|
+
the balance. Quote the debt figure, and say that credits go to the debt first: a
|
|
563
|
+
purchase adds to the balance only the part that exceeds the debt. Whether the
|
|
564
|
+
human has to buy more than the debt depends on what the balance already holds,
|
|
565
|
+
and this refusal does not say — the balance is not what blocks the run.
|
|
541
566
|
|
|
542
567
|
Do not read a source into the refusal. A `429` or a `5xx` can come from the
|
|
543
568
|
Clipwright API itself or from anything between you and it, and the fields above
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { ClipwrightClient } from "@clipwright/sdk";
|
|
4
|
+
import { apiBaseUrlFromEnv } from "@clipwright/core";
|
|
4
5
|
import { createServer } from "./server.js";
|
|
5
6
|
const apiKey = process.env.CLIPWRIGHT_API_KEY;
|
|
6
7
|
if (!apiKey) {
|
|
7
8
|
console.error("CLIPWRIGHT_API_KEY env var is required");
|
|
8
9
|
process.exit(1);
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
let baseUrl;
|
|
12
|
+
try {
|
|
13
|
+
baseUrl = apiBaseUrlFromEnv(process.env.CLIPWRIGHT_API_URL);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const client = new ClipwrightClient({ apiKey, baseUrl });
|
|
11
20
|
const server = createServer(client);
|
|
12
21
|
const transport = new StdioServerTransport();
|
|
13
22
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,IAAI,OAA2B,CAAC;AAChC,IAAI,CAAC;IACH,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAC9D,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AAEzD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAChC,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
3
|
// Схемы приходят из barrel `@clipwright/core` (единый источник правды, правило №2),
|
|
4
4
|
// а НЕ из `/idempotency` — там живёт только ключ идемпотентности, не shape.
|
|
5
5
|
// `offeredUgcInputShape`, а НЕ полный `makeUgcInputShape`: поля с диспозицией
|
|
6
6
|
// `rejected` не должны предлагаться вызывателю, которого мы за их использование
|
|
7
7
|
// накажем четырёхсотым (блокер B4). Множество выводится из реестра диспозиций,
|
|
8
8
|
// поэтому смена решения по полю переносит его между поверхностями одной строкой.
|
|
9
|
-
import { offeredUgcInputShape, agentRetryShape } from "@clipwright/core";
|
|
9
|
+
import { offeredUgcInputShape, agentRetryShape, runId, MAKE_UGC_DESCRIPTION, MAKE_UGC_AGENT_PROTOCOL, } from "@clipwright/core";
|
|
10
10
|
import { formatGetRun, withFailureReport } from "./format.js";
|
|
11
|
+
// Версия — из манифеста: литерал расходился с ним молча, а импорт JSON не проходит `rootDir`.
|
|
12
|
+
const packageVersion = () => createRequire(import.meta.url)("../package.json").version;
|
|
11
13
|
/**
|
|
12
14
|
* Строит MCP-сервер с зарегистрированными тулами, БЕЗ подключения транспорта.
|
|
13
15
|
*
|
|
@@ -18,16 +20,10 @@ import { formatGetRun, withFailureReport } from "./format.js";
|
|
|
18
20
|
* тонкий bootstrap).
|
|
19
21
|
*/
|
|
20
22
|
export function createServer(client) {
|
|
21
|
-
const server = new McpServer({ name: "clipwright", version:
|
|
23
|
+
const server = new McpServer({ name: "clipwright", version: packageVersion() });
|
|
22
24
|
// Полная схема публикуется в tools/list (у конкурента она пустая — антипаттерн).
|
|
23
25
|
server.registerTool("make_ugc", {
|
|
24
|
-
description:
|
|
25
|
-
"the actor in words with person. Fields the renderer does not honor yet carry a NOT HONORED YET " +
|
|
26
|
-
"note in their own description — read it instead of guessing. " +
|
|
27
|
-
"Captions are OPT-IN: ASK the user first. Call quote_ugc before generating and show the cost. " +
|
|
28
|
-
"This does NOT wait for the video: it starts the run and returns a run_id IMMEDIATELY. You MUST " +
|
|
29
|
-
"then poll get_run with that run_id until the state is 'succeeded' (video_url) or 'failed'. " +
|
|
30
|
-
"Pass attempt=2,3,… to deliberately start a NEW run for the same input (retry after a failure).",
|
|
26
|
+
description: `${MAKE_UGC_DESCRIPTION} ${MAKE_UGC_AGENT_PROTOCOL}`,
|
|
31
27
|
// [Правило №2] Плоская схема = предлагаемый вход рендера + агентный ретрай,
|
|
32
28
|
// собранная из shape core. Проза описания держится в согласии со схемой
|
|
33
29
|
// намеренно: LLM-вызыватель читает описание РАНЬШЕ схемы, поэтому обещание
|
|
@@ -41,30 +37,15 @@ export function createServer(client) {
|
|
|
41
37
|
const { attempt, ...input } = args;
|
|
42
38
|
// [A5] Возврат СРАЗУ после старта (state≈"queued"), без bounded-wait и без цикла.
|
|
43
39
|
const run = await client.startUgc(input, { attempt });
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
type: "text",
|
|
48
|
-
text: JSON.stringify({
|
|
49
|
-
status: "IN_PROGRESS",
|
|
50
|
-
run_id: run.run_id,
|
|
51
|
-
state: run.state,
|
|
52
|
-
video_url: null,
|
|
53
|
-
next_action: "Video is NOT ready. Call get_run with run_id=" +
|
|
54
|
-
run.run_id +
|
|
55
|
-
" in ~5 seconds. Repeat until state is 'succeeded' or 'failed'. Do NOT tell the " +
|
|
56
|
-
"user the video is done until you have a video_url.",
|
|
57
|
-
}),
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
isError: false,
|
|
61
|
-
};
|
|
40
|
+
// ТЕМ ЖЕ ФОРМАТТЕРОМ, ЧТО И `get_run`: своя копия формы IN_PROGRESS теряла
|
|
41
|
+
// `run.warnings`, и агент узнавал о снапе уже после платного старта.
|
|
42
|
+
return formatGetRun(run);
|
|
62
43
|
}));
|
|
63
44
|
server.registerTool("get_run", {
|
|
64
45
|
description: "Check the status of a video generation started by make_ugc. Pass the run_id. While the run " +
|
|
65
46
|
"is still working it returns status IN_PROGRESS with a next_action telling you to poll again; " +
|
|
66
47
|
"repeat every ~5 seconds until it reaches a terminal state — SUCCEEDED (with video_url) or FAILED.",
|
|
67
|
-
inputSchema: { run_id:
|
|
48
|
+
inputSchema: { run_id: runId },
|
|
68
49
|
}, async ({ run_id }) => withFailureReport(async () => {
|
|
69
50
|
// Терминальная форма + деградация вынесены в чистый `formatGetRun` (тест
|
|
70
51
|
// без stdio-транспорта). Терминал несёт И `status`, И `state` (US-501).
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipwright/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for Clipwright: generate UGC videos from your coding agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
30
|
"zod": "^4.4.3",
|
|
31
|
-
"@clipwright/core": "0.
|
|
32
|
-
"@clipwright/sdk": "0.
|
|
31
|
+
"@clipwright/core": "0.3.0",
|
|
32
|
+
"@clipwright/sdk": "0.3.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^26.1.1"
|