@kitsra/kavio-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 ADDED
@@ -0,0 +1,94 @@
1
+ Elastic License 2.0
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject to
14
+ the limitations and conditions below.
15
+
16
+ Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other
27
+ notices of the licensor in the software. Any use of the licensor's trademarks
28
+ is subject to applicable law.
29
+
30
+ Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to
37
+ the software.
38
+
39
+ If you or your company make any written claim that the software infringes or
40
+ contributes to infringement of any patent, your patent license for the software
41
+ granted under these terms ends immediately. If your company makes such a claim,
42
+ your patent license ends immediately for work on behalf of your company.
43
+
44
+ Notices
45
+
46
+ You must ensure that anyone who gets a copy of any part of the software from
47
+ you also gets a copy of these terms.
48
+
49
+ If you modify the software, you must include in any modified copies of the
50
+ software prominent notices stating that you have modified the software.
51
+
52
+ No Other Rights
53
+
54
+ These terms do not imply any licenses other than those expressly granted in
55
+ these terms.
56
+
57
+ Termination
58
+
59
+ If you use the software in violation of these terms, such use is not licensed,
60
+ and your licenses will automatically terminate. If the licensor provides you
61
+ with a notice of your violation, and you cease all violation of this license no
62
+ later than 30 days after you receive that notice, your licenses will be
63
+ reinstated retroactively. However, if you violate these terms after such
64
+ reinstatement, any additional violation of these terms will cause your licenses
65
+ to terminate automatically and permanently.
66
+
67
+ No Liability
68
+
69
+ As far as the law allows, the software comes as is, without any warranty or
70
+ condition, and the licensor will not be liable to you for any damages arising
71
+ out of these terms or the use or nature of the software, under any kind of
72
+ legal claim.
73
+
74
+ Definitions
75
+
76
+ The licensor is the entity offering these terms, and the software is the
77
+ software the licensor makes available under these terms, including any portion
78
+ of it.
79
+
80
+ you refers to the individual or entity agreeing to these terms.
81
+
82
+ your company is any legal entity, sole proprietorship, or other kind of
83
+ organization that you work for, plus all organizations that have control over,
84
+ are under the control of, or are under common control with that organization.
85
+ control means ownership of substantially all the assets of an entity, or the
86
+ power to direct its management and policies by vote, contract, or otherwise.
87
+ Control can be direct or indirect.
88
+
89
+ your licenses are all the licenses granted to you for the software under these
90
+ terms.
91
+
92
+ use means anything you do with the software requiring one of your licenses.
93
+
94
+ trademark means trademarks, service marks, and similar rights.
@@ -0,0 +1,9 @@
1
+ import type { JsonSchema, ToolDefinition } from "../types.js";
2
+ export interface AnthropicTool {
3
+ name: string;
4
+ description: string;
5
+ input_schema: JsonSchema;
6
+ }
7
+ /** Convert the catalog tools to Anthropic tool-use definitions. */
8
+ export declare function toAnthropicTools(tools: readonly ToolDefinition[]): AnthropicTool[];
9
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,UAAU,CAAC;CAC1B;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,aAAa,EAAE,CAMlF"}
@@ -0,0 +1,8 @@
1
+ /** Convert the catalog tools to Anthropic tool-use definitions. */
2
+ export function toAnthropicTools(tools) {
3
+ return tools.map((tool) => ({
4
+ name: tool.name,
5
+ description: tool.description,
6
+ input_schema: tool.inputSchema
7
+ }));
8
+ }
@@ -0,0 +1,12 @@
1
+ import type { JsonSchema, ToolDefinition } from "../types.js";
2
+ export interface GeminiFunctionDeclaration {
3
+ name: string;
4
+ description: string;
5
+ parameters: JsonSchema;
6
+ }
7
+ export interface GeminiTool {
8
+ functionDeclarations: GeminiFunctionDeclaration[];
9
+ }
10
+ /** Convert the catalog tools to Gemini function declarations (schema sanitized). */
11
+ export declare function toGeminiTools(tools: readonly ToolDefinition[]): GeminiTool[];
12
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG9D,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,oBAAoB,EAAE,yBAAyB,EAAE,CAAC;CACnD;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,UAAU,EAAE,CAU5E"}
@@ -0,0 +1,13 @@
1
+ import { stripForGemini } from "./sanitize.js";
2
+ /** Convert the catalog tools to Gemini function declarations (schema sanitized). */
3
+ export function toGeminiTools(tools) {
4
+ return [
5
+ {
6
+ functionDeclarations: tools.map((tool) => ({
7
+ name: tool.name,
8
+ description: tool.description,
9
+ parameters: stripForGemini(tool.inputSchema)
10
+ }))
11
+ }
12
+ ];
13
+ }
@@ -0,0 +1,12 @@
1
+ import type { JsonSchema, ToolDefinition } from "../types.js";
2
+ export interface OpenAITool {
3
+ type: "function";
4
+ function: {
5
+ name: string;
6
+ description: string;
7
+ parameters: JsonSchema;
8
+ };
9
+ }
10
+ /** Convert the catalog tools to OpenAI function-calling definitions. */
11
+ export declare function toOpenAITools(tools: readonly ToolDefinition[]): OpenAITool[];
12
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,UAAU,CAAC;KACxB,CAAC;CACH;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,UAAU,EAAE,CAS5E"}
@@ -0,0 +1,11 @@
1
+ /** Convert the catalog tools to OpenAI function-calling definitions. */
2
+ export function toOpenAITools(tools) {
3
+ return tools.map((tool) => ({
4
+ type: "function",
5
+ function: {
6
+ name: tool.name,
7
+ description: tool.description,
8
+ parameters: tool.inputSchema
9
+ }
10
+ }));
11
+ }
@@ -0,0 +1,4 @@
1
+ import type { JsonSchema } from "../types.js";
2
+ /** Strip JSON Schema keys that Gemini's function-declaration subset rejects. */
3
+ export declare function stripForGemini(schema: JsonSchema): JsonSchema;
4
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/adapters/sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAE7D"}
@@ -0,0 +1,21 @@
1
+ const GEMINI_UNSUPPORTED_KEYS = new Set(["$schema", "$id", "additionalProperties"]);
2
+ /** Strip JSON Schema keys that Gemini's function-declaration subset rejects. */
3
+ export function stripForGemini(schema) {
4
+ return strip(schema);
5
+ }
6
+ function strip(value) {
7
+ if (Array.isArray(value)) {
8
+ return value.map(strip);
9
+ }
10
+ if (value !== null && typeof value === "object") {
11
+ const output = {};
12
+ for (const [key, nested] of Object.entries(value)) {
13
+ if (GEMINI_UNSUPPORTED_KEYS.has(key)) {
14
+ continue;
15
+ }
16
+ output[key] = strip(nested);
17
+ }
18
+ return output;
19
+ }
20
+ return value;
21
+ }
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { toAnthropicTools } from "./adapters/anthropic.js";
6
+ import { toGeminiTools } from "./adapters/gemini.js";
7
+ import { toOpenAITools } from "./adapters/openai.js";
8
+ import { createCatalog } from "./catalog.js";
9
+ import { createServer } from "./server.js";
10
+ async function emitAdapters(outDir) {
11
+ const tools = createCatalog().tools;
12
+ await mkdir(outDir, { recursive: true });
13
+ await writeFile(resolve(outDir, "anthropic.tools.json"), `${JSON.stringify(toAnthropicTools(tools), null, 2)}\n`);
14
+ await writeFile(resolve(outDir, "openai.tools.json"), `${JSON.stringify(toOpenAITools(tools), null, 2)}\n`);
15
+ await writeFile(resolve(outDir, "gemini.tools.json"), `${JSON.stringify(toGeminiTools(tools), null, 2)}\n`);
16
+ }
17
+ async function serve() {
18
+ const server = createServer(createCatalog());
19
+ await server.connect(new StdioServerTransport());
20
+ }
21
+ async function main(argv) {
22
+ if (argv[2] === "emit-adapters") {
23
+ const flagIndex = argv.indexOf("--out");
24
+ const outDir = flagIndex >= 0 ? argv[flagIndex + 1] : undefined;
25
+ if (outDir === undefined || outDir.length === 0) {
26
+ process.stderr.write("Usage: kavio-mcp emit-adapters --out <dir>\n");
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+ await emitAdapters(outDir);
31
+ process.stdout.write(`Wrote anthropic/openai/gemini tool files to ${outDir}\n`);
32
+ return;
33
+ }
34
+ await serve();
35
+ }
36
+ void main(process.argv).catch((error) => {
37
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
38
+ process.exitCode = 1;
39
+ });
@@ -0,0 +1,8 @@
1
+ import { type RenderHandlerDeps } from "./handlers.js";
2
+ import type { Catalog } from "./types.js";
3
+ export interface CreateCatalogOptions {
4
+ render?: RenderHandlerDeps;
5
+ }
6
+ /** Build the Kavio tool/resource/prompt catalog (the single source of truth). */
7
+ export declare function createCatalog(options?: CreateCatalogOptions): Catalog;
8
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,OAAO,EAA8B,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AA4BD,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAkDzE"}
@@ -0,0 +1,75 @@
1
+ import { inspectComposition, listExportPresets, migrateComposition, planRender, renderHandler, resolveProps, validateComposition } from "./handlers.js";
2
+ import { prompts } from "./prompts.js";
3
+ import { resources } from "./resources.js";
4
+ const documentSchema = { type: "object", description: "A Kavio composition document." };
5
+ function documentInput(extra = {}) {
6
+ return {
7
+ type: "object",
8
+ properties: { document: documentSchema, ...extra },
9
+ required: ["document"],
10
+ additionalProperties: false
11
+ };
12
+ }
13
+ function batchInput(extra = {}) {
14
+ return {
15
+ type: "object",
16
+ properties: {
17
+ document: documentSchema,
18
+ props: { type: "object", description: "Prop values for a single render." },
19
+ presets: { type: "array", items: { type: "string" }, description: "Export preset names." },
20
+ rows: { type: "array", description: "Batch prop rows: [{ id?, props? }]." },
21
+ ...extra
22
+ },
23
+ required: ["document"],
24
+ additionalProperties: false
25
+ };
26
+ }
27
+ /** Build the Kavio tool/resource/prompt catalog (the single source of truth). */
28
+ export function createCatalog(options = {}) {
29
+ const renderDeps = options.render ?? {};
30
+ const tools = [
31
+ {
32
+ name: "validate_composition",
33
+ description: "Validate a Kavio composition; returns path-keyed errors for repair.",
34
+ inputSchema: documentInput(),
35
+ handler: validateComposition
36
+ },
37
+ {
38
+ name: "inspect_composition",
39
+ description: "Summarize a composition: dimensions, fps, duration, counts, and transition overlap windows.",
40
+ inputSchema: documentInput(),
41
+ handler: inspectComposition
42
+ },
43
+ {
44
+ name: "migrate_composition",
45
+ description: "Migrate a composition to the latest Kavio schema version.",
46
+ inputSchema: documentInput(),
47
+ handler: migrateComposition
48
+ },
49
+ {
50
+ name: "resolve_props",
51
+ description: "Resolve {{prop}} placeholders in a composition using the given prop values.",
52
+ inputSchema: documentInput({ props: { type: "object", description: "Prop values." } }),
53
+ handler: resolveProps
54
+ },
55
+ {
56
+ name: "list_export_presets",
57
+ description: "List the standard Kavio social export presets shared with the builder SDK.",
58
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
59
+ handler: listExportPresets
60
+ },
61
+ {
62
+ name: "plan_render",
63
+ description: "Expand template x rows x presets and assemble the FFmpeg command(s) without rendering. Always available.",
64
+ inputSchema: batchInput(),
65
+ handler: planRender
66
+ },
67
+ {
68
+ name: "render",
69
+ description: "Render a composition to MP4 (browser capture + FFmpeg). Returns a BINARY_MISSING error if Chromium/FFmpeg are unavailable.",
70
+ inputSchema: batchInput({ outDir: { type: "string", description: "Output directory (default: renders)." } }),
71
+ handler: (input) => renderHandler(input, renderDeps)
72
+ }
73
+ ];
74
+ return { tools, resources, prompts };
75
+ }
@@ -0,0 +1,16 @@
1
+ import { type FfmpegRunner } from "@kitsra/kavio-render";
2
+ import { type BrowserDriver } from "@kitsra/kavio-render-worker";
3
+ import type { ToolResult } from "./types.js";
4
+ export declare const STANDARD_PRESETS: readonly [import("@kitsra/kavio-builder").SocialMediaPresetDefinition, import("@kitsra/kavio-builder").SocialMediaPresetDefinition, import("@kitsra/kavio-builder").SocialMediaPresetDefinition, import("@kitsra/kavio-builder").SocialMediaPresetDefinition, import("@kitsra/kavio-builder").SocialMediaPresetDefinition, import("@kitsra/kavio-builder").SocialMediaPresetDefinition, import("@kitsra/kavio-builder").SocialMediaPresetDefinition];
5
+ export declare function validateComposition(input: unknown): ToolResult;
6
+ export declare function inspectComposition(input: unknown): ToolResult;
7
+ export declare function migrateComposition(input: unknown): ToolResult;
8
+ export declare function resolveProps(input: unknown): ToolResult;
9
+ export declare function listExportPresets(_input: unknown): ToolResult;
10
+ export declare function planRender(input: unknown): ToolResult;
11
+ export interface RenderHandlerDeps {
12
+ driver?: BrowserDriver;
13
+ ffmpegRunner?: FfmpegRunner;
14
+ }
15
+ export declare function renderHandler(input: unknown, deps?: RenderHandlerDeps): Promise<ToolResult>;
16
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AASA,OAAO,EAAsC,KAAK,YAAY,EAA2B,MAAM,sBAAsB,CAAC;AACtH,OAAO,EAAqB,KAAK,aAAa,EAA8C,MAAM,6BAA6B,CAAC;AAEhI,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,gBAAgB,sbAAqB,CAAC;AAEnD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAU9D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAiD7D;AAmFD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAgB7D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAWvD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,CAE7D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAsCrD;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAoCrG"}
@@ -0,0 +1,275 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ import { applyExportPreset, collectCompositionResourceLimitInputs, collectResourceLimitViolations, compileTransitionOverlapWindows, resolveTemplateProps } from "@kitsra/kavio-core";
3
+ import { socialMediaPresets } from "@kitsra/kavio-builder";
4
+ import { assembleRenderCommand, renderBatch } from "@kitsra/kavio-render";
5
+ import { expandRenderBatch } from "@kitsra/kavio-render-worker";
6
+ import { schemaVersion, validateComposition as schemaValidate } from "@kitsra/kavio-schema";
7
+ export const STANDARD_PRESETS = socialMediaPresets;
8
+ export function validateComposition(input) {
9
+ const document = readDocument(input);
10
+ if (document.ok === false) {
11
+ return document.result;
12
+ }
13
+ const result = schemaValidate(document.value);
14
+ if (result.ok) {
15
+ return { ok: true, data: { ok: true, errors: [] } };
16
+ }
17
+ return { ok: false, errors: result.errors };
18
+ }
19
+ export function inspectComposition(input) {
20
+ const document = readDocument(input);
21
+ if (document.ok === false) {
22
+ return document.result;
23
+ }
24
+ const validation = schemaValidate(document.value);
25
+ if (!validation.ok) {
26
+ return { ok: false, errors: validation.errors };
27
+ }
28
+ const doc = document.value;
29
+ const fps = Number(doc.composition.fps);
30
+ return {
31
+ ok: true,
32
+ data: {
33
+ version: doc.version,
34
+ composition: {
35
+ width: doc.composition.width,
36
+ height: doc.composition.height,
37
+ fps,
38
+ durationFrames: doc.composition.durationFrames,
39
+ durationSeconds: fps > 0 ? doc.composition.durationFrames / fps : 0
40
+ },
41
+ props: { count: doc.props === undefined ? 0 : Object.keys(doc.props).length },
42
+ assets: { count: Object.keys(doc.assets).length, types: countTypes(Object.values(doc.assets)) },
43
+ layers: { count: doc.layers.length, types: countTypes(doc.layers) },
44
+ masks: inspectMasks(doc),
45
+ tracks: {
46
+ count: doc.tracks === undefined ? 0 : doc.tracks.length,
47
+ clipCount: doc.tracks === undefined ? 0 : doc.tracks.reduce((total, track) => total + track.clips.length, 0),
48
+ transitionWindows: compileTransitionOverlapWindows(doc.tracks).map((window) => ({
49
+ trackId: window.trackId,
50
+ previousClipId: window.previousClipId,
51
+ previousLayerId: window.previousLayerId,
52
+ nextClipId: window.nextClipId,
53
+ nextLayerId: window.nextLayerId,
54
+ startFrame: window.startFrame,
55
+ endFrame: window.endFrame,
56
+ durationFrames: window.durationFrames,
57
+ transitionType: window.transition.type
58
+ }))
59
+ },
60
+ audio: { count: doc.audio === undefined ? 0 : doc.audio.length },
61
+ exports: {
62
+ count: doc.exports.length,
63
+ names: doc.exports.map((entry, index) => (typeof entry.name === "string" ? entry.name : `export-${index + 1}`))
64
+ }
65
+ }
66
+ };
67
+ }
68
+ function inspectMasks(document) {
69
+ const summary = {
70
+ count: 0,
71
+ shapeCount: 0,
72
+ assetMasks: [],
73
+ proceduralMasks: [],
74
+ invertedCount: 0
75
+ };
76
+ for (const layer of document.layers) {
77
+ const mask = layer.mask;
78
+ if (!mask) {
79
+ continue;
80
+ }
81
+ summary.count += 1;
82
+ if (mask.invert === true) {
83
+ summary.invertedCount += 1;
84
+ }
85
+ switch (mask.source.kind) {
86
+ case "shape":
87
+ summary.shapeCount += 1;
88
+ break;
89
+ case "asset":
90
+ summary.assetMasks.push(withResolution({
91
+ layerId: layer.id,
92
+ asset: mask.source.asset,
93
+ mode: mask.source.mode ?? "alpha"
94
+ }, mask.source.resolution));
95
+ break;
96
+ case "procedural":
97
+ summary.proceduralMasks.push(withResolution({
98
+ layerId: layer.id,
99
+ type: mask.source.type,
100
+ seed: mask.source.seed,
101
+ ...(mask.source.direction === undefined ? {} : { direction: mask.source.direction })
102
+ }, mask.source.resolution));
103
+ break;
104
+ }
105
+ }
106
+ return summary;
107
+ }
108
+ function withResolution(value, resolution) {
109
+ return (resolution === undefined
110
+ ? value
111
+ : {
112
+ ...value,
113
+ width: resolution.width,
114
+ height: resolution.height
115
+ });
116
+ }
117
+ export function migrateComposition(input) {
118
+ const document = readDocument(input);
119
+ if (document.ok === false) {
120
+ return document.result;
121
+ }
122
+ const version = document.value.version;
123
+ if (typeof version !== "string") {
124
+ return { ok: false, errors: [mcpError("Document is missing a string version.")] };
125
+ }
126
+ if (version !== schemaVersion) {
127
+ return {
128
+ ok: false,
129
+ errors: [mcpError(`No migration path from ${version} to ${schemaVersion}.`, "MCP_MIGRATION_UNSUPPORTED")]
130
+ };
131
+ }
132
+ return { ok: true, data: { changed: false, fromVersion: version, toVersion: schemaVersion, document: document.value } };
133
+ }
134
+ export function resolveProps(input) {
135
+ if (!isRecord(input) || !("document" in input)) {
136
+ return { ok: false, errors: [mcpError("Expected { document, props } input.")] };
137
+ }
138
+ const props = input.props;
139
+ const values = isRecord(props) ? props : {};
140
+ const resolution = resolveTemplateProps(input.document, values);
141
+ if (resolution.ok) {
142
+ return { ok: true, data: resolution.value };
143
+ }
144
+ return { ok: false, errors: resolution.errors };
145
+ }
146
+ export function listExportPresets(_input) {
147
+ return { ok: true, data: STANDARD_PRESETS.map((preset) => ({ ...preset, preset: { ...preset.preset } })) };
148
+ }
149
+ export function planRender(input) {
150
+ const parsed = parseBatchInput(input);
151
+ if (parsed.ok === false) {
152
+ return { ok: false, errors: [parsed.error] };
153
+ }
154
+ const jobs = [];
155
+ let expanded;
156
+ try {
157
+ expanded = expandRenderBatch(parsed.batchInput);
158
+ }
159
+ catch (error) {
160
+ return { ok: false, errors: toErrors(error, "MCP_PLAN_FAILED") };
161
+ }
162
+ for (const job of expanded) {
163
+ const resolution = resolveTemplateProps(job.document, job.props);
164
+ if (!resolution.ok) {
165
+ return { ok: false, errors: resolution.errors };
166
+ }
167
+ try {
168
+ const view = applyExportPreset(resolution.value, job.preset);
169
+ const violations = collectResourceLimitViolations(collectCompositionResourceLimitInputs(view));
170
+ if (violations.length > 0) {
171
+ return { ok: false, errors: violations };
172
+ }
173
+ const ffmpegArgs = assembleRenderCommand({
174
+ view,
175
+ preset: job.preset,
176
+ framePattern: "overlay-%05d.png",
177
+ outputPath: job.outputName
178
+ });
179
+ jobs.push({ id: job.id, outputName: job.outputName, preset: job.preset, ffmpegArgs });
180
+ }
181
+ catch (error) {
182
+ return { ok: false, errors: toErrors(error, "MCP_PLAN_FAILED") };
183
+ }
184
+ }
185
+ return { ok: true, data: { jobs } };
186
+ }
187
+ export async function renderHandler(input, deps = {}) {
188
+ const parsed = parseBatchInput(input);
189
+ if (parsed.ok === false) {
190
+ return { ok: false, errors: [parsed.error] };
191
+ }
192
+ const outDir = safeRenderOutDir(isRecord(input) ? input.outDir : undefined);
193
+ if (outDir.ok === false) {
194
+ return { ok: false, errors: [outDir.error] };
195
+ }
196
+ const options = { outDir: outDir.value };
197
+ if (deps.driver !== undefined) {
198
+ options.driver = deps.driver;
199
+ }
200
+ if (deps.ffmpegRunner !== undefined) {
201
+ options.ffmpegRunner = deps.ffmpegRunner;
202
+ }
203
+ let results;
204
+ try {
205
+ results = await renderBatch(parsed.batchInput, options);
206
+ }
207
+ catch (error) {
208
+ return { ok: false, errors: toErrors(error, "RENDER_FAILED") };
209
+ }
210
+ const outputs = results.map((item) => item.result.ok
211
+ ? { id: item.id, outputName: item.outputName, ok: true, outputPath: item.result.outputPath, metadata: item.result.metadata }
212
+ : { id: item.id, outputName: item.outputName, ok: false, errors: item.result.errors });
213
+ const allOk = results.length > 0 && results.every((item) => item.result.ok);
214
+ if (allOk) {
215
+ return { ok: true, data: { outputs } };
216
+ }
217
+ const failures = results.flatMap((item) => (item.result.ok ? [] : item.result.errors));
218
+ return { ok: false, data: { outputs }, errors: failures };
219
+ }
220
+ function readDocument(input) {
221
+ if (!isRecord(input) || !("document" in input)) {
222
+ return { ok: false, result: { ok: false, errors: [mcpError("Expected { document } input.")] } };
223
+ }
224
+ return { ok: true, value: input.document };
225
+ }
226
+ function parseBatchInput(input) {
227
+ if (!isRecord(input) || !("document" in input)) {
228
+ return { ok: false, error: mcpError("Expected { document } input.") };
229
+ }
230
+ const document = input.document;
231
+ const rowsInput = input.rows;
232
+ const presetsInput = input.presets;
233
+ const propsInput = input.props;
234
+ const rows = Array.isArray(rowsInput)
235
+ ? rowsInput
236
+ : [{ props: (isRecord(propsInput) ? propsInput : {}) }];
237
+ const batchInput = { template: document, rows };
238
+ if (Array.isArray(presetsInput)) {
239
+ batchInput.presets = presetsInput;
240
+ }
241
+ return { ok: true, batchInput };
242
+ }
243
+ function safeRenderOutDir(input) {
244
+ const base = resolve("renders");
245
+ const requested = typeof input === "string" && input.length > 0 ? input : ".";
246
+ const outDir = resolve(base, requested);
247
+ const relativePath = relative(base, outDir);
248
+ if (relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath)) {
249
+ return {
250
+ ok: false,
251
+ error: mcpError("outDir must stay inside the renders directory.", "MCP_INPUT_INVALID")
252
+ };
253
+ }
254
+ return { ok: true, value: outDir };
255
+ }
256
+ function countTypes(items) {
257
+ const counts = {};
258
+ for (const item of items) {
259
+ const type = isRecord(item) && typeof item.type === "string" ? item.type : "unknown";
260
+ counts[type] = (counts[type] ?? 0) + 1;
261
+ }
262
+ return counts;
263
+ }
264
+ function mcpError(message, code = "MCP_INPUT_INVALID") {
265
+ return { code, severity: "error", message, path: "", stage: "validation", retryable: false };
266
+ }
267
+ function toErrors(error, fallbackCode) {
268
+ if (isRecord(error) && typeof error.code === "string" && typeof error.stage === "string") {
269
+ return [error];
270
+ }
271
+ return [mcpError(error instanceof Error ? error.message : String(error), fallbackCode)];
272
+ }
273
+ function isRecord(value) {
274
+ return typeof value === "object" && value !== null && !Array.isArray(value);
275
+ }
@@ -0,0 +1,10 @@
1
+ export declare const KAVIO_MCP_PACKAGE = "@kitsra/kavio-mcp";
2
+ export { createCatalog, type CreateCatalogOptions } from "./catalog.js";
3
+ export type { Catalog, JsonSchema, PromptArgument, PromptDefinition, ResourceDefinition, ToolDefinition, ToolResult } from "./types.js";
4
+ export { toAnthropicTools, type AnthropicTool } from "./adapters/anthropic.js";
5
+ export { toOpenAITools, type OpenAITool } from "./adapters/openai.js";
6
+ export { toGeminiTools, type GeminiFunctionDeclaration, type GeminiTool } from "./adapters/gemini.js";
7
+ export { stripForGemini } from "./adapters/sanitize.js";
8
+ export { resources } from "./resources.js";
9
+ export { prompts } from "./prompts.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EACV,OAAO,EACP,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,KAAK,yBAAyB,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtG,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export const KAVIO_MCP_PACKAGE = "@kitsra/kavio-mcp";
2
+ export { createCatalog } from "./catalog.js";
3
+ export { toAnthropicTools } from "./adapters/anthropic.js";
4
+ export { toOpenAITools } from "./adapters/openai.js";
5
+ export { toGeminiTools } from "./adapters/gemini.js";
6
+ export { stripForGemini } from "./adapters/sanitize.js";
7
+ export { resources } from "./resources.js";
8
+ export { prompts } from "./prompts.js";
9
+ // Note: server.ts / bin.ts are intentionally NOT re-exported here so that
10
+ // library consumers that only want the catalog or adapters do not pull in the
11
+ // MCP SDK. The server is reached via the `kavio-mcp` bin.
@@ -0,0 +1,3 @@
1
+ import type { PromptDefinition } from "./types.js";
2
+ export declare const prompts: PromptDefinition[];
3
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,eAAO,MAAM,OAAO,EAAE,gBAAgB,EAgErC,CAAC"}
@@ -0,0 +1,62 @@
1
+ export const prompts = [
2
+ {
3
+ name: "author_kavio_video",
4
+ description: "Generate a Kavio video composition (JSON) from a natural-language brief.",
5
+ arguments: [
6
+ { name: "brief", description: "What the video should contain", required: true },
7
+ { name: "width", description: "Canvas width in px (default 1080)", required: false },
8
+ { name: "height", description: "Canvas height in px (default 1920)", required: false },
9
+ { name: "durationSeconds", description: "Duration in seconds (default 10)", required: false }
10
+ ],
11
+ render: (args) => [
12
+ "You are authoring a Kavio video composition as JSON.",
13
+ "",
14
+ `Brief: ${args.brief}`,
15
+ `Canvas: ${args.width ?? "1080"}x${args.height ?? "1920"}, duration ${args.durationSeconds ?? "10"}s @ 30fps.`,
16
+ "",
17
+ "Rules:",
18
+ "- Follow the schema resource `kavio://schema/0.1.json` exactly; use only allowed enum values (`kavio://enums.json`).",
19
+ "- Use `kavio://examples/basic.json` as a structural reference.",
20
+ "- Time is in integer frames; positions in composition pixels; anchor defaults to top-left.",
21
+ "- Emit ONLY valid Kavio JSON, no prose.",
22
+ "",
23
+ "Then call `validate_composition`. If it returns errors, fix the exact JSON paths and re-validate until it passes."
24
+ ].join("\n")
25
+ },
26
+ {
27
+ name: "repair_kavio_json",
28
+ description: "Repair a Kavio composition using structured validation errors.",
29
+ arguments: [
30
+ { name: "document", description: "The invalid Kavio JSON", required: true },
31
+ { name: "errors", description: "The errors array returned by validate_composition (JSON)", required: true }
32
+ ],
33
+ render: (args) => [
34
+ "The following Kavio composition failed validation.",
35
+ "",
36
+ "Document:",
37
+ args.document,
38
+ "",
39
+ "Errors (each has a JSON `path`):",
40
+ args.errors,
41
+ "",
42
+ "Fix ONLY the fields at the listed paths. Do not restructure anything else.",
43
+ "Return the corrected full Kavio JSON, then call `validate_composition` again to confirm."
44
+ ].join("\n")
45
+ },
46
+ {
47
+ name: "adapt_for_platform",
48
+ description: "Adapt a Kavio composition to a platform export preset.",
49
+ arguments: [
50
+ { name: "document", description: "The Kavio JSON to adapt", required: true },
51
+ { name: "platform", description: "Target preset: reels | square | landscape", required: true }
52
+ ],
53
+ render: (args) => [
54
+ `Adapt this Kavio composition for the "${args.platform}" platform preset (see \`kavio://presets.json\`).`,
55
+ "Set the export to the target dimensions and adjust layer positions / safe areas so the layout fits the new aspect ratio.",
56
+ "Prefer per-export layerOverrides over editing shared layers where possible.",
57
+ "",
58
+ "Document:",
59
+ args.document
60
+ ].join("\n")
61
+ }
62
+ ];
@@ -0,0 +1,3 @@
1
+ import type { ResourceDefinition } from "./types.js";
2
+ export declare const resources: ResourceDefinition[];
3
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgPrD,eAAO,MAAM,SAAS,EAAE,kBAAkB,EAoCzC,CAAC"}
@@ -0,0 +1,267 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { DEFAULT_RESOURCE_LIMITS } from "@kitsra/kavio-core";
4
+ import { STANDARD_PRESETS } from "./handlers.js";
5
+ const require = createRequire(import.meta.url);
6
+ function readSchema() {
7
+ return readFileSync(require.resolve("@kitsra/kavio-schema/schema"), "utf8");
8
+ }
9
+ const BASIC_EXAMPLE = {
10
+ version: "0.1",
11
+ composition: { width: 1080, height: 1920, fps: 30, durationFrames: 90, background: "#101820" },
12
+ props: {
13
+ headline: { type: "string", default: "Your headline" }
14
+ },
15
+ assets: {},
16
+ layers: [
17
+ { id: "bg", type: "shape", shape: "rect", fill: "#101820", startFrame: 0, durationFrames: 90 },
18
+ {
19
+ id: "headline",
20
+ type: "text",
21
+ text: "{{headline}}",
22
+ startFrame: 6,
23
+ durationFrames: 78,
24
+ position: { x: 540, y: 860 },
25
+ anchor: "center",
26
+ style: { fontFamily: "Inter", fontSize: 72, fontWeight: 800, color: "#FFFFFF", align: "center" },
27
+ keyframes: {
28
+ opacity: [
29
+ { frame: 0, value: 0 },
30
+ { frame: 12, value: 1, easing: "outCubic" }
31
+ ]
32
+ }
33
+ }
34
+ ],
35
+ audio: [],
36
+ exports: [{ name: "reels", format: "mp4", codec: "h264", width: 1080, height: 1920 }]
37
+ };
38
+ const ENUMS = {
39
+ layerType: ["video", "image", "text", "shape", "caption"],
40
+ assetType: ["video", "image", "audio", "font"],
41
+ fit: ["cover", "contain", "fill", "none"],
42
+ anchor: ["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"],
43
+ easing: [
44
+ "linear",
45
+ "inQuad",
46
+ "outQuad",
47
+ "inOutQuad",
48
+ "inCubic",
49
+ "outCubic",
50
+ "inOutCubic",
51
+ "inCirc",
52
+ "outCirc",
53
+ "inOutCirc",
54
+ "inExpo",
55
+ "outExpo",
56
+ "inOutExpo",
57
+ "anticipate",
58
+ "back",
59
+ "inBack",
60
+ "outBack",
61
+ "inOutBack",
62
+ "inElastic",
63
+ "outElastic",
64
+ "inOutElastic",
65
+ "inBounce",
66
+ "outBounce",
67
+ "inOutBounce"
68
+ ],
69
+ timing: ["tween", "spring", "steps", "sequence", "stagger"],
70
+ preset: ["fadeIn", "fadeOut", "slideUp", "slideDown", "slideLeft", "slideRight", "popIn", "zoomIn"],
71
+ presetNamespace: ["transition", "cinematic", "textMotion", "camera", "effect"],
72
+ effect: ["blur", "brightness", "contrast", "saturate", "tint"],
73
+ transitionTiming: ["tween"],
74
+ maskSourceKind: ["shape", "asset", "procedural"],
75
+ maskShape: ["rect", "circle", "diamond"],
76
+ proceduralMask: ["linearGradient", "radialGradient", "scanlines"],
77
+ maskAssetMode: ["alpha"],
78
+ transition: [
79
+ "fade",
80
+ "slide",
81
+ "wipe",
82
+ "crossfade",
83
+ "zoom",
84
+ "push",
85
+ "spin",
86
+ "rotate",
87
+ "flip",
88
+ "blurDissolve",
89
+ "colorDissolve",
90
+ "dip",
91
+ "iris",
92
+ "stretch",
93
+ "squeeze",
94
+ "clockWipe",
95
+ "barWipe",
96
+ "gridWipe",
97
+ "tileReveal",
98
+ "radialBlur",
99
+ "zoomBlur",
100
+ "bookFlip",
101
+ "pageCurlLite",
102
+ "skewSlide",
103
+ "expandMask",
104
+ "letterboxReveal",
105
+ "filmFlash",
106
+ "cameraWhip"
107
+ ],
108
+ cameraMotion: ["kenBurns", "pushIn", "pullBack", "pan", "tilt"],
109
+ audioRole: ["music", "voiceover", "sfx", "source"],
110
+ exportFormat: ["mp4", "webm", "mov", "gif", "png-sequence"],
111
+ propType: ["string", "number", "boolean", "color", "url", "enum", "asset"]
112
+ };
113
+ const CURRENT_TRANSITION_SUPPORT = Object.fromEntries(ENUMS.transition.map((transition) => [
114
+ transition,
115
+ {
116
+ browserPreview: "stable",
117
+ stillFrameRender: "stable",
118
+ opaqueVideoRender: "stable",
119
+ transparentVideoRender: "unsupported",
120
+ gifRender: "unsupported",
121
+ pngSequenceRender: "unsupported",
122
+ nativeRender: "unsupported"
123
+ }
124
+ ]));
125
+ const CURRENT_EFFECT_SUPPORT = Object.fromEntries(ENUMS.effect.map((effect) => [
126
+ effect,
127
+ {
128
+ browserPreview: "unsupported",
129
+ stillFrameRender: "unsupported",
130
+ opaqueVideoRender: "unsupported",
131
+ transparentVideoRender: "unsupported",
132
+ gifRender: "unsupported",
133
+ pngSequenceRender: "unsupported",
134
+ nativeRender: "unsupported",
135
+ note: "Schema-declared layer effects are not applied by the current evaluator or browser renderer."
136
+ }
137
+ ]));
138
+ const MASK_SOURCE_SUPPORT = {
139
+ shape: {
140
+ stable: ["rect", "circle", "diamond"],
141
+ experimental: ["bar", "grid", "tiles"],
142
+ unsupported: [],
143
+ browserPreview: "stable",
144
+ stillFrameRender: "stable",
145
+ opaqueVideoRender: "stable",
146
+ transparentVideoRender: "unsupported",
147
+ gifRender: "unsupported",
148
+ pngSequenceRender: "unsupported",
149
+ nativeRender: "unsupported"
150
+ },
151
+ asset: {
152
+ stable: ["image-alpha"],
153
+ experimental: ["image-luma"],
154
+ unsupported: ["video-luma"],
155
+ browserPreview: "stable",
156
+ stillFrameRender: "stable",
157
+ opaqueVideoRender: "stable",
158
+ transparentVideoRender: "unsupported",
159
+ gifRender: "unsupported",
160
+ pngSequenceRender: "unsupported",
161
+ nativeRender: "unsupported",
162
+ note: "Stable asset masks reference declared image assets and use alpha sampling only."
163
+ },
164
+ procedural: {
165
+ stable: ["linearGradient", "radialGradient", "scanlines"],
166
+ experimental: ["noise", "threshold", "dither"],
167
+ unsupported: ["liquid", "shaderImageReveal"],
168
+ browserPreview: "stable",
169
+ stillFrameRender: "stable",
170
+ opaqueVideoRender: "stable",
171
+ transparentVideoRender: "unsupported",
172
+ gifRender: "unsupported",
173
+ pngSequenceRender: "unsupported",
174
+ nativeRender: "unsupported",
175
+ note: "Stable procedural masks require an integer seed even when the current CSS rendering is simple."
176
+ },
177
+ generatedOverlay: {
178
+ stable: [],
179
+ experimental: [],
180
+ unsupported: ["filmFlash", "lightLeak", "filmBurn", "grain", "vignette", "chromaticOffset"],
181
+ browserPreview: "unsupported",
182
+ stillFrameRender: "unsupported",
183
+ opaqueVideoRender: "unsupported",
184
+ transparentVideoRender: "unsupported",
185
+ gifRender: "unsupported",
186
+ pngSequenceRender: "unsupported",
187
+ nativeRender: "unsupported",
188
+ note: "Generated overlays are reserved for future deterministic overlay/effect work and are not valid layer masks today."
189
+ }
190
+ };
191
+ const MOTION_SUPPORT = {
192
+ supportStates: ["stable", "previewOnly", "experimental", "unsupported"],
193
+ renderTargets: [
194
+ "browserPreview",
195
+ "stillFrameRender",
196
+ "opaqueVideoRender",
197
+ "transparentVideoRender",
198
+ "gifRender",
199
+ "pngSequenceRender",
200
+ "nativeRender"
201
+ ],
202
+ transitions: CURRENT_TRANSITION_SUPPORT,
203
+ transitionSeries: {
204
+ schema: "stable",
205
+ coreEvaluation: "stable",
206
+ cliInspect: "stable",
207
+ mcpInspect: "stable",
208
+ browserPreview: "stable",
209
+ stillFrameRender: "stable",
210
+ opaqueVideoRender: "stable",
211
+ transparentVideoRender: "unsupported",
212
+ gifRender: "unsupported",
213
+ pngSequenceRender: "unsupported",
214
+ nativeRender: "unsupported",
215
+ note: "Tracks compile to explicit overlap windows for outgoing and incoming clip evaluation; browser-backed preview and render paths share the same frame evaluator."
216
+ },
217
+ effects: CURRENT_EFFECT_SUPPORT,
218
+ masks: MASK_SOURCE_SUPPORT,
219
+ performanceBudgets: {
220
+ maxBlurRadius: DEFAULT_RESOURCE_LIMITS.maxBlurRadius,
221
+ maxFilteredLayers: DEFAULT_RESOURCE_LIMITS.maxFilteredLayers,
222
+ maxMaskedLayers: DEFAULT_RESOURCE_LIMITS.maxMaskedLayers,
223
+ maxMaskSourceWidth: DEFAULT_RESOURCE_LIMITS.maxMaskSourceWidth,
224
+ maxMaskSourceHeight: DEFAULT_RESOURCE_LIMITS.maxMaskSourceHeight,
225
+ maxTextMotionFragments: DEFAULT_RESOURCE_LIMITS.maxTextMotionFragments,
226
+ maxProceduralMaskPixels: DEFAULT_RESOURCE_LIMITS.maxProceduralMaskPixels,
227
+ maxTransitionDurationFrames: DEFAULT_RESOURCE_LIMITS.maxTransitionDurationFrames,
228
+ note: "Render planning uses these budgets after export preset expansion."
229
+ }
230
+ };
231
+ export const resources = [
232
+ {
233
+ uri: "kavio://schema/0.1.json",
234
+ name: "Kavio JSON Schema 0.1",
235
+ description: "The canonical JSON Schema for a Kavio composition — the generation contract.",
236
+ mimeType: "application/json",
237
+ read: readSchema
238
+ },
239
+ {
240
+ uri: "kavio://presets.json",
241
+ name: "Export presets",
242
+ description: "Standard social export presets shared with the Kavio builder SDK.",
243
+ mimeType: "application/json",
244
+ read: () => `${JSON.stringify(STANDARD_PRESETS, null, 2)}\n`
245
+ },
246
+ {
247
+ uri: "kavio://examples/basic.json",
248
+ name: "Basic example composition",
249
+ description: "A minimal valid Kavio composition with a background, animated headline, and an export.",
250
+ mimeType: "application/json",
251
+ read: () => `${JSON.stringify(BASIC_EXAMPLE, null, 2)}\n`
252
+ },
253
+ {
254
+ uri: "kavio://enums.json",
255
+ name: "Enum reference",
256
+ description: "Allowed values for layer/asset types, easings, timing types, effects, transitions, formats, and prop types.",
257
+ mimeType: "application/json",
258
+ read: () => `${JSON.stringify(ENUMS, null, 2)}\n`
259
+ },
260
+ {
261
+ uri: "kavio://motion-support.json",
262
+ name: "Motion support matrix",
263
+ description: "Current transition/effect support states by render target plus motion performance budgets.",
264
+ mimeType: "application/json",
265
+ read: () => `${JSON.stringify(MOTION_SUPPORT, null, 2)}\n`
266
+ }
267
+ ];
@@ -0,0 +1,10 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import type { Catalog } from "./types.js";
3
+ /**
4
+ * Wire a Catalog to an MCP server using the SDK's low-level Server +
5
+ * setRequestHandler, so the catalog's raw JSON Schemas flow straight through
6
+ * (the high-level registerTool API is zod-centric and would break the shared
7
+ * source of truth used by the adapters).
8
+ */
9
+ export declare function createServer(catalog: Catalog): Server;
10
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAWnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CA6DrD"}
package/dist/server.js ADDED
@@ -0,0 +1,60 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ErrorCode, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
3
+ /**
4
+ * Wire a Catalog to an MCP server using the SDK's low-level Server +
5
+ * setRequestHandler, so the catalog's raw JSON Schemas flow straight through
6
+ * (the high-level registerTool API is zod-centric and would break the shared
7
+ * source of truth used by the adapters).
8
+ */
9
+ export function createServer(catalog) {
10
+ const server = new Server({ name: "kavio", version: "0.1.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
11
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
12
+ tools: catalog.tools.map((tool) => ({
13
+ name: tool.name,
14
+ description: tool.description,
15
+ inputSchema: tool.inputSchema
16
+ }))
17
+ }));
18
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
19
+ const tool = catalog.tools.find((candidate) => candidate.name === request.params.name);
20
+ if (tool === undefined) {
21
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
22
+ }
23
+ const result = await tool.handler(request.params.arguments ?? {});
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(result.ok ? result.data : result.errors, null, 2) }],
26
+ isError: !result.ok
27
+ };
28
+ });
29
+ server.setRequestHandler(ListResourcesRequestSchema, () => ({
30
+ resources: catalog.resources.map((resource) => ({
31
+ uri: resource.uri,
32
+ name: resource.name,
33
+ description: resource.description,
34
+ mimeType: resource.mimeType
35
+ }))
36
+ }));
37
+ server.setRequestHandler(ReadResourceRequestSchema, (request) => {
38
+ const resource = catalog.resources.find((candidate) => candidate.uri === request.params.uri);
39
+ if (resource === undefined) {
40
+ throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`);
41
+ }
42
+ return { contents: [{ uri: resource.uri, mimeType: resource.mimeType, text: resource.read() }] };
43
+ });
44
+ server.setRequestHandler(ListPromptsRequestSchema, () => ({
45
+ prompts: catalog.prompts.map((prompt) => ({
46
+ name: prompt.name,
47
+ description: prompt.description,
48
+ arguments: prompt.arguments
49
+ }))
50
+ }));
51
+ server.setRequestHandler(GetPromptRequestSchema, (request) => {
52
+ const prompt = catalog.prompts.find((candidate) => candidate.name === request.params.name);
53
+ if (prompt === undefined) {
54
+ throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${request.params.name}`);
55
+ }
56
+ const args = (request.params.arguments ?? {});
57
+ return { messages: [{ role: "user", content: { type: "text", text: prompt.render(args) } }] };
58
+ });
59
+ return server;
60
+ }
@@ -0,0 +1,37 @@
1
+ import type { KavioError } from "@kitsra/kavio-schema";
2
+ export type JsonSchema = Record<string, unknown>;
3
+ export interface ToolResult {
4
+ ok: boolean;
5
+ data?: unknown;
6
+ errors?: KavioError[];
7
+ }
8
+ export interface ToolDefinition {
9
+ name: string;
10
+ description: string;
11
+ inputSchema: JsonSchema;
12
+ handler: (input: unknown) => Promise<ToolResult> | ToolResult;
13
+ }
14
+ export interface ResourceDefinition {
15
+ uri: string;
16
+ name: string;
17
+ description: string;
18
+ mimeType: string;
19
+ read: () => string;
20
+ }
21
+ export interface PromptArgument {
22
+ name: string;
23
+ description: string;
24
+ required: boolean;
25
+ }
26
+ export interface PromptDefinition {
27
+ name: string;
28
+ description: string;
29
+ arguments: PromptArgument[];
30
+ render: (args: Record<string, string>) => string;
31
+ }
32
+ export interface Catalog {
33
+ tools: ToolDefinition[];
34
+ resources: ResourceDefinition[];
35
+ prompts: PromptDefinition[];
36
+ }
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;CAC/D;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;CAClD;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@kitsra/kavio-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol server and provider tool adapters for Kavio.",
5
+ "license": "Elastic-2.0",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "bin": {
11
+ "kavio-mcp": "./dist/bin.js"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.25.0",
15
+ "@kitsra/kavio-render": "0.1.0",
16
+ "@kitsra/kavio-render-worker": "0.1.0",
17
+ "@kitsra/kavio-builder": "0.1.0",
18
+ "@kitsra/kavio-schema": "0.1.0",
19
+ "@kitsra/kavio-core": "0.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "!dist/**/*.tsbuildinfo"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "check": "tsc -p tsconfig.json --noEmit",
37
+ "test": "tsc -p tsconfig.json && node --test test/*.test.mjs"
38
+ }
39
+ }