From 71bb019dfe43b4d07bc4bb7eb7f70564c5a217c7 Mon Sep 17 00:00:00 2001 From: "yuito.watanabe" <72675978+wwwyo@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:57:31 +0900 Subject: [PATCH] feat(mcp): expose project source frames over HTTP Allow authenticated MCP clients to inspect recordings before editing while restricting frame reads to project assets and one decoder window at a time. --- electron/ipc/handlers.ts | 2 + electron/mcp/media-tools.ts | 2 +- electron/mcp/openscreen-mcp-server.test.ts | 80 +++++++++++++++- electron/mcp/openscreen-mcp-server.ts | 8 +- electron/mcp/project-frame-tools.ts | 93 +++++++++++++++++++ website/docs/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- .../current/ai-editing.md | 2 +- 13 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 electron/mcp/project-frame-tools.ts diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index ca1bc88b3..1e46430fb 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -78,6 +78,7 @@ import { setRecordingsDir, } from "../main"; import { EditorDocumentHost } from "../mcp/editor-document-host"; +import { openVideoInHiddenWindow } from "../mcp/hidden-video"; import { McpController } from "../mcp/mcp-controller"; import { McpSettingsStore } from "../mcp/mcp-settings-store"; import type { McpToolDeps } from "../mcp/openscreen-mcp-server"; @@ -5038,6 +5039,7 @@ export function registerIpcHandlers( editsAllowed: () => mcpSettings.getSettings().allowEdits, cursor: agentCursorTelemetryReader, version: app.getVersion(), + openVideo: openVideoInHiddenWindow, }; const mcpController = new McpController(mcpSettings, mcpToolDeps); diff --git a/electron/mcp/media-tools.ts b/electron/mcp/media-tools.ts index 4c7fc2bb0..4d282769a 100644 --- a/electron/mcp/media-tools.ts +++ b/electron/mcp/media-tools.ts @@ -60,7 +60,7 @@ export interface MediaToolDeps { export const MAX_FRAMES = 12; // Wide enough to read UI text, small enough that a dozen frames stay cheap. -const FRAME_WIDTH = 1280; +export const FRAME_WIDTH = 1280; // A seek to the exact end shows no frame in Chromium; stop one frame short of it. const END_MARGIN_SEC = 1 / 60; diff --git a/electron/mcp/openscreen-mcp-server.test.ts b/electron/mcp/openscreen-mcp-server.test.ts index 485a95826..cca2af0f1 100644 --- a/electron/mcp/openscreen-mcp-server.test.ts +++ b/electron/mcp/openscreen-mcp-server.test.ts @@ -18,6 +18,7 @@ import { import { OPENSCREEN_TOOL_NAMES } from "../ai-edition/agent-tools"; import { TOOL_DESCRIPTIONS } from "../ai-edition/deep-agent/service"; import { DocumentService } from "../ai-edition/document-service"; +import type { OpenedVideo } from "./hidden-video"; import { LIST_PROJECTS_TOOL, MCP_ENDPOINT_PATH, @@ -123,8 +124,21 @@ let running: RunningMcpServer | null = null; let client: Client | null = null; let dir: string; let projects: DocumentService; +const openVideo = vi.fn( + async (_videoPath: string): Promise => ({ + durationSec: 10, + width: 1920, + height: 1080, + capture: async (timeSec) => ({ + mimeType: "image/jpeg", + data: Buffer.from(`frame@${timeSec}`).toString("base64"), + }), + close: () => undefined, + }), +); beforeEach(() => { + openVideo.mockClear(); dir = mkdtempSync(path.join(os.tmpdir(), "openscreen-mcp-projects-")); projects = new DocumentService(path.join(dir, "projects"), dir); }); @@ -149,6 +163,7 @@ async function connect( projects, editsAllowed: () => options.editsAllowed ?? true, version: "0.0.0", + openVideo, }, }); client = new Client({ name: "test", version: "0.0.0" }); @@ -167,7 +182,7 @@ function resultText(result: Awaited>): string { } describe("the MCP tool surface", () => { - it("is listProjects, then exactly the in-app agent's tools with its descriptions, then the checkpoints", async () => { + it("lists document tools, checkpoints, and source frames", async () => { const mcp = await connect(new FakeEditor()); const { tools } = await mcp.listTools(); expect(tools.map((t) => t.name)).toEqual([ @@ -175,6 +190,7 @@ describe("the MCP tool surface", () => { ...OPENSCREEN_TOOL_NAMES, "createCheckpoint", "restoreCheckpoint", + "getFrames", ]); for (const tool of tools.slice(1, 1 + OPENSCREEN_TOOL_NAMES.length)) { expect(tool.description).toBe(TOOL_DESCRIPTIONS[tool.name]); @@ -182,7 +198,7 @@ describe("the MCP tool surface", () => { expect(Object.keys(tool.inputSchema.properties ?? {})).toContain("projectId"); expect(tool.inputSchema.required ?? []).not.toContain("projectId"); } - for (const tool of tools.slice(-2)) { + for (const tool of tools.slice(-3, -1)) { expect(Object.keys(tool.inputSchema.properties ?? {})).toContain("projectId"); } // The zod schemas survive the trip to JSON Schema with their fields intact. @@ -206,6 +222,66 @@ describe("the MCP tool surface", () => { expect(byName.get("removeClip")?.destructiveHint).toBe(true); expect(byName.get("createCheckpoint")?.readOnlyHint).toBe(true); expect(byName.get("restoreCheckpoint")?.destructiveHint).toBe(true); + expect(byName.get("getFrames")?.readOnlyHint).toBe(true); + }); + + it("returns only frames from the selected project's video asset", async () => { + const mcp = await connect(new FakeEditor()); + const { tools } = await mcp.listTools(); + const framesTool = tools.find((tool) => tool.name === "getFrames"); + expect(Object.keys(framesTool?.inputSchema.properties ?? {})).not.toContain("path"); + const result = await mcp.callTool({ name: "getFrames", arguments: { atSec: [1, 5] } }); + expect(result.isError).toBeFalsy(); + expect(openVideo).toHaveBeenCalledWith("/tmp/rec.mp4"); + const content = result.content as Array<{ type: string }>; + expect(content.filter((part) => part.type === "image")).toHaveLength(2); + expect(resultText(result)).toContain("These do not show edits"); + const missing = await mcp.callTool({ name: "getFrames", arguments: { assetId: "unknown" } }); + expect(missing.isError).toBe(true); + expect(openVideo).toHaveBeenCalledTimes(1); + }); + + it("reads a named project without enabling edits", async () => { + const other = await projects.createProject("Other"); + const source = path.join(dir, "other.mp4"); + writeFileSync(source, "video stub"); + await projects.addAsset(other.project.id, { path: source }); + const mcp = await connect(new FakeEditor(), { editsAllowed: false }); + const result = await mcp.callTool({ + name: "getFrames", + arguments: { projectId: other.project.id, atSec: [2] }, + }); + expect(result.isError).toBeFalsy(); + expect(openVideo).toHaveBeenCalledWith(source); + }); + + it("limits concurrent frame extraction to one decoder window", async () => { + let releaseCapture: () => void = () => undefined; + const paused = new Promise((resolve) => { + releaseCapture = resolve; + }); + openVideo.mockImplementationOnce(async () => ({ + durationSec: 10, + width: 1920, + height: 1080, + capture: async () => { + await paused; + return { mimeType: "image/jpeg", data: "aGVsbG8=" }; + }, + close: () => undefined, + })); + const mcp = await connect(new FakeEditor()); + const first = mcp.callTool({ name: "getFrames", arguments: { atSec: [1] } }); + try { + await vi.waitFor(() => expect(openVideo).toHaveBeenCalledTimes(1)); + const second = await mcp.callTool({ name: "getFrames", arguments: { atSec: [2] } }); + expect(second.isError).toBe(true); + expect(resultText(second)).toContain("already running"); + expect(openVideo).toHaveBeenCalledTimes(1); + } finally { + releaseCapture(); + } + expect((await first).isError).toBeFalsy(); }); it("hands the client the in-app agent's guidance as server instructions", async () => { diff --git a/electron/mcp/openscreen-mcp-server.ts b/electron/mcp/openscreen-mcp-server.ts index a353b6301..d799a802b 100644 --- a/electron/mcp/openscreen-mcp-server.ts +++ b/electron/mcp/openscreen-mcp-server.ts @@ -52,6 +52,8 @@ import { type ProjectSummary, type ProjectVersion, } from "../ai-edition/document-service"; +import type { OpenedVideo } from "./hidden-video"; +import { createProjectFrameToolRegistrar } from "./project-frame-tools"; export const MCP_SERVER_NAME = "openscreen"; export const MCP_ENDPOINT_PATH = "/mcp"; @@ -106,6 +108,8 @@ export interface McpToolDeps { editsAllowed(): boolean; cursor?: CursorTelemetryReader; version: string; + /** Available in the GUI process for read-only source frames over HTTP. */ + openVideo?: (videoPath: string) => Promise; } export const LIST_PROJECTS_TOOL = "listProjects"; @@ -150,7 +154,7 @@ const DESTRUCTIVE_TOOLS: ReadonlySet = new Set([ ]); const MCP_PREAMBLE = [ - "These tools act on the user's OpenScreen projects. Without a projectId they act on the project open in the editor; call listProjects to find any other project and pass its id as projectId. Every edit is saved straight away. An edit to the open project appears in the editor, where the user can undo it with Ctrl/Cmd+Z; an edit to any other project is saved to its file and is not on the editor's undo stack. Nothing here records, exports or imports media.", + "These tools act on the user's OpenScreen projects. Without a projectId they act on the project open in the editor; call listProjects to find any other project and pass its id as projectId. Every edit is saved straight away. An edit to the open project appears in the editor, where the user can undo it with Ctrl/Cmd+Z; an edit to any other project is saved to its file and is not on the editor's undo stack. getFrames reads source recording frames, not the edited preview or export. Nothing here records, exports or imports media.", "Before a series of edits, call createCheckpoint with the same projectId. Undo is one step per call, so if the result is not what the user wanted, restoreCheckpoint takes the project back in one step instead of many. A project that is not open in the editor cannot be edited until it has a checkpoint, since restoreCheckpoint is its only undo.", "", ].join("\n"); @@ -453,6 +457,7 @@ export async function startMcpHttpServer(options: { }): Promise { const { token, deps } = options; const runTool = createToolRunner(deps); + const registerFrames = createProjectFrameToolRegistrar(deps); let boundPort = options.port; const handle = async (req: IncomingMessage, res: ServerResponse) => { @@ -470,6 +475,7 @@ export async function startMcpHttpServer(options: { // for a server that keeps no per-session state. Tools read the live editor // on every call, so there is nothing a session would need to remember. const server = createOpenScreenMcpServer(deps, runTool); + registerFrames(server); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, diff --git a/electron/mcp/project-frame-tools.ts b/electron/mcp/project-frame-tools.ts new file mode 100644 index 000000000..bdf835a0c --- /dev/null +++ b/electron/mcp/project-frame-tools.ts @@ -0,0 +1,93 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { documentSchema } from "../../src/lib/ai-edition/schema"; +import { DocumentNotFoundError } from "../ai-edition/document-service"; +import type { OpenedVideo } from "./hidden-video"; +import { FRAME_WIDTH, frameTimes, MAX_FRAMES } from "./media-tools"; +import type { McpToolDeps } from "./openscreen-mcp-server"; + +function error(message: string): CallToolResult { + return { content: [{ type: "text", text: message }], isError: true }; +} + +export function createProjectFrameToolRegistrar(deps: McpToolDeps): (server: McpServer) => void { + const openVideo = deps.openVideo; + if (!openVideo) return () => undefined; + let busy = false; + return (server) => + server.registerTool( + "getFrames", + { + description: + "Read still frames from a video asset in an OpenScreen project. These are frames of the original recording, not the edited preview or export. Omit projectId for the project open in the editor; omit assetId for its primary video. Times are seconds in that source video. At most 12 frames per call.", + inputSchema: z.object({ + projectId: z.string().optional(), + assetId: z.string().optional(), + atSec: z.array(z.number().nonnegative()).max(MAX_FRAMES).optional(), + count: z.number().int().min(1).max(MAX_FRAMES).optional(), + }), + annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, + }, + async ({ projectId, assetId, atSec, count }) => { + if (busy) return error("Frame extraction is already running. Try again when it finishes."); + busy = true; + try { + const snapshot = await deps.host.snapshot(); + let rawDocument: unknown; + if (projectId === undefined) { + if (!snapshot) return error("No project is open in the OpenScreen editor."); + rawDocument = snapshot.document; + } else if ( + (snapshot?.document as { project?: { id?: unknown } } | null)?.project?.id === projectId + ) { + rawDocument = snapshot?.document; + } else { + try { + rawDocument = (await deps.projects.getProjectForUpdate(projectId)).document; + } catch (cause) { + if (cause instanceof DocumentNotFoundError) + return error(`No project has the id "${projectId}".`); + throw cause; + } + } + const parsed = documentSchema.safeParse(rawDocument); + if (!parsed.success) return error("The project could not be read."); + const document = parsed.data; + const selectedId = assetId ?? document.project.primaryAssetId; + const asset = document.assets.find( + (item) => item.id === selectedId && item.kind === "video", + ); + if (!asset) return error("No matching video asset exists in this project."); + + let video: OpenedVideo; + try { + video = await openVideo(asset.originalPath); + } catch { + return error("OpenScreen could not decode this project's video asset."); + } + try { + const times = frameTimes(video.durationSec, atSec ?? null, count ?? 6); + const content: CallToolResult["content"] = [ + { + type: "text", + text: `${times.length} source frame(s) from asset ${asset.id}; ${video.width}x${video.height}, ${video.durationSec.toFixed(2)}s. These do not show edits.`, + }, + ]; + for (const time of times) { + const frame = await video.capture(time, FRAME_WIDTH); + content.push({ type: "text", text: `${time.toFixed(2)}s in source video` }); + content.push({ type: "image", data: frame.data, mimeType: frame.mimeType }); + } + return { content }; + } catch { + return error("OpenScreen could not capture frames from this project's video asset."); + } finally { + video.close(); + } + } finally { + busy = false; + } + }, + ); +} diff --git a/website/docs/ai-editing.md b/website/docs/ai-editing.md index 9a02d3947..d687d6816 100644 --- a/website/docs/ai-editing.md +++ b/website/docs/ai-editing.md @@ -67,7 +67,7 @@ If you already use an AI coding agent such as Claude Code or Codex, it can drive 2. Copy the **Claude Code** or **Codex** command shown there and run it in a terminal. The Claude Code command carries the access token. Codex reads it from the `OPENSCREEN_MCP_TOKEN` environment variable instead: set that in the shell you start Codex from. 3. Keep a project open in the OpenScreen editor, then ask your agent for the edit. -The tools are the ones the built-in agent uses, and they act on the project open in the editor. MCP clients can only **read** the project until you also turn on **Project edits** in the MCP server section; this switch is separate from the built-in agent's, and it starts off. Once on, each edit is saved as it lands and undone with `Ctrl/Cmd + Z`. Clients are told to save a checkpoint before a series of edits, so you can ask them to put the project back as it was in a single step, which `Ctrl/Cmd + Z` can itself undo. **Regenerate** the token to disconnect every client set up with the old one. +The tools are the ones the built-in agent uses, and they act on the project open in the editor. The HTTP server also offers `getFrames` for a project's original video asset. Its images show the source recording, not trims, zooms or other edits; it does not accept an arbitrary file path. MCP clients can only **read** the project until you also turn on **Project edits** in the MCP server section; this switch is separate from the built-in agent's, and it starts off. Once on, each edit is saved as it lands and undone with `Ctrl/Cmd + Z`. Clients are told to save a checkpoint before a series of edits, so you can ask them to put the project back as it was in a single step, which `Ctrl/Cmd + Z` can itself undo. **Regenerate** the token to disconnect every client set up with the old one. ## Letting an agent make the whole video diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/de/docusaurus-plugin-content-docs/current/ai-editing.md index 1b00bbf75..b455af195 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ Wenn du bereits einen KI-Coding-Agenten wie Claude Code oder Codex nutzt, kann e 2. Kopiere den dort angezeigten Befehl für **Claude Code** oder **Codex** und führe ihn in einem Terminal aus. Der Befehl für Claude Code enthält das Zugriffstoken. Codex liest es stattdessen aus der Umgebungsvariable `OPENSCREEN_MCP_TOKEN`: Setze sie in der Shell, aus der du Codex startest. 3. Lass ein Projekt im OpenScreen-Editor geöffnet und bitte dann deinen Agenten um die Änderung. -Die Werkzeuge sind die, die der eingebaute Agent nutzt, und sie wirken auf das im Editor geöffnete Projekt. MCP-Clients können das Projekt nur **lesen**, bis du im Abschnitt MCP-Server auch **Project edits** einschaltest; dieser Schalter ist von dem des eingebauten Agenten getrennt und steht anfangs auf aus. Ist er an, wird jede Änderung gespeichert, sobald sie ankommt, und mit `Ctrl/Cmd + Z` rückgängig gemacht. Clients werden angewiesen, vor einer Reihe von Änderungen einen Wiederherstellungspunkt zu speichern. So kannst du sie bitten, das Projekt in einem einzigen Schritt wiederherzustellen, wie es war, und auch diesen Schritt kann `Ctrl/Cmd + Z` rückgängig machen. Wenn du das Token mit **Regenerate** ersetzt, werden alle Clients getrennt, die mit dem alten eingerichtet wurden. +Die Werkzeuge sind die, die der eingebaute Agent nutzt, und sie wirken auf das im Editor geöffnete Projekt. Der HTTP-Server bietet außerdem `getFrames` für Einzelbilder aus dem Originalvideo eines Projekts. Die Bilder zeigen die Quellaufnahme ohne Schnitte, Zooms oder andere Bearbeitungen; ein beliebiger Dateipfad ist nicht zulässig. MCP-Clients können das Projekt nur **lesen**, bis du im Abschnitt MCP-Server auch **Project edits** einschaltest; dieser Schalter ist von dem des eingebauten Agenten getrennt und steht anfangs auf aus. Ist er an, wird jede Änderung gespeichert, sobald sie ankommt, und mit `Ctrl/Cmd + Z` rückgängig gemacht. Clients werden angewiesen, vor einer Reihe von Änderungen einen Wiederherstellungspunkt zu speichern. So kannst du sie bitten, das Projekt in einem einzigen Schritt wiederherzustellen, wie es war, und auch diesen Schritt kann `Ctrl/Cmd + Z` rückgängig machen. Wenn du das Token mit **Regenerate** ersetzt, werden alle Clients getrennt, die mit dem alten eingerichtet wurden. ## Einen Agenten das ganze Video erstellen lassen {#letting-an-agent-make-the-whole-video} diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/es/docusaurus-plugin-content-docs/current/ai-editing.md index c43ae2eef..4db1b5774 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ Si ya usas un agente de código con IA como Claude Code o Codex, puede manejar l 2. Copia el comando de **Claude Code** o de **Codex** que aparece ahí y ejecútalo en una terminal. El comando de Claude Code lleva el token de acceso. Codex, en cambio, lo lee de la variable de entorno `OPENSCREEN_MCP_TOKEN`: defínela en el shell desde el que inicias Codex. 3. Mantén un proyecto abierto en el editor de OpenScreen y luego pídele la edición a tu agente. -Las herramientas son las que usa el agente integrado, y actúan sobre el proyecto abierto en el editor. Los clientes MCP solo pueden **leer** el proyecto hasta que también actives **Ediciones del proyecto** en la sección Servidor MCP; este interruptor es independiente del del agente integrado, y empieza desactivado. Una vez activado, cada edición se guarda en cuanto se aplica y se deshace con `Ctrl/Cmd + Z`. A los clientes se les indica que guarden un punto de restauración antes de una serie de ediciones, así que puedes pedirles que dejen el proyecto como estaba en un solo paso, que el propio `Ctrl/Cmd + Z` puede deshacer. **Regenerar** el token desconecta a todos los clientes configurados con el anterior. +Las herramientas son las que usa el agente integrado, y actúan sobre el proyecto abierto en el editor. El servidor HTTP también ofrece `getFrames` para obtener fotogramas del vídeo original de un proyecto. Las imágenes muestran la grabación de origen, sin recortes, zooms ni otras ediciones; no acepta una ruta de archivo arbitraria. Los clientes MCP solo pueden **leer** el proyecto hasta que también actives **Ediciones del proyecto** en la sección Servidor MCP; este interruptor es independiente del del agente integrado, y empieza desactivado. Una vez activado, cada edición se guarda en cuanto se aplica y se deshace con `Ctrl/Cmd + Z`. A los clientes se les indica que guarden un punto de restauración antes de una serie de ediciones, así que puedes pedirles que dejen el proyecto como estaba en un solo paso, que el propio `Ctrl/Cmd + Z` puede deshacer. **Regenerar** el token desconecta a todos los clientes configurados con el anterior. ## Dejar que un agente haga todo el video {#letting-an-agent-make-the-whole-video} diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/ai-editing.md index ee65529d0..656ac6427 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ Si vous utilisez déjà un agent de code IA comme Claude Code ou Codex, il peut 2. Copiez la commande **Claude Code** ou **Codex** affichée à cet endroit et lancez-la dans un terminal. La commande Claude Code contient le jeton d'accès. Codex, lui, le lit dans la variable d'environnement `OPENSCREEN_MCP_TOKEN` : définissez-la dans le shell depuis lequel vous lancez Codex. 3. Gardez un projet ouvert dans l'éditeur d'OpenScreen, puis demandez la modification à votre agent. -Les outils sont ceux qu'utilise l'agent intégré, et ils agissent sur le projet ouvert dans l'éditeur. Les clients MCP ne peuvent que **lire** le projet tant que vous n'avez pas aussi activé **Modifications du projet** dans la section Serveur MCP ; cet interrupteur est distinct de celui de l'agent intégré, et il est désactivé au départ. Une fois activé, chaque modification est enregistrée dès qu'elle est appliquée et s'annule avec `Ctrl/Cmd + Z`. Les clients ont pour consigne d'enregistrer un point de restauration avant une série de modifications : vous pouvez donc leur demander de remettre le projet dans son état d'avant en une seule étape, que `Ctrl/Cmd + Z` peut lui-même annuler. **Régénérer** le jeton déconnecte tous les clients configurés avec l'ancien. +Les outils sont ceux qu'utilise l'agent intégré, et ils agissent sur le projet ouvert dans l'éditeur. Le serveur HTTP propose aussi `getFrames` pour extraire des images de la vidéo d'origine d'un projet. Ces images montrent l'enregistrement source, sans les coupes, les zooms ni les autres modifications ; il n'accepte pas de chemin de fichier arbitraire. Les clients MCP ne peuvent que **lire** le projet tant que vous n'avez pas aussi activé **Modifications du projet** dans la section Serveur MCP ; cet interrupteur est distinct de celui de l'agent intégré, et il est désactivé au départ. Une fois activé, chaque modification est enregistrée dès qu'elle est appliquée et s'annule avec `Ctrl/Cmd + Z`. Les clients ont pour consigne d'enregistrer un point de restauration avant une série de modifications : vous pouvez donc leur demander de remettre le projet dans son état d'avant en une seule étape, que `Ctrl/Cmd + Z` peut lui-même annuler. **Régénérer** le jeton déconnecte tous les clients configurés avec l'ancien. ## Laisser un agent faire toute la vidéo {#letting-an-agent-make-the-whole-video} diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/ai-editing.md index d8ee21745..6cdae8986 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ Claude Code や Codex などの AI コーディングエージェントをすで 2. そこに表示される **Claude Code** または **Codex** のコマンドをコピーし、ターミナルで実行します。Claude Code のコマンドにはアクセストークンが含まれています。Codex は代わりに環境変数 `OPENSCREEN_MCP_TOKEN` からトークンを読み取るため、Codex を起動するシェルでこの変数を設定してください。 3. OpenScreen のエディターでプロジェクトを開いたまま、エージェントに編集を依頼します。 -ツールは組み込みのエージェントが使うものと同じで、エディターで開いているプロジェクトに作用します。MCP サーバーのセクションで**プロジェクトの編集**もオンにするまで、MCP クライアントはプロジェクトを**読み取る**ことしかできません。このスイッチは組み込みのエージェントのものとは別で、最初はオフです。オンにすると、各編集は反映された時点で保存され、`Ctrl/Cmd + Z` で元に戻せます。クライアントには一連の編集の前にチェックポイントを保存するよう指示されているため、プロジェクトを 1 回の操作で元の状態に戻すよう頼むことができ、その操作自体も `Ctrl/Cmd + Z` で取り消せます。トークンを**再生成**すると、古いトークンで設定したクライアントはすべて切断されます。 +ツールは組み込みのエージェントが使うものと同じで、エディターで開いているプロジェクトに作用します。HTTP サーバーには、プロジェクト内の元動画から静止画を取得する `getFrames` もあります。取得できるのは元の録画映像で、トリムやズームなどの編集結果は反映されません。任意のファイルパスは指定できません。MCP サーバーのセクションで**プロジェクトの編集**もオンにするまで、MCP クライアントはプロジェクトを**読み取る**ことしかできません。このスイッチは組み込みのエージェントのものとは別で、最初はオフです。オンにすると、各編集は反映された時点で保存され、`Ctrl/Cmd + Z` で元に戻せます。クライアントには一連の編集の前にチェックポイントを保存するよう指示されているため、プロジェクトを 1 回の操作で元の状態に戻すよう頼むことができ、その操作自体も `Ctrl/Cmd + Z` で取り消せます。トークンを**再生成**すると、古いトークンで設定したクライアントはすべて切断されます。 ## エージェントに動画全体を作らせる {#letting-an-agent-make-the-whole-video} diff --git a/website/i18n/pt-BR/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/pt-BR/docusaurus-plugin-content-docs/current/ai-editing.md index 3de3c038b..ea64bccf8 100644 --- a/website/i18n/pt-BR/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/pt-BR/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ Se você já usa um agente de código com IA, como o Claude Code ou o Codex, ele 2. Copie o comando do **Claude Code** ou do **Codex** exibido ali e execute-o em um terminal. O comando do Claude Code leva o token de acesso. Já o Codex o lê da variável de ambiente `OPENSCREEN_MCP_TOKEN`: defina-a no shell a partir do qual você inicia o Codex. 3. Mantenha um projeto aberto no editor do OpenScreen e depois peça a edição ao seu agente. -As ferramentas são as que o agente integrado usa, e elas agem sobre o projeto aberto no editor. Os clientes MCP só podem **ler** o projeto até você também ativar **Edições do projeto** na seção Servidor MCP; esse interruptor é separado do interruptor do agente integrado e começa desativado. Depois de ativado, cada edição é salva assim que é aplicada e se desfaz com `Ctrl/Cmd + Z`. Os clientes são instruídos a salvar um ponto de restauração antes de uma série de edições, então você pode pedir a eles que deixem o projeto como estava em um único passo, que o próprio `Ctrl/Cmd + Z` pode desfazer. **Gerar novo** token desconecta todos os clientes configurados com o antigo. +As ferramentas são as que o agente integrado usa, e elas agem sobre o projeto aberto no editor. O servidor HTTP também oferece `getFrames` para obter quadros do vídeo original de um projeto. As imagens mostram a gravação de origem, sem cortes, zooms ou outras edições; ele não aceita um caminho de arquivo arbitrário. Os clientes MCP só podem **ler** o projeto até você também ativar **Edições do projeto** na seção Servidor MCP; esse interruptor é separado do interruptor do agente integrado e começa desativado. Depois de ativado, cada edição é salva assim que é aplicada e se desfaz com `Ctrl/Cmd + Z`. Os clientes são instruídos a salvar um ponto de restauração antes de uma série de edições, então você pode pedir a eles que deixem o projeto como estava em um único passo, que o próprio `Ctrl/Cmd + Z` pode desfazer. **Gerar novo** token desconecta todos os clientes configurados com o antigo. ## Como deixar um agente fazer o vídeo inteiro {#letting-an-agent-make-the-whole-video} diff --git a/website/i18n/zh-CN/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/zh-CN/docusaurus-plugin-content-docs/current/ai-editing.md index abbf8f33c..2d64c80b2 100644 --- a/website/i18n/zh-CN/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/zh-CN/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ ChatGPT 和 GitHub Copilot 登录选项已**在 1.8.0 中移除**。它们的工 2. 复制那里显示的 **Claude Code** 或 **Codex** 命令,并在终端中运行。Claude Code 命令中带有访问令牌。Codex 则改为从 `OPENSCREEN_MCP_TOKEN` 环境变量读取令牌:请在启动 Codex 的终端中设置它。 3. 在 OpenScreen 编辑器中保持一个项目处于打开状态,然后让你的 AI 代理进行编辑。 -这些工具就是内置 AI 代理所使用的工具,它们作用于编辑器中打开的项目。在你同时开启 MCP 服务器部分中的**项目编辑**之前,MCP 客户端只能**读取**项目;这个开关独立于内置 AI 代理的开关,默认处于关闭状态。开启后,每项编辑都会在生效时立即保存,并可用 `Ctrl/Cmd + Z` 撤销。客户端会被要求在进行一系列编辑之前保存一个检查点,因此你可以让它们一步把项目恢复原样,而这一步本身也可以用 `Ctrl/Cmd + Z` 撤销。**重新生成**令牌,即可断开所有使用旧令牌配置的客户端。 +这些工具就是内置 AI 代理所使用的工具,它们作用于编辑器中打开的项目。HTTP 服务器还提供 `getFrames`,可从项目的原始视频中获取静止画面。画面显示的是原始录制内容,不包含裁剪、缩放或其他编辑效果;不能指定任意文件路径。在你同时开启 MCP 服务器部分中的**项目编辑**之前,MCP 客户端只能**读取**项目;这个开关独立于内置 AI 代理的开关,默认处于关闭状态。开启后,每项编辑都会在生效时立即保存,并可用 `Ctrl/Cmd + Z` 撤销。客户端会被要求在进行一系列编辑之前保存一个检查点,因此你可以让它们一步把项目恢复原样,而这一步本身也可以用 `Ctrl/Cmd + Z` 撤销。**重新生成**令牌,即可断开所有使用旧令牌配置的客户端。 ## 让 AI 代理制作整段视频 {#letting-an-agent-make-the-whole-video} diff --git a/website/i18n/zh-TW/docusaurus-plugin-content-docs/current/ai-editing.md b/website/i18n/zh-TW/docusaurus-plugin-content-docs/current/ai-editing.md index 0c2bd4672..1f6f02bbd 100644 --- a/website/i18n/zh-TW/docusaurus-plugin-content-docs/current/ai-editing.md +++ b/website/i18n/zh-TW/docusaurus-plugin-content-docs/current/ai-editing.md @@ -67,7 +67,7 @@ ChatGPT 與 GitHub Copilot 的登入選項已**在 1.8.0 中移除**。它們的 2. 複製那裡顯示的 **Claude Code** 或 **Codex** 指令,並在終端機中執行。Claude Code 指令中帶有存取權杖。Codex 則改為從 `OPENSCREEN_MCP_TOKEN` 環境變數讀取權杖:請在啟動 Codex 的終端機中設定它。 3. 讓專案在 OpenScreen 編輯器中保持開啟,然後請你的代理進行剪輯。 -這些工具就是內建代理所使用的工具,作用於編輯器中開啟的專案。在你同時開啟 MCP 伺服器區段中的**專案編輯**之前,MCP 用戶端只能**讀取**專案;這個開關與內建代理的開關各自獨立,預設為關閉。開啟後,每項編輯都會在生效時立即儲存,並可用 `Ctrl/Cmd + Z` 復原。用戶端會被告知在進行一系列編輯之前先儲存檢查點,因此你可以請它們一步將專案還原成原本的樣子,而這一步本身也可以用 `Ctrl/Cmd + Z` 復原。**重新產生**權杖,即可中斷所有使用舊權杖設定的用戶端。 +這些工具就是內建代理所使用的工具,作用於編輯器中開啟的專案。HTTP 伺服器也提供 `getFrames`,可從專案的原始影片擷取靜態畫面。畫面顯示原始錄製內容,不包含裁切、縮放或其他編輯效果;不能指定任意檔案路徑。在你同時開啟 MCP 伺服器區段中的**專案編輯**之前,MCP 用戶端只能**讀取**專案;這個開關與內建代理的開關各自獨立,預設為關閉。開啟後,每項編輯都會在生效時立即儲存,並可用 `Ctrl/Cmd + Z` 復原。用戶端會被告知在進行一系列編輯之前先儲存檢查點,因此你可以請它們一步將專案還原成原本的樣子,而這一步本身也可以用 `Ctrl/Cmd + Z` 復原。**重新產生**權杖,即可中斷所有使用舊權杖設定的用戶端。 ## 讓代理製作整支影片 {#letting-an-agent-make-the-whole-video}