Protocol types¶
JSON-RPC envelope, MCP message types, and error codes.
JSON-RPC¶
JsonRpcRequest
dataclass
¶
A JSON-RPC 2.0 request: has id and expects a response.
JsonRpcNotification
dataclass
¶
A JSON-RPC 2.0 notification: no id, no response expected.
JsonRpcResponse
dataclass
¶
A JSON-RPC 2.0 response: exactly one of result or error is set.
result_type
class-attribute
instance-attribute
¶
Stamped into the result object as resultType.
Every result carries it from 2026-07-28 onward, so it is applied here —
the one place a handler's return value becomes wire JSON — rather than in
each handler. A result that has already named itself wins, which leaves room
for a non-complete one to be built where it is produced.
JsonRpcError
dataclass
¶
JSON-RPC 2.0 error object.
code is typed as int so server-defined codes outside the
JsonRpcErrorCode enum can still
be represented faithfully.
JsonRpcErrorCode ¶
Bases: IntEnum
JSON-RPC 2.0 standard error codes plus MCP-specific reservations.
JSON-RPC defines -32700 through -32600 and -32603. MCP then partitions the
server-error range: -32000..-32019 is implementation-defined,
-32020..-32099 is reserved for the spec itself, and everything
allocated here sits in the implementation-defined half — with one
exception.
-32002 is not ours to allocate. The resources spec names it for
"Resource not found", and the 2026-07-28 revision singles it out as the
one legacy code clients should keep recognising, so a spec-following client
reads it as a missing resource whatever a server spends it on. It belongs
to RESOURCE_NOT_FOUND and nothing else.
-32003 and -32004 are burned rather than reused: they were this
package's own not-found codes before the wire values were aligned with the
spec, so an older client still reads them as "resource/prompt not found"
and "unknown tool". The next implementation-defined code allocated is
-32006.
parse_message ¶
Classify a parsed JSON object as request / notification / response.
Raises ValueError if the payload has no recognizable shape — caller
should translate that to a JSON-RPC -32600 (Invalid Request) error.
Display metadata¶
Icon
dataclass
¶
One entry in a wire type's icons array.
Icons are pure display metadata: a client shows them beside a tool, resource, prompt or the server itself. This package only emits them — fetching, sanitising and rendering are the client's problem, and the spec puts a long list of MUSTs on the consumer side precisely because icon bytes are untrusted input.
Attributes:
| Name | Type | Description |
|---|---|---|
src |
str
|
URI pointing at the image. Must be |
mime_type |
str | None
|
Overrides the type the source serves, for a source that
serves a generic |
sizes |
tuple[str, ...]
|
WxH strings — |
theme |
IconTheme | None
|
Which background the icon was drawn for. Omit when it works on both. |
IconTheme ¶
Bases: str, Enum
Which background an Icon
was designed for.
Omitting the theme (None) tells the client the icon works on either,
which is right for most artwork. Declare it only when shipping a
light/dark pair.
Result envelope¶
ResultType ¶
Bases: str, Enum
The discriminator every result carries from 2026-07-28 onward.
A MUST for servers implementing that revision and harmless before it —
a legacy result object is an open shape, and a client on an older revision
reads an absent resultType as complete — so it is emitted
unconditionally rather than era-branched.
Attributes:
| Name | Type | Description |
|---|---|---|
COMPLETE |
The result is the answer. |
|
INPUT_REQUIRED |
The result asks the client for input and expects the original request to be retried with the answers. Nothing here produces one yet; the vocabulary is the spec's. |
TASK
class-attribute
instance-attribute
¶
A durable handle instead of the result — see rest_framework_mcp.tasks.
The spec types ResultType as "complete" | "input_required" | string,
the open tail being how extensions add their own. Servers MUST NOT set
this on any result other than a CreateTaskResult, which is why nothing
stamps it centrally the way COMPLETE is stamped — the task handler sets
it explicitly and no other handler can reach it.
CacheScope ¶
Bases: str, Enum
How widely a cacheable result may be reused, per Cache-Control.
Derived, never configured. PUBLIC licenses any intermediary to
serve the response across authorization contexts, so a result that varies
by caller and is labelled PUBLIC is a cross-tenant disclosure with a
cache in front of it — precisely the mistake a settings knob would invite.
The handlers work it out from what shaped the response: a
permission-filtered listing is PRIVATE, an unfiltered one PUBLIC,
and a resource body always PRIVATE.
Discovery and the initialize handshake¶
DiscoverResult
dataclass
¶
The server's response to a server/discover request.
server/discover is the 2026-07-28 revision's replacement for the
initialize handshake: the same three answers, but as an ordinary request
rather than a stateful negotiation, so it can be cached, repeated or
skipped.
Two shape differences from
InitializeResult,
neither cosmetic:
supportedVersionsis a list, not a negotiated single version. Nothing is agreed here; the client picks one and puts it on subsequent requests.serverInfomoves into_metaunder a reserved key, because it is self-reported and unverified and clients SHOULD NOT change behaviour or make security decisions from it.
Implementation
dataclass
¶
Identifies an MCP client or server: name + version, with an optional title.
Mirrors the spec's Implementation extends BaseMetadata, Icons.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
"Intended for programmatic or logical use" — the stable
identifier. What distinguishes two servers to a client, and what
server-scoped state keys off. Not interchangeable with
|
version |
str
|
The implementation's version string. |
title |
str | None
|
"Intended for UI and end-user contexts" — the human-readable
label. Clients fall back to |
description |
str | None
|
UI copy a client shows next to the server's name in a
connection list. Not the |
website_url |
str | None
|
A link a client can offer alongside the name. |
icons |
tuple[Icon, ...]
|
Display icons, emitted only when non-empty. |
ClientCapabilities
dataclass
¶
Capability bundle the client advertises in initialize.
All fields are open-ended dicts because the MCP spec leaves room for future capability sub-keys. We round-trip them verbatim.
extensions
class-attribute
instance-attribute
¶
Protocol extensions the client implements, keyed by extension id — how a
client says it supports MCP Apps (io.modelcontextprotocol/ui).
Parsed for introspection only; nothing gates on it, since the spec merely SHOULD-s a check and unsupporting clients are required to ignore what they did not ask for.
ServerCapabilities
dataclass
¶
Capability bundle the server advertises in the initialize response.
Every field defaults to None and is omitted from the payload when unset,
because a capability is a promise: a client that sees resources will
call resources/list, and one that sees completions will send
completion/complete. handle_initialize populates only what this
server can actually answer.
There is deliberately no logging field — the 2026-07-28 revision
deprecated the logging utility outright.
extensions
class-attribute
instance-attribute
¶
Extensions this server supports, keyed by identifier.
First-class in the 2026-07-28 schema and the counterpart to the
per-request clientCapabilities.extensions. Values are per-extension
settings objects; {} means "supported, nothing to configure", which is
what every extension here uses.
Distinct from experimental: that is a free-for-all with no naming
rules, while extension keys are namespaced identifiers a client matches
exactly.
InitializeParams
dataclass
¶
Parsed initialize request params.
InitializeResult
dataclass
¶
The server's response to an initialize request.
Tools¶
Tool
dataclass
¶
An MCP tool descriptor as returned by tools/list.
input_schema and output_schema are JSON Schema documents.
annotations carries the MCP ToolAnnotations hint bundle
(readOnlyHint, destructiveHint), a closed spec-defined set.
meta is the base-protocol _meta bundle, emitted under "_meta".
It stays a free-form dict at this wire boundary rather than a closed
dataclass because _meta is MCP's open extension namespace — any
extension may add its own key. The same holds for every meta field on
the other wire types here.
icons
class-attribute
instance-attribute
¶
Display icons for this entry, emitted only when non-empty.
ToolContentBlock
dataclass
¶
One entry in a
ToolResult's
content array.
The spec models content as a five-member union — text, image,
audio, resource_link, resource — whose members share no fields
beyond type, annotations and _meta. This is one dataclass with
every member's fields optional rather than five behind a union: content
is a wire boundary, the shape is decided by type, and to_dict
emits only what is set.
Construct through the classmethods, not the initialiser. Each takes
exactly the fields its block type requires, which is where the spec's rules
live — an image without a mimeType or a resource_link without a
uri is not a block a client can use, and the constructors make those
unrepresentable. text_block and embedded_resource are named around
the text and resource fields they would otherwise shadow.
annotations carry audience / priority / lastModified — the
identical bundle resources use, which is why it stays a free-form dict.
text_block
classmethod
¶
text_block(
text: str,
*,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
) -> ToolContentBlock
A plain text block — what every tool result carries today.
image
classmethod
¶
image(
data: bytes | str,
*,
mime_type: str,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
) -> ToolContentBlock
An image block. data may be raw bytes or an existing base64 string.
mime_type is required by the spec, not merely recommended — a client
has no other way to know how to decode the bytes.
audio
classmethod
¶
audio(
data: bytes | str,
*,
mime_type: str,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
) -> ToolContentBlock
An audio block — the same contract as image.
resource_link
classmethod
¶
resource_link(
uri: str,
*,
name: str,
description: str | None = None,
mime_type: str | None = None,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
) -> ToolContentBlock
A pointer to a resource the client can read separately.
The cheapest way to return something large or non-JSON: the URI is one
this server's own resources/read already serves, so no bytes ride on
the tool-result path. Per the spec a linked resource need not appear in
resources/list.
embedded_resource
classmethod
¶
embedded_resource(
resource: ResourceContents,
*,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
) -> ToolContentBlock
A resource's contents inlined into the result.
Prefer resource_link unless the client cannot make a second
round trip — embedding spends the caller's context on bytes it may not
need.
ToolContentKind ¶
Bases: str, Enum
What a tool's rendered payload becomes in the result's content array.
Declared per binding rather than sniffed from the payload, for the same reason as
ResourceEncoding: a base64
str and a text body are indistinguishable by inspection.
Embedded (resource) blocks have no kind here on purpose — inlining contents
means producing them, which resources/read already does, so a tool returns a
RESOURCE_LINK and lets the client decide whether to spend context on the body.
ToolContentBlock.embedded_resource
remains available for a caller building blocks by hand.
Attributes:
| Name | Type | Description |
|---|---|---|
TEXT |
One text block rendered per the binding's |
|
IMAGE |
The payload is the media itself — |
|
AUDIO |
As |
|
RESOURCE_LINK |
The payload names resources rather than containing them
— one mapping with |
ToolResult
dataclass
¶
The result of a successful tools/call.
Attributes:
| Name | Type | Description |
|---|---|---|
content |
list[ToolContentBlock]
|
The human-readable / token-efficient projection the encoder produces (JSON, TOON, or a media block). |
structured_content |
Any
|
The same answer JSON-shaped, for clients to parse
directly. |
is_error |
bool
|
|
meta |
dict[str, Any] | None
|
The base-protocol |
Resources¶
Resource
dataclass
¶
A concrete MCP resource as returned by resources/list.
uri is the canonical address (e.g. "invoices://1"); mime_type
advertises how the contents will be encoded by resources/read.
icons
class-attribute
instance-attribute
¶
Display icons for this entry, emitted only when non-empty.
ResourceTemplate
dataclass
¶
A parameterised resource address (RFC 6570 URI Template).
Returned by resources/templates/list. Clients fill in the template
variables and call resources/read with the resulting URI.
icons
class-attribute
instance-attribute
¶
Display icons for this entry, emitted only when non-empty.
ResourceContents
dataclass
¶
One contents entry returned by resources/read.
Either text or blob is set — never both. blob is base64-
encoded per the MCP spec.
Prompts¶
Prompt
dataclass
¶
An MCP prompt descriptor as returned by prompts/list.
A server-defined template the client invokes by name to get back a sequence
of LLM messages. Arguments are filled in at prompts/get time and
threaded into the rendering callable as kwargs.
icons
class-attribute
instance-attribute
¶
Display icons for this entry, emitted only when non-empty.
PromptArgument
dataclass
¶
One declared input to an MCP prompt.
Mirrors the spec's PromptArgument: a name plus optional description,
with a flag for whether the client must supply it. Advertised in
prompts/list.
PromptMessage
dataclass
¶
One conversation turn returned by prompts/get.
The spec accepts user or assistant for role, and content is
one content block — text, image, audio or an embedded
resource. It stays a plain dict because that is exactly the wire shape;
block is the typed way in and text covers the common case.
text
classmethod
¶
Convenience constructor for the common case of a text turn.
block
classmethod
¶
Build a turn from any content block.
The spec uses one content vocabulary across tool results and prompt messages, so
this reuses
ToolContentBlock
rather than growing a parallel set. resource_link is the one member prompts
do not accept — a prompt message embeds content, it does not point at it.
GetPromptResult
dataclass
¶
Result envelope returned by prompts/get.
description surfaces the prompt's purpose to the client UI alongside
the rendered messages.
Completion¶
Completion
dataclass
¶
The completion object inside a completion/complete result.
Attributes:
| Name | Type | Description |
|---|---|---|
values |
tuple[str, ...]
|
Suggestions ranked by relevance, capped at
|
total |
int | None
|
Count of all matches. Optional, and left unset by this package: knowing it would mean counting every match, which is the work a queryset-backed completer should be allowed to skip. |
has_more |
bool
|
Whether the completer had more to give. |
Subscriptions¶
See Server-pushed notifications for how to wire one up.
SubscriptionFilter
dataclass
¶
What a subscription asked for, and what the server agreed to.
One type for both directions, because they are the same shape: the client
sends a filter on subscriptions/listen and the server answers with the
subset it will honour. Two types could drift, and the point of the
acknowledgement is that the client can compare them.
Empty means nothing is delivered. The spec makes every type opt-in and
says the server MUST NOT send what was not requested, so an absent field
is a refusal rather than a default: a client that sends {} is
acknowledged with nothing and its stream closes immediately.
from_params
classmethod
¶
Read a filter off the wire, ignoring anything unusable.
A malformed entry is dropped rather than failing the request: the acknowledgement reports what actually took effect, so the drop is visible, which beats rejecting a subscription over one bad key.
to_dict ¶
The wire form, omitting everything not asked for.
Only truthy entries appear: the acknowledgement reads as "these are the
things you will receive", so a false or an empty list in it would
read as a promise about something.
NotificationKind ¶
Bases: str, Enum
The notification types a client can opt in to, other than by URI or id.
Opt-in is a MUST, not a courtesy: "the server MUST NOT send notification types the client has not explicitly requested." So this is a closed set, and anything outside the request's filter is not sent.
The values are the notification methods with the notifications/ prefix
stripped, and the filter field names are their camelCase forms — kept
mechanically related so a new kind cannot be added to one and forgotten in
the other.
SubscriptionBroker ¶
Bases: Protocol
Topic-keyed pub/sub for notifications, with many subscribers per topic.
Deliberately not SSEBroker, which this package already has for the
legacy GET stream: that one keys on session id and allows a session at most
one live subscriber. Here a notification is addressed to a topic, whose
subscriber set is discovered at publish time, and several clients — or
several subscriptions from one client — legitimately watch the same
resource, so replacing the previous subscriber would silently disconnect
them.
Topics are opaque strings built by
rest_framework_mcp.subscriptions.utils — a resource URI, a task id, a
notification kind. The broker never interprets them, so adding a new
notification type needs no change here.
A topic is not an authorization boundary. Anyone who can name a topic can receive it, so the subscription checks what the caller may watch before it attaches — once per subscription rather than once per delivery, and without the broker having to understand principals.
subscribe returns a queue that receives every payload published to
any of topics until unsubscribe. One queue per subscription, not
per topic — a subscription watching five resources reads one stream, which
is what the wire format wants. It is awaitable because that is load-bearing:
it must not return until the subscription is genuinely live, or the caller
emits "you are subscribed" while publishes still go nowhere.
active_subscriptions
property
¶
How many subscriptions this broker is currently feeding.
Per process, not per cluster. It bounds this worker's occupancy —
see MAX_CONCURRENT_SUBSCRIPTIONS — and a cluster-wide count would
cost a round trip per subscribe to bound a per-worker resource.
InMemorySubscriptionBroker ¶
In-process topic fan-out, for development and tests.
A multi-worker deployment needs a cross-process broker. The write that
triggers a notification lands on whichever worker served that request while
the subscriber's stream is parked on a different one, so an in-process
broker delivers to nobody, silently, and the failure looks exactly like "the
resource never changed". Subscriptions are a single-worker feature until
a cross-process broker is passed to MCPServer(subscription_broker=…).
Not a default. MCPServer constructs no broker at all when none is
given, precisely so this class cannot be reached by accident.
Unlike the session broker this keeps a set of queues per topic, and a
queue may sit under several topics at once — one subscription watching five
resources reads a single stream. Both directions of that mapping are kept so
unsubscribe does not have to walk every topic.
publish
async
¶
Deliver to every subscriber of topic; returns how many got it.
0 means nobody was listening, which is ordinary and not an error:
notifications are best-effort by design, and a client that missed one
re-reads the resource.
RedisSubscriptionBroker ¶
Cross-process topic fan-out over Redis pub/sub. The deployable one.
InMemorySubscriptionBroker
delivers to nobody once the publisher and the subscriber's stream land on
different workers, and does so
silently, so anything past one worker wants this:
from redis.asyncio import Redis
from rest_framework_mcp.subscriptions.redis_subscription_broker import (
RedisSubscriptionBroker,
)
server = MCPServer(
name="my-app",
subscription_broker=RedisSubscriptionBroker(
Redis.from_url("redis://…"), namespace="my-app"
),
)
Pass namespace whenever one Redis serves more than one server.
Topic names are built from caller-supplied values — a notification kind, a
resource URI — so two servers that register the same SelectorSpec under
the same URI derive the same topic. Sharing a broker (or two brokers on the
same default prefix) then routes one server's change signals to the other's
subscribers, past the resources/read permission grant_subscription
gates them on. The cache-backed session and task stores fold the server's
name into their key prefix for exactly this reason; a Redis client is
the consumer's to construct, so here it is a constructor argument. The value
is hashed into the prefix, since name is free-form.
One Redis channel per topic, one listener task per subscription. The mapping is many-to-many — a subscription watches several topics and several subscriptions watch one topic — so each subscription gets a single task subscribed to all of its channels, feeding the one queue its stream reads. That keeps the task count proportional to live subscriptions rather than to topics, and it is subscriptions that connections bound.
The Redis client's lifecycle belongs to the consumer — close it during ASGI lifespan shutdown, as with the other Redis collaborators here.
subscribe
async
¶
Register the channels before returning, then pump them.
The await is the whole point. Registering inside the background task would return a queue that is not yet subscribed, and the caller emits "you are subscribed" immediately afterwards, so everything published in that window goes nowhere while the client has been told otherwise — a race this class's own deployment makes likely, since the publisher is another process and waits for nobody.
publish
async
¶
Publish to topic's channel; returns Redis's subscriber count.
The count is cluster-wide receivers, not confirmed deliveries, and a listener still connecting reports as zero. A diagnostic, not a delivery guarantee: notifications are best-effort and a client that missed one re-reads the resource.
topic_for_resource ¶
The topic notifications/resources/updated for uri goes to.
Exact URI, never a prefix. The spec permits notifying about a
sub-resource of the one subscribed to, which invites prefix matching, but
a prefix match over a free-form URI guesses at a scheme this package does
not own and fails both ways: invoices://1 would match invoices://11,
while a tenant-scoped t1/invoices://… would match nothing.
A publisher that wants a collection watched says so, by publishing the
collection URI alongside the instance one — an invalidates= naming both
invoices://{pk} and invoices:// is explicit and reviewable where a
matching rule is neither.
Tasks¶
The io.modelcontextprotocol/tasks extension. See
Long-running work for how to wire it
up; these are the types.
Task
dataclass
¶
The wire shape of a task, in every message that carries one.
One type serves three roles the spec names separately — the
CreateTaskResult body, the tasks/get result and the
notifications/tasks params — because they are the same object with the
same fields. The spec's WorkingTask / InputRequiredTask /
CompletedTask / FailedTask / CancelledTask split is only which
extra field is present, which is a property of status;
to_dict emits the right one and refuses the wrong one.
Timestamps are ISO 8601 strings, stored as strings. They come from the
store and go out verbatim; nothing here parses or compares them. A
datetime would invite a comparison against now(), and TTL expiry
belongs to the store — the only component that knows its backend's clock.
ttl_ms is None for "no expiry", the spec's own encoding
(ttlMs: number | null) rather than an omission: the field is always
present.
to_dict ¶
Project to the wire, carrying only the field this status licenses.
Gated on the status rather than on whether a field happens to be set, so
a record holding both a result and an error cannot emit a shape
no spec variant describes.
TaskStatus ¶
Bases: str, Enum
Lifecycle of a task, per the extension.
WORKING and INPUT_REQUIRED are live; the other three are terminal
and a task never leaves them.
FAILED is narrower than it looks. The spec forbids using the status
to represent non-JSON-RPC errors. A tool that raises ServiceError has
completed — it produced a well-formed CallToolResult carrying
isError: true. FAILED is for the task machinery itself failing: the
worker died, the payload could not be revived. Getting this backwards would
hide every tool error behind a status the client reads as "the server
broke".
TaskPolicy ¶
Bases: str, Enum
Whether a binding may — or must — answer with a task handle.
A policy surface, not a wire field. The shipped extension makes "the server the sole decider; clients do not signal task preference on the request itself", so the decision has to be made on this side, and the binding is where every other per-tool knob lives.
Attributes:
| Name | Type | Description |
|---|---|---|
FORBIDDEN |
Never a task. The default, so every tool registered before this existed behaves exactly as it did. |
|
OPTIONAL |
A task for a client that declared the extension, an ordinary inline call for one that did not. The safe choice for a slow tool that can still run inline. |
|
REQUIRED |
A task, or nothing — a client that did not declare the
extension gets |
TaskStore ¶
Bases: Protocol
Pluggable persistence for tasks, mirroring SessionStore.
Unlike SessionStore, an in-process implementation is not deployable. A
session only has to be recognised by the process that minted it, while a task is
created by a web worker and finished by a different process. So
InMemoryTaskStore
is a development and test convenience, and
DjangoCacheTaskStore
is the default.
Four operations, no more:
createwrites the seed record and must not return until the task is durable: the spec forbids answering with aCreateTaskResultbefore atasks/getfor that id would resolve, and answering first is the one race that hands a client an id it cannot use.getreads one back, orNoneif it never existed or has expired. Both are the same answer to a caller:-32602.saveoverwrites in place, preserving the original expiry.deleteremoves one.
Expiry belongs to the store. ttlMs is on the record, but only the
store knows what clock its backend keeps and only the store can drop an
entry unasked. Callers never compare a timestamp to now.
No locking, deliberately. Last write wins, and the guard against a
finished task being reopened lives in
rest_framework_mcp.tasks.transition_task — one rule in one place
beats four backends implementing compare-and-swap differently.
TaskExecutor ¶
Bases: Protocol
Where a created task goes to be worked on.
One method taking one string, because that is the entire seam. The record is already in the store when this is called, so the id finds everything, and keeping the payload out of the queue message stops the queue holding a copy of the arguments that can drift from the stored ones or outlive the task.
A Celery consumer writes:
@shared_task
def run_mcp_task(task_id: str) -> None:
my_server.run_task(task_id)
class CeleryExecutor:
def enqueue(self, task_id: str) -> None:
run_mcp_task.delay(task_id)
…and nothing in this package imports Celery. The protocol is satisfied just
as well by an RQ or Dramatiq call, a ThreadPoolExecutor.submit in a
test, or a management command that drains the store on a schedule.
enqueue must not run the work. It is called on the request path with
the client waiting for its CreateTaskResult, so anything slow here
reintroduces exactly the blocking the extension exists to remove.
Failures here are not silent. If enqueue raises, the task is already
durable and would otherwise sit in working forever, so the caller marks
it failed and the client finds out by polling — a status it can act on,
rather than a handle that never resolves.
TaskRecord
dataclass
¶
What a store holds: the wire
Task plus what a worker needs.
A superset rather than a parallel type. The extra fields never reach the client — no task message has a slot for them — but they are why a task can outlive the request that created it: the worker that finishes it shares nothing with that request except this record.
The scopes are stored, and that is the point. Without them the worker
rebuilds a token that proves nothing, and every ScopeRequired binding
denies the call it had already been authorized for — silently, long after
the client was told the work had started.
TokenInfo.raw is not stored: it is backend-defined credential
material, and persisting it would put that in a cache with a week-long
fallback TTL. A rehydrated token has raw=None, so a permission reaching
into it is one that cannot run as a task.
The separation is a security boundary. to_wire is the only route
from a record to a message, which is what stops a principal id or a scope
list leaking into a response because a field was added to the wrong
dataclass.
Attributes:
| Name | Type | Description |
|---|---|---|
task |
Task
|
The wire task — the only part a client ever sees. |
tool_name |
str
|
The tool the worker replays, verbatim. |
arguments |
dict[str, Any]
|
The arguments it replays, verbatim. |
principal_id |
str
|
The owner, in the form
|
user_pk |
Any
|
Rehydrates the user on the worker. |
scopes |
tuple[str, ...]
|
Rebuild the worker's |
audience |
str | None
|
Rebuilds that |
enqueued |
bool
|
Whether the task reached the executor, so a worker cannot be tricked into running it twice. |
progress
class-attribute
instance-attribute
¶
How far along the running task said it was, or None if it never said.
Written by
report_task_progress,
what a task's progress kwarg-pool seed resolves to. Server-side only,
by protocol: the wire Task carries statusMessage and no numeric
field, so a polling client sees only the rendered string this and
total produce.
total
class-attribute
instance-attribute
¶
What progress counts toward, or None for an open-ended count.
None is the ordinary case for work that cannot say how much there is —
the reporter renders a bare count rather than inventing a denominator.
input_responses
class-attribute
instance-attribute
¶
Answers the client has supplied via tasks/update, keyed as the
matching inputRequests were.
Accumulated rather than replaced, because the spec lets a client answer a
strict subset of what is outstanding and come back for the rest; a worker
parked on input_required reads this to find out what it was told.
The spec requires the server never to reuse a key over a task's lifetime, so a key appearing here is answered for good and a later request picks a new one.
DjangoCacheTaskStore ¶
Task store backed by django.core.cache. The default.
Cross-process, which for tasks is not a nice-to-have: the web worker that
creates a task and the worker that finishes it are different processes, so
a store they do not share cannot work at all (see
InMemoryTaskStore).
Namespacing follows
DjangoCacheSessionStore
exactly — the server's name, hashed into the key prefix, so two servers in one
project cannot read or overwrite each other's tasks. The digest is for key hygiene
(name is free-form; memcached rejects spaces and caps length), not secrecy.
Records are serialised to plain dicts rather than pickled. The cache holds them across a deploy, and a pickled dataclass is a version of this package's class definition: rename a field and every in-flight task becomes unreadable exactly when a worker tries to finish it. Plain dicts also keep the store usable under a JSON-serialising backend.
Expiry is absolute, stamped once at create from ttlMs and
carried in the envelope, so save renews the cache timeout to the
remaining lifetime rather than restarting the clock — otherwise a task
reporting progress often would never expire.
A malformed id is a miss, not an exception. taskId comes off the
wire and lands in a cache key, and the memcached backends reject keys with
spaces or control characters and keys over 250 bytes — raising out of a
handler with no arm for it. An id that cannot be one this package minted
cannot name a record either, so it is answered as the absence it is.
InMemoryTaskStore ¶
Task store held in one process's memory. Development and tests only.
Not a deployable backend, and not for the usual reason. An in-memory session
store is merely restart-fragile; an in-memory task store is broken by design,
because a task is created on a web worker and finished somewhere else — the worker
writes its result into a dict the web process cannot see, and every poll answers
"unknown task" until the client gives up. It fails silently and looks like a hung
job, which is why
DjangoCacheTaskStore
is the default. This class exists so tests and a single-process runserver can
exercise the machinery without a cache.
No expiry: ttlMs is advisory here, and a process short-lived enough to
use this store is its own garbage collection.