@filmwhisper/mcp-server 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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/data-integrity.test.d.ts +2 -0
  3. package/dist/__tests__/data-integrity.test.d.ts.map +1 -0
  4. package/dist/__tests__/data-integrity.test.js +171 -0
  5. package/dist/__tests__/data-integrity.test.js.map +1 -0
  6. package/dist/__tests__/engine/ipc-contract.test.d.ts +2 -0
  7. package/dist/__tests__/engine/ipc-contract.test.d.ts.map +1 -0
  8. package/dist/__tests__/engine/ipc-contract.test.js +122 -0
  9. package/dist/__tests__/engine/ipc-contract.test.js.map +1 -0
  10. package/dist/__tests__/fw-project-init.test.d.ts +2 -0
  11. package/dist/__tests__/fw-project-init.test.d.ts.map +1 -0
  12. package/dist/__tests__/fw-project-init.test.js +96 -0
  13. package/dist/__tests__/fw-project-init.test.js.map +1 -0
  14. package/dist/__tests__/helpers/mock-engine.d.ts +2 -0
  15. package/dist/__tests__/helpers/mock-engine.d.ts.map +1 -0
  16. package/dist/__tests__/helpers/mock-engine.js +81 -0
  17. package/dist/__tests__/helpers/mock-engine.js.map +1 -0
  18. package/dist/__tests__/r1-quality.test.d.ts +2 -0
  19. package/dist/__tests__/r1-quality.test.d.ts.map +1 -0
  20. package/dist/__tests__/r1-quality.test.js +539 -0
  21. package/dist/__tests__/r1-quality.test.js.map +1 -0
  22. package/dist/__tests__/search-fts5.test.d.ts +2 -0
  23. package/dist/__tests__/search-fts5.test.d.ts.map +1 -0
  24. package/dist/__tests__/search-fts5.test.js +135 -0
  25. package/dist/__tests__/search-fts5.test.js.map +1 -0
  26. package/dist/__tests__/tools/assemble.test.d.ts +2 -0
  27. package/dist/__tests__/tools/assemble.test.d.ts.map +1 -0
  28. package/dist/__tests__/tools/assemble.test.js +203 -0
  29. package/dist/__tests__/tools/assemble.test.js.map +1 -0
  30. package/dist/__tests__/tools/export.test.d.ts +2 -0
  31. package/dist/__tests__/tools/export.test.d.ts.map +1 -0
  32. package/dist/__tests__/tools/export.test.js +126 -0
  33. package/dist/__tests__/tools/export.test.js.map +1 -0
  34. package/dist/__tests__/tools/integration.test.d.ts +2 -0
  35. package/dist/__tests__/tools/integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/tools/integration.test.js +707 -0
  37. package/dist/__tests__/tools/integration.test.js.map +1 -0
  38. package/dist/__tests__/tools/relink.test.d.ts +2 -0
  39. package/dist/__tests__/tools/relink.test.d.ts.map +1 -0
  40. package/dist/__tests__/tools/relink.test.js +107 -0
  41. package/dist/__tests__/tools/relink.test.js.map +1 -0
  42. package/dist/__tests__/tools/render-preview.test.d.ts +2 -0
  43. package/dist/__tests__/tools/render-preview.test.d.ts.map +1 -0
  44. package/dist/__tests__/tools/render-preview.test.js +99 -0
  45. package/dist/__tests__/tools/render-preview.test.js.map +1 -0
  46. package/dist/engine/engine-client.d.ts +29 -0
  47. package/dist/engine/engine-client.d.ts.map +1 -0
  48. package/dist/engine/engine-client.js +211 -0
  49. package/dist/engine/engine-client.js.map +1 -0
  50. package/dist/engine/engine-manager.d.ts +31 -0
  51. package/dist/engine/engine-manager.d.ts.map +1 -0
  52. package/dist/engine/engine-manager.js +144 -0
  53. package/dist/engine/engine-manager.js.map +1 -0
  54. package/dist/engine/types.d.ts +6 -0
  55. package/dist/engine/types.d.ts.map +1 -0
  56. package/dist/engine/types.js +2 -0
  57. package/dist/engine/types.js.map +1 -0
  58. package/dist/helpers/errors.d.ts +13 -0
  59. package/dist/helpers/errors.d.ts.map +1 -0
  60. package/dist/helpers/errors.js +14 -0
  61. package/dist/helpers/errors.js.map +1 -0
  62. package/dist/helpers/media-extensions.d.ts +8 -0
  63. package/dist/helpers/media-extensions.d.ts.map +1 -0
  64. package/dist/helpers/media-extensions.js +39 -0
  65. package/dist/helpers/media-extensions.js.map +1 -0
  66. package/dist/helpers/undo.d.ts +55 -0
  67. package/dist/helpers/undo.d.ts.map +1 -0
  68. package/dist/helpers/undo.js +142 -0
  69. package/dist/helpers/undo.js.map +1 -0
  70. package/dist/index.d.ts +7 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +108 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/prompts.d.ts +6 -0
  75. package/dist/prompts.d.ts.map +1 -0
  76. package/dist/prompts.js +62 -0
  77. package/dist/prompts.js.map +1 -0
  78. package/dist/resources.d.ts +7 -0
  79. package/dist/resources.d.ts.map +1 -0
  80. package/dist/resources.js +59 -0
  81. package/dist/resources.js.map +1 -0
  82. package/dist/server.d.ts +19 -0
  83. package/dist/server.d.ts.map +1 -0
  84. package/dist/server.js +33 -0
  85. package/dist/server.js.map +1 -0
  86. package/dist/storage/project-store.d.ts +92 -0
  87. package/dist/storage/project-store.d.ts.map +1 -0
  88. package/dist/storage/project-store.js +399 -0
  89. package/dist/storage/project-store.js.map +1 -0
  90. package/dist/storage/sqlite-store.d.ts +207 -0
  91. package/dist/storage/sqlite-store.d.ts.map +1 -0
  92. package/dist/storage/sqlite-store.js +983 -0
  93. package/dist/storage/sqlite-store.js.map +1 -0
  94. package/dist/tools/assemble.d.ts +17 -0
  95. package/dist/tools/assemble.d.ts.map +1 -0
  96. package/dist/tools/assemble.js +237 -0
  97. package/dist/tools/assemble.js.map +1 -0
  98. package/dist/tools/edit.d.ts +7 -0
  99. package/dist/tools/edit.d.ts.map +1 -0
  100. package/dist/tools/edit.js +159 -0
  101. package/dist/tools/edit.js.map +1 -0
  102. package/dist/tools/export.d.ts +7 -0
  103. package/dist/tools/export.d.ts.map +1 -0
  104. package/dist/tools/export.js +108 -0
  105. package/dist/tools/export.js.map +1 -0
  106. package/dist/tools/inspect.d.ts +7 -0
  107. package/dist/tools/inspect.d.ts.map +1 -0
  108. package/dist/tools/inspect.js +238 -0
  109. package/dist/tools/inspect.js.map +1 -0
  110. package/dist/tools/process.d.ts +7 -0
  111. package/dist/tools/process.d.ts.map +1 -0
  112. package/dist/tools/process.js +211 -0
  113. package/dist/tools/process.js.map +1 -0
  114. package/dist/tools/project.d.ts +7 -0
  115. package/dist/tools/project.d.ts.map +1 -0
  116. package/dist/tools/project.js +178 -0
  117. package/dist/tools/project.js.map +1 -0
  118. package/dist/tools/search.d.ts +7 -0
  119. package/dist/tools/search.d.ts.map +1 -0
  120. package/dist/tools/search.js +75 -0
  121. package/dist/tools/search.js.map +1 -0
  122. package/dist/tools/utility.d.ts +7 -0
  123. package/dist/tools/utility.d.ts.map +1 -0
  124. package/dist/tools/utility.js +108 -0
  125. package/dist/tools/utility.js.map +1 -0
  126. package/package.json +40 -0
@@ -0,0 +1,178 @@
1
+ import { z } from "zod";
2
+ import { randomUUID } from "node:crypto";
3
+ import { SqliteStore } from "../storage/sqlite-store.js";
4
+ import { mcpError } from "../helpers/errors.js";
5
+ import { MCP_ERROR_CODES } from "@filmwhisper/shared-types";
6
+ export function registerProjectTools(server, deps) {
7
+ // ── fw_project_init ──────────────────────────────────────
8
+ server.tool("fw_project_init", "Initialize a new FilmWhisper project from a source directory", {
9
+ name: z.string().min(1).describe("Project name"),
10
+ source_path: z.string().min(1).describe("Path to source media directory"),
11
+ proxy_spec: z.string().optional().describe("Proxy resolution (e.g. 1280x720)"),
12
+ language: z.string().optional().describe("Primary language code (e.g. ko, en)"),
13
+ target_duration_seconds: z.number().positive().optional().describe("Target output duration in seconds"),
14
+ }, async (params) => {
15
+ try {
16
+ const projectId = `proj_${randomUUID().slice(0, 8)}`;
17
+ const projectPath = params.source_path;
18
+ const { assetsDiscovered } = await deps.projectStore.create(projectPath, {
19
+ name: params.name,
20
+ source_path: params.source_path,
21
+ language: params.language,
22
+ proxy_spec: params.proxy_spec,
23
+ target_duration_seconds: params.target_duration_seconds,
24
+ });
25
+ const sqliteStore = new SqliteStore(`${projectPath}/.fw/state.sqlite`);
26
+ deps.projects.set(projectId, { projectPath, sqliteStore });
27
+ const result = {
28
+ project_id: projectId,
29
+ assets_discovered: assetsDiscovered,
30
+ assets_new: assetsDiscovered,
31
+ assets_unchanged: 0,
32
+ assets_changed: 0,
33
+ revision: sqliteStore.getRevision("project"),
34
+ };
35
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
36
+ }
37
+ catch (err) {
38
+ const message = err instanceof Error ? err.message : String(err);
39
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
40
+ }
41
+ });
42
+ // ── fw_project_status ────────────────────────────────────
43
+ server.tool("fw_project_status", "Get comprehensive project status", { project_id: z.string().min(1) }, async (params) => {
44
+ const ctx = deps.projects.get(params.project_id);
45
+ if (!ctx)
46
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
47
+ try {
48
+ const assets = await deps.projectStore.listAssets(ctx.projectPath);
49
+ const reviews = ctx.sqliteStore.getReviews();
50
+ const { clips, totalDurationMs, revision: timelineRev } = ctx.sqliteStore.getTimeline();
51
+ const counters = (key) => ({
52
+ pending: assets.filter(a => a[key] === "pending").length,
53
+ running: assets.filter(a => a[key] === "running").length,
54
+ complete: assets.filter(a => a[key] === "complete").length,
55
+ failed: assets.filter(a => a[key] === "failed").length,
56
+ });
57
+ const keepCount = reviews.filter(r => r.status === "keep").length;
58
+ const excludeCount = reviews.filter(r => r.status === "exclude").length;
59
+ const needsReviewCount = reviews.filter(r => r.status === "needs_review").length;
60
+ const totalSegments = assets.reduce((sum, a) => sum + (a.segments?.length ?? 0), 0);
61
+ const reviewedSegments = reviews.length;
62
+ const result = {
63
+ assets: {
64
+ total: assets.length,
65
+ transcode: counters("transcode_status"),
66
+ transcribe: counters("transcribe_status"),
67
+ analyze: counters("analyze_status"),
68
+ },
69
+ reviews: {
70
+ keep: keepCount,
71
+ exclude: excludeCount,
72
+ needs_review: needsReviewCount,
73
+ unreviewed: Math.max(0, totalSegments - reviewedSegments),
74
+ },
75
+ edit_package: clips.length > 0 ? {
76
+ version: timelineRev,
77
+ status: "draft",
78
+ clip_count: clips.length,
79
+ duration: totalDurationMs,
80
+ } : null,
81
+ recommended_actions: generateRecommendations(assets),
82
+ engine_status: "stopped",
83
+ revisions: {
84
+ timeline: timelineRev,
85
+ reviews: ctx.sqliteStore.getRevision("reviews"),
86
+ jobs: ctx.sqliteStore.getRevision("jobs"),
87
+ },
88
+ };
89
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
90
+ }
91
+ catch (err) {
92
+ const message = err instanceof Error ? err.message : String(err);
93
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
94
+ }
95
+ });
96
+ // ── fw_project_delete ────────────────────────────────────
97
+ server.tool("fw_project_delete", "Delete a FilmWhisper project", {
98
+ project_id: z.string().min(1),
99
+ confirm: z.literal(true),
100
+ delete_source_files: z.boolean().optional(),
101
+ }, async (params) => {
102
+ const ctx = deps.projects.get(params.project_id);
103
+ if (!ctx)
104
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
105
+ try {
106
+ ctx.sqliteStore.close();
107
+ const { deletedFiles } = await deps.projectStore.deleteProject(ctx.projectPath);
108
+ deps.projects.delete(params.project_id);
109
+ const result = {
110
+ deleted_assets_count: deletedFiles.filter(f => f.endsWith(".fw.json")).length,
111
+ deleted_files: deletedFiles,
112
+ };
113
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
114
+ }
115
+ catch (err) {
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
118
+ }
119
+ });
120
+ // ── fw_project_rescan ────────────────────────────────────
121
+ server.tool("fw_project_rescan", "Rescan source directory for new, changed, or removed media files", { project_id: z.string().min(1) }, async (params) => {
122
+ const ctx = deps.projects.get(params.project_id);
123
+ if (!ctx)
124
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
125
+ try {
126
+ const scanResult = await deps.projectStore.rescan(ctx.projectPath);
127
+ // Cross-store reconciliation: find JSON asset files with no SQLite entries
128
+ const jsonAssets = await deps.projectStore.listAssets(ctx.projectPath);
129
+ const sqliteHashes = ctx.sqliteStore.getAssetHashes();
130
+ // Note: JSON-only hashes (no SQLite entries) are expected for newly-created assets
131
+ // Detect hashes SQLite knows about but that have no JSON asset file
132
+ const jsonHashSet = new Set(jsonAssets.map((a) => a.asset_hash));
133
+ const sqliteOnlyHashes = [];
134
+ for (const hash of sqliteHashes) {
135
+ if (!jsonHashSet.has(hash)) {
136
+ sqliteOnlyHashes.push(hash);
137
+ console.warn(`[rescan] SQLite references asset ${hash} but no JSON asset file found`);
138
+ }
139
+ }
140
+ const result = {
141
+ assets_new: scanResult.assetsNew,
142
+ assets_changed: scanResult.assetsChanged,
143
+ assets_removed: scanResult.assetsRemoved,
144
+ assets_unchanged: scanResult.assetsUnchanged,
145
+ reconciliation: {
146
+ sqlite_only_hashes: sqliteOnlyHashes,
147
+ mismatch_count: sqliteOnlyHashes.length,
148
+ },
149
+ revisions: {
150
+ timeline: ctx.sqliteStore.getRevision("timeline"),
151
+ reviews: ctx.sqliteStore.getRevision("reviews"),
152
+ fts: ctx.sqliteStore.getRevision("fts"),
153
+ },
154
+ };
155
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
156
+ }
157
+ catch (err) {
158
+ const message = err instanceof Error ? err.message : String(err);
159
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
160
+ }
161
+ });
162
+ }
163
+ function generateRecommendations(assets) {
164
+ const actions = [];
165
+ const pendingTranscode = assets.filter(a => a.transcode_status === "pending").length;
166
+ const pendingTranscribe = assets.filter(a => a.transcribe_status === "pending").length;
167
+ const pendingAnalyze = assets.filter(a => a.analyze_status === "pending").length;
168
+ if (pendingTranscode > 0)
169
+ actions.push(`Run fw_process to transcode ${pendingTranscode} asset(s)`);
170
+ if (pendingTranscribe > 0)
171
+ actions.push(`Run fw_process to transcribe ${pendingTranscribe} asset(s)`);
172
+ if (pendingAnalyze > 0)
173
+ actions.push(`Run fw_process to analyze ${pendingAnalyze} asset(s)`);
174
+ if (assets.length === 0)
175
+ actions.push("Add media files to the source directory and run fw_project_rescan");
176
+ return actions;
177
+ }
178
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/tools/project.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SEARCH tools — L0 in-memory + L1 FTS5 text search across project assets.
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import type { ServerDeps } from "../server.js";
6
+ export declare function registerSearchTools(server: McpServer, deps: ServerDeps): void;
7
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CA6F7E"}
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ import { mcpError } from "../helpers/errors.js";
3
+ import { MCP_ERROR_CODES } from "@filmwhisper/shared-types";
4
+ export function registerSearchTools(server, deps) {
5
+ // ── fw_search ─────────────────────────────────────────────
6
+ server.tool("fw_search", "Search through project assets using in-memory (L0) or FTS5 (L1) text search", {
7
+ project_id: z.string().min(1),
8
+ query: z.string().min(1),
9
+ limit: z.number().int().min(1).max(100).optional(),
10
+ cursor: z.string().optional(),
11
+ }, async (params) => {
12
+ const ctx = deps.projects.get(params.project_id);
13
+ if (!ctx)
14
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
15
+ try {
16
+ const assets = await deps.projectStore.listAssets(ctx.projectPath);
17
+ const threshold = parseInt(process.env.FW_SEARCH_THRESHOLD ?? "20", 10);
18
+ const limit = params.limit ?? 20;
19
+ const startIndex = params.cursor ? parseInt(params.cursor, 10) : 0;
20
+ // Use FTS5 when segments_source has data AND asset count >= threshold
21
+ const useFts5 = assets.length >= threshold && ctx.sqliteStore.hasSegmentsData();
22
+ if (useFts5) {
23
+ const ftsResults = ctx.sqliteStore.searchFts5(params.query, limit, startIndex);
24
+ const result = {
25
+ results: ftsResults.map((r) => ({
26
+ asset_hash: r.asset_hash,
27
+ segment_index: r.segment_index,
28
+ score: Math.abs(r.rank), // FTS5 rank is negative, lower is better
29
+ snippet: r.transcript_text ?? r.analysis_summary ?? "",
30
+ editorial_role: r.editorial_role ?? "",
31
+ })),
32
+ backend_used: "fts5",
33
+ };
34
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
35
+ }
36
+ // L0: in-memory scan for small projects or when FTS5 not populated
37
+ const queryLower = params.query.toLowerCase();
38
+ const results = [];
39
+ for (const asset of assets) {
40
+ const segments = asset.segments ?? [];
41
+ for (const segment of segments) {
42
+ const text = segment.summary;
43
+ const textLower = text.toLowerCase();
44
+ if (!textLower.includes(queryLower))
45
+ continue;
46
+ // Score: 2 for exact match at word boundary, 1 for substring match
47
+ const exactWordMatch = new RegExp(`\\b${queryLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(text);
48
+ const score = exactWordMatch ? 2 : 1;
49
+ results.push({
50
+ asset_hash: asset.asset_hash,
51
+ segment_index: segment.segment_index,
52
+ score,
53
+ snippet: text,
54
+ editorial_role: segment.editorial_role,
55
+ });
56
+ }
57
+ }
58
+ // Sort by score descending (exact matches first)
59
+ results.sort((a, b) => b.score - a.score);
60
+ const page = results.slice(startIndex, startIndex + limit);
61
+ const nextCursor = startIndex + limit < results.length ? String(startIndex + limit) : undefined;
62
+ const result = {
63
+ results: page,
64
+ backend_used: "memory",
65
+ ...(nextCursor ? { next_cursor: nextCursor } : {}),
66
+ };
67
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
68
+ }
69
+ catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
72
+ }
73
+ });
74
+ }
75
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,IAAgB;IACrE,6DAA6D;IAC7D,MAAM,CAAC,IAAI,CACT,WAAW,EACX,6EAA6E,EAC7E;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAClD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC9B,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjD,IAAI,CAAC,GAAG;YAAE,OAAO,QAAQ,CAAC,eAAe,CAAC,iBAAiB,EAAE,WAAW,MAAM,CAAC,UAAU,YAAY,CAAC,CAAC;QAEvG,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACnE,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;YACxE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAEnE,sEAAsE;YACtE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,SAAS,IAAI,GAAG,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC;YAEhF,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;gBAE/E,MAAM,MAAM,GAAG;oBACb,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBAC9B,UAAU,EAAE,CAAC,CAAC,UAAU;wBACxB,aAAa,EAAE,CAAC,CAAC,aAAa;wBAC9B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,yCAAyC;wBAClE,OAAO,EAAE,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,gBAAgB,IAAI,EAAE;wBACtD,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,EAAE;qBACvC,CAAC,CAAC;oBACH,YAAY,EAAE,MAAe;iBAC9B,CAAC;gBACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAChF,CAAC;YAED,mEAAmE;YACnE,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YAU9C,MAAM,OAAO,GAAmB,EAAE,CAAC;YAEnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;gBACtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;oBAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;oBAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;oBAErC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC;wBAAE,SAAS;oBAE9C,mEAAmE;oBACnE,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAChH,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAErC,OAAO,CAAC,IAAI,CAAC;wBACX,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,aAAa,EAAE,OAAO,CAAC,aAAa;wBACpC,KAAK;wBACL,OAAO,EAAE,IAAI;wBACb,cAAc,EAAE,OAAO,CAAC,cAAc;qBACvC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,iDAAiD;YACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YAE1C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,KAAK,CAAC,CAAC;YAC3D,MAAM,UAAU,GAAG,UAAU,GAAG,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAEhG,MAAM,MAAM,GAAG;gBACb,OAAO,EAAE,IAAI;gBACb,YAAY,EAAE,QAAiB;gBAC/B,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnD,CAAC;YACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,QAAQ,CAAC,eAAe,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * UTILITY tools — undo, redo, and asset removal.
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import type { ServerDeps } from "../server.js";
6
+ export declare function registerUtilityTools(server: McpServer, deps: ServerDeps): void;
7
+ //# sourceMappingURL=utility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utility.d.ts","sourceRoot":"","sources":["../../src/tools/utility.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK/C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAoI9E"}
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { mcpError } from "../helpers/errors.js";
3
+ import { MCP_ERROR_CODES } from "@filmwhisper/shared-types";
4
+ import { existsSync } from "node:fs";
5
+ export function registerUtilityTools(server, deps) {
6
+ // ── fw_undo ──────────────────────────────────────────────
7
+ server.tool("fw_undo", "Undo the last operation(s)", {
8
+ project_id: z.string().min(1),
9
+ steps: z.number().int().positive().optional(),
10
+ }, async (params) => {
11
+ const ctx = deps.projects.get(params.project_id);
12
+ if (!ctx)
13
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
14
+ const { undoneOperations, remainingHistoryCount, revision } = ctx.sqliteStore.undo(params.steps);
15
+ const result = {
16
+ undone_operations: undoneOperations.map(op => ({ scope: op.scope, type: op.type })),
17
+ remaining_history_count: remainingHistoryCount,
18
+ revision,
19
+ };
20
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
21
+ });
22
+ // ── fw_redo ──────────────────────────────────────────────
23
+ server.tool("fw_redo", "Redo the last undone operation(s)", {
24
+ project_id: z.string().min(1),
25
+ steps: z.number().int().positive().optional(),
26
+ }, async (params) => {
27
+ const ctx = deps.projects.get(params.project_id);
28
+ if (!ctx)
29
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
30
+ const { redoneOperations, revision } = ctx.sqliteStore.redo(params.steps);
31
+ const result = {
32
+ redone_operations: redoneOperations.map(op => ({ scope: op.scope, type: op.type })),
33
+ revision,
34
+ };
35
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
36
+ });
37
+ // ── fw_asset_remove ──────────────────────────────────────
38
+ server.tool("fw_asset_remove", "Remove assets from the project (cascade deletes related data)", {
39
+ project_id: z.string().min(1),
40
+ asset_ids: z.array(z.string().min(1)),
41
+ remove_from_timeline: z.boolean().optional(),
42
+ expected_revision: z.number().int().optional(),
43
+ }, async (params) => {
44
+ const ctx = deps.projects.get(params.project_id);
45
+ if (!ctx)
46
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
47
+ try {
48
+ if (params.expected_revision !== undefined) {
49
+ ctx.sqliteStore.checkExpectedRevision("timeline", params.expected_revision);
50
+ }
51
+ const { timelineClipsAffected } = ctx.sqliteStore.removeAssetData(params.asset_ids, params.remove_from_timeline ?? true);
52
+ const removedCount = await deps.projectStore.removeAssets(ctx.projectPath, params.asset_ids);
53
+ const result = {
54
+ removed_count: removedCount,
55
+ timeline_clips_affected: timelineClipsAffected,
56
+ revisions: {
57
+ timeline: ctx.sqliteStore.getRevision("timeline"),
58
+ reviews: ctx.sqliteStore.getRevision("reviews"),
59
+ },
60
+ };
61
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
62
+ }
63
+ catch (err) {
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ if (message.includes("REVISION_CONFLICT")) {
66
+ return mcpError(MCP_ERROR_CODES.REVISION_CONFLICT, message);
67
+ }
68
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
69
+ }
70
+ });
71
+ // ── fw_project_relink ──────────────────────────────────
72
+ server.tool("fw_project_relink", "Relink asset source paths after moving project files", {
73
+ project_id: z.string().min(1),
74
+ old_base_path: z.string().min(1),
75
+ new_base_path: z.string().min(1),
76
+ }, async (params) => {
77
+ const ctx = deps.projects.get(params.project_id);
78
+ if (!ctx)
79
+ return mcpError(MCP_ERROR_CODES.PROJECT_NOT_FOUND, `Project ${params.project_id} not found`);
80
+ try {
81
+ const assets = await deps.projectStore.listAssets(ctx.projectPath);
82
+ let relinkedCount = 0;
83
+ const missingFiles = [];
84
+ for (const asset of assets) {
85
+ if (asset.source_path.startsWith(params.old_base_path)) {
86
+ const newPath = asset.source_path.replace(params.old_base_path, params.new_base_path);
87
+ await deps.projectStore.updateAssetSourcePath(ctx.projectPath, asset.asset_hash, newPath);
88
+ relinkedCount++;
89
+ if (!existsSync(newPath)) {
90
+ missingFiles.push(newPath);
91
+ }
92
+ }
93
+ }
94
+ const result = {
95
+ relinked_count: relinkedCount,
96
+ missing_count: missingFiles.length,
97
+ missing_files: missingFiles,
98
+ revision: ctx.sqliteStore.getRevision("project"),
99
+ };
100
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
101
+ }
102
+ catch (err) {
103
+ const message = err instanceof Error ? err.message : String(err);
104
+ return mcpError(MCP_ERROR_CODES.INVALID_INPUT, message);
105
+ }
106
+ });
107
+ }
108
+ //# sourceMappingURL=utility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utility.js","sourceRoot":"","sources":["../../src/tools/utility.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@filmwhisper/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for AI-driven video editing",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./server": "./dist/server.js",
10
+ "./storage/project-store": "./dist/storage/project-store.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "package.json",
15
+ "README.md"
16
+ ],
17
+ "bin": {
18
+ "filmwhisper-mcp": "./dist/index.js"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.12.0",
22
+ "better-sqlite3": "^12.8.0",
23
+ "zod": "^4.3.6",
24
+ "@filmwhisper/shared-types": "0.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/better-sqlite3": "^7.6.13",
28
+ "@types/node": "^25.5.0",
29
+ "tsx": "^4.19.0",
30
+ "typescript": "^5.7.0",
31
+ "vitest": "^3.2.4"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "dev": "tsx watch src/index.ts",
36
+ "test": "vitest run --passWithNoTests",
37
+ "type-check": "tsc --noEmit",
38
+ "lint": "echo 'no lint configured yet'"
39
+ }
40
+ }