feat(mcp): async submit-then-poll generation, drop parts (v0.4.0) - #9
Merged
Conversation
Implements _finish_workflow to fetch terminal workflow results, persist conversation history, cache outputs, and build per-operation payloads. Adds _get_workflow_store factory and _EDIT_BACKEND_OPERATION map. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert regenerate_part, add_part, and articulate_model to the same
async submit-then-poll pattern as generate_3d. Each tool now calls
client.start_* and returns {workflow_id, status:"running", failed:false}
immediately, storing context via WorkflowStore for _finish_workflow to
consume when get_generation_status is polled. Drops blocking calls,
glb_url, and parts from edit-tool responses.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mpletion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…structions - server.py: replace instructions= with async WORKFLOW block (no parts); remove _make_progress_callback and WorkflowStatus import - client.py: delete blocking generate/regenerate_part/add_part/articulate_model and _poll_and_collect; remove now-unused asyncio/Callable/Awaitable/POLL_INTERVAL_SECONDS - models.py: delete _extract_part_names, remove GenerationResult.parts field and its call site in from_api; remove unused re import - tests: remove 5 _make_progress_callback tests from test_server.py; remove tests for blocking methods and parts assertions from test_client.py - pyproject.toml, server.json: version 0.3.0 → 0.4.0 73/73 tests pass, ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, 401) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…result errors - Wrap `_finish_workflow` call in try/except: recoverable Nova3DError and httpx.TimeoutException both return a keep-polling running payload instead of leaking a raw exception; non-recoverable Nova3DError returns failed=True. - Add `import httpx` to server.py (needed for the new except branch). - Update Nova3DClient docstring example from deleted `generate()` to the real async start_generate/get_status/get_result submit-then-poll pattern. - Add three covering tests: recoverable error keeps polling, non-recoverable error returns failed, timeout keeps polling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Converts the Nova3D MCP server's four long-running tools from blocking to
async submit-then-poll, and removes the always-empty
partsfield. Ships asnova3d-mcp0.4.0.Motivation:
generations, which can run for minutes (regenerate_part up to ~45 min).
Blocking tools couldn't survive that window.
partswas always empty by construction — the backend returns a CASreference (no inline content), so all three extraction strategies produced
nothing. It wasn't load-bearing (the backend takes a free-text part name), so
it's dropped rather than papered over.
No backend changes: the Temporal backend already splits submit (
202+workflow_id) from status/result — this PR just stops blocking on it client-side.How it works
generate_3d/regenerate_part/add_part/articulate_modelsubmit theworkflow and return
{workflow_id, status:"running", conversation_url?, failed:false}immediately — input signatures unchanged, only the return shape.WorkflowStore(mirrorssession_store.py, 24h TTL,0o600)carries per-workflow context and a completion cache; survives MCP restart.
get_generation_status(workflow_id)polls and, on terminal completion,fetches the result, embeds metadata, persists history, caches, and returns the
full payload. Cache hit → no client built; failures are not cached, so a
transient backend blip can't poison a workflow_id.
Removed
Blocking client methods,
_poll_and_collect,_make_progress_callback,_extract_part_names,GenerationResult.parts. FastMCP instructions rewrittenfor the async workflow. No sync shim — clean break at 0.4.0.
Testing
id, recoverable-vs-non-recoverable poll errors, finish-phase transient
/resulterrors (keep-polling), and
start_generateerror paths (not-ready/402/401).Scope & version
mcp/only — no changes toapp/,blender-plugin/, or the backend.0.4.0inmcp/pyproject.tomlandmcp/server.json(both version fields).