@instavar/mcp-server 0.2.0 → 0.2.1

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 (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +139 -64
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @instavar/mcp-server
2
2
 
3
+ [![CI](https://github.com/instavar/mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/instavar/mcp-server/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/%40instavar%2Fmcp-server)](https://www.npmjs.com/package/@instavar/mcp-server)
5
+ [![MCP server on Glama](https://glama.ai/mcp/servers/instavar/mcp-server/badge)](https://glama.ai/mcp/servers/instavar/mcp-server)
6
+
3
7
  Drive the full [Instavar Studio](https://instavar.com) video workflow —
4
8
  **create → render → approve → publish → metrics** — from Claude Code or Codex,
5
9
  without leaving your terminal.
package/dist/index.js CHANGED
@@ -246,22 +246,33 @@ var editBriefShape = {
246
246
 
247
247
  // src/tools.ts
248
248
  var uuid = (desc) => z2.string().uuid().describe(desc);
249
+ var READ_ONLY = { readOnlyHint: true, openWorldHint: false };
250
+ var SAFE_WRITE = {
251
+ readOnlyHint: false,
252
+ destructiveHint: false,
253
+ idempotentHint: false,
254
+ openWorldHint: false
255
+ };
249
256
  function registerTools(server) {
250
- server.tool(
257
+ server.registerTool(
251
258
  "list_jobs",
252
- "List recent video jobs for the current organization (newest first).",
253
259
  {
254
- limit: z2.number().int().min(1).max(50).default(10).describe("Number of jobs to return (default 10, max 50)"),
255
- status: z2.enum([
256
- "queued",
257
- "rendering",
258
- "draft_ready",
259
- "approved",
260
- "publish_requested",
261
- "publishing",
262
- "published",
263
- "failed"
264
- ]).optional().describe("Filter by job status")
260
+ title: "List jobs",
261
+ description: "List recent video jobs for the current organization (newest first).",
262
+ inputSchema: {
263
+ limit: z2.number().int().min(1).max(50).default(10).describe("Number of jobs to return (default 10, max 50)"),
264
+ status: z2.enum([
265
+ "queued",
266
+ "rendering",
267
+ "draft_ready",
268
+ "approved",
269
+ "publish_requested",
270
+ "publishing",
271
+ "published",
272
+ "failed"
273
+ ]).optional().describe("Filter by job status")
274
+ },
275
+ annotations: READ_ONLY
265
276
  },
266
277
  async ({ limit, status }) => {
267
278
  const q = new URLSearchParams({ limit: String(limit) });
@@ -269,26 +280,38 @@ function registerTools(server) {
269
280
  return runTool(() => apiGet(`/api/v1/jobs?${q.toString()}`));
270
281
  }
271
282
  );
272
- server.tool(
283
+ server.registerTool(
273
284
  "get_job_status",
274
- "Get detailed status for a job: runs, artifacts, verifications, plus the current video and thumbnail URLs.",
275
- { jobId: uuid("The job ID to check") },
285
+ {
286
+ title: "Get job status",
287
+ description: "Get detailed status for a job: runs, artifacts, verifications, plus the current video and thumbnail URLs.",
288
+ inputSchema: { jobId: uuid("The job ID to check") },
289
+ annotations: READ_ONLY
290
+ },
276
291
  async ({ jobId }) => runTool(() => apiGet(`/api/v1/jobs/${encodeURIComponent(jobId)}`))
277
292
  );
278
- server.tool(
293
+ server.registerTool(
279
294
  "get_video_state",
280
- "Get the materialized video state for a job (composition decisions: scenes, duration, aspect ratio, etc.).",
281
- { jobId: uuid("The job ID") },
295
+ {
296
+ title: "Get video state",
297
+ description: "Get the materialized video state for a job (composition decisions: scenes, duration, aspect ratio, etc.).",
298
+ inputSchema: { jobId: uuid("The job ID") },
299
+ annotations: READ_ONLY
300
+ },
282
301
  async ({ jobId }) => runTool(
283
302
  () => apiGet(`/api/v1/jobs/${encodeURIComponent(jobId)}/video-state`)
284
303
  )
285
304
  );
286
- server.tool(
305
+ server.registerTool(
287
306
  "get_job_metrics",
288
- "Get the latest platform engagement metric snapshots for a published job (newest first). Read-only.",
289
307
  {
290
- jobId: uuid("The job ID"),
291
- limit: z2.number().int().min(1).max(20).default(10).describe("Max metric snapshots (default 10, max 20)")
308
+ title: "Get job metrics",
309
+ description: "Get the latest platform engagement metric snapshots for a published job (newest first). Read-only.",
310
+ inputSchema: {
311
+ jobId: uuid("The job ID"),
312
+ limit: z2.number().int().min(1).max(20).default(10).describe("Max metric snapshots (default 10, max 20)")
313
+ },
314
+ annotations: READ_ONLY
292
315
  },
293
316
  async ({ jobId, limit }) => runTool(
294
317
  () => apiGet(
@@ -296,14 +319,18 @@ function registerTools(server) {
296
319
  )
297
320
  )
298
321
  );
299
- server.tool(
322
+ server.registerTool(
300
323
  "get_cost_summary",
301
- "Query production infrastructure costs (Lambda, RunPod, R2, WaveSpeed, PoYo), optionally with reconciliation drift.",
302
324
  {
303
- since: z2.string().optional().describe("ISO date \u2014 only include events after this date"),
304
- provider: z2.enum(["runpod", "lambda", "r2", "wavespeed", "poyo", "all"]).default("all").describe("Filter by provider"),
305
- groupBy: z2.enum(["provider", "job", "day"]).default("provider").describe("How to aggregate the summary"),
306
- includeReconciliation: z2.boolean().default(false).describe("Include account-wide reconciliation events")
325
+ title: "Get cost summary",
326
+ description: "Query production infrastructure costs (Lambda, RunPod, R2, WaveSpeed, PoYo), optionally with reconciliation drift.",
327
+ inputSchema: {
328
+ since: z2.string().optional().describe("ISO date \u2014 only include events after this date"),
329
+ provider: z2.enum(["runpod", "lambda", "r2", "wavespeed", "poyo", "all"]).default("all").describe("Filter by provider"),
330
+ groupBy: z2.enum(["provider", "job", "day"]).default("provider").describe("How to aggregate the summary"),
331
+ includeReconciliation: z2.boolean().default(false).describe("Include account-wide reconciliation events")
332
+ },
333
+ annotations: READ_ONLY
307
334
  },
308
335
  async ({ since, provider, groupBy, includeReconciliation }) => {
309
336
  const q = new URLSearchParams({ provider, groupBy });
@@ -312,16 +339,24 @@ function registerTools(server) {
312
339
  return runTool(() => apiGet(`/api/v1/cost-summary?${q.toString()}`));
313
340
  }
314
341
  );
315
- server.tool(
342
+ server.registerTool(
316
343
  "create_video_brief",
317
- "Create a new video job from a structured brief and trigger the render pipeline. Returns the job and run IDs.",
318
- createBriefShape,
344
+ {
345
+ title: "Create video brief",
346
+ description: "Create a new video job from a structured brief and trigger the render pipeline. Returns the job and run IDs.",
347
+ inputSchema: createBriefShape,
348
+ annotations: SAFE_WRITE
349
+ },
319
350
  async (input) => runTool(() => apiPost(`/api/v1/jobs`, input))
320
351
  );
321
- server.tool(
352
+ server.registerTool(
322
353
  "edit_video_brief",
323
- "Edit an existing job's brief (only provided fields change). Changing the script, caption, title, lessonTitle, objective, steps, scenes or aspectRatio triggers a re-render and returns the new run ID; objective also re-routes the composition. Changing only publishTarget updates the job's target platform without re-rendering.",
324
- editBriefShape,
354
+ {
355
+ title: "Edit video brief",
356
+ description: "Edit an existing job's brief (only provided fields change). Changing the script, caption, title, lessonTitle, objective, steps, scenes or aspectRatio triggers a re-render and returns the new run ID; objective also re-routes the composition. Changing only publishTarget updates the job's target platform without re-rendering.",
357
+ inputSchema: editBriefShape,
358
+ annotations: SAFE_WRITE
359
+ },
325
360
  async (input) => {
326
361
  const { jobId, ...patch } = input;
327
362
  return runTool(
@@ -329,52 +364,92 @@ function registerTools(server) {
329
364
  );
330
365
  }
331
366
  );
332
- server.tool(
367
+ server.registerTool(
333
368
  "approve_job",
334
- "Approve a rendered job so it becomes publishable (draft_ready/awaiting_review/needs_changes -> approved) and revoke active review links.",
335
- { jobId: uuid("The job ID to approve") },
369
+ {
370
+ title: "Approve job",
371
+ description: "Approve a rendered job so it becomes publishable (draft_ready/awaiting_review/needs_changes -> approved) and revoke active review links.",
372
+ inputSchema: { jobId: uuid("The job ID to approve") },
373
+ annotations: {
374
+ readOnlyHint: false,
375
+ destructiveHint: false,
376
+ // Approving an already-approved job does not change state further.
377
+ idempotentHint: true,
378
+ openWorldHint: false
379
+ }
380
+ },
336
381
  async ({ jobId }) => runTool(
337
382
  () => apiPost(`/api/v1/jobs/${encodeURIComponent(jobId)}/approve`, {})
338
383
  )
339
384
  );
340
- server.tool(
385
+ server.registerTool(
341
386
  "publish_job",
342
- "Publish an APPROVED job to its connected social destination via the gated publish API. All production gates run (paid plan, ownership, abuse, QA, disclosure, destination, rights, moderation). For YouTube pass youtubePrivacyStatus:'private' for safe runs.",
343
387
  {
344
- jobId: uuid("The approved job to publish"),
345
- youtubePrivacyStatus: z2.enum(["private", "unlisted", "public"]).optional().describe("YouTube only. Use 'private' for safe dogfood runs."),
346
- tiktokPostMode: z2.enum(["direct_publish", "draft_upload"]).optional().describe("TikTok only. Use 'draft_upload' for safe dogfood runs."),
347
- connectedAccountDestinationId: z2.string().uuid().optional().describe(
348
- "Specific destination when the org has multiple accounts for the provider."
349
- ),
350
- confirmSyntheticDisclosure: z2.boolean().optional().describe("Set true when the job requires an AI synthetic-media disclosure."),
351
- confirmQaOverride: z2.boolean().optional().describe("Set true to override a non-passing QA gate.")
388
+ title: "Publish job",
389
+ description: "Publish an APPROVED job to its connected social destination via the gated publish API. All production gates run (paid plan, ownership, abuse, QA, disclosure, destination, rights, moderation). For YouTube pass youtubePrivacyStatus:'private' for safe runs.",
390
+ inputSchema: {
391
+ jobId: uuid("The approved job to publish"),
392
+ youtubePrivacyStatus: z2.enum(["private", "unlisted", "public"]).optional().describe("YouTube only. Use 'private' for safe dogfood runs."),
393
+ tiktokPostMode: z2.enum(["direct_publish", "draft_upload"]).optional().describe("TikTok only. Use 'draft_upload' for safe dogfood runs."),
394
+ connectedAccountDestinationId: z2.string().uuid().optional().describe(
395
+ "Specific destination when the org has multiple accounts for the provider."
396
+ ),
397
+ confirmSyntheticDisclosure: z2.boolean().optional().describe(
398
+ "Set true when the job requires an AI synthetic-media disclosure."
399
+ ),
400
+ confirmQaOverride: z2.boolean().optional().describe("Set true to override a non-passing QA gate.")
401
+ },
402
+ annotations: {
403
+ readOnlyHint: false,
404
+ // A live post on an external platform cannot be recalled by the API.
405
+ destructiveHint: true,
406
+ idempotentHint: false,
407
+ openWorldHint: true
408
+ }
352
409
  },
353
410
  async ({ jobId, ...rest }) => (
354
411
  // NOTE: publish is NOT under /api/v1 — it is the gated /api/jobs route.
355
- runTool(() => apiPost(`/api/jobs/${encodeURIComponent(jobId)}/publish`, rest))
412
+ runTool(
413
+ () => apiPost(`/api/jobs/${encodeURIComponent(jobId)}/publish`, rest)
414
+ )
356
415
  )
357
416
  );
358
- server.tool(
417
+ server.registerTool(
359
418
  "connect_account",
360
- "Start connecting a social account headlessly (no local browser needed). Returns an approve URL + a short confirm code: open the URL in a browser signed in to Instavar, check the code matches THIS terminal, and approve. Then poll connect_account_status with the returned pairingId until it reports 'connected'.",
361
419
  {
362
- provider: z2.enum([
363
- "youtube",
364
- "x",
365
- "tiktok",
366
- "linkedin",
367
- "facebook",
368
- "instagram",
369
- "threads"
370
- ]).describe("The social platform to connect")
420
+ title: "Connect social account",
421
+ description: "Start connecting a social account headlessly (no local browser needed). Returns an approve URL + a short confirm code: open the URL in a browser signed in to Instavar, check the code matches THIS terminal, and approve. Then poll connect_account_status with the returned pairingId until it reports 'connected'.",
422
+ inputSchema: {
423
+ provider: z2.enum([
424
+ "youtube",
425
+ "x",
426
+ "tiktok",
427
+ "linkedin",
428
+ "facebook",
429
+ "instagram",
430
+ "threads"
431
+ ]).describe("The social platform to connect")
432
+ },
433
+ annotations: {
434
+ readOnlyHint: false,
435
+ destructiveHint: false,
436
+ idempotentHint: false,
437
+ // The pairing hands off to the platform's own OAuth flow.
438
+ openWorldHint: true
439
+ }
371
440
  },
372
441
  async ({ provider }) => runTool(() => apiPost(`/api/v1/connect/start`, { provider }))
373
442
  );
374
- server.tool(
443
+ server.registerTool(
375
444
  "connect_account_status",
376
- "Poll a pending account connection started by connect_account. Returns status 'pending' | 'connected' | 'expired', plus the connected account label once connected.",
377
- { pairingId: uuid("The pairingId returned by connect_account") },
445
+ {
446
+ title: "Check account connection",
447
+ description: "Poll a pending account connection started by connect_account. Returns status 'pending' | 'connected' | 'expired', plus the connected account label once connected.",
448
+ inputSchema: {
449
+ pairingId: uuid("The pairingId returned by connect_account")
450
+ },
451
+ annotations: READ_ONLY
452
+ },
378
453
  async ({ pairingId }) => runTool(
379
454
  () => apiGet(
380
455
  `/api/v1/connect/poll?pairingId=${encodeURIComponent(pairingId)}`
@@ -386,7 +461,7 @@ function registerTools(server) {
386
461
  // package.json
387
462
  var package_default = {
388
463
  name: "@instavar/mcp-server",
389
- version: "0.2.0",
464
+ version: "0.2.1",
390
465
  mcpName: "com.instavar/mcp-server",
391
466
  description: "Instavar Studio MCP server \u2014 drive the full video create\u2192render\u2192approve\u2192publish\u2192metrics workflow from Claude Code or Codex with an API key.",
392
467
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instavar/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "mcpName": "com.instavar/mcp-server",
5
5
  "description": "Instavar Studio MCP server — drive the full video create→render→approve→publish→metrics workflow from Claude Code or Codex with an API key.",
6
6
  "type": "module",