Alastair
05/22/2026, 8:35 PMMarvin
05/22/2026, 8:36 PMMarvin
05/22/2026, 8:38 PMcreate_flow_run_from_deployment(...)
3. Return the Prefect flow_run_id to the MCP client immediately
4. Stream status/progress updates to the MCP client by either:
- subscribing to FlowRunSubscriber, or
- polling flow run state + progress artifacts
5. On reconnect/retry, use a stable idempotency_key so you don’t create duplicate flow runs
The relevant Prefect APIs are real/available in 3.x:
- FlowRunSubscriber(flow_run_id, straggler_timeout=3, reconnection_attempts=10)
- DeploymentAsyncClient.create_flow_run_from_deployment(..., idempotency_key=..., tags=..., job_variables=...)
- prefect.artifacts.create_progress_artifact(...)
- prefect.artifacts.update_progress_artifact(...)
How to kick off the deployment safely
Use the async client and always set an idempotency_key derived from the MCP operation/request ID.
python
from prefect import get_client
async def start_prefect_run(deployment_id, parameters, request_id):
async with get_client() as client:
flow_run = await client.create_flow_run_from_deployment(
deployment_id=deployment_id,
parameters=parameters,
idempotency_key=f"mcp:{request_id}",
tags=["mcp"],
)
return flow_run
If your MCP process dies and retries the same request with the same idempotency_key, Prefect should return the existing run instead of creating a new one. That’s the main safeguard against accidental resubmission.
Polling vs subscribing
I’d think about it this way:
Option 1: Poll state + progress artifacts
- Simpler and more robust operationally
- Easier if the MCP client already has a “check status” / “resume task” pattern
- Good if you want the MCP task to survive temporary disconnects
A common flow-side pattern is:
- create one progress artifact once
- update it as work advances
- MCP side polls:
- read_flow_run(...) for state
- artifacts for latest progress value / description
Prefect has first-class progress artifact helpers:
python
from prefect.artifacts import create_progress_artifact, update_progress_artifact
artifact_id = create_progress_artifact(
progress=0.0,
key="mcp-progress",
description="Starting work"
)
update_progress_artifact(
artifact_id=artifact_id,
progress=25.0,
description="Finished step 1 of 4"
)
This is probably the easiest way to expose user-friendly incremental progress from a flow to an MCP client.
Option 2: Subscribe to flow-run events/logs
- Better for near-real-time streaming
- Lower latency than polling
- More moving parts: websocket/subscription lifecycle, reconnect handling, client backpressure
Prefect exposes FlowRunSubscriber for this:
python
from prefect.events.subscribers import FlowRunSubscriber
async def stream_prefect_updates(flow_run_id):
async with FlowRunSubscriber(flow_run_id=flow_run_id) as subscriber:
async for item in subscriber:
yield item
This is attractive if your MCP long-running task protocol wants continuous live updates.
My recommendation
- Use events/logs subscription for live UXMarvin
05/22/2026, 8:38 PMScheduled, Pending, Running, Completed, Failed, Cancelled, etc.
- Progress:
- a progress artifact (0-100)
- optional descriptive text (“loaded dataset”, “processed 3/10 partitions”)
- Detail stream:
- logs and/or emitted events
So for an MCP client, I’d model updates like:
text
status = Running
progress = 42
message = "Processed 21/50 files"
flow_run_id = ...
Then on terminal state:
text
status = Completed
progress = 100
message = "Run finished successfully"
Should you use Prefect events emitted from the flow?
Possibly, but I would not make custom events your only progress channel.
Why:
- Events are great for observability/automation/richer signaling
- But for “current progress”, artifacts are a better fit because they represent latest durable state
- Events are append-only signals; progress is naturally “latest value”
So:
- use artifacts for current progress
- use events/logs for live feed / richer lifecycle updates
If the MCP worker/server dies
Important point: once the flow run has been created, it is managed by Prefect. The MCP process is not the owner of execution.
So if your MCP process dies:
- the Prefect flow run continues independently
- if it was submitted to a work pool, a worker can still pick it up / keep running it
- your MCP layer just loses its in-memory subscription state
What you need for recovery:
- persist the mapping mcp_request_id -> prefect_flow_run_id
- use a stable idempotency_key
- on restart:
- first look up the known flow_run_id
- if missing, retry submission with same idempotency_key
- then resume monitoring via polling or subscription
Could it resubmit the deployment?
Yes, if you implement it naively.
If the MCP process crashes after submitting but before persisting the flow_run_id, then on restart it may try again.
To avoid duplicate runs:
1. Generate stable idempotency_key from the MCP request/task ID
2. Persist that request ID externally if possible
3. Treat Prefect as the source of truth for run existence
That makes retries safe.
Operational considerations / gotchas
A few things to keep in mind:
- Don’t rely on only an in-memory websocket subscription
- if the MCP process dies, you lose the stream
- back it with polling/artifact reads
- Progress artifacts are durable, but not a full event history
- great for current progress
- not enough if you need every transition
- Logs can be a good lightweight progress stream too
- especially if you already emit structured log messages from the flow
- Use tags/labels on MCP-triggered runs
- for example tags=["mcp", "customer-facing"]
- makes them easier to find/debug
- Think about idempotency scope carefully
- same request should reuse same key
- different user actions should get different keys
- Return flow_run_id to the MCP client as early as possible
- that gives the client something durable to reconnect to
Practical architecture I’d suggest
For FastMCP specifically, I’d implement it like this:
- Start tool
- submits deployment with idempotency_key
- returns:
- flow_run_id
- initial state
- maybe a status endpoint/handle in your MCP abstraction
- Progress stream
- primary: subscribe with FlowRunSubscriber
- fallback: poll read_flow_run + latest progress artifact
- Recovery
- if server restarts, resume from flow_run_id
- if flow_run_id was never saved, retry same idempotency_key
- Flow code
- update one progress artifact throughout the run
- optionally log milestone messagesMarvin
05/22/2026, 8:38 PMNate
05/22/2026, 8:39 PMAlastair
05/22/2026, 8:39 PMNate
05/22/2026, 8:40 PMAlastair
05/22/2026, 8:40 PMAlastair
05/22/2026, 8:41 PMNate
05/22/2026, 8:41 PMAlastair
05/22/2026, 8:41 PMAlastair
05/22/2026, 8:42 PMNate
05/22/2026, 8:45 PMNate
05/22/2026, 8:46 PMAlastair
05/22/2026, 9:12 PMAlastair
05/22/2026, 9:12 PMNate
05/22/2026, 9:13 PMAlastair
05/22/2026, 9:13 PMAlastair
05/22/2026, 9:13 PMNate
05/22/2026, 9:14 PMAlastair
05/22/2026, 9:15 PMAlastair
05/22/2026, 9:15 PMNate
05/22/2026, 9:16 PMAlastair
05/22/2026, 9:17 PMNate
05/22/2026, 9:17 PMAlastair
05/22/2026, 9:19 PMAlastair
05/22/2026, 9:20 PMprefect_api.execute_deployment(n) 🤣Alastair
05/22/2026, 9:20 PMNate
05/22/2026, 9:21 PMAlastair
05/22/2026, 9:22 PMNate
05/22/2026, 9:22 PMAlastair
05/22/2026, 9:24 PMNate
05/22/2026, 9:24 PMAlastair
05/22/2026, 9:31 PM