Skip to content

feat(mcp): async submit-then-poll generation, drop parts (v0.4.0) - #9

Merged
mhb11 merged 10 commits into
mainfrom
feat/mcp-async-generation
Jul 1, 2026
Merged

feat(mcp): async submit-then-poll generation, drop parts (v0.4.0)#9
mhb11 merged 10 commits into
mainfrom
feat/mcp-async-generation

Conversation

@mhb11

@mhb11 mhb11 commented Jul 1, 2026

Copy link
Copy Markdown
Member

What & why

Converts the Nova3D MCP server's four long-running tools from blocking to
async submit-then-poll, and removes the always-empty parts field. Ships as
nova3d-mcp 0.4.0.

Motivation:

  • Codex (and similar clients) impose a ~5-minute tool timeout that killed
    generations, which can run for minutes (regenerate_part up to ~45 min).
    Blocking tools couldn't survive that window.
  • parts was always empty by construction — the backend returns a CAS
    reference (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

  1. generate_3d / regenerate_part / add_part / articulate_model submit the
    workflow and return {workflow_id, status:"running", conversation_url?, failed:false} immediately — input signatures unchanged, only the return shape.
  2. A file-backed WorkflowStore (mirrors session_store.py, 24h TTL, 0o600)
    carries per-workflow context and a completion cache; survives MCP restart.
  3. 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 rewritten
for the async workflow. No sync shim — clean break at 0.4.0.

Testing

  • 79/79 passing, ruff clean.
  • Covers submit paths, poll+finish, cache hit, budget-exhausted, unknown/expired
    id, recoverable-vs-non-recoverable poll errors, finish-phase transient /result
    errors (keep-polling), and start_generate error paths (not-ready/402/401).

Scope & version

  • mcp/ only — no changes to app/, blender-plugin/, or the backend.
  • 0.4.0 in mcp/pyproject.toml and mcp/server.json (both version fields).

mhb11 and others added 10 commits June 30, 2026 13:39
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>
@mhb11
mhb11 merged commit 7a83929 into main Jul 1, 2026
4 checks passed
@mhb11
mhb11 deleted the feat/mcp-async-generation branch July 1, 2026 00:51
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