MCPServer¶
register_tool registers a kernel spec as a tool, register_tools every spec
in a kernel SpecRegistry, and register_resource a kernel RETRIEVE
selector as a resource.
MCPServer ¶
A pluggable MCP server serving kernel specs.
The server owns its tool, resource and prompt registries, an auth backend,
and a session store — all instance state, no module-level singletons. A
tool or a resource is a django_service_specs spec, registered by
passing it in:
from django_service_specs import SelectorKind, SelectorSpec, ServiceSpec
server = MCPServer(name="my-app")
server.register_tool(
ServiceSpec(service=create_invoice, permissions=[IsBilling()]),
name="invoices.create",
)
server.register_resource(
SelectorSpec(
kind=SelectorKind.RETRIEVE, selector=get_invoice, permissions=[IsBilling()]
),
name="invoice",
uri_template="invoices://{pk}",
)
A SpecRegistry registers in one call, register_tools, with the MCP
knobs per tool in overrides.
Mount the URLs in your URL conf the admin.site.urls way — .urls is a
namespaced (patterns, app_name, namespace) triple path() mounts
directly (no include()):
urlpatterns = [path("mcp/", server.urls)]
# reverse("mcp:endpoint") · reverse("mcp:protected-resource-metadata")
What every spec on the mount may read -- a tenant, a locale, a clock -- is
registered once as pool_seeds=, a kernel
PoolSeeds:
server = MCPServer(
pool_seeds=DEFAULT_POOL_SEEDS.extend(tenant=lambda *, user: user.tenant),
)
Every spec the server runs receives the seeds (tools over either
transport, call_tool / acall_tool, task workers and resource
reads), every operation condition it asks is asked with them,
tools/list availability included, and a registered name is reserved: a
client argument cannot occupy it, and a spec read or a URI-template
variable naming it is refused at registration.
What the server tells a model in its own words -- the handle description and
line, a paged tool's scope sentence, the missing-argument message -- is
conventions=, an
AgentConventions.
Change one sentence and keep the rest:
server = MCPServer(
conventions=AgentConventions(handle_line="Pass ids to other tools; never show them."),
)
The server still decides where each sentence appears; the conventions decide what it says, on every route the seeds above reach.
config
property
¶
This server's resolved scalars — a frozen snapshot taken at construction.
pool_seeds
property
¶
The pool_seeds= every dispatch and condition on this server receives.
conventions
property
¶
The conventions= every listing and refusal on this server is worded with.
auth_backend
property
¶
The auth_backend= given at construction, or None when there is none.
urls
property
¶
Sync URL patterns. Suitable for any deployment (WSGI or ASGI).
Returns the namespaced (patterns, app_name, namespace) triple
path() mounts directly — path("mcp/", server.urls), the
admin.site.urls idiom — so the endpoints reverse within the
namespace (reverse("mcp:endpoint")). Use async_urls instead
when running under ASGI to get non-blocking dispatch for the I/O-bound
handlers.
async_urls
property
¶
Async URL patterns for ASGI deployments.
The namespaced triple (like urls), but tools/call,
resources/read and prompts/get dispatch through async-native
runners. Sync collaborators (auth backend, session store, custom
permissions) are bridged via asgiref.sync.sync_to_async, so a
fully sync stack still works; async-native ones are detected by
signature and called directly.
register_tool ¶
register_tool(
spec: ServiceSpec | SelectorSpec,
*,
name: str,
description: str | None = None,
title: str | None = None,
display_name: str | None = None,
display_description: str | None = None,
icons: tuple[Icon, ...] = (),
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
ui: UIToolMeta | None = None,
content_kind: ToolContentKind = ToolContentKind.TEXT,
content_mime_type: str | None = None,
include_structured_content: bool | None = None,
include_output_schema: bool | None = None,
task_policy: TaskPolicy = TaskPolicy.FORBIDDEN,
invalidates: tuple[str, ...] | list[str] = (),
paginate: bool = False,
max_page_size: int | None | UnsetType = UNSET,
max_result_bytes: int | None | UnsetType = UNSET,
dispatch_timeout: float | None | UnsetType = UNSET,
always_listed: bool = False,
rate_limits: list[Any] | None = None,
scopes: str | Sequence[str] | None = None,
unknown_arguments: UnknownArguments = UnknownArguments.REJECT,
server_arguments: ServerArguments | None = None,
) -> KernelToolBinding
Register a kernel spec as a tool: a ServiceSpec or a SelectorSpec.
spec is a django_service_specs spec, and its class says which kind
of tool this is. A service tool's arguments are the spec's parameters at
the top level of arguments, as the kernel's spec_input_schema
advertises them; a selector tool reads, and a LIST one paginates
under paginate=True. The call runs through the kernel's dispatch
/ adispatch, which asks the spec's own permissions of the
principal the auth backend resolved for the request.
Anything that is not a kernel spec is refused with a TypeError. A
knob of the other kind of tool is
refused: paginate / max_page_size on a service tool,
invalidates on a selector tool.
A LIST selector tool registered without paginate=True warns
UnboundedListWarning, and REQUIRE_LIST_PAGINATION refuses it. A
paginated one whose spec declares a read named page or limit is
refused, since the pagination takes both names from the arguments
before the selector runs. A presenter whose audience markings cannot be
projected (two labels, say) is refused naming the tool, rather than
failing every tools/list of the server.
A declaration no call on this server can run as written is refused
naming the tool, by the kernel's check_seeds with the server's
pool_seeds: a parameter named like a registered seed or the call's
own request or view, a positional-only parameter with no
default, a keyword the callable requires that nothing fills, and an
optional read over a keyword the callable requires. A read
server_arguments binds a value to is filled on every call, so it is
never refused as optional.
display_name and display_description are labels for in-process
consumers, read off the binding and never sent on the wire.
scopes are the OAuth scopes the caller's token must carry, every one
of them; a token missing one is answered -32006 with every declared
scope in requiredScopes, as a prompt's or a view's scope gate
answers. A bare string is one scope. scopes=()
is refused, since it would read as a gate and admit every caller. The
scope gate is a transport check and does not count as the tool's guard
for REQUIRE_TOOL_PERMISSIONS, which reads the spec's own
permissions.
task_policy means the same for a service tool and a selector tool:
OPTIONAL answers a client that declared the tasks extension
with a task handle and runs inline for one that did not, and
REQUIRED refuses the latter with -32021. The spec's own checks
are asked before the task is created, and the worker runs the call,
under either policy, through the same dispatch a direct call takes,
under the server's pool_seeds and conventions.
A refusal from dispatch is answered on the wire as
kernel_refusals words it: NotPermitted and PrincipalUnavailable as
-32006, InvalidArguments, ServiceValidationError and a bare
DispatchError as the validation_error result, an
AdditionalInputRequired as MRTR's input_required, and any other
ServiceError as the service_error result.
register_tools ¶
register_tools(
registry: SpecRegistry, *, overrides: Mapping[str, Mapping[str, Any]] | None = None
) -> tuple[KernelToolBinding, ...]
Register every kernel spec in a SpecRegistry as a tool, in order.
Each entry registers through register_tool under its registry name,
with the knobs overrides holds under that name, so an entry and a
direct register_tool of the same spec are the same tool. An
overrides key the registry does not hold raises ValueError, as
a typo rather than a no-op, and an entry that is not a kernel
ServiceSpec or SelectorSpec (a SpecSource among them) is
refused as register_tool refuses it. A refusal of any entry
registers none of them: every entry is built and checked first.
Returns the bindings in registration order.
call_tool ¶
call_tool(
name: str,
arguments: dict[str, Any] | None = None,
*,
user: Any,
request: Any = None,
) -> ToolResult
Invoke a registered tool off the HTTP / JSON-RPC path.
The transport-neutral entry point: hand a tool name, a flat
arguments dict and the acting user, and get back the same
ToolResult the
wire handlers build. An in-process consumer calls this instead of
re-implementing dispatch.
The call runs through the kernel's dispatch as on the wire: the
binding's server_arguments in place, the spec's own permissions
asked of user, then the spec run and presented, a paged tool's
pagination included. The transport-level checks are not consulted: the
scopes= gate and rate limits read a token, and there is none in
process. For those, and for tool listing, use acall_tool /
list_tools.
A refusal the wire answers -32006 (NotPermitted,
PrincipalUnavailable) is raised for the caller to map to its own
wire; every other refusal comes back as the isError result the wire
serves.
request is the caller's own Django request when there is one, and
the call never writes its user to it: the spec is handed a copy whose
user is user, so a view handing the request it is serving keeps
serving as its own user after the call. The copy shares the handed
request's body, stream and parsed POST and FILES, so a body a
spec reads is read for the caller, and an object a spec mutates through
it, such as META or the session, is the caller's; an attribute a
spec sets or deletes changes the copy alone. With no request, a
minimal POST HttpRequest bearing user is synthesised.
Raises KeyError when no tool is registered under name.
list_tools ¶
list_tools(
cursor: str | None = None,
*,
user: Any,
request: Any = None,
scopes: Sequence[str] | None = None,
include_unavailable: bool = False,
) -> dict[str, Any] | JsonRpcError
List the tools this server exposes, exactly as the wire would.
The in-process twin of a tools/list request: one page of the tool catalog
with the same inputSchema the HTTP transport advertises (the spec's
parameters, a paginated tool's page / limit and the
additionalProperties policy), the same per-caller listing-permission filter
(FILTER_LISTINGS_BY_PERMISSIONS) and the same opaque-cursor pagination —
pass the returned nextCursor back for the next page. A
JsonRpcError
signals a bad cursor.
scopes are the caller's granted scopes; pass them so a tool
registered with scopes= is visible under
FILTER_LISTINGS_BY_PERMISSIONS exactly as it would be on the wire.
A tool whose operation-scope affordances condition is unmet for
user right now is left out, as on the wire, unless
include_unavailable=True, which lists every tool the caller may see
and asks no condition. That is for a consumer that builds its tool
definitions once and asks unavailable_tools each step: it needs a
definition for a tool that is unavailable when it lists and becomes
available later. The wire has no such parameter.
Unlike call_tool (the spec core) this is the full transport
surface. Under an event loop use alist_tools — a listing
permission filter or an affordance condition that hits the DB raises
SynchronousOnlyOperation from a sync call on the loop.
alist_tools
async
¶
alist_tools(
cursor: str | None = None,
*,
user: Any,
request: Any = None,
scopes: Sequence[str] | None = None,
include_unavailable: bool = False,
) -> dict[str, Any] | JsonRpcError
Async list_tools — safe to call from an event loop.
Listing itself is pure Python, but the per-caller permission filter
(FILTER_LISTINGS_BY_PERMISSIONS) and a tool's operation-scope
affordances condition may each run a DB-backed check, which raises
SynchronousOnlyOperation when reached synchronously from within an
event loop. The whole sync handler therefore runs in Django's
thread-sensitive executor.
unavailable_tools ¶
unavailable_tools(
*, user: Any, request: Any = None, scopes: Sequence[str] | None = None
) -> dict[str, Affordance]
The tools list_tools leaves out for this caller right now, and why.
Maps each omitted tool's name to the operation-scope Affordance whose
condition is unmet: its code and its reason, the sentence written
for people and models alike. The answer comes from the same pass tools/list makes, over the
same tools the caller may see (FILTER_LISTINGS_BY_PERMISSIONS
included), so a name is here exactly when the listing omits it, and a
tool hidden from this caller is never named with its reason.
For an in-process consumer that lists once with
include_unavailable=True and asks this each step: it offers its model
what a fresh tools/list would, and can tell the model why the rest
are missing, which a listing cannot, since it only leaves them out.
always_listed=True tools are never here, as they are never left out.
Advisory like the listing: acall_tool still enforces every condition.
Under an event loop use aunavailable_tools; a condition or listing
permission that queries raises SynchronousOnlyOperation from here.
aunavailable_tools
async
¶
aunavailable_tools(
*, user: Any, request: Any = None, scopes: Sequence[str] | None = None
) -> dict[str, Affordance]
Async unavailable_tools -- safe to call from an event loop.
Runs in Django's thread-sensitive executor, as alist_tools does, since
a condition or a listing permission may query. A server none of whose
tools declares an operation-scope condition answers {} without the
hop: that is decided from the declarations alone, and a consumer asking
on every model step should not pay a thread switch per step to learn it.
acall_tool
async
¶
acall_tool(
name: str,
arguments: dict[str, Any] | None = None,
*,
user: Any,
request: Any = None,
scopes: Sequence[str] | None = None,
) -> dict[str, Any] | JsonRpcError
Invoke a tool off the HTTP path with full transport semantics (async).
The in-process twin of a tools/call request: routes through the same async
handler the wire uses, so the scopes= gate and the rate limits apply —
what call_tool omits — beside the spec's own permissions and the
pagination of a LIST tool. Returns the wire's result payload (a
dict carrying content / structuredContent / isError), or a
JsonRpcError
for a protocol fault (unknown tool, malformed arguments shape, denied
permission).
request is the originating Django request when there is one, handled
as call_tool handles it: the call works on a copy bearing user
and never writes its user to the handed request. A minimal one is
synthesised otherwise.
scopes populate the synthetic token so a tool registered with
scopes= is invokable in-process just as it is on the wire.
run_task ¶
Execute a queued task. This is what a worker calls.
The other end of task_executor.enqueue, and the whole public
surface of the worker side:
@shared_task
def run_mcp_task(task_id: str) -> None:
my_server.run_task(task_id)
Everything it needs comes out of the store: the tool, the arguments,
and the authorization context to re-check them under. Nothing is
returned — the client learns the outcome by polling tasks/get.
Safe to call for an id that is unknown, already claimed or already finished: each is a no-op. Queues deliver at least once, and a retried delivery must not run a mutation twice.
Raises when the server has no task store: a worker calling this on a server that cannot run tasks would otherwise fail as silence — the job "succeeds" and the client polls a handle forever.
notify_resource_updated
async
¶
Tell every subscriber watching uri that it changed.
The explicit trigger, and the one that always works. Call it from
wherever the write actually happens — a management command, a Celery
job, a save() override, a signal handler you wrote yourself:
await server.notify_resource_updated(f"invoices://{invoice.pk}")
Returns how many subscribers were reached — a diagnostic, not a
guarantee: 0 means nobody was listening, which is the ordinary case
and not an error. Notifications are best-effort by design; a client
that misses one re-reads the resource.
A URI, not a template. Publish the concrete URI that changed, and
the collection URI too if watchers of the collection should hear about
it — matching is exact, deliberately (see
topic_for_resource).
Publish after the transaction commits. Inside
transaction.atomic() this announces a change that may still roll
back, and a subscriber that re-reads immediately sees the old value —
worse than no notification at all. transaction.on_commit is the
Django-native answer.
notify_list_changed
async
¶
Tell subscribers that one of the catalogs changed.
Registration happens once at configuration time, so the set of
registered bindings is fixed for the life of the process. Two things
still change a catalog under a running client: a server that registers
tools from data — a plugin loader, a per-tenant catalog — and a tool
whose operation-scope affordances condition flips, which
tools/list answers on every request and so starts or stops listing
it. Call this with TOOLS_LIST_CHANGED after the event that flipped
the condition, once it has committed. It reaches only clients this
server can push to: a subscription broker configured and a modern-era
caller listening, the same two things listChanged is advertised on.
register_resource ¶
register_resource(
spec: SelectorSpec,
/,
*,
name: str,
uri_template: str,
description: str | None = None,
title: str | None = None,
icons: tuple[Icon, ...] = (),
mime_type: str = "application/json",
encoding: ResourceEncoding = ResourceEncoding.JSON,
rate_limits: list[Any] | None = None,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
always_listed: bool = False,
cache_ttl_ms: int | UnsetType = UNSET,
completions: dict[str, Callable[..., Any]] | None = None,
scopes: str | Sequence[str] | None = None,
) -> KernelResourceBinding
Register a kernel RETRIEVE SelectorSpec as an MCP resource.
register_resource(spec, uri_template=..., name=...) takes a
django_service_specs spec positionally. The read runs it through the
kernel's dispatch / adispatch, with the URI-template variables
as its arguments, so the spec's reads name them and its presenter
renders the row. Anything that is not a kernel RETRIEVE
SelectorSpec is refused with a TypeError. scopes gate the
read as they gate register_tool.
meta is the generic _meta bundle for this resource's listing
entry — resources/list for a concrete URI,
resources/templates/list for a template — and for the contents
block resources/read returns.
encoding decides how what the presenter returns becomes the read
body. JSON pretty-prints it; TEXT returns it verbatim, and it
must be a str; BLOB base64-encodes it into the contents
entry's blob instead of text, and it must be bytes,
bytearray or memoryview. A value of the wrong type is answered
at read time as an INTERNAL_ERROR naming what produced it. Anything
whose mime_type is not JSON — Markdown, CSV, plain text — wants
TEXT, or the document comes back wrapped in a quoted string literal,
and an image wants BLOB: a presenter rendering the row as Markdown
pairs with encoding=TEXT and mime_type="text/markdown".
mime_type is advertised as given and never inferred from or checked
against the encoding, so TEXT left at the default
application/json serves a str that is already a JSON document as
it is. The result ceiling and the cache hints apply to every encoding.
A TEXT or BLOB resource is refused, naming both, beside a spec
declaring affordances, which the kernel adds to a presented object,
or allow_none, under which a missing row presents as None
without the presenter running: neither is a document or bytes. For an
HTML view use register_ui_resource, which sets both.
A uri_template variable is a caller-controlled name that reaches the
read's arguments, so one named after a pool seed (user,
request, data …) or declared twice raises here rather than
letting a URI segment stand in for the authenticated identity. So does
a variable no read of the spec declares, and one the server fills for
the call. A spec no read can run as written is refused naming the
resource, as register_tool refuses one. A read the URI template
names is filled on every read, as a value server_arguments binds is
on every call, so it is never refused as optional.
A resource whose spec declares no permissions is refused for the
same reason a tool is — see REQUIRE_TOOL_PERMISSIONS. The same
selector exposed as a resource is as reachable as it is exposed as a
tool.
register_ui_resource ¶
register_ui_resource(
*,
name: str,
uri: str,
template_name: str | None = None,
body_template_name: str | None = None,
diagnostics: bool | None = None,
html: str | None = None,
render: Callable[[], str] | None = None,
description: str | None = None,
title: str | None = None,
icons: tuple[Icon, ...] = (),
ui: UIResourceMeta | None = None,
mime_type: str = UI_RESOURCE_MIME_TYPE,
permissions: Sequence[PermissionCheck] | None = None,
scopes: str | Sequence[str] | None = None,
rate_limits: list[Any] | None = None,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
always_listed: bool = False,
cache_ttl_ms: int | UnsetType = UNSET,
) -> ResourceBinding
Register an interactive HTML view (an MCP App) as a resource.
A tool links to the view and a host renders it inline in the chat, inside a sandboxed iframe it constructs itself. The iframe and the CSP enforcement are the host's and are deliberately not implemented here.
The ui/* postMessage bridge is not in that list, and reading it
as though it were is what this parameter set exists to prevent. The
bridge has two ends: the host runs its end, and the document runs the
other. That second end is a view's mandatory startup handshake, every
one of its failure modes is silent, and one of them leaves the frame
hidden with the view unable to say why.
So give exactly one content source, and prefer the first:
body_template_name— a Django template holding the view's markup only. The package wraps it in a document whose bridge is already written and already correct.diagnostics=decides whether that bridge writes a protocol failure into the document as well as logging it;Nonefollowssettings.DEBUG, because the text is written for whoever wrote the view and a rendered view's audience is whoever is using the product.template_name— a Django template holding a whole document, whose bridge is then yours to write.html— a literal document, same.render— a callable returning one, which must take no arguments: one that declares a parameter is refused at registration, because the read path would fill it from a pool carryingrequestanduser.
Keep tenant data out of the view. Hosts may prefetch and cache a view before any tool call, so it is a shell that hydrates itself at runtime from tool results — which is also why the template renders with no context.
ui= is the typed
UIResourceMeta
— CSP origins, browser permissions, publisher domain, border preference —
which serialises into _meta under the extension's key. meta= remains
available for other extensions; passing both ui= and that same key inside
meta= raises, rather than letting one silently win.
The result is an ordinary
ResourceBinding,
so it shares one URI namespace with data resources (a collision raises as
always), appears in resources/list, and honours permissions /
scopes / always_listed. Views default to unguarded — the MCP
session is already authenticated and a view is a static asset, not tenant
data.
permissions are kernel PermissionChecks, the instances a spec
carries, each asked has_permission(principal, binding) with the view's
ResourceBinding as the spec; scopes are the OAuth scopes the
caller's token must carry, every one, asked first. Both gate the listing,
the read and a subscription alike.
register_prompt ¶
register_prompt(
*,
name: str,
render: Callable[..., Any],
description: str | None = None,
title: str | None = None,
icons: tuple[Icon, ...] = (),
arguments: list[PromptArgument] | None = None,
completions: dict[str, Callable[..., Any]] | None = None,
permissions: Sequence[PermissionCheck] | None = None,
scopes: str | Sequence[str] | None = None,
rate_limits: list[Any] | None = None,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
always_listed: bool = False,
) -> PromptBinding
Register a render callable as an MCP prompt.
render receives the prompt arguments as kwargs (plus request and
user if it declares them) and returns either a string, a list of strings, a
list of
PromptMessage,
or a coroutine yielding any of those — the dispatch layer normalises the result.
request and user are seeded over the client's arguments at
prompts/get, so an argument named after one of them never reaches
render in the seed's place.
permissions are kernel PermissionChecks, the instances a spec
carries: a prompt is not a spec, so each is asked
has_permission(principal, binding) with the prompt's
PromptBinding as the spec, and a check on the principal alone answers
a prompt as it answers a spec. scopes are the OAuth scopes the
caller's token must carry, every one, asked first; a bare string is one
scope. Both gate prompts/list, prompts/get and a completion alike.
A prompt with neither is refused like a tool — see
REQUIRE_TOOL_PERMISSIONS; permissions=[Unrestricted()] is the
way to say it is open. A render callable reads whatever its
author gave it access to, so nothing about a prompt makes it safe by
construction.
meta is the generic _meta bundle for this prompt's
prompts/list entry — see register_tool.
prompt ¶
prompt(
*,
name: str | None = None,
description: str | None = None,
title: str | None = None,
icons: tuple[Icon, ...] = (),
arguments: list[PromptArgument] | None = None,
completions: dict[str, Callable[..., Any]] | None = None,
permissions: Sequence[PermissionCheck] | None = None,
scopes: str | Sequence[str] | None = None,
rate_limits: list[Any] | None = None,
annotations: dict[str, Any] | None = None,
meta: dict[str, Any] | None = None,
always_listed: bool = False,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]
Decorator form: register the wrapped callable as a prompt.
notify
async
¶
Push a JSON-RPC payload to a session's open SSE stream.
Returns True if a subscriber was present, False if no client is
connected — a missed push is not generally an error, since clients pull
state via tools/call round-trips. The broker enforces one
subscriber per session: re-subscribing replaces the old queue silently.
With a
SSEReplayBuffer
configured the payload is recorded before publishing, so the frame carries an
event ID the SSE generator emits on the wire (id: <id>\ndata:
<payload>\n\n) and a later reconnect with Last-Event-ID drains what it
missed before resuming live mode. Without a buffer there are no id: lines
and resume is disabled.
Multi-process deployments need an out-of-process broker to fan out across workers; the in-process broker only sees its own.
AgentConventions
dataclass
¶
The sentences this server writes for a model, one field per sentence.
Passed as MCPServer(conventions=...), per server, because two servers in
one project can be talking to different readers. AgentConventions() is
the default, and its fields carry the wording a server uses when it is given
none; change one field and every other sentence stays the package's, so a
later correction to a line you did not override still reaches you.
The server still decides whether each sentence appears: a tool with no
handle gets no handle line, a tool that returns no page no scope sentence.
A field changes what it says, never when. None drops a sentence where
dropping one leaves a well-formed answer.
Every field is a str.format template, rendered wherever it lands
whether or not it has a placeholder, so a literal brace is written twice,
{{ or }}, in every field alike. The placeholders a field accepts are
the ones listed on it, and only missing_arguments accepts one. Anything
the server could not render raises ImproperlyConfigured naming the field
when the conventions are built, so a typo fails when the server is, rather
than inside the first call that reaches it. This is the Pydantic-AI
toolset's rule for its AgentConventions, so one template reads the same
on both transports.
Some of these sentences state facts about how the server behaves: that a
selection applies to each item and never to the envelope, that the
envelope's keys are items, page, totalPages and hasNext.
Wording you supply owns keeping those facts true.
Raises:
| Type | Description |
|---|---|
ImproperlyConfigured
|
A field uses a placeholder it does not accept, is
not a valid format string, cannot be rendered with the values it
will be given ( |
handle_field_description
class-attribute
instance-attribute
¶
The outputSchema description of a handle field that declares none of
its own, beside the field in every tool's output schema. None leaves
such a field undescribed. A FieldMarking.handle("...") keeps its own
words whatever this says. No placeholders.
handle_line
class-attribute
instance-attribute
¶
The line appended to the description of a tool whose output carries a
handle, after Identify records by `<label>`. when the presenter
declares a label. None drops the line and that prefix with it. No
placeholders.
query_param_on_pages
class-attribute
instance-attribute
¶
What a read-shaping parameter, one of a spec's shapes, applies to on
a tool that returns a page. Appended to the param's description in the tool's inputSchema, and
to the isError message when a value it supplied is refused while that
tool's result is rendered. None drops it from both. No placeholders.
missing_arguments
class-attribute
instance-attribute
¶
The message of a validation_error result for a call that left out
arguments the tool's selectors cannot run without: a selector parameter
with no default or a service tool's target lookup. {names} is the missing names, sorted, each
in backticks, joined with ", "; the detail stays keyed by each name.
A validator's own refusal, a missing field included, keeps
"Invalid arguments". Never None, empty or only whitespace: it is
the result's whole message.