# Artillect MCP — short agent recipes

Public documentation: https://app.artillect.pro/knowledge/#knowledge-search . This same recipes text is available at https://app.artillect.pro/docs/mcp/recipes.md .

> **BETA.** This MCP server is in public beta. If anything here misbehaves, misleads
> you, or is missing, send a report: `submit_feedback({ kind, summary, details })`.
> Free, charges nothing, goes straight to the Artillect team — include `request_id`
> from the error. Reporting friction is expected, not an imposition.

**Start:** call tool `get_started` (same content + balance). Then use core tools below.

**Core tools:** `get_started`, `get_balance`, `estimate`, `request_user_upload`, `get_user_upload`, `upload_file`, `generate_image`, `generate_video`, `get_task`, `list_models` (slim by default), `recommend_model`, `create_image_element`, `create_video_element`.
Character video also: `list_projects`, `create_project`, `create_image_element`.
Ignore mesh/audio/switchx unless the user asks.

User/chat files: request_user_upload → user drops image/video/audio files and presses Finish upload → get_user_upload until status=ready. Use files[].url in generate_image.input_urls, generate_video.start_image_url or the selected reference field, generate_mesh.image_url (image_urls for multiple ordered views), or generate_audio.audio_url/video_url where supported. The uploader is shared across modalities, including photo-to-3D; it does not accept 3D mesh files. Do not default to create_upload_url + shell PUT for a user photo.

## Sandbox network FAQ

**First action for “make a 3D model from my photo”:** request_user_upload → wait for the user to upload and press Finish upload → get_user_upload status:ready → generate_mesh with image_url → get_task → labelled GLB link. Do not test shell connectivity, mint a presigned PUT, install software or demand network settings before trying this supported path. Uploading and generation are different steps; say what the tools actually confirmed.

### Before uploading: do I have the reference?

- **I can see the photo in the conversation. Can I upload what I see?** Seeing an image does not prove you have its original bytes or a readable file path. If your host exposes an actual file, that is a separate capability; do not assume a Windows path is readable in a Linux VM or by hosted MCP. Default to the widget for a user attachment. Explain simply: “Please drop the original photo into this upload card and press Finish upload so Artillect can use it as the reference.” Do not ask for Blender or an API key in chat.
- **Will uploading again charge for another generation?** Uploading is not generation submission. It is subject to file/storage limits; generate_mesh is the token-spending step. Check balance/model estimate and avoid promising every operation is free. A failed download does not require a new generation.
- **Should I turn the visible photo into a text-to-3D prompt instead?** No. A user asking for a model from their photo expects image-to-3D. Use a supported i2m model and image_url. A prompt describing the photo is not a replacement for the source file; do not silently switch to t2m or add conversion/rigging/animation tasks the user did not request.
- **Does “same S3 as Studio” mean I must access S3 myself?** No. It describes where Artillect stores media, not a required client network path. The widget and server handle storage; the agent passes returned URLs between MCP tools. Reuse an already-ready Artillect media URL directly instead of downloading and re-uploading it.
- **Which uploader applies to 3D/audio?** The file kind and the output modality are separate. A photo for 3D is an image upload; audio/video inputs use the same widget when the chosen model supports them. Use list_models for actual input fields and constraints. There is no requirement for a dedicated “3D uploader” to accept a photo.

### Network and host capabilities

- **MCP works, but PUT/download returns 403. Is Artillect broken?** These are separate network paths: MCP tool transport, the user's browser/widget, and agent shell/VM HTTP. An explicit proxy denial identifies a host network restriction; a bare HTTP 403 alone does not. Check the failing operation, response body/headers and Artillect request_id when available. An Artillect permissions error or an expired/invalid S3 presigned signature needs a different fix. Do not claim which domains a host allows without evidence. Report MCP/API failures with submit_feedback; never include API keys, upload tickets or signed URL query strings.
- **Do I need app.artillect.pro and s3.ru1.storage.beget.cloud in an allowlist?** Not for the standard user-file upload and generation flow: call request_user_upload, then get_user_upload; the widget transfers the file through the user's browser or its host bridge, not your shell PUT. A returned media URL can be submitted to generate_* without downloading it. The S3 hostname here is an example from a reported failure, not a permanent endpoint: use the URL actually returned by the tool. The widget can still have its own host/CSP restrictions; shell connectivity does not diagnose those.
- **Should the user change Admin settings → Capabilities?** Do not prescribe that menu as a universal fix. Network controls belong to the host, not Artillect, and their names, availability and policy vary. Only investigate the host's actual network controls if the user explicitly requests local processing that needs direct HTTP. Ordinary photo-to-3D needs neither a shell allowlist change nor Blender, a second VM or a VPN. Do not promise a particular process or VM has unrestricted internet.
- **If shell cannot GET the image URL, can Meshy still use it?** Yes, shell access is not a prerequisite for submitting a ready URL through MCP; Artillect/provider input handling happens outside that shell. Submit the exact ready URL in the model's input field. Do not change the hostname, strip signed query parameters, preflight it with curl or attach your API key to storage URLs. If generate_mesh actually returns an input-fetch error, investigate that error rather than assuming success or blaming the shell.
- **If a VM also gets 403, does that prove Blender will work?** No. It only describes those observed requests. The screenshot's claim that Windows/Blender has full internet is not a guarantee or a product requirement. A different process can share the same proxy or lack the user's file. Do not move credentials or executable scripts into unrelated applications to solve an ordinary upload.
- **Are MCP capabilities, widget CSP and Admin Capabilities the same setting?** No. MCP capabilities advertise tools/resources/extensions; widget CSP governs requests made by the embedded app; a host's network policy governs shell/VM egress. Do not treat a successful MCP handshake as proof of shell internet, or a denied curl as proof that MCP Apps are unavailable. Use the observed error from the specific channel.

### Use the supported upload path

- **Photo attachment → 3D: where is the uploader?** It is the same request_user_upload widget as for image/video/audio. Call it with expected_count:1 (or the required number of ordered views), wait for get_user_upload status:ready, then use files[0].url as generate_mesh.image_url; for multi-view use files[].url as image_urls and a supported multi-i2m model. A chat image is not file bytes: do not reconstruct it as base64 or replace it with a guessed text description. Pass project_id when the user explicitly chooses a project; do not assume upload alone submits generation.
- **Must I call finalize_upload after the widget?** No. Ready widget files are already usable. finalize_upload belongs only to create_upload_url → successful direct PUT → finalize_upload. Use that path for integrations with actual bytes and confirmed HTTP access, not as the default for a chat attachment. If PUT is blocked, switch to the widget rather than repeatedly finalizing an absent upload.
- **I already minted a presigned URL and PUT failed. What now?** A URL or pending asset_id does not prove bytes arrived. Stop retrying the blocked shell PUT; do not finalize an absent object or tell the user that upload completed. Use request_user_upload and the ready URL from its new ticket. If a PUT really succeeded but finalize fails, keep its asset_id and investigate the actual finalize error; do not assume that widget completion needs this finalize tool.
- **The widget says collecting or pending. Can I submit now?** No. Ask the user to finish the card, wait for status:ready and use the completed ordered batch. Do not silently generate from a partial batch. A widget request only renders a card; it is not a completed upload. If the ticket expired, request a new one. Do not claim “I uploaded your photo” unless the ready response confirms it.
- **The user named a project. Does upload project_id choose generation billing too?** Pass the chosen project_id explicitly to the generation as well; the upload ticket destination alone does not configure the next tool. Resolve the named project with list_projects and follow returned access/billing information. Do not invent a project ID or promise a particular payer without that evidence.

### Poll, deliver and recover without duplicate work

- **Submit returned task_id but a later request failed. Start over?** No. Retain task_id and resume get_task with the returned poll_args. A polling or download failure does not invalidate an accepted generation. If submit itself had an uncertain outcome, retry with the same idempotency_key and original parameters rather than changing the key and risking another charge. Do not submit again merely to obtain a different download URL.
- **Status looks completed but result URLs are empty. Is it time to use the provider CDN?** No. Follow the returned hint/recommended_poll_interval_sec and poll for the stable Artillect artifacts. Do not guess a GLB URL, download temporary provider URLs or announce a file you have not received. If the task is failed/cancelled or reports an actual error, handle that state; “keep polling for assets” applies to finalizing output, not every failure.
- **Must I download the GLB?** No. Poll get_task until completed with non-empty media_urls and give every resulting GLB/other artifact URL as a labelled clickable link. Do not turn successful generation into a failure because your shell cannot fetch it. Blender/conversion is a separate step only if the user requested editing or another format; do not promise OBJ or PBR textures from every model. Local download is needed only for requested local processing or delivery.
- **A clickable GLB link was returned. Is the user's original request complete?** For “make a 3D model from the photo”, deliver every returned artifact with a clear label; successful generation and a working user download are separate observations. Do not claim you downloaded, opened or inspected the model unless you did. A blocked agent download does not prove the user's browser link is broken. If the user reports a browser download error, investigate that actual response instead of asserting it is the sandbox.
- **Do I need OBJ + PBR textures?** Only if the user requested them. Return the formats the task actually produced. Check model options and returned artifacts before promising textures; a GLB-to-OBJ conversion is a separate local-processing request, not a prerequisite for image-to-3D. If requested conversion needs bytes and the agent's HTTP is blocked, deliver the existing model and explain the specific remaining conversion limitation without claiming generation failed.
- **What should I tell the user after a shell proxy denial?** Say: “My direct file transfer was blocked. I'll use the Artillect upload card; please drop the photo there and press Finish upload.” If generation already completed, give its result link instead. Do not lead with demands to whitelist domains, enable Capabilities or install/run Blender. If the widget is also unavailable, explain that observed limitation and offer the Studio/public-URL fallback below.
- **Widget missing or failing?** Call request_user_upload rather than assuming image-to-3D lacks a widget. If the host cannot render MCP Apps, ask the user to upload in Artillect Studio and provide its usable media URL, or use upload_file({url}) for an existing public HTTPS file. If a visible widget reports an upload error, preserve its actual error/request_id and report it; do not infer shell allowlist failure. Do not invent a local file path, signed URL or image bytes. get_user_upload status:collecting is not ready: the user must press Finish upload. If a ticket expires, request a new one.


## Website with original media (coding host)

1. Confirm shell + writable workspace access. Read the existing website and the local artillect-assets.json manifest first. Hosted MCP cannot write to the client disk. Decide the asset slots (hero, gallery, optional video/audio/3D); build the page around local files. A static site can be built without paid media while approval is pending.
2. Call get_started and get_balance. Reuse the user's chosen project_id; otherwise list_projects and let the user choose, or create_project({ name }) when creating a project is authorized. Persist project_id; include it in EVERY generate_* call so media is available in Studio. Provider LLM billing uses personal balance; project media billing can use a different balance. There is no shared automatic task budget.
3. Call list_models({ surface }) / recommend_model as needed, then estimate with the EXACT model and generation options for every asset slot. Show estimates and the total before spending; continue within the user's already authorized budget without asking again. Ask only when spend is not yet authorized or would exceed that budget. Re-estimate changes; estimates are not a shared spending cap.
4. BEFORE submit, save a stable idempotency_key for each slot in artillect-assets.json. Serialize generate_* submissions (at least 0.5s apart). Persist returned task_id immediately, together with slot, type, model, project_id and state. Never save API credentials or media capability URLs in the manifest. For an uncertain submit retry ONLY the identical payload + same idempotency_key within the API's 24-hour retention window; after expiry reconcile project history first, do not blindly submit again.
5. Resume any existing task_id using get_task({ task_id, wait_sec: 60 }) and returned poll_args. Continue queued/running tasks. A successful terminal task must have media_urls; stop on failed/cancelled or expired jobs and explain the error. An empty successful result is a delivery issue, not permission to regenerate. After a confirmed failed/cancelled task, re-estimate any replacement and continue only within existing spend authorization; ask if the replacement exceeds that budget or the prior charge/outcome is ambiguous.
6. Download completed assets into public/assets using the existing CLI, with the same account/project permissions: artillect generate wait TASK_ID --out public/assets --json. This polls the EXISTING task and returns saved[] paths; it never submits another generation. Read the CLI JSON locally: do not paste capability URLs into source code, logs or chat. If CLI is unavailable, download the current get_task media_urls directly using the host's file tools when allowed; do not send the API key to media hosts. Validate nonempty bytes, content type and that the file opens. Rename to a descriptive local name retaining the real format (coffee-hero.webp, not a guessed .png); store its path and optional checksum in the manifest. Use relative paths from the website code and absolute local paths only for host previews.
7. Manifest shape (metadata only): { version: 1, project_id: 12, assets: [{ slot: "hero", type: "image", model: "chosen-model", idempotency_key: "stable-per-slot-key", task_id: "returned-task-id", state: "downloaded", local_path: "public/assets/coffee-hero.webp" }] }. Keep an incomplete slot state as submitted or download_pending. If download fails, retain task_id and retry delivery from get_task / generate wait; NEVER buy another generation to repair a download. If the local file already validates, reuse it without download or generation. Missing local files mean redownload, not regenerate. Keep the manifest free of signed URLs and credentials before committing.
8. Integrate local assets; add meaningful alt text, video controls/poster and captions when applicable, and avoid autoplay audio. Build/test the site and inspect screenshots with a vision-capable model where the host supports image input. Report what was actually checked. Surface every resulting artifact using the host delivery procedure; inline previews depend on the harness. Publishing needs the user's hosting access and authorization, separate from building the site.


## Provider + MCP: модели и медиа для агента
Hermes / OpenCode думает через Provider, создаёт медиа через MCP и исполняет задачи в harness.
Нужны только модели для кода/чата — подключите Provider. Нужны только медиа у уже настроенного агента — подключите MCP, менять его LLM не обязательно.
Оба подключения полезны для лендинга с оригинальными фото, рекламного комплекта с видео/озвучкой или игровых ассетов: агент связывает код и медиа, а проект сохраняет результат для Studio.
Provider — LLM inference: Chat Completions, Responses, Anthropic Messages; возвращает ответы и предложения tool calls, сам инструменты не исполняет.
MCP — инструменты Artillect: каталог, estimate, генерация изображений/видео/аудио/3D, get_task, проекты и доставка ассетов.
Harness (Hermes, OpenCode и другие клиенты) — агентский цикл: вызывает инструменты, а при наличии разрешённых shell/files пишет код и скачивает файлы.
Поиск публичной справки: https://app.artillect.pro/knowledge/#knowledge-search ; рецепты MCP: https://app.artillect.pro/docs/mcp/recipes.md . Поиск не читает личные проекты и файлы.
Подключения независимы: MCP работает с другим LLM-провайдером; Provider работает без MCP и без генерации медиа. Вместе дают агенту модели и медиаматериалы одного сервиса.
Setup: /provider/, /provider/hermes/, /provider/opencode/; MCP: /docs/mcp; API-ключ: /api/. Provider URL https://app.artillect.pro/api/v1/provider; MCP URL https://app.artillect.pro/mcp.
Для совместного API-key setup: provider:write, models:read, images:write (также audio/mesh), videos:write, projects:write. files:write — только для загрузки референсов. MCP отдельно поддерживает OAuth. Ключи не публикуйте и не коммитьте.
Бесплатно напрямую: GET /api/v1/provider/models/, GET /api/v1/me/; MCP initialize/tools list, get_started/get_balance. Это auth/discovery, не проверка платного inference. Сообщение LLM-агенту оплачивается, даже если инструмент бесплатный.
1. discover: Выбрать проект, найти модель и входы в каталоге. Зачем: project_id сохраняет медиа для продолжения в Studio; capabilities.input в живом Provider /models определяет vision.
2. estimate: Оценить точные параметры каждого ассета; сверить баланс и авторизованный бюджет. Зачем: Оценка до submit помогает контролировать расходы; уже разрешённый бюджет не требует повторных подтверждений.
3. submit: Сериализовать generate_*; заранее сохранить стабильный idempotency_key, затем task_id. Зачем: Media submit — платный. Тот же payload/key защищает повтор в пределах 24 часов; после окна сначала сверить историю.
4. poll: get_task с task_id/poll_args до terminal; ошибка останавливает polling, успех требует media_urls. Зачем: Асинхронная задача продолжается после обрыва; повторный submit может купить дубликат.
5. deliver: Показать результат; в coding host скачать существующий TASK_ID через artillect generate wait TASK_ID --out public/assets --json; вести artillect-assets.json. Зачем: Manifest хранит project/task IDs и проверенные local paths без ключей/capability URLs. Сбой скачивания чинится повторной доставкой, готовый файл переиспользуется.
6. verify: Проверить реальные файлы и сайт сборкой/тестами; скриншоты — только при поддержке vision в host. Зачем: Постер — один кадр, не просмотр видео. Сборка сайта и публикация различаются; hosting доступ и разрешение нужны отдельно.
Provider использует личный накопительный ledger: округляется общая стоимость, новый запрос списывает её прирост. Старые запросы остаются по прежней политике. Studio/media тарифы не меняются; project media может использовать другой баланс. Общего автоматического жёсткого бюджета LLM+media нет.
GET /api/v1/provider/requests/?id=<X-Artillect-Request-Id> с тем же user/API key и provider:write показывает исход и расчёт. Status lookup не воспроизводит ответ; inference idempotent replay отсутствует. При неоднозначном обрыве сначала status, не слепой retry.
MCP image/poster blocks предназначены user+assistant, но передачу байтов модели и показ превью решает host. Нужны vision-модель и поддержка клиента; сервер не гарантирует одинаковый UI или автоматическое зрение.
Studio Assistant направляет запросы на создание сайта или целого приложения к Artillect Provider в Hermes/OpenCode; не пишет целое приложение в чате. Он объясняет подключение и помогает подготовить задачу; для медиа предлагает MCP. Его catalog tools не устанавливают конфиги и не исполняют shell/deployment; generation offers подтверждаются через UI. Внешний агент пишет код и собирает проект в пределах своих прав и бюджета.
Каждый запрос Provider получает короткий контекст Artillect и условных возможностей MCP. Он сохраняет инструкции клиента; наличие контекста не подключает MCP и не добавляет инструментов. Дополнительный вход учитывается в резерве и фактической стоимости LLM.

## Photo → 3D mesh

1. Call request_user_upload({ purpose: "Photo for 3D model", expected_count: 1 }).
2. Ask the user to drop the photo and press Finish upload; poll get_user_upload until status:ready.
3. Call generate_mesh({ model: "meshy-v6-i2m", image_url: files[0].url }) (project_id only when explicitly chosen). Use list_models and estimate for model-specific options and price.
4. Poll get_task using poll_args until done:true and media_urls is non-empty; deliver every artifact as a labelled clickable link. No shell PUT, GLB download or Blender is needed to create the model.

## Image outpaint / i2i (file → 16:9)

1. `get_balance`
2. Get an Artillect media URL (reusable `{ key, url }`) — **same S3 media store as Studio**, different door (Public API `/api/v1/files/*` + API key/OAuth, not Studio session multipart):
   - **Default for a user file in chat or on their machine:** `request_user_upload({ purpose, expected_count })` (set `expected_count` for a sequence) → the user drops one or more images, videos, or audio files in order and presses **Finish upload** → poll `get_user_upload({ ticket_id })` until `status: "ready"` → use `files[].url`. Also retain and report `files[].asset_id` (`ast_…`) so the user can refer to the same upload later. `collecting` is not ready: never submit from a partial batch. The widget uploads bytes directly; **never put file bytes/base64 into a tool call or the model context.**
   - **Best when you already have a public URL:** `upload_file({ url: "https://…" })` — server fetches public HTTPS (SSRF-hardened, 1 redirect hop) into our media.
   - Or reuse a prior generation `media_urls[0]`.
   - Integration with actual **image/audio** bytes and confirmed direct HTTP access (not the default for user/chat files): `create_upload_url({ filename: "shot.jpg", size_bytes })` → **you** HTTP PUT bytes to `upload_url` with `required_headers` → `finalize_upload({ asset_id })` → use finalized `url`.
   - `upload_file({ file_base64, filename })` is **deprecated as an agent path**: use it only if the widget and URL/presigned-upload paths are unavailable. Base64 consumes excessive model-context tokens.
   - Do **not** use `file_path` on hosted MCP (no client disk). Stdio MCP may use absolute `file_path`.
   - Outside the widget, video uses multipart `upload_file` / `from-url` only (no presign).
   - You can also pass a public HTTPS URL straight into `generate_image.input_urls` without upload (mirrored at submit).
   - A chat attachment is a picture in your context, not a file. **Never** substitute your own description of it for the reference; use the widget instead.
3. Optional: `estimate({ type: "image", model: "seedream-5-pro", aspect_ratio: "16:9" })`
4. `generate_image({ prompt, model: "seedream-5-pro", aspect_ratio: "16:9", input_urls: [url] })`
   - `input_urls` / `images` switches to edit/i2i automatically.
   - Recommended default: **seedream-5-pro** (photoreal t2i/i2i; injected when `model` omitted). For heavy multi-image edit / outpaint prefer **gpt-image-25-flare** or **gpt-image-2** (16 inputs). Also: gpt-image-25-sunburst, nano-banana-2, flux2-pro.
   - Common `aspect_ratio`: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`.
   - `project_id` optional (billing / library).
5. `get_task({ task_id, wait_sec: 60 })` → read `media_urls[]` (Artillect media only). Status stays `running` until MinIO URLs exist (never stop on empty `media_urls`; never download tempfile.aiquickdraw.com).
6. **Dump results into the user chat** (default — see Delivery below). Skip only if the user explicitly said not to show the result.

**Submit rate limit:** 0.5s min between `generate_*` per API key (HTTP 429 + `retry_after_sec`). Serialize batches; do not fire 7 parallel submits. Retrying a submit you are unsure went through? Pass the same `idempotency_key` — the original result comes back instead of a second charge.

## Character video (@element)

1. `list_projects` or `create_project({ name })` → `project_id`
2. Image: default to `request_user_upload` for a user-provided file; otherwise use `upload_file({ url })` or reuse `media_urls[0]`. Avoid `file_base64` except as a last-resort compatibility fallback.
3. `create_image_element({ project_id, name: "Hero", image_url })`  
   (builds Kling-shaped `media_json.images` + `preview_url`; or use `create_project_element` with full JSON)
4. `generate_video` with an element-capable model, `project_id`, prompt containing `@Hero`, and `start_image_url` when using kling-3 (required for kling-3 + elements)
5. `get_task({ task_id, wait_sec: 60 })` — repeat until `done`, then deliver (video URL in chat)

## Polling

- `generate_*` returns after provider acceptance with `poll_args` (includes `wait_sec: 60`) and canonical `task_id`; `id` / `taskId` are compatibility aliases.
- Use `get_task({ task_id, wait_sec: 60 })` as the single polling tool for every modality (kind is inferred from `task_id`), then repeat; cap is 300s but long single waits risk HTTP timeouts.

## Delivery (default — do this every time)

GENERATION COMPLETE IS NOT DELIVERY COMPLETE. After done:true with media_urls, surface EVERY result in the user-facing response in the same turn. Completed images come back as MCP image content (base64) on the get_task result: that block is the delivery when the host renders MCP images; check host support and state what is actually visible. Skip delivery ONLY if the user explicitly asked not to see the result. Host procedure: (A) EVERY HOST: where the host renders MCP image content, reference each image in your reply; MCP Apps preview cards depend on host support. If the host does not render these, use labelled links or verified local files. (B) CODING HOST WITH SHELL + WRITABLE WORKSPACE (Codex Desktop, Claude Code): for images, additionally download each media_url when direct HTTP is available (?key=… is a capability token; no auth header), verify the file is non-empty and opens, and embed the absolute local path — ![description](/absolute/path/result.png) — so the user keeps the full-resolution file. If download is blocked, use the inline image where the host renders it, otherwise a labelled link; do not claim local delivery. For video/audio/mesh use the clickable links in (C); download them only for user-requested local processing or local file delivery. Do not require Blender or host allowlist/Capabilities changes for ordinary generation. (C) NOT INLINED (video, audio, mesh, or inline_images_attached:0): give every media_url as a clearly labelled clickable link and say the media could not be embedded. Video results carry their first frame as MCP image content (inline_poster_attached:true, _meta.poster_for) — that is a still, so describe it as the opening frame and never claim you watched the clip. Never claim a delivery you did not perform.
