stream_start
Start a periodic screen capture session, saving frames to disk and returning a session ID to retrieve or stop the stream.
Instructions
Start a periodic capture session. Saves frames to disk every intervalSeconds for at most durationSeconds, keeping the last ringCapacity frames in memory. Returns a session id used by stream_status / stream_latest / stream_stop. Streams default to disk-only to keep LLM context lean - call stream_latest with includeBase64=true when you actually want to look at a frame.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| intervalSeconds | Yes | Seconds between frames. Minimum 0.25. | |
| durationSeconds | Yes | Total duration of the stream in seconds. | |
| cursorRadius | No | ||
| format | No | jpeg | |
| quality | No | ||
| maxEdge | No | Longest edge in px. Default 1920 for full-screen frames; cursor crops keep native resolution unless overridden. | |
| ringCapacity | No | Maximum number of recent frames kept in memory. Older frames are evicted (still on disk). |
Implementation Reference
- src/stream.ts:32-78 (handler)Core handler function `startStream()` — creates a stream session, starts periodic capture on an interval, returns session id and expected frame count.
export function startStream(args: StartStreamArgs): { id: string; expectedFrames: number } { const intervalMs = Math.max(250, Math.floor(args.intervalSeconds * 1000)); const durationMs = Math.max(intervalMs, Math.floor(args.durationSeconds * 1000)); const id = `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`; const sess: StreamSession = { id, startedAt: Date.now(), intervalMs, durationMs, frames: [], capacity: Math.max(1, args.ringCapacity ?? 60), options: { cursorRadius: args.cursorRadius, format: args.format, quality: args.quality, maxEdge: args.maxEdge, includeBase64: false /* streams default to disk-only; pull frames on demand */, }, outDir: args.outDir, stopAt: Date.now() + durationMs, done: false, }; const tick = async () => { if (sess.done) return; if (Date.now() >= sess.stopAt) { stopStream(id); return; } try { const frame = await capture({ ...sess.options, outDir: sess.outDir }); sess.frames.push(frame); while (sess.frames.length > sess.capacity) sess.frames.shift(); } catch (e) { sess.error = (e as Error).message; } }; /* Fire one immediately so the first frame is available right away, then keep * ticking on interval. */ void tick(); sess.ticker = setInterval(tick, sess.intervalMs); sessions.set(id, sess); const expectedFrames = Math.max(1, Math.floor(durationMs / intervalMs)); return { id, expectedFrames }; } - src/index.ts:190-203 (handler)Call handler dispatch for tool "stream_start" — extracts arguments from the request and delegates to `startStream()`.
case "stream_start": { const cursorRadius = numArg(args, "cursorRadius", 0); const r = startStream({ intervalSeconds: numArg(args, "intervalSeconds", 1), durationSeconds: numArg(args, "durationSeconds", 10), cursorRadius, format: strEnum(args, "format", ["png", "jpeg", "webp"] as const, "jpeg"), quality: numArg(args, "quality", 72), maxEdge: optionalNumArg(args, "maxEdge", cursorRadius > 0 ? 0 : 1920), ringCapacity: numArg(args, "ringCapacity", 60), outDir: DEFAULT_OUT_DIR, }); return text(r); } - src/index.ts:81-116 (schema)Tool registration including name, description, and inputSchema for stream_start (intervalSeconds, durationSeconds, cursorRadius, format, quality, maxEdge, ringCapacity).
name: "stream_start", description: "Start a periodic capture session. Saves frames to disk every intervalSeconds " + "for at most durationSeconds, keeping the last ringCapacity frames in memory. " + "Returns a session id used by stream_status / stream_latest / stream_stop. " + "Streams default to disk-only to keep LLM context lean - call stream_latest " + "with includeBase64=true when you actually want to look at a frame.", inputSchema: { type: "object", required: ["intervalSeconds", "durationSeconds"], properties: { intervalSeconds: { type: "number", minimum: 0.25, description: "Seconds between frames. Minimum 0.25.", }, durationSeconds: { type: "number", minimum: 0.25, description: "Total duration of the stream in seconds.", }, cursorRadius: { type: "integer", default: 0 }, format: { type: "string", enum: ["png", "jpeg", "webp"], default: "jpeg" }, quality: { type: "integer", minimum: 1, maximum: 100, default: 72 }, maxEdge: { type: "integer", description: "Longest edge in px. Default 1920 for full-screen frames; cursor crops keep native resolution unless overridden.", }, ringCapacity: { type: "integer", description: "Maximum number of recent frames kept in memory. Older frames are evicted (still on disk).", default: 60, }, }, }, }, - src/index.ts:37-165 (registration)All tools are registered in a single ListToolsRequestSchema handler; stream_start is one of the tools listed at lines 80-116.
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "screenshot", description: "Capture a single screenshot of the desktop. Persists the file to disk and " + "optionally returns a base64 payload. Set cursorRadius>0 to crop a square " + "region around the mouse cursor instead of the full screen.", inputSchema: { type: "object", properties: { cursorRadius: { type: "integer", description: "If >0, crop a square of (2*radius)x(2*radius) px centered on the cursor. 0 = full screen.", default: 0, }, format: { type: "string", enum: ["png", "jpeg", "webp"], default: "jpeg" }, quality: { type: "integer", minimum: 1, maximum: 100, default: 82 }, maxEdge: { type: "integer", description: "Resize the longest edge to this many pixels. 0 disables resizing. " + "Default 2400 for full screen; with cursorRadius>0 the cursor crop is kept at native resolution unless overridden.", }, display: { type: "integer", description: "Optional display index for multi-monitor setups (omit for primary).", }, includeBase64: { type: "boolean", description: "If true, include the image bytes inline in the response. Default true.", default: true, }, }, }, }, { name: "cursor_info", description: "Return the current mouse cursor position, the foreground window title, and the " + "title of the window directly under the cursor (Windows only; other platforms " + "report position only when available).", inputSchema: { type: "object", properties: {} }, }, { name: "stream_start", description: "Start a periodic capture session. Saves frames to disk every intervalSeconds " + "for at most durationSeconds, keeping the last ringCapacity frames in memory. " + "Returns a session id used by stream_status / stream_latest / stream_stop. " + "Streams default to disk-only to keep LLM context lean - call stream_latest " + "with includeBase64=true when you actually want to look at a frame.", inputSchema: { type: "object", required: ["intervalSeconds", "durationSeconds"], properties: { intervalSeconds: { type: "number", minimum: 0.25, description: "Seconds between frames. Minimum 0.25.", }, durationSeconds: { type: "number", minimum: 0.25, description: "Total duration of the stream in seconds.", }, cursorRadius: { type: "integer", default: 0 }, format: { type: "string", enum: ["png", "jpeg", "webp"], default: "jpeg" }, quality: { type: "integer", minimum: 1, maximum: 100, default: 72 }, maxEdge: { type: "integer", description: "Longest edge in px. Default 1920 for full-screen frames; cursor crops keep native resolution unless overridden.", }, ringCapacity: { type: "integer", description: "Maximum number of recent frames kept in memory. Older frames are evicted (still on disk).", default: 60, }, }, }, }, { name: "stream_status", description: "Snapshot of a running or finished stream session - frame count, time remaining, last frames metadata.", inputSchema: { type: "object", required: ["id"], properties: { id: { type: "string" }, lastN: { type: "integer", default: 8 }, }, }, }, { name: "stream_latest", description: "Read the most recent frame of a stream from disk and return it as base64. Use sparingly - this is the path that actually puts pixels into the LLM context.", inputSchema: { type: "object", required: ["id"], properties: { id: { type: "string" }, }, }, }, { name: "stream_stop", description: "Stop a running stream early. Frames already on disk remain.", inputSchema: { type: "object", required: ["id"], properties: { id: { type: "string" } }, }, }, { name: "stream_list", description: "List active and completed stream sessions known to this process.", inputSchema: { type: "object", properties: {} }, }, { name: "stream_drop", description: "Forget a finished stream session (frees its in-memory ring; on-disk files are preserved).", inputSchema: { type: "object", required: ["id"], properties: { id: { type: "string" } }, }, }, ], })); - src/stream.ts:1-30 (helper)Import of `capture` and `CaptureResult` (used by startStream) and TypeScript definitions for StreamSession and StartStreamArgs.
import { capture, CaptureOptions, CaptureResult } from "./capture.js"; interface StreamSession { id: string; startedAt: number; intervalMs: number; durationMs: number; /* Frames are kept in a bounded ring so a long stream doesn't bloat memory. */ frames: CaptureResult[]; capacity: number; options: Omit<CaptureOptions, "outDir">; outDir: string; ticker?: NodeJS.Timeout; stopAt: number; done: boolean; error?: string; } const sessions = new Map<string, StreamSession>(); export interface StartStreamArgs { intervalSeconds: number; durationSeconds: number; cursorRadius?: number; format?: "png" | "jpeg" | "webp"; quality?: number; maxEdge?: number; ringCapacity?: number; outDir: string; }