v0.3.0
Changed
-
Structural cleanup (Phase 10h) — pure refactor, no behavior
change. Public top-levelrest_framework_mcpre-exports unchanged.types/sub-packages. Every parent package that mixed type
declarations with functionality now has atypes/sibling that
holds the dataclasses and Protocols. Affected packages:
registry/,protocol/,auth/,auth/permissions/,
auth/rate_limits/,transport/,handlers/,server/,
contrib/oauth/,contrib/oauth/adapters/. Internal imports
point at the new leaf paths; package__init__.pyre-exports
preserve the existing public surface.dict[str, Any]→ dataclasses for the OAuth/auth metadata
payloads. NewProtectedResourceMetadata,
AuthorizationServerMetadata,OpenIDDiscoveryPayload,
DynamicClientRegistrationRequest,
DynamicClientRegistrationResponse(underauth/types/and
contrib/oauth/types/).MCPAuthBackendProtocol signatures
updated; both shipped backends and the OAuth views serialize via
.to_dict().DynamicClientRegistrationSerializer→DataclassSerializer
over the new request dataclass. The DCR ViewSet consumes the
typed instance viaserializer.save().View/APIView→ViewSetfor every package-owned HTTP
endpoint. Files + classes renamed:
ProtectedResourceMetadataView → ProtectedResourceMetadataViewSet,
AuthorizationServerMetadataView → AuthorizationServerMetadataViewSet,
OpenIDDiscoveryView → OpenIDDiscoveryViewSet,
DynamicClientRegistrationView → DynamicClientRegistrationViewSet,
StreamableHttpView → StreamableHttpViewSet,
AsyncStreamableHttpView → AsyncStreamableHttpViewSet. Each
mounts via.as_view({method: action}, ...); canonical action
mapsSTREAMABLE_HTTP_ACTION_MAPand
ASYNC_STREAMABLE_HTTP_ACTION_MAPre-exported from the
ViewSet modules for terse URL conf. The async transport
additionally overridesas_view/dispatchso Django routes
the coroutine-returning view correctly (DRF's ViewSet dispatch
is still sync-only as of 3.17).AuthorizePassthroughView
stays as a DOTAuthorizationViewsubclass — documented
exception, since the parent class lives in DOT.
-
Bumped the
djangorestframework-servicespin from==0.11.0to
==0.12.0. The MCP layer now honors three sister-repo additions
automatically — no migration steps for existing tools / resources, but
spec authors can now lean on them instead of duplicating the same
configuration at the MCP registration call:spec.permission_classes(DRFBasePermissionclasses) on both
ServiceSpecandSelectorSpec(and on theSelectorSpecused for
resources) is wrapped via the newDRFPermissionAdapterand
prepended to the per-bindingpermissionstuple. Spec-declared
permissions run first; tool-levelMCPPermissioninstances run
after. Misconfigurations (instances instead of classes,
non-BasePermissionsubclasses) raiseTypeErrorat registration
time. The same spec that backs an HTTP view now governs the MCP
binding without restating the permission contract.- Per-spec QuerySet shaping (
select_related,prefetch_related,
annotations,extend_queryset) onSelectorSpecis applied to
the queryset returned by the selector before the FilterSet / ordering
/ pagination pipeline.extend_querysetruns last so it always sees
the fully statically-shaped queryset, matching sister-repo's
ordering. Non-queryset returns (lists, scalars) pass through
shaping unchanged. - Per-spec serializer context (
input_serializer_contextand
output_serializer_contextonServiceSpec,
output_serializer_contextonSelectorSpec) is invoked with the
synthesised view + DRF request and forwarded ascontext=into the
serializer constructor — both sync and async dispatch paths.
Added
- New
REST_FRAMEWORK_MCP["FILTER_LISTINGS_BY_PERMISSIONS"]setting
(defaultFalse). When enabled,tools/list,resources/list,
resources/templates/list, andprompts/listdrop bindings whose
permissionsdeny the current caller before paginating, so
nextCursorreflects the user-visible slice. Per-binding
always_listed=True(ToolBinding/SelectorToolBinding/
ResourceBinding/PromptBinding, plus the matching server
registration entry points andToolDefinition) opts a binding back
into the listing as a discovery aid for admin-only operations where
the caller should see the name but can't invoke it. Custom
permissions can override list-time visibility by declaring an
is_listable(token)method alongsidehas_permission; the default
falls back tohas_permission(synthetic_request, token)for
binding-level permissions likeScopeRequired/
DRFPermissionAdapter. Filter is point-in-time only —
per-call-argument permissions evaluate against a data-less request at
list time, so this is binding-level gating, not per-record gating. - Conformance test suite (
tests/conformance/) — drives every Phase 10
feature through the live Django URL conf + JSON-RPC transport so the
wire shape is what an MCP client actually sees. Covers
ArgumentBinding.MERGEspread + pool-seed protection, all three
UnknownArgumentspolicies,register_toolsbulk-registration
parity,spec.permission_classesdenial through the transport,
every PRM / AS / OIDC / DCR endpoint in the contrib mount including
alias-renders-not-redirects, and the SimpleJWT cookie adapter
hydratingrequest.userbefore DOT'sAuthorizationViewdispatches.
Suite shares the existingjsonrpc/initialized_sessionfixtures
but routes throughtests.conformance.urls(mounted via
per-modulepytestmark = pytest.mark.urls(...)). - New
AuthUserAdapterProtocol (rest_framework_mcp.contrib.oauth.adapters)
plus a referenceSimpleJWTCookieAdapterimplementation behind a new
[jwt]extra. Adapters hydraterequest.userbefore DOT's
AuthorizationViewdispatches — the typical "DRF backend with
SimpleJWT cookies on the same host" deployment where DOT's view
doesn't know about the JWT cookie and would otherwise treat the user
as anonymous. Configure viaREST_FRAMEWORK_MCP["AUTH_USER_ADAPTER"]
(dotted path) plusREST_FRAMEWORK_MCP["SIMPLEJWT_ACCESS_COOKIE"]
(cookie name, default"access"). Mount the passthrough by passing
include_authorize=Truetobuild_oauth_urlpatterns(...)— the
resulting view is a thin DOTAuthorizationViewsubclass with the
adapter hook bolted ontodispatch. Without an adapter configured
it's functionally identical to DOT's view, so the flag is safe to
enable in every deployment. - New
rest_framework_mcp.contrib.oauthnamespace with
build_oauth_urlpatterns(server=, include_dcr=, include_aliases=, include_openid_discovery=)plus the underlying views
(AuthorizationServerMetadataView,OpenIDDiscoveryView,
DynamicClientRegistrationView,DynamicClientRegistrationSerializer).
Opt-in glue — the coreMCPServer.urlsmount stays minimal (PRM
only). Mount the helper alongside your server URLs to expose the full
endpoint matrix MCP / LLM-host clients probe (RFC 8414 + RFC 9728 +
OIDC discovery + RFC 7591 DCR) plus the alias paths different
clients use. Aliases render the canonical payload — they're not HTTP
redirects. DCR is gated behind two new settings:
REST_FRAMEWORK_MCP["DCR_ENABLED"](defaultFalse) and
REST_FRAMEWORK_MCP["DCR_INITIAL_ACCESS_TOKEN"](defaultNone). MCPAuthBackendProtocol gained anauthorization_server_metadata()
method.DjangoOAuthToolkitBackendimplements it (RFC 8414 payload
derived fromSERVER_INFO).AllowAnyBackendraises
NotImplementedError— the contrib mount surfaces that as501 Not Implementedon the AS endpoints so a dev-mode server doesn't have to
silently serve a fake AS metadata payload.- New
register_tools(server, definitions, *, selector_defaults, service_defaults)
bulk-registration entry point plus the supportingToolDefinition,
SelectorDefaults,ServiceDefaultsdataclasses andToolKind
discriminator enum. Additive — the existing imperative and decorator
registration surfaces are unchanged.ToolDefinition.service(...)/
ToolDefinition.selector(...)are the typed entry points; passing a
list of definitions plus per-kind defaults dataclasses collapses
repetitive registration boilerplate without parallelising the
dispatch engine (it loops over the existing per-tool methods, so
every guarantee and bug fix applies automatically). Per-definition
kwargs win over defaults on conflict;Noneis the "no override"
sentinel across both layers. Returns the list of resulting bindings
in input order so test harnesses can introspect. - New
UnknownArgumentsenum and matchingunknown_arguments=kwarg
onregister_service_tool,register_selector_tool, and their
decorator forms. Controls how MCPargumentskeys outside the
binding's declared field set are handled:UnknownArguments.REJECT(default) — outerinputSchema
advertises"additionalProperties": falseand the validator
rejects unknown keys with-32602(per-field
non_field_errors). Selector tools' pipeline-reserved keys
(ordering/page/limitand filter-set property names) are
automatically considered "known" so the policy doesn't fight the
post-fetch pipeline.UnknownArguments.PASSTHROUGH— outerinputSchemaadvertises
"additionalProperties": true; unknown keys survive validation
and are merged onto the validated payload before binding. The
serializer's coerced values for declared fields still win over
the raw values. Only effective on plainSerializeroutputs;
bare-dataclass inputs receiveIGNORE-equivalent behaviour
(a frozen dataclass instance isn't a merge target).UnknownArguments.IGNORE— outerinputSchemaadvertises
"additionalProperties": true; unknown keys are silently dropped
after validation (the DRF default). Forward-compat mode.
Reserved transport-pool seeds (request/user/data) are
never treated as "unknown" — they're handled by the dispatch pipeline
and silently dropped from any client spread regardless of policy.
- New
ArgumentBindingenum and matchingargument_binding=kwarg on
register_service_tool,register_selector_tool, and their
decorator forms. Controls how MCPargumentsflow into the kwarg
pool of the dispatched callable:ArgumentBinding.DATA_ONLY(default for service tools) — historical
shape,argumentsonly enter the pool asdata=<validated>.ArgumentBinding.MERGE(default for selector tools) — every key
from the validated arguments (or raw arguments when no validator)
is added to the pool as a top-level kwarg, so selectors can declare
individual parameters (def list_drafts(*, project_id, page=1)).
spec.kwargs(...)overrides on conflict so author-declared
invariants win over client-supplied values.ArgumentBinding.REPLACE— likeMERGE, but the spread wins on
conflict sospec.kwargs(...)can supply client-overridable defaults.
Reserved keys (ordering/page/limitfrom the selector-tool
post-fetch pipeline;request/user/datafrom the pool seeds)
are stripped from the spread inMERGE/REPLACEmodes, so clients
can't poison transport-controlled state. The default for selector
tools flips from data-only to merge; selectors that were registered
expectingdata=<arguments-dict>continue to receive it (data=is
still in the pool in every mode) but can now also be declared with
individual parameters.
- New
DRFPermissionAdapterclass (rest_framework_mcp.auth.permissions)
that bridges a DRFBasePermissionclass into theMCPPermission
Protocol. Re-exported from the top-levelrest_framework_mcp. Construct
one directly if you need the same DRF permission gating without going
throughspec.permission_classes(e.g. for tool-level overrides). - New
REQUIRE_PROTOCOL_VERSION_HEADERsetting (defaultTrue). Some MCP
clients omit theMCP-Protocol-Versionheader entirely on non-initialize
requests, which the spec-compliant default rejects with HTTP 400. Set this
toFalseto accept those requests by falling back to the first entry of
PROTOCOL_VERSIONS. A present-but-unsupported version is still rejected
either way — silently downgrading would mask a real version mismatch. - New
INCLUDE_STRUCTURED_CONTENTsetting (defaultTrue) plus a matching
per-tool overrideinclude_structured_contentonregister_service_tool,
register_selector_tool, and their decorator forms. Controls whether
tools/callresponses include thestructuredContentfield alongside
the human-readablecontent[0]text. Set the global toFalse(or the
per-tool override) to omit it for clients that echo both fields back to
the LLM and burn context, or that choke on the field altogether. The text
payload still carries the full data, so no information is lost; clients
just have to re-parse instead of getting a pre-parsed dict. The
per-binding override is tri-state —None(default) inherits the
global,True/Falseforce the behavior regardless of the setting.
WhenstructuredContentis omitted for a binding, itsoutputSchemais
also dropped fromtools/list— the MCP tools spec requires that a tool
declaringoutputSchemaalways return conformingstructuredContent, so
the two are kept in lockstep to avoid advertising a contract the server
then refuses to honor.