@offlinecreator/mcp
Advanced tools
+21
| MIT License | ||
| Copyright (c) 2026 OfflineCreator Studio | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+1
-18
@@ -9,4 +9,2 @@ import { StudioApiClient } from "./client.js"; | ||
| offlinecreator-mcp balance | ||
| offlinecreator-mcp topups | ||
| offlinecreator-mcp topup --id topup-1000 | ||
| offlinecreator-mcp generate --model <id> --prompt "…" [--wait] | ||
@@ -18,3 +16,2 @@ | ||
| OFFLINECREATOR_UPLOAD_ROOT Optional root for upload_input paths | ||
| OFFLINECREATOR_ALLOW_INSECURE_API_BASE Set true only for private staging hosts | ||
| `); | ||
@@ -35,16 +32,2 @@ } | ||
| } | ||
| case "topups": { | ||
| printJson(await client.listTopUps()); | ||
| return 0; | ||
| } | ||
| case "topup": { | ||
| const options = parseFlags(rest); | ||
| const topUpId = options.id ?? options.topUpId; | ||
| if (topUpId !== "topup-1000" && topUpId !== "topup-3000") { | ||
| console.error("Usage: offlinecreator-mcp topup --id topup-1000|topup-3000"); | ||
| return 1; | ||
| } | ||
| printJson(await client.createTopUpCheckout(topUpId)); | ||
| return 0; | ||
| } | ||
| case "generate": { | ||
@@ -55,3 +38,3 @@ const options = parseFlags(rest); | ||
| if (!modelId || !prompt) { | ||
| console.error("Usage: offlinecreator-mcp generate --model flux-schnell --prompt \"…\" [--wait]"); | ||
| console.error('Usage: offlinecreator-mcp generate --model flux-schnell --prompt "…" [--wait]'); | ||
| return 1; | ||
@@ -58,0 +41,0 @@ } |
+0
-16
@@ -18,18 +18,2 @@ import type { McpConfig } from "./config.js"; | ||
| }>; | ||
| listTopUps(): Promise<{ | ||
| topups: { | ||
| id: string; | ||
| credits: number; | ||
| priceUsd: number; | ||
| }[]; | ||
| note?: string; | ||
| }>; | ||
| createTopUpCheckout(topUpId: "topup-1000" | "topup-3000"): Promise<{ | ||
| url: string; | ||
| sessionId: string; | ||
| topUpId: string; | ||
| credits: number; | ||
| priceUsd: number; | ||
| message?: string; | ||
| }>; | ||
| listGenerations(limit?: number): Promise<{ | ||
@@ -36,0 +20,0 @@ generations: unknown[]; |
+1
-11
@@ -1,2 +0,2 @@ | ||
| import { decodeSafeImageBase64, MCP_MAX_INPUT_BYTES, } from "./safe-image.js"; | ||
| import { decodeSafeImageBase64, MCP_MAX_INPUT_BYTES } from "./safe-image.js"; | ||
| const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; | ||
@@ -43,12 +43,2 @@ export class StudioApiError extends Error { | ||
| } | ||
| async listTopUps() { | ||
| return this.request("/api/v1/topups"); | ||
| } | ||
| async createTopUpCheckout(topUpId) { | ||
| return this.request("/api/v1/checkout/topup", { | ||
| method: "POST", | ||
| headers: { "content-type": "application/json" }, | ||
| body: JSON.stringify({ topUpId }), | ||
| }); | ||
| } | ||
| async listGenerations(limit = 20) { | ||
@@ -55,0 +45,0 @@ return this.request(`/api/v1/generations?limit=${encodeURIComponent(String(limit))}`); |
+5
-9
| const DEFAULT_API_BASE = "https://offlinecreatorstudio.com"; | ||
| const TRUSTED_API_HOSTS = new Set([ | ||
| "offlinecreatorstudio.com", | ||
| "offlinecreator-studio-staging.offlinecreator.workers.dev", | ||
| ]); | ||
| const TRUSTED_API_HOSTS = new Set(["offlinecreatorstudio.com"]); | ||
| export function loadConfig(env = process.env) { | ||
@@ -31,6 +28,5 @@ const apiKey = env.OFFLINECREATOR_API_KEY?.trim(); | ||
| const isLoopback = host === "localhost" || host === "127.0.0.1" || host === "::1"; | ||
| const allowInsecure = env.OFFLINECREATOR_ALLOW_INSECURE_API_BASE === "true" || isLoopback; | ||
| if (url.protocol === "http:") { | ||
| if (!allowInsecure) { | ||
| throw new Error("OFFLINECREATOR_API_BASE must use https unless it is localhost or OFFLINECREATOR_ALLOW_INSECURE_API_BASE=true."); | ||
| if (!isLoopback) { | ||
| throw new Error("OFFLINECREATOR_API_BASE must use https unless it is localhost."); | ||
| } | ||
@@ -41,6 +37,6 @@ } | ||
| } | ||
| if (!isLoopback && !TRUSTED_API_HOSTS.has(host) && !allowInsecure) { | ||
| throw new Error(`OFFLINECREATOR_API_BASE host "${host}" is not trusted. Use an OfflineCreator host, localhost, or set OFFLINECREATOR_ALLOW_INSECURE_API_BASE=true for private staging.`); | ||
| if (!isLoopback && !TRUSTED_API_HOSTS.has(host)) { | ||
| throw new Error(`OFFLINECREATOR_API_BASE host "${host}" is not trusted. Use the OfflineCreator API host or localhost.`); | ||
| } | ||
| return apiBase.replace(/\/$/, ""); | ||
| } |
+3
-1
@@ -32,4 +32,6 @@ #!/usr/bin/env node | ||
| main().catch((error) => { | ||
| console.error(error instanceof Error ? error.message : "Failed to start offlinecreator-mcp"); | ||
| console.error(error instanceof Error | ||
| ? error.message | ||
| : "Failed to start offlinecreator-mcp"); | ||
| process.exitCode = 1; | ||
| }); |
@@ -18,4 +18,4 @@ const OAUTH_USER_HEADER = "x-oc-oauth-user"; | ||
| try { | ||
| const padded = value.replace(/-/g, "+").replace(/_/g, "/") | ||
| + "=".repeat((4 - (value.length % 4)) % 4); | ||
| const padded = value.replace(/-/g, "+").replace(/_/g, "/") + | ||
| "=".repeat((4 - (value.length % 4)) % 4); | ||
| const binary = atob(padded); | ||
@@ -83,4 +83,3 @@ return Uint8Array.from(binary, (character) => character.charCodeAt(0)); | ||
| const skew = input.maxClockSkewSeconds ?? 60; | ||
| if (!Number.isInteger(issuedAt) | ||
| || Math.abs(now - issuedAt) > skew) { | ||
| if (!Number.isInteger(issuedAt) || Math.abs(now - issuedAt) > skew) { | ||
| return null; | ||
@@ -90,4 +89,4 @@ } | ||
| const scopes = normalizeScopes(requestedScopes); | ||
| if (scopes.length !== requestedScopes.length | ||
| || scopes.join(" ") !== requestedScopes.join(" ")) { | ||
| if (scopes.length !== requestedScopes.length || | ||
| scopes.join(" ") !== requestedScopes.join(" ")) { | ||
| return null; | ||
@@ -104,4 +103,4 @@ } | ||
| export function hasOAuthBridgeHeaders(request) { | ||
| return request.headers.has(OAUTH_USER_HEADER) | ||
| || request.headers.has(OAUTH_SIGNATURE_HEADER); | ||
| return (request.headers.has(OAUTH_USER_HEADER) || | ||
| request.headers.has(OAUTH_SIGNATURE_HEADER)); | ||
| } |
+2
-10
@@ -5,9 +5,3 @@ import { open } from "node:fs/promises"; | ||
| export { decodeSafeImageBase64, MCP_MAX_INPUT_BYTES, sniffImageType, } from "./safe-image.js"; | ||
| const ALLOWED_EXTENSIONS = new Set([ | ||
| ".png", | ||
| ".jpg", | ||
| ".jpeg", | ||
| ".webp", | ||
| ".gif", | ||
| ]); | ||
| const ALLOWED_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]); | ||
| /** | ||
@@ -67,5 +61,3 @@ * Read a local image for MCP upload_input with hard limits: | ||
| const relative = path.relative(root, resolved); | ||
| return (relative !== "" && | ||
| !relative.startsWith("..") && | ||
| !path.isAbsolute(relative)); | ||
| return (relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)); | ||
| } |
+51
-0
| import { McpServer } from "@modelcontextprotocol/server"; | ||
| import type { McpConfig } from "./config.js"; | ||
| export declare const MCP_SERVER_INFO: { | ||
| readonly name: "offlinecreator-studio"; | ||
| readonly version: string; | ||
| }; | ||
| export type ServerOptions = { | ||
| scopes?: string[]; | ||
| }; | ||
| export declare const TOOL_ANNOTATIONS: { | ||
| readonly list_models: { | ||
| readonly title: "List models"; | ||
| readonly readOnlyHint: true; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly get_credits: { | ||
| readonly title: "Get credits"; | ||
| readonly readOnlyHint: true; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly list_generations: { | ||
| readonly title: "List generations"; | ||
| readonly readOnlyHint: true; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly get_generation: { | ||
| readonly title: "Get generation"; | ||
| readonly readOnlyHint: true; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly wait_generation: { | ||
| readonly title: "Wait for generation"; | ||
| readonly readOnlyHint: true; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly download_output: { | ||
| readonly title: "Download output URL"; | ||
| readonly readOnlyHint: true; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly generate: { | ||
| readonly title: "Generate"; | ||
| readonly readOnlyHint: false; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly upload_input: { | ||
| readonly title: "Upload input image"; | ||
| readonly readOnlyHint: false; | ||
| readonly destructiveHint: false; | ||
| }; | ||
| readonly cancel_generation: { | ||
| readonly title: "Cancel generation"; | ||
| readonly readOnlyHint: false; | ||
| readonly destructiveHint: true; | ||
| }; | ||
| }; | ||
| export declare function createOfflineCreatorServer(config: McpConfig, options?: ServerOptions): McpServer; |
+65
-6
| import { McpServer } from "@modelcontextprotocol/server"; | ||
| import * as z from "zod/v4"; | ||
| import packageMetadata from "../package.json" with { type: "json" }; | ||
| import { StudioApiClient, StudioApiError } from "./client.js"; | ||
| export const MCP_SERVER_INFO = { | ||
| name: "offlinecreator-studio", | ||
| version: packageMetadata.version, | ||
| }; | ||
| const TOOL_SCOPES = { | ||
@@ -15,2 +20,49 @@ list_models: "models", | ||
| }; | ||
| export const TOOL_ANNOTATIONS = { | ||
| list_models: { | ||
| title: "List models", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| }, | ||
| get_credits: { | ||
| title: "Get credits", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| }, | ||
| list_generations: { | ||
| title: "List generations", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| }, | ||
| get_generation: { | ||
| title: "Get generation", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| }, | ||
| wait_generation: { | ||
| title: "Wait for generation", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| }, | ||
| download_output: { | ||
| title: "Download output URL", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| }, | ||
| generate: { | ||
| title: "Generate", | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| }, | ||
| upload_input: { | ||
| title: "Upload input image", | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| }, | ||
| cancel_generation: { | ||
| title: "Cancel generation", | ||
| readOnlyHint: false, | ||
| destructiveHint: true, | ||
| }, | ||
| }; | ||
| function textResult(data, isError = false) { | ||
@@ -47,6 +99,3 @@ return { | ||
| const scopes = options.scopes; | ||
| const server = new McpServer({ | ||
| name: "offlinecreator-studio", | ||
| version: "0.1.0", | ||
| }); | ||
| const server = new McpServer(MCP_SERVER_INFO); | ||
| function register(name, configBlock, handler) { | ||
@@ -62,2 +111,3 @@ const required = TOOL_SCOPES[name]; | ||
| inputSchema: z.object({}), | ||
| annotations: TOOL_ANNOTATIONS.list_models, | ||
| }, async () => { | ||
@@ -75,2 +125,3 @@ try { | ||
| inputSchema: z.object({}), | ||
| annotations: TOOL_ANNOTATIONS.get_credits, | ||
| }, async () => { | ||
@@ -101,2 +152,3 @@ try { | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.generate, | ||
| }, async ({ modelId, prompt, aspectRatio, wait }) => { | ||
@@ -119,3 +171,3 @@ try { | ||
| title: "Upload input image", | ||
| description: "Upload a source image for a reserved image-to-video generation, then submit it to the provider. Local filePath must stay inside the upload root.", | ||
| description: "Upload a source image for a reserved image-to-video generation, then start processing. Local filePath must stay inside the upload root.", | ||
| inputSchema: z.object({ | ||
@@ -137,2 +189,3 @@ generationId: z.string().describe("Reserved generation id"), | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.upload_input, | ||
| }, async ({ generationId, filePath, imageBase64, contentType, aspectRatio, wait, }) => { | ||
@@ -162,2 +215,3 @@ try { | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.get_generation, | ||
| }, async ({ generationId }) => { | ||
@@ -178,2 +232,3 @@ try { | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.wait_generation, | ||
| }, async ({ generationId, timeoutSeconds }) => { | ||
@@ -193,6 +248,8 @@ try { | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.download_output, | ||
| }, async ({ generationId }) => { | ||
| try { | ||
| const status = client.withAbsoluteOutput(await client.getGeneration(generationId)); | ||
| if (status.status !== "completed" || typeof status.outputUrl !== "string") { | ||
| if (status.status !== "completed" || | ||
| typeof status.outputUrl !== "string") { | ||
| return textResult({ | ||
@@ -221,2 +278,3 @@ error: "Generation is not completed with an output yet.", | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.cancel_generation, | ||
| }, async ({ generationId }) => { | ||
@@ -236,2 +294,3 @@ try { | ||
| }), | ||
| annotations: TOOL_ANNOTATIONS.list_generations, | ||
| }, async ({ limit }) => { | ||
@@ -238,0 +297,0 @@ try { |
+39
-5
| { | ||
| "name": "@offlinecreator/mcp", | ||
| "version": "0.1.1", | ||
| "description": "OfflineCreator Studio MCP server for Cursor, Claude, Hermes, and other MCP clients", | ||
| "version": "0.1.2", | ||
| "mcpName": "com.offlinecreatorstudio/mcp", | ||
| "description": "OfflineCreator Studio MCP server for remote OAuth and local stdio clients", | ||
| "type": "module", | ||
@@ -33,3 +34,4 @@ "bin": { | ||
| "!dist/**/*.test.d.ts", | ||
| "README.md" | ||
| "README.md", | ||
| "LICENSE" | ||
| ], | ||
@@ -39,5 +41,23 @@ "scripts": { | ||
| "dev": "tsc -p tsconfig.json --watch", | ||
| "format": "prettier --write .", | ||
| "format:check": "prettier --check .", | ||
| "start": "node dist/index.js", | ||
| "test": "npm run build && node --test dist/config.test.js dist/oauth-bridge.test.js dist/safe-fs.test.js", | ||
| "prepublishOnly": "npm run build && npm test" | ||
| "typecheck": "tsc -p tsconfig.json --noEmit", | ||
| "test": "npm run build && node --test dist/config.test.js dist/oauth-bridge.test.js dist/safe-fs.test.js dist/server.test.js", | ||
| "test:stdio": "npm run build && node scripts/smoke-stdio.mjs", | ||
| "test:remote": "node scripts/smoke-remote.mjs", | ||
| "audit:dependencies": "npm audit --audit-level=high", | ||
| "check:licenses": "node scripts/check-licenses.mjs", | ||
| "check:action-pins": "node scripts/verify-action-pins.mjs", | ||
| "validate:manifests": "node scripts/validate-manifests.mjs", | ||
| "validate:schemas": "node scripts/validate-official-schemas.mjs", | ||
| "validate:docs": "node scripts/check-doc-links.mjs", | ||
| "validate:assets": "node scripts/validate-assets.mjs", | ||
| "validate:pack": "node scripts/validate-pack.mjs", | ||
| "scan:public": "node scripts/scan-public.mjs", | ||
| "release:prepare": "node scripts/prepare-release.mjs", | ||
| "release:verify": "node scripts/verify-release-artifacts.mjs", | ||
| "scan:history": "node scripts/scan-git-history.mjs", | ||
| "validate": "npm run format:check && npm run typecheck && npm test && npm run validate:manifests && npm run validate:schemas && npm run validate:docs && npm run validate:assets && npm run validate:pack && npm run scan:public && npm run scan:history && npm run check:licenses && npm run check:action-pins && npm run test:stdio", | ||
| "prepublishOnly": "npm run validate" | ||
| }, | ||
@@ -57,2 +77,13 @@ "engines": { | ||
| "homepage": "https://offlinecreatorstudio.com/mcp-cli", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/OfflineCreatorStudio/offlinecreator-mcp.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/OfflineCreatorStudio/offlinecreator-mcp/issues" | ||
| }, | ||
| "author": { | ||
| "name": "OfflineCreator Studio", | ||
| "url": "https://offlinecreatorstudio.com" | ||
| }, | ||
| "publishConfig": { | ||
@@ -67,4 +98,7 @@ "access": "public" | ||
| "@types/node": "^20", | ||
| "ajv": "^8.20.0", | ||
| "ajv-formats": "^3.0.1", | ||
| "prettier": "^3.9.6", | ||
| "typescript": "^5" | ||
| } | ||
| } |
+31
-167
@@ -1,43 +0,13 @@ | ||
| # @offlinecreator/mcp | ||
| # OfflineCreator Studio MCP | ||
| MCP server + thin CLI for [OfflineCreator Studio](https://offlinecreatorstudio.com). | ||
| [OfflineCreator Studio](https://offlinecreatorstudio.com) tools for image and video generation through the Model Context Protocol (MCP). | ||
| Gives Cursor, Claude Desktop, Claude Code, Hermes, Windsurf, and other MCP clients tools to list models, check credits, generate images/video, and wait for results. | ||
| **Version:** 0.1.2 | ||
| **Recommended:** `https://mcp.offlinecreatorstudio.com/mcp` (Streamable HTTP with client-managed OAuth) | ||
| **Local fallback:** `npx -y @offlinecreator/mcp` (stdio; `OFFLINECREATOR_API_KEY` required) | ||
| **Current release:** [`@offlinecreator/mcp@0.1.1`](https://www.npmjs.com/package/@offlinecreator/mcp) | ||
| ## Quick start | ||
| **Recommended remote MCP:** `https://mcp.offlinecreatorstudio.com/mcp` (Streamable HTTP + OAuth 2.1) | ||
| For remote clients, add only the endpoint URL. Compatible clients manage OAuth authentication and credential storage. Do not add an API key to remote configuration. | ||
| **API-key fallback:** local stdio or `https://offlinecreatorstudio.com/mcp` | ||
| ## Setup | ||
| Add the recommended URL to your MCP client, then complete Studio browser | ||
| sign-in and consent. No API key is stored in the client configuration. | ||
| For local stdio or legacy remote fallback, create a scoped key in **Settings** | ||
| and copy the secret once (`oc_live_…` or `oc_test_…`). | ||
| ### Environment | ||
| | Variable | Required | Description | | ||
| |----------|----------|-------------| | ||
| | `OFFLINECREATOR_API_KEY` | Yes | Personal API key from Settings | | ||
| | `OFFLINECREATOR_API_BASE` | No | API origin. Default `https://offlinecreatorstudio.com`. Localhost allowed; other hosts need `OFFLINECREATOR_ALLOW_INSECURE_API_BASE=true`. | | ||
| | `OFFLINECREATOR_UPLOAD_ROOT` | No | Directory that `upload_input` file paths must stay inside (default: process cwd). | | ||
| | `OFFLINECREATOR_ALLOW_INSECURE_API_BASE` | No | Set `true` only for private/non-prod API hosts. | | ||
| ## Security notes | ||
| - API keys stay in env / MCP client config — tools never echo them. | ||
| - `upload_input` cannot read arbitrary disk paths: paths must stay under the upload root, use image extensions, pass size limits, and match image magic bytes. | ||
| - The client only calls `/api/v1/*` on a trusted API origin (HTTPS except localhost). | ||
| - Generations still use Studio moderation, credit reserve/refund, and ownership checks. | ||
| ## Install by client | ||
| ### Cursor | ||
| Recommended remote OAuth: | ||
| ```json | ||
@@ -53,3 +23,3 @@ { | ||
| API-key stdio fallback: | ||
| For local stdio: | ||
@@ -62,5 +32,3 @@ ```json | ||
| "args": ["-y", "@offlinecreator/mcp"], | ||
| "env": { | ||
| "OFFLINECREATOR_API_KEY": "oc_live_…" | ||
| } | ||
| "env": { "OFFLINECREATOR_API_KEY": "${OFFLINECREATOR_API_KEY}" } | ||
| } | ||
@@ -71,139 +39,35 @@ } | ||
| Never put the key in the URL or use `?api_key=`. | ||
| See [installation](docs/install.md), [client setup](docs/clients.md), and [OAuth](docs/oauth.md). | ||
| Local monorepo development: | ||
| ## Tools | ||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "offlinecreator": { | ||
| "command": "node", | ||
| "args": ["C:/path/to/mcp_tool/packages/mcp/dist/index.js"], | ||
| "env": { | ||
| "OFFLINECREATOR_API_KEY": "oc_test_…", | ||
| "OFFLINECREATOR_API_BASE": "http://localhost:3000" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| `list_models`, `get_credits`, `generate`, `upload_input`, `get_generation`, `wait_generation`, `download_output`, `cancel_generation`, and `list_generations`. | ||
| ### Claude Desktop | ||
| The public package contains the thin API client, stdio MCP server, and the reusable cryptographic request bridge exported as `@offlinecreator/mcp/oauth-bridge`. The hosted API, OAuth Worker, moderation, account, credit, and generation backend are proprietary and are not included in this repository. | ||
| Add `https://mcp.offlinecreatorstudio.com/mcp` in **Settings → Connectors → | ||
| Add custom connector**. Use the stdio JSON above in | ||
| `%APPDATA%\Claude\claude_desktop_config.json` as the API-key fallback. | ||
| ## Development | ||
| ### Claude Code | ||
| Remote OAuth: | ||
| ```bash | ||
| claude mcp add offlinecreator --transport http https://mcp.offlinecreatorstudio.com/mcp | ||
| npm ci | ||
| npm run validate | ||
| ``` | ||
| Local stdio: | ||
| The validation suite also checks formatting, types, dependency licenses, | ||
| workflow action pins, stdio initialization, manifest/version consistency, local | ||
| documentation links, the exact npm file list, and public files and Git history | ||
| for secret patterns. See the [release and supply-chain operations](docs/releasing.md) | ||
| for protected publishing and maintainer settings. | ||
| ```bash | ||
| claude mcp add offlinecreator --env OFFLINECREATOR_API_KEY=oc_live_… -- npx -y @offlinecreator/mcp | ||
| ``` | ||
| ## Policy and legal | ||
| ### Hermes | ||
| Anthropic's current Software Directory Policy does not accept software whose primary service is AI-generated images, video, or audio. OfflineCreator Studio is therefore not eligible for the public Claude Connectors Directory at this time. Claude custom connectors remain supported through the remote MCP URL. | ||
| Add to `~/.hermes/config.yaml`, then run `/reload-mcp`: | ||
| - [Security](SECURITY.md) | ||
| - [Release operations](docs/releasing.md) | ||
| - [Support](SUPPORT.md) | ||
| - [Contributing](CONTRIBUTING.md) | ||
| - [Privacy](https://offlinecreatorstudio.com/privacy) | ||
| - [Terms](https://offlinecreatorstudio.com/terms) | ||
| - [Acceptable use](https://offlinecreatorstudio.com/acceptable-use) | ||
| ```yaml | ||
| mcp_servers: | ||
| offlinecreator: | ||
| url: "https://mcp.offlinecreatorstudio.com/mcp" | ||
| ``` | ||
| ### Windsurf | ||
| Add to `%USERPROFILE%\.codeium\windsurf\mcp_config.json`: | ||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "offlinecreator": { | ||
| "serverUrl": "https://mcp.offlinecreatorstudio.com/mcp" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| ### VS Code / Copilot | ||
| VS Code uses `servers` rather than `mcpServers`. Add to `.vscode/mcp.json` or | ||
| the MCP user configuration: | ||
| ```json | ||
| { | ||
| "servers": { | ||
| "offlinecreator": { | ||
| "type": "http", | ||
| "url": "https://mcp.offlinecreatorstudio.com/mcp" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| ### Legacy remote API-key fallback | ||
| Clients that support fixed headers can still use: | ||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "offlinecreator": { | ||
| "url": "https://offlinecreatorstudio.com/mcp", | ||
| "headers": { | ||
| "Authorization": "Bearer ${env:OFFLINECREATOR_API_KEY}" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| Never put a key in the URL or use `?api_key=`. | ||
| ## Tools | ||
| | Tool | Purpose | | ||
| |------|---------| | ||
| | `list_models` | Launch catalog + credit costs | | ||
| | `get_credits` | Current balance | | ||
| | `generate` | Start generation (`wait: true` to poll) | | ||
| | `upload_input` | Attach image for image-to-video, then submit | | ||
| | `get_generation` | Current status | | ||
| | `wait_generation` | Poll until done | | ||
| | `download_output` | Short-lived signed URL for a completed output | | ||
| | `cancel_generation` | Cancel reserved job + refund | | ||
| | `list_generations` | Recent jobs | | ||
| Tools are filtered by key scope (`models`, `read`, `generate`). Top-up | ||
| discovery and Checkout creation are CLI/API features, not MCP tools. They | ||
| always return a URL for a human browser action; agents never charge a payment | ||
| method directly. | ||
| ## CLI sugar | ||
| ```bash | ||
| npx @offlinecreator/mcp models | ||
| npx @offlinecreator/mcp balance | ||
| npx @offlinecreator/mcp topups | ||
| npx @offlinecreator/mcp topup --id topup-1000 | ||
| npx @offlinecreator/mcp generate --model flux-schnell --prompt "A clean product still" --wait | ||
| ``` | ||
| ## Development | ||
| ```bash | ||
| npm install | ||
| npm run mcp:build | ||
| OFFLINECREATOR_API_KEY=oc_test_… OFFLINECREATOR_API_BASE=http://localhost:3000 npm run mcp:start | ||
| ``` | ||
| ## Privacy note | ||
| Studio generations run on disclosed cloud providers (not on-device). LocalForge remains the offline product. | ||
| Licensed under the [MIT License](LICENSE). |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
45732
3.65%19
5.56%1053
5.62%0
-100%4
-20%5
150%71
-65.7%