Skip to content

[BETA] Desktop app MCP stdio layer#1649

Open
tanish2k09 wants to merge 12 commits into
mainfrom
worktree-mcp-mvp-spike
Open

[BETA] Desktop app MCP stdio layer#1649
tanish2k09 wants to merge 12 commits into
mainfrom
worktree-mcp-mvp-spike

Conversation

@tanish2k09

Copy link
Copy Markdown
Collaborator

Incomplete deep support for MCP features for external LLM providers like Claude based on local desktop data:

  • Supports generating images
  • Supports customizing the image config (prompt, resolution, aspect ratio, count, model etc)
  • Tested working on Claude (Opus 4.8)

tanish2k09 added 12 commits June 8, 2026 12:01
Standalone crate at crates/cli/artcraft-mcp/ exposing a single MCP tool
that submits an image generation via /v1/omni_gen/generate/image, polls
/v1/jobs/session until the job completes, and returns the CDN URL.

Reads Storyteller cookies from ~/Artcraft/credentials/ written by the
desktop app — no separate sign-in needed. 90s poll timeout.

Kept as its own [workspace] during the spike so the parent workspace
Cargo.toml stays untouched and parallel work on main doesn't conflict.
Workspace registration deferred to a final commit once the spike is
validated end-to-end against Claude Desktop.
Production desktop v0.32 keeps Storyteller cookies only in the vendored
tauri-plugin-http jar at <app_cache_dir>/.cookies; persist_all_to_disk()
for the text-file path in the dev source is commented out, so shipped
builds never populate those files.

Add a JSON-parsing fallback that reads the .cookies jar, filters to
api.storyteller.ai, and extracts the session + visitor cookies. The
text-file path stays as a secondary fallback for forward-compat.

macOS-only path resolution via directories::ProjectDirs for the
"ai.artcraft.app" bundle id. Linux/Windows resolutions differ from
Tauri's app_cache_dir() and are deferred.
The OmniGenImageCostAndGenerateRequest doc comment claims the server
auto-generates an idempotency_token when None is passed, but the
backend actually rejects requests without one ("idempotency token does
not match the expected format"). Match the desktop app's behavior at
handle_artcraft_via_omni_endpoint.rs:30 by sending a fresh hyphenated
v4 UUID per call.
Previously the tool returned only Content::text(cdn_url), which Claude
Desktop renders as a "show image" link. Switch to fetching the
generated image bytes from the CDN, base64-encoding them, and
returning both Content::image and Content::text so the chat shows the
image inline while keeping the URL clickable.

On fetch failure (timeout, CDN error, etc.) we fall back to the
URL-only response so the tool still succeeds.
Claude Desktop rejects tool results over 1 MB; base64 adds ~33%
overhead so the raw payload must stay under ~750 KB. Full-res PNGs
from Artcraft (often 1024+ px) routinely exceed that.

Use media_links.maybe_thumbnail_template at width 768 for the inline
preview when the API returns one; fall back to the full cdn_url
otherwise. The full URL is still returned as text so the user can
open the original at full resolution.
The tool now accepts an optional `model` arg (snake_case id matching
CommonImageModel's serde rename, e.g. nano_banana_pro,
flux_pro_1p1_ultra, gpt_image_2, midjourney_8, seedream_4, etc.).
Defaults to nano_banana_pro when omitted. Unknown ids return a
structured error so the LLM can correct itself.

Valid model ids are listed in the JSON schema description so Claude
can pick one before calling the tool.
Split main.rs (398 lines) into modules: main.rs, server.rs, creds.rs,
inline_image.rs, tools/{generate_image,list_image_models,
get_user_status,estimate_image_cost}.rs.

New tools:

- list_image_models — GET /v1/omni_gen/models/image; returns each
  model's constraints (aspect_ratio_options, resolution_options,
  quality_options, batch_size_max, image_refs_supported, image_refs_max,
  text_prompt_max_length, etc.) so Claude can pick valid params before
  calling generate_image. Optional provider filter.

- get_user_status — parallel GETs to /v1/credits/namespace/artcraft +
  /v1/subscriptions/namespace/artcraft. Returns credits (free, monthly,
  banked, total) and active subscription info.

- estimate_image_cost — POST /v1/generate/cost_estimate/image. Takes
  model + num_reference_images + aspect_ratio + resolution + quality +
  num_images. Returns cost_in_credits, cost_in_usd_cents, is_free,
  is_unlimited, is_rate_limited, has_watermark — so Claude can confirm
  with the user before spending credits.

Expanded generate_image:

- New optional params: aspect_ratio, resolution, quality, num_images,
  reference_image_urls.
- reference_image_urls fetches each https URL, validates as image/*
  (PNG/JPEG/GIF, max 20 MB), uploads via /v1/media_files/..., and
  passes the resulting MediaFileToken to omni_gen. Marked as
  intermediate system files so they don't appear in the user's gallery.

Constraint validation is left to backend's structured errors; Claude
is expected to consult list_image_models first.
Make failure modes legible to both the LLM and the user:

errors.rs:
- ErrorCode enum: artcraft_not_installed, not_logged_in,
  session_expired, backend_unavailable, invalid_params,
  generation_timeout, generation_failed, reference_image_failed,
  internal.
- ToolError { error_code, message, remediation, details? } that
  serializes as JSON in CallToolResult::error content blocks.
- not_logged_in() detects whether the app is installed
  (/Applications/ArtCraft.app or the cache dir) and returns the
  appropriate code + remediation text.

creds.rs:
- load_session() now returns Result<_, ToolError>. The user sees a
  proper sign-in remediation instead of a generic anyhow message.

tools/check_artcraft_connection.rs (new):
- Always succeeds, never errors. Reports desktop_app_installed,
  signed_in, session_valid, error_code, remediation. Detects expired
  sessions by making a cheap authenticated get_session_credits call.

tools/list_image_models.rs:
- 401/403 from the API now maps to session_expired.

All existing tools converted from anyhow::Result to Result<_, ToolError>
with appropriate error codes per failure path.

server.rs:
- Updated instructions teach Claude how to react to each error code
  (open desktop app and sign in, download from getartcraft.com, etc.)
  and to call check_artcraft_connection when troubleshooting.
- error_result() now serializes the full ToolError struct as JSON
  inside the error content block.

main.rs:
- New '--check' / '-c' CLI flag prints a human-readable diagnostic to
  stdout and exits, useful when the user can't see the Artcraft tools
  in Claude and wants to debug from a terminal without round-tripping
  through MCP.
When num_images>1, the omni_gen response is still a single job with a
single inline result; the rest of the batch is reachable through the
result's maybe_batch_token via GET /v1/media_files/batch_gen_redux/{token}.

Previously we only surfaced the single inline result, so 'num_images: 4'
returned just 1 image. Now after poll detects CompleteSuccess we follow
the batch token to fetch every member and return them all.

Inline rendering:
- Single result: 768 px thumbnail (unchanged).
- Batch: 512 px per image — 4 thumbnails at 512 px JPEG stay well under
  Claude Desktop's 1 MB tool-result cap.

Per-image inline fetch failures are logged and skipped (still return
the URL as text), so a single bad thumbnail doesn't poison the whole
response.

Multi-image text output is one URL per line, prefixed 'Image N:'.
Drop the standalone [workspace] block from the crate's Cargo.toml and
add 'crates/cli/artcraft-mcp' to the root workspace members list.

Kept this as the final commit on the branch — until this lands the
crate was a separate workspace, which kept the root Cargo.toml
untouched during the spike (avoiding conflicts with parallel work on
main) but meant 'cargo build' at the repo root didn't include it.

After this commit, 'cargo check -p artcraft-mcp' works from the repo
root and the crate is a first-class workspace member.
Records rmcp + transitive deps added when the crate joined the parent
workspace.
Lets the LLM truthfully answer 'what did that just cost?' without
remembering the pre-flight estimate. The backend has no per-job cost
endpoint or credit-transactions API; the only way to know the realized
cost is to diff the credit balance before and after.

generate_image::run now:
- Captures get_session_credits before submit.
- Runs generation + poll as before.
- Captures get_session_credits after success.
- Computes credits_charged = balance_before - balance_after.
- Attaches { credits_charged, credits_balance_after } to the response.

Best-effort: if either credit lookup fails the generation still
succeeds and the cost just isn't reported. Caveat noted in comments:
concurrent generations settling inside this window can over-count the
diff.

The tool's text content gains a 'Cost: N credits charged. Balance
after: M credits.' line when cost info is available — picked up by
both the user reading the chat and the LLM answering follow-up
questions about cost.
@netlify

netlify Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploy Preview for artcraft-website ready!

Name Link
🔨 Latest commit eff94be
🔍 Latest deploy log https://app.netlify.com/projects/artcraft-website/deploys/6a3095500c98b2000872625e
😎 Deploy Preview https://deploy-preview-1649--artcraft-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploy Preview for genhub-website failed. Why did it fail? →

Name Link
🔨 Latest commit eff94be
🔍 Latest deploy log https://app.netlify.com/projects/genhub-website/deploys/6a3095504260a60008356cc5

@netlify

netlify Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploy Preview for artcraft-webapp ready!

Name Link
🔨 Latest commit eff94be
🔍 Latest deploy log https://app.netlify.com/projects/artcraft-webapp/deploys/6a30955016e51000087520b9
😎 Deploy Preview https://deploy-preview-1649--artcraft-webapp.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@tanish2k09 tanish2k09 requested a review from echelon June 16, 2026 00:14
@netlify

netlify Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploy Preview for storyteller-docs failed. Why did it fail? →

Name Link
🔨 Latest commit eff94be
🔍 Latest deploy log https://app.netlify.com/projects/storyteller-docs/deploys/6a3095504e65230008566d79

@tanish2k09

Copy link
Copy Markdown
Collaborator Author

@echelon code touches rust, requesting review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant