realtime-avatar-mcp 0.5.0 → 0.5.2
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 +17 -10
- package/dist/server.d.ts +1 -1
- package/dist/server.js +63 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,24 +29,28 @@ Working on it in this repo? `npm run build`, then point `command` at `node` with
|
|
|
29
29
|
| `get_avatar` | Full detail for one |
|
|
30
30
|
| `credit_balance` | Balance, and what in-flight calls have reserved |
|
|
31
31
|
| `list_sessions` | The itemised bill — when each session ran, how long, what it cost |
|
|
32
|
+
| `list_clips` | An avatar's clip library: every clip, its status, the revision and rest pose |
|
|
32
33
|
|
|
33
34
|
With `REALTIME_AVATAR_ALLOW_WRITES=1`:
|
|
34
35
|
|
|
35
36
|
| Tool | | Bills? |
|
|
36
37
|
| --- | --- | --- |
|
|
37
|
-
| `
|
|
38
|
+
| `set_loop` | Re-direct her resting loop from a new description | no |
|
|
39
|
+
| `set_clip_library` | Declare an avatar's full clip set as JSON — the platform renders it | no |
|
|
40
|
+
| `sync_clips` | **Deprecated.** The external-URL tier. Use `set_clip_library` | no |
|
|
38
41
|
| `upload_asset` | Upload a file **from this machine's disk**, get a public URL | no |
|
|
39
42
|
| `create_remote_asset` | Register a file already on the internet — no local copy | no |
|
|
40
|
-
| `
|
|
43
|
+
| `create_avatar_from_image` | Build an avatar from ONE still — the loop is generated | no |
|
|
44
|
+
| `create_avatar_from_video` | **Deprecated.** Closed to new callers (422). Use the image tool | no |
|
|
41
45
|
| `start_call` | Mint a live session to verify an integration | **yes, per second** |
|
|
42
46
|
|
|
43
47
|
## Spending money is opt-in, and twice gated
|
|
44
48
|
|
|
45
|
-
The
|
|
49
|
+
The five tools above are **read-only**, and each carries `readOnlyHint` so a host can gate on
|
|
46
50
|
the annotation rather than on a name it has to recognise. Pointed at a production key, this
|
|
47
51
|
server is no more dangerous than a dashboard you left open.
|
|
48
52
|
|
|
49
|
-
`REALTIME_AVATAR_ALLOW_WRITES=1` adds the
|
|
53
|
+
`REALTIME_AVATAR_ALLOW_WRITES=1` adds the eight write tools. Only `start_call` costs credits,
|
|
50
54
|
and it **refuses a `tic_live_` key outright** — an operator who armed writes against a test
|
|
51
55
|
key and later swapped in a production one should not discover it by being billed. Two
|
|
52
56
|
independent gates, because one is a single mistake away from being none.
|
|
@@ -56,16 +60,19 @@ Two things to understand before arming writes:
|
|
|
56
60
|
- **`upload_asset` reads local disk.** It opens whatever absolute path the agent names, on the
|
|
57
61
|
machine running the server. Relative paths are refused rather than resolved against
|
|
58
62
|
whatever directory the host spawned it in.
|
|
59
|
-
-
|
|
60
|
-
from
|
|
61
|
-
retired — because "queued: 1" alone reads like nothing else
|
|
63
|
+
- **The clip tools retire by omission.** Both `set_clip_library` and `sync_clips` take the
|
|
64
|
+
COMPLETE set you want live; anything missing from it stops being served. Each reports all
|
|
65
|
+
three buckets — kept/queued/retired — because "queued: 1" alone reads like nothing else
|
|
66
|
+
changed. Declare with `set_clip_library`: clips are JSON (`motionPrompt` or an uploaded
|
|
67
|
+
`assetId`) and the platform renders and hosts them. `sync_clips` is the deprecated tier
|
|
68
|
+
that takes URLs on your own storage.
|
|
62
69
|
|
|
63
70
|
## Why an MCP server at all
|
|
64
71
|
|
|
65
72
|
An agent that reads docs still guesses. `ava_…` ids cannot be inferred, and the failures that
|
|
66
|
-
cost the most time here are invisible in a type signature:
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
cost the most time here are invisible in a type signature: a reshaped connection payload is
|
|
74
|
+
rejected by the browser client; a 429 on a call is the queue, not an error; the resting loop
|
|
75
|
+
is the avatar's source rather than a library clip, so no declaration changes it.
|
|
69
76
|
|
|
70
77
|
`list_avatars` marks which avatars are actually usable. The server's `instructions` carry the
|
|
71
78
|
relay rule and the per-second billing model. That is context an agent cannot derive, delivered
|
package/dist/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
5
5
|
* `test/server.test.ts` asserts the two stay equal; the equivalent constant in
|
|
6
6
|
* `realtime-avatar` had that guard and this one did not, which is how it drifted.
|
|
7
7
|
*/
|
|
8
|
-
export declare const MCP_VERSION = "0.5.
|
|
8
|
+
export declare const MCP_VERSION = "0.5.2";
|
|
9
9
|
/**
|
|
10
10
|
* The Realtime Avatar MCP server.
|
|
11
11
|
*
|
package/dist/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import { RealtimeAvatar, isQueued } from "realtime-avatar";
|
|
|
10
10
|
* `test/server.test.ts` asserts the two stay equal; the equivalent constant in
|
|
11
11
|
* `realtime-avatar` had that guard and this one did not, which is how it drifted.
|
|
12
12
|
*/
|
|
13
|
-
export const MCP_VERSION = "0.5.
|
|
13
|
+
export const MCP_VERSION = "0.5.2";
|
|
14
14
|
const MICROS_PER_CREDIT = 1_000_000;
|
|
15
15
|
/** Extension -> asset kind, so a caller does not have to restate what the filename says. */
|
|
16
16
|
const KIND_BY_EXT = {
|
|
@@ -51,19 +51,26 @@ export function createServer(options) {
|
|
|
51
51
|
server.registerTool("list_avatars", {
|
|
52
52
|
title: "List avatars",
|
|
53
53
|
description: "Every avatar on this account, with the id you pass to startCall. Call this before " +
|
|
54
|
-
"writing code — avatar ids cannot be guessed.
|
|
55
|
-
"
|
|
54
|
+
"writing code — avatar ids cannot be guessed. Callable means status 'ready' AND " +
|
|
55
|
+
"idleVideoStatus 'ready': the loop is attached. An avatar reads sourceKind 'video' " +
|
|
56
|
+
"once that happens, including the ones built from a single image — that is the " +
|
|
57
|
+
"normal end state, not a warning.",
|
|
56
58
|
inputSchema: {},
|
|
57
59
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
58
60
|
}, async () => {
|
|
59
61
|
const avatars = await rta.listAvatars();
|
|
60
62
|
if (avatars.length === 0) {
|
|
61
|
-
return text("No avatars yet. Create one from a
|
|
63
|
+
return text("No avatars yet. Create one from a single portrait image before starting a call.");
|
|
62
64
|
}
|
|
63
|
-
const rows = avatars.map((a) => `${a.id} ${a.status.padEnd(13)} ${a.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const rows = avatars.map((a) => `${a.id} ${a.status.padEnd(13)} ${a.idleVideoStatus.padEnd(10)} ${a.displayName}`);
|
|
66
|
+
// Usable = ready WITH A LOOP ATTACHED. The old test was `sourceKind === "video"`,
|
|
67
|
+
// which happened to work only because an image-sourced avatar flips to `video` once
|
|
68
|
+
// its loop lands — it was reading the consequence, not the fact. `idleVideoStatus` is
|
|
69
|
+
// the fact, and it also distinguishes the three ways an avatar can be un-callable:
|
|
70
|
+
// still rendering (`queued`/`generating`), never had one (`none`), gave up (`failed`).
|
|
71
|
+
const usable = avatars.filter((a) => a.status === "ready" && a.idleVideoStatus === "ready");
|
|
72
|
+
return text(`${avatars.length} avatar(s). ${usable.length} ready with a loop attached (usable for a live call).\n\n` +
|
|
73
|
+
`id status loop name\n${rows.join("\n")}`);
|
|
67
74
|
});
|
|
68
75
|
server.registerTool("get_avatar", {
|
|
69
76
|
title: "Get one avatar",
|
|
@@ -199,6 +206,28 @@ export function createServer(options) {
|
|
|
199
206
|
? "\n\nQueued clips render in the background — poll list_clips until no row is queued or generating."
|
|
200
207
|
: ""));
|
|
201
208
|
});
|
|
209
|
+
server.registerTool("set_loop", {
|
|
210
|
+
title: "Re-direct the resting loop",
|
|
211
|
+
description: "Re-generate the RESTING LOOP — the video she plays when nothing else is happening — " +
|
|
212
|
+
"from a new one-sentence description. NOT a clip: a clip with role 'idle' is a " +
|
|
213
|
+
"variant spliced over the loop, and declaring one never changes what she rests in. " +
|
|
214
|
+
"Accepted immediately (202) and rendered over minutes; she stays ready and keeps " +
|
|
215
|
+
"serving her previous loop the whole time, and the clip library is untouched. Bills " +
|
|
216
|
+
"one video generation per call, so do not send it speculatively.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
avatarId: z.string(),
|
|
219
|
+
motionPrompt: z.string().max(1200)
|
|
220
|
+
.describe("How she should idle. Describe a CLOSED arc that ends where it began, or the " +
|
|
221
|
+
"loop snaps every time it wraps — e.g. 'leans in, listening, a slow blink, " +
|
|
222
|
+
"settles back'."),
|
|
223
|
+
},
|
|
224
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
225
|
+
}, async ({ avatarId, motionPrompt }) => {
|
|
226
|
+
const result = await rta.setLoop(avatarId, { motionPrompt });
|
|
227
|
+
return text(`Re-direct accepted (${result.loopStatus}).\n` +
|
|
228
|
+
`She is still playing the previous loop until the new one is ready:\n ${result.servingUrl ?? "—"}\n\n` +
|
|
229
|
+
"Poll get_avatar; the swap publishes in one step. Her clips are unaffected.");
|
|
230
|
+
});
|
|
202
231
|
server.registerTool("sync_clips", {
|
|
203
232
|
title: "Sync an avatar's clips",
|
|
204
233
|
description: "DEPRECATED — this serves the sunsetting external-URL clip tier; declare the " +
|
|
@@ -274,10 +303,33 @@ export function createServer(options) {
|
|
|
274
303
|
const asset = await rta.createRemoteAsset({ kind, remoteUrl });
|
|
275
304
|
return text(`Created ${asset.id} (${kind})\n${asset.url}`);
|
|
276
305
|
});
|
|
306
|
+
server.registerTool("create_avatar_from_image", {
|
|
307
|
+
title: "Create an avatar from one still image",
|
|
308
|
+
description: "Build an avatar from a SINGLE still image — the shortest path, and the one to reach " +
|
|
309
|
+
"for by default. The platform generates the resting loop she idles in and a starter " +
|
|
310
|
+
"motion library rendered against her rest pose; no footage and no clip URLs are " +
|
|
311
|
+
"involved. 'motionPrompt' directs the resting loop and is the ONLY chance to direct " +
|
|
312
|
+
"it — no endpoint re-generates a loop after creation. Returns while she is still " +
|
|
313
|
+
"'preprocessing'; poll get_avatar.",
|
|
314
|
+
inputSchema: {
|
|
315
|
+
displayName: z.string(),
|
|
316
|
+
imageUrl: z.string().url().describe("Publicly reachable still of the character, face in frame"),
|
|
317
|
+
motionPrompt: z.string().max(1200).optional()
|
|
318
|
+
.describe("Art direction for the generated resting loop. Describe a CLOSED arc that ends " +
|
|
319
|
+
"where it began, or the loop snaps every time it wraps — e.g. 'settles into " +
|
|
320
|
+
"frame, breathes gently, a slow blink'. Omit for the house default."),
|
|
321
|
+
},
|
|
322
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
323
|
+
}, async ({ displayName, imageUrl, motionPrompt }) => {
|
|
324
|
+
const avatar = await rta.createAvatarFromImage({ displayName, imageUrl, motionPrompt });
|
|
325
|
+
return text(`Created ${avatar.id} (${avatar.status}). Poll get_avatar until it is ready.`);
|
|
326
|
+
});
|
|
277
327
|
server.registerTool("create_avatar_from_video", {
|
|
278
|
-
title: "Create an avatar from a video",
|
|
279
|
-
description: "
|
|
280
|
-
"
|
|
328
|
+
title: "Create an avatar from a video (deprecated)",
|
|
329
|
+
description: "DEPRECATED AND CLOSED — this answers 422 unless the tenant was already creating " +
|
|
330
|
+
"from video. Use create_avatar_from_image instead: one still, and the platform " +
|
|
331
|
+
"renders the loop and the motion library from it. Do not reach for this tool " +
|
|
332
|
+
"because an image is inconvenient to obtain; it will simply fail.",
|
|
281
333
|
inputSchema: {
|
|
282
334
|
displayName: z.string(),
|
|
283
335
|
videoUrl: z.string().url().describe("Publicly reachable mp4, opening and closing on the same rest pose"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "realtime-avatar-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "MCP server for Realtime Avatar — let a coding agent read your avatars, balance and bill directly.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "The Influence Company",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
58
|
-
"realtime-avatar": "0.5.
|
|
58
|
+
"realtime-avatar": "0.5.2",
|
|
59
59
|
"zod": "^4.4.3"
|
|
60
60
|
}
|
|
61
61
|
}
|