| Internet-Draft | MCP over MOQT | November 2026 |
| Jennings, et al. | Expires 5 May 2027 | [Page] |
This document defines how to use Media over QUIC Transport (MOQT) as the underlying transport protocol for the Model Context Protocol (MCP). MCP enables integration between language model applications and external data sources and tools. MOQT provides publish-subscribe delivery over QUIC and WebTransport with native prioritization, relay caching, and multiplexing.¶
This specification maps MCP messages onto MOQT objects and tracks. It covers MCP's tools, resources, prompts, multi round-trip requests, and the Tasks extension, and it uses MOQT relays to cache content that MCP marks as public across clients and to let a response outlive the connection that carried its request.¶
The document also describes Agent Skills, composed instructions that extend AI capabilities beyond atomic tool operations, using progressive loading aligned with MOQT's object-based delivery. The same tracks can run standalone or inside a dialog of the Protocol for Agent Communication Exchange (PACE).¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://github.com/fluffy/mcp-moq. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-jennings-agentproto-mcp-over-moqt/.¶
Discussion of this document takes place on the Agent Communication Protocols Working Group mailing list (mailto:agentproto@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/agentproto/. Subscribe at https://www.ietf.org/mailman/listinfo/agentproto/.¶
Source for this draft and an issue tracker can be found at https://github.com/fluffy/mcp-moq.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 5 May 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The Model Context Protocol (MCP) [MCP] enables integration between LLM applications and external data sources using JSON-RPC 2.0 over pluggable transports. Media over QUIC Transport (MOQT) [MOQT] provides publish-subscribe delivery over QUIC [QUIC] with relay support.¶
MCP is stateless: every request carries its protocol version and the client's capabilities, and servers keep no per-connection session. That model fits MOQT well. MCP's existing transports have limitations that MOQT addresses:¶
Streamable HTTP: A broken response stream loses the in-flight request, which the client must re-issue as a new request. There is no per-message prioritization, and shared caching is limited to what HTTP intermediaries provide.¶
stdio: Single-process only; no network distribution.¶
MOQT provides:¶
Recovery: A response is published into a known group, so a client that reconnects receives the rest of it instead of re-issuing the request (Section 3.3.1).¶
Shared caching: Content that MCP marks as public, such as the server's capabilities and tool definitions, is published once and served to every client from relay caches (Section 5).¶
Edge serving: A publisher tells relays how long they may cache each track's objects, so FETCH and fill fetch, including a reconnecting client's catch-up, are answered by the nearest relay instead of the server (Section 5.1.1).¶
Priority-aware delivery: Critical operations receive appropriate bandwidth via per-object priorities.¶
Multiplexing without HOL blocking: QUIC streams prevent interference between concurrent operations.¶
Network resilience: QUIC connection migration.¶
The Protocol for Agent Communication Exchange (PACE) [PACE] is a dialog-management protocol for agent communication over MOQT. Application-layer agent protocols such as MCP and A2A run inside a PACE dialog as bindings, and PACE's MCP binding adopts the track semantics, operation mapping, and relay behavior defined in this document. This specification therefore serves two deployment modes:¶
Standalone: MCP over MOQT using the (mcp, <server-id>, ...)
namespace defined in Section 2.¶
Within PACE: the same tracks are carried inside a PACE dialog, as described in Section 8.¶
Both modes use the same MOQT primitives and the same encoding; they differ only in namespace layout.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses terminology from [MCP] and [MOQT], including Track Namespace, Track Name, and Full Track Name as defined in [MOQT]. Additionally:¶
Name notation: A Track Namespace is written as (field1, field2, ...)
in normative definitions and as field1/field2/... in prose. A Full
Track Name is written (field1, field2, ...) -- (name). These are
informal notations of this document, not the serialized formats of
[MOQT]; a / that appears inside a field or Track Name (for
example, in a resource URI) is part of that field.¶
Server ID: An identifier of an MCP server, used as the second field of every namespace this document defines (Section 2.1).¶
Client ID: A pairwise identifier of an MCP client at one server (Section 2.2).¶
Server scope / client scope: The namespaces under
(mcp, <server-id>) that all clients of a server share, and the
namespaces under (mcp, <server-id>, clients, <client-id>) that
belong to one client (Section 2.1).¶
Request track / response track: A pair of tracks used for one kind of JSON-RPC exchange. The client publishes the request track and the server publishes the response track (see Section 3.3).¶
MCP maps to MOQT tracks for the catalog, control, tools, prompts,
resources, tasks, and skills (Section 2). A client needs no handshake: after
MOQT setup it derives its client ID, subscribes to the server's tracks,
and starts sending requests. Every request carries the per-request
_meta fields of [MCP]; the examples in this document omit them for
brevity. JSON-RPC requests and responses are carried on request and
response tracks (Section 3.3).¶
Each MOQT object carries one payload, with no framing beyond the object boundaries. The payload is one of:¶
One MCP JSON-RPC 2.0 message, as UTF-8 encoded JSON, on control, request, and response tracks.¶
One UTF-8 encoded JSON document on catalog, task, and skill tracks, with the content defined for each track.¶
A chunk of the resource's content on resource tracks (Section 2.7). The resource's media type is given in the catalog (Section 2.3).¶
+-----------------+ MOQT Session +-----------------+ | MCP Client |<==================>| MCP Server | | | | | | +-------------+ | | +-------------+ | | |MCP Transport| | | |MCP Transport| | | | Adapter | | | | Adapter | | | +-------------+ | | +-------------+ | | +-------------+ | | +-------------+ | | | MOQT | | | | MOQT | | | | Endpoint | | | | Endpoint | | | +-------------+ | | +-------------+ | +-----------------+ +-----------------+¶
The MCP client-host-server architecture maps to MOQT as follows:¶
MCP Host: Acts as MOQT client, managing multiple server connections¶
MCP Client: Implemented as MCP transport adapter within the host¶
MCP Server: Exposes resources, tools, and prompts¶
Both the MCP client and the MCP server are MOQT publishers and subscribers: each publishes the tracks it owns (Section 3.3) and subscribes to its peer's.¶
Every namespace defined by this document begins with
(mcp, <server-id>). A client learns the server ID from
configuration, from the server's moqt:// URI, from a registry or
Poppy entry (Section 10), or, where a relay offers one, from a
server listing (Section 5.2).¶
Tracks fall into two scopes:¶
Server scope, (mcp, <server-id>, ...) without a clients field:
tracks the server publishes once for all of its clients. They carry
only content whose MCP cacheScope is public: the catalog, public
resources, and skills. Relays serve them to every client from cache.¶
Client scope, (mcp, <server-id>, clients, <client-id>, ...):
tracks that belong to one client. They carry the client's requests,
the server's responses, task status, and any content whose
cacheScope is private. Only that client and the server can
publish or subscribe to them (Section 9).¶
The client scope is a routing and authorization boundary, not MCP
state. A server MUST NOT use it in place of the per-request _meta
fields, which [MCP] requires on every request.¶
A client ID is a pairwise identifier: a client uses a different, unlinkable ID at each server. A client derives it from a secret key K that it holds and the server ID:¶
client-id = base64url(first 16 bytes of HMAC-SHA256(K, server-id))¶
The derivation gives the same ID every time the client connects to the same server, without the client storing any per-server state, so responses and tasks carry over a reconnection (Section 3.4). Two servers cannot link a client by its IDs. A client that wants a fresh identity at a server changes K, or derives the ID from a key specific to that server. A client MAY instead generate a random 128-bit ID per server and store it.¶
A client ID is not a credential. The server binds a client ID to the authenticated principal of the first request it receives in that client scope, and MUST reject requests in that scope from any other principal, except a party to which that principal has delegated a task (Section 2.8), within that task's scope. Relays authorize access to a client scope as described in Section 9.¶
The catalog is the server's capability listing, published in the server scope.¶
Track namespace: (mcp, <server-id>)
Track name: (catalog)¶
Each catalog version is a new group whose Object 0 is a JSON object
(Section 4). A server whose listings differ between
clients publishes those listings in a catalog track in each client
scope instead, (mcp, <server-id>, clients, <client-id>) -- (catalog),
and the client uses it in preference to the shared catalog.¶
The control tracks carry every MCP request that has no dedicated track below. They are an ordinary request/response pair (Section 3.3).¶
Track namespace: (mcp, <server-id>, clients, <client-id>, control)¶
| Direction | Track Name | Publisher | Content |
|---|---|---|---|
| Client-to-Server |
(request)
|
MCP Client | requests listed below |
| Server-to-Client |
(response)
|
MCP Server | responses |
The control request track carries the following MCP requests. The first two rows are also available from the catalog (Section 2.3), so a client that subscribes to the catalog does not need them.¶
| Purpose | Requests |
|---|---|
| Discovery (also in the catalog) |
server/discover
|
| Listing (also in the catalog) |
tools/list, resources/list, resources/templates/list, prompts/list
|
| Resources not on a resource track |
resources/read
|
| Completion |
completion/complete
|
| Tasks (also on the task's state track) |
tasks/get
|
Tool tracks carry tool invocation requests and responses. Each tool has its own request/response track pair (Section 3.3), so access to an individual tool can be authorized by namespace.¶
Track namespace: (mcp, <server-id>, clients, <client-id>, tools, <tool-name>)¶
| Direction | Track Name | Publisher | Content |
|---|---|---|---|
| Client-to-Server |
(request)
|
MCP Client |
tools/call requests |
| Server-to-Client |
(response)
|
MCP Server | progress notifications and the tool result |
Each invocation creates a new group on the request track. Object 0 of
that group contains the JSON-RPC tools/call request. The server
publishes the response in the group with the same Group ID on the
response track: zero or more progress notifications followed by the
final JSON-RPC result or error, which has the highest Object ID in the
group (Section 3.3.1). The final result is a complete result, an
input-required result (Section 3.3.2), or, with the Tasks extension, a task
handle (Section 2.8).¶
A server can use several subgroups within a response group to send parts of a tool response in parallel, each on its own QUIC stream. For example, it can send progress notifications in one subgroup while it streams a large result in another, so neither waits for the other and loss on one stream does not stall the rest (Section 3.3.1).¶
A client cancels an in-flight invocation as described in Section 3.3.3. Tool definitions are not carried on these tracks; they are published in the catalog (Section 4).¶
Prompt tracks carry prompts/get requests and responses. Each prompt
has its own request/response track pair (Section 3.3), as tools do,
so access to an individual prompt can be authorized by namespace and
given its own priority.¶
Track namespace: (mcp, <server-id>, clients, <client-id>, prompts, <prompt-name>)¶
| Direction | Track Name | Publisher | Content |
|---|---|---|---|
| Client-to-Server |
(request)
|
MCP Client |
prompts/get requests |
| Server-to-Client |
(response)
|
MCP Server | the prompt result |
Each request creates a new group on the request track, and the server publishes the result, which can be an input-required result (Section 3.3.2), in the group with the same Group ID on the response track. Prompt definitions are published in the catalog (Section 4).¶
Resource tracks deliver server-published content (files, API schemas, database results) that MCP exposes as read-only data sources.¶
Track namespace: (mcp, <server-id>, resources) for public resources,
or (mcp, <server-id>, clients, <client-id>, resources) for private
ones
Track name: (<resource-uri>)¶
Each resource version creates a new group (IDs incrementing from 0). Within a group, objects represent chunks of the resource content (object IDs starting at 0). This enables streaming delivery of large resources and version-based caching at relays. The server ends the group when it has published the whole version, so a receiver knows the resource is complete.¶
A server can spread the chunks of a large resource over several subgroups of the group, each on its own QUIC stream, so that parts of the resource are sent in parallel and loss on one stream does not stall the others. The receiver reassembles the resource in Object ID order.¶
A client performs the equivalent of resources/read by FETCH of the
latest group of the resource track, and follows changes to the
resource by SUBSCRIBE to it; each new group is an update. A client
that joins late, or after a disconnect, subscribes with
FILL_PARAMETERS: it receives the current version through a fill fetch
and then each later version, with no gap between them
(Section 3.4). A resource
that the server can only serve with additional input from the client
(Section 3.3.2) is not published on a resource track; the client reads it
with resources/read on its control track.¶
Task tracks carry the MCP Tasks extension [MCP-TASKS], which lets a server answer a request with a durable task handle instead of blocking. Each task has its own scope within the client scope, so everything about the task, its state and the client's answers, lives in one namespace:¶
Track namespace: (mcp, <server-id>, clients, <client-id>, tasks, <task-id>)¶
| Track Name | Publisher | Content |
|---|---|---|
(state)
|
MCP Server | task state changes |
(request)
|
MCP Client |
tasks/update and tasks/cancel requests |
(response)
|
MCP Server | responses to those requests |
Each task state change creates a new group on the state track whose
Object 0 contains the full task state, as carried in the extension's
notifications/tasks: its status (working, input_required,
completed, failed, or cancelled), and, in terminal states, the
result or error. Instead of polling with tasks/get, a client
subscribes to the state track and receives each change as it happens.
A client that reconnects mid-task uses a fill fetch (Section 3.4)
to retrieve the latest state and then follows live changes.¶
When the status is input_required, the state includes
inputRequests. The client answers with tasks/update, and cancels
the task with tasks/cancel, on the task's request track; these form
an ordinary request/response pair (Section 3.3).¶
Because the task has its own scope:¶
A token for one task's scope lets another party, such as the user's phone answering an approval, take part in that task without access to the rest of the client scope (Section 9).¶
A client, or another device of the same user, can rebuild the task by fetching one namespace.¶
Answers that unblock a task can be given their own priority.¶
The session begins with a QUIC connection or WebTransport
[WebTransport] session. Each endpoint then sends a SETUP message as
the first message on its control stream, carrying its Setup Options.
Setup Options are hop-by-hop: when a relay is present they are
exchanged with the relay, not with the far-end MCP peer. MCP needs no
negotiation at setup, because each request carries its protocol
version and capabilities in _meta [MCP].¶
Endpoints push their tracks to the next hop with PUBLISH, follow live tracks with SUBSCRIBE, and retrieve already-published objects with FETCH. After a reconnect, an endpoint catches up on missed objects with a fill fetch, requested through FILL_PARAMETERS on a SUBSCRIBE. A subscriber that may reach a relay before the publisher includes RENDEZVOUS_TIMEOUT so the relay holds the subscription. Track discovery uses SUBSCRIBE_TRACKS, which includes handling PUBLISH_SKIPPED, together with SUBSCRIBE_NAMESPACE and PUBLISH_NAMESPACE. Access control uses the AUTHORIZATION TOKEN Setup Option and message parameter. Implementations MUST support all of these [MOQT] mechanisms.¶
[MOQT] answers every request with a REQUEST_OK or REQUEST_ERROR message. This document uses names such as SUBSCRIBE_OK and FETCH_ERROR as shorthand for the REQUEST_OK or REQUEST_ERROR that answers the named request.¶
MCP endpoints MAY be identified using the moqt:// URI scheme defined
in [MOQT]. All namespaces whose first tuple element begins with a
period (0x2e) are reserved by MOQT; MCP implementations MUST NOT use
such prefixes.¶
A client that has established a MOQT session with the server, or with a relay in front of it:¶
Derives its client ID for the server (Section 2.2).¶
Sends SUBSCRIBE_TRACKS for its client scope
(mcp, <server-id>, clients, <client-id>), so that it receives
every response, task, and private track the server publishes for
it.¶
Subscribes to the catalog (mcp, <server-id>) -- (catalog) with
FILL_PARAMETERS, so that it receives the current catalog and then
each update.¶
Publishes requests on its request tracks (Section 3.3).¶
A server that serves clients through a relay sends SUBSCRIBE_TRACKS
for (mcp, <server-id>, clients), so that it receives the request
tracks of every client, including clients that arrive later. It
learns of a new client when the relay sends it a PUBLISH for the
client's first request track.¶
In MCP, the client sends requests and the server answers them. MOQT requests (SUBSCRIBE, FETCH) name a track but do not carry an object payload, so they cannot carry a JSON-RPC request. This document therefore maps every JSON-RPC exchange onto a pair of tracks in the client scope:¶
The client publishes requests on a request track.¶
The server publishes responses on a response track.¶
Each request is a new group on the request track. The response uses the same Group ID on the response track and can span several objects (for example, progress notifications followed by a result); see Section 3.3.1.¶
The JSON-RPC id is the authoritative correlation key. Matching
Group IDs are a convenience for relays and for recovery; a receiver
MUST NOT rely on Group IDs alone to match a response to a request.¶
Requests MAY be concurrent. Each exchange occupies its own group, and Group IDs on a request track increase with each new request.¶
Because a response lives in a known group, it can outlast the link that carried the request. Losing the MOQT session does not cancel a request: the server keeps the work running and keeps publishing the response into the same group, and retains it for a period of its choosing (Section 3.4). When the client reconnects, it resubscribes to the response track with FILL_PARAMETERS, or FETCHes that group starting at the first Object ID it has not received, and receives the rest of the response. Neither side resends what the other already has.¶
Within the group, the server MAY spread the response over several subgroups. Each subgroup is sent on its own QUIC stream, so a long result does not hold back progress notifications, and loss on one stream does not block the others. For example, a server can send progress notifications in one subgroup and the chunks of a large result in another. Object IDs increase across the whole group, and the final JSON-RPC result or error has the highest Object ID. A receiver orders the objects of a response by Object ID, not by arrival order, and treats the response as complete once it has the final result or error.¶
MCP servers do not send requests to clients. When a server needs more
input to complete a tools/call, prompts/get, or resources/read,
such as an elicitation, a sampling result, or the client's roots, it
returns a result with resultType input_required that lists the
requests in inputRequests and MAY include an opaque requestState
[MCP]. The client gathers the input and retries the original
request, with a new JSON-RPC id, inputResponses, and the
requestState unchanged.¶
Over MOQT, the input-required result is the final object of the original request's response group. The retry is a new request, so it is a new group on the same request track, and its response is in the matching group of the response track. No separate tracks are needed for elicitation, sampling, or roots.¶
A client cancels a request by publishing an MCP
notifications/cancelled message, with the request's JSON-RPC id as
requestId, as the next object in the request's own group on the
request track. If the subgroup that carried the request has ended, the
client opens a new subgroup in that group. The cancellation is thus
correlated by its group, covered by the same authorization as the
request track, and visible to a server that FETCHes the group after a
reconnect. A server that receives it stops the work and ends the
response with a JSON-RPC error or, if the work has already finished,
with its result. Work that runs as a task is cancelled with
tasks/cancel on the task's request track (Section 2.8).¶
MOQT operates hop by hop. A client sends PUBLISH for its request track to its first hop, which accepts it with PUBLISH_OK. When the first hop is the server, the server receives the request track directly. When it is a relay, the PUBLISH ends there, and the relay delivers the track onward to each downstream subscriber whose subscription matches it:¶
To an endpoint holding a SUBSCRIBE_TRACKS whose prefix matches the
track's namespace, such as the server's SUBSCRIBE_TRACKS on
(mcp, <server-id>, clients) (Section 3.2), the relay sends
a PUBLISH for the track towards that endpoint, and then forwards the
track's objects.¶
To an endpoint that already holds a SUBSCRIBE for the track, the relay sends the track's objects on that subscription.¶
Response tracks reach the client the same way, in the other direction: the server publishes them to its first hop, and the client's SUBSCRIBE_TRACKS on its client scope, which excludes the client's own request tracks, delivers each one. A client that subscribes to a response track before the server has published it includes RENDEZVOUS_TIMEOUT, so that the relay holds the subscription until the track appears.¶
FETCH is never used to send a request. It is used only to retrieve objects that are already published: the catalog, a resource, skill metadata and instructions, task state, or a response that was missed during a disconnect (Section 3.4).¶
Request and response tracks MUST NOT use a finite delivery timeout (SUBGROUP_DELIVERY_TIMEOUT or OBJECT_DELIVERY_TIMEOUT), because a lost request or response cannot be told apart from one that was never sent.¶
If the underlying QUIC connection migrates (e.g., network change on a mobile device), the MOQT session persists and nothing changes for MCP. If the connection is lost, the client reconnects, to the same relay or another one, and repeats Section 3.2. Because it derives the same client ID, its client scope, and the responses and tasks in it, are unchanged.¶
A client that did not receive all of the response to an in-flight
request recovers the rest from the corresponding group of the
response track (Section 3.3.1). A server retains responses and
task state for a period of its choosing; tasks follow the ttlMs of
[MCP-TASKS]. Relays that cache them within their MAX_CACHE_DURATION
(Section 5.1.1) answer the client's catch-up without involving
the server. A request whose response cannot be recovered SHOULD be
treated as failed, and the client MAY retry it as a new request.¶
To catch up on objects missed during a disconnect without a separate FETCH round-trip, a client re-subscribing to a track SHOULD use a fill fetch: it issues SUBSCRIBE with FILL_PARAMETERS on the track (e.g., a task track or a resource track), and the publisher opens a unidirectional fill fetch stream carrying the historical range (typically the latest complete group) before live objects resume on the subscription. This makes "retrieve what I missed, then follow live" atomic. For deterministic recovery of a specific object range (e.g., replaying a task's state history), the client uses FETCH with explicit Start and End Locations; the publisher confirms the actual End Location in FETCH_OK.¶
MOQT subscriber priorities (0-255, lower is higher priority) optimize MCP message delivery:¶
| Priority | Range | Track Type | Example |
|---|---|---|---|
| HIGH | 1-30 | Control, Tools, Prompts, Tasks, Skill execution | tool calls, task state |
| MEDIUM | 31-90 | Catalog, Skills | capability changes |
| LOW | 91-127 | Resources | documentation |
Applications MAY dynamically adjust priorities based on user interaction state (e.g., boosting skill loading when a user is actively waiting).¶
Within a PACE dialog (Section 8), MCP tracks instead use the priority tiers of [PACE], so that they are ordered correctly against PACE dialog control and other bindings:¶
| MCP Track Type | PACE Tier | Range |
|---|---|---|
| Control, Tools, Prompts, Tasks, Skill execution | Standard Operations | 96-127 |
| Catalog, Skills | Discovery | 128-159 |
| Resources | Bulk Transfer | 192-255 |
MOQT reports errors through four separate code spaces [MOQT]. The same name (for example, EXPIRED_AUTH_TOKEN) can appear in more than one of them with a different code point, so an implementation needs to know which mechanism carried an error before acting on it.¶
Request errors, carried in REQUEST_ERROR in response to SUBSCRIBE, PUBLISH, FETCH, SUBSCRIBE_TRACKS, SUBSCRIBE_NAMESPACE or PUBLISH_NAMESPACE (SUBSCRIBE_ERROR, FETCH_ERROR and so on are shorthands for REQUEST_ERROR):¶
| Code | Meaning | MCP Recovery |
|---|---|---|
| DOES_NOT_EXIST | Unknown server, tool or resource track | Check the server ID or refresh the catalog |
| UNAUTHORIZED | Token lacks the required scope | Obtain a token with broader scope (Section 9) |
| EXPIRED_AUTH_TOKEN | Auth token expired | Re-authenticate and retry |
| TIMEOUT | No publisher appeared within RENDEZVOUS_TIMEOUT | Retry, or treat the server as unavailable |
| INVALID_RANGE | FETCH range has no published objects | Treat the object as not yet published |
| EXCESSIVE_LOAD | Endpoint overloaded | Back off with exponential delay |
| NAMESPACE_TOO_LARGE | Relay refuses to enumerate the prefix | Subscribe to specific tracks |
PUBLISH_DONE status codes, which end a subscription:¶
| Code | Meaning | MCP Recovery |
|---|---|---|
| TRACK_ENDED | Publisher finished the track | Normal; no action |
| TOO_FAR_BEHIND | Subscriber could not keep up | Re-subscribe with a fill fetch; reduce subscription scope |
| EXPIRED | Subscription reached its expiry | Re-subscribe |
| UNAUTHORIZED | Subscriber is no longer authorized | Re-authenticate |
| EXCESSIVE_LOAD | Publisher overloaded | Back off with exponential delay |
| GOING_AWAY | Publisher sent or received GOAWAY | Reconnect |
Stream reset codes, used when an individual data stream is reset:¶
| Code | Meaning | MCP Recovery |
|---|---|---|
| DELIVERY_TIMEOUT | Delivery timeout exceeded | Not expected on request/response tracks (Section 3.3); FETCH the missing group |
| SESSION_CLOSED | Session is closing | Reconnect if needed |
| CANCELLED | Stream cancelled by either endpoint | Check PUBLISH_DONE for detail |
Session termination codes and GOAWAY end the whole MOQT session. After GOAWAY, clients SHOULD reconnect as described in Section 3.4.¶
PUBLISH_DONE indicates the publisher has finished sending objects for a subscription. This is not an error. Clients SHOULD re-subscribe if the content is expected to continue (e.g., a resource that receives periodic updates).¶
MCP errors are JSON-RPC error responses within MOQT objects:¶
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method not found",
"data": { "details": "Tool 'unknown_tool' not found" }
}
}
¶
Besides the standard JSON-RPC error codes, [MCP] defines -32021
(missing required client capability) and -32022 (unsupported protocol
version), and reports an unknown resource with -32602 (invalid
params). A tool that receives invalid input SHOULD report it as a tool
execution error (an isError: true result on the tool track) rather
than a JSON-RPC protocol error, so the model can self-correct.¶
Transient errors: Retry with exponential backoff¶
Unsupported protocol version: Retry with a version from the
error's supported list¶
Resource errors: Invalidate cache and re-fetch¶
Skill errors: See Section 6.4¶
The catalog replaces server/discover and the list requests for a
client that subscribes to it. Its Object 0 is a JSON object whose
members are those of the server/discover result (supportedVersions,
capabilities, instructions, and the server's identity in _meta)
together with tools, resources, resourceTemplates, and prompts
holding the definitions that tools/list, resources/list,
resources/templates/list, and prompts/list return [MCP]. The
ttlMs and cacheScope members of those results also apply to the
catalog; the server-scope catalog has cacheScope public.¶
{
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"resources": {},
"prompts": {},
"extensions": { "io.modelcontextprotocol/tasks": {} }
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "ExampleServer",
"version": "2.0.0"
}
},
"instructions": "Project file and build tools.",
"tools": [
{
"name": "read_file",
"description": "Read a file from the project",
"inputSchema": {
"type": "object",
"properties": { "path": { "type": "string" } },
"required": ["path"]
}
}
],
"resources": [
{
"uri": "file:///project/README.md",
"name": "README",
"mimeType": "text/markdown"
}
],
"prompts": [
{ "name": "code_review", "description": "Review a change" }
],
"ttlMs": 3600000,
"cacheScope": "public"
}
¶
Clients FETCH the latest group of the catalog for one-time discovery,
or SUBSCRIBE for real-time updates when capabilities change. Catalog
entries SHOULD carry the icons and description fields defined by
[MCP]. Tool inputSchema and outputSchema use JSON Schema
2020-12, the default dialect for MCP schema definitions.¶
MOQT offers two prefix-based mechanisms, and they discover different things [MOQT]:¶
SUBSCRIBE_TRACKS discovers and subscribes to tracks. The publisher (or relay) sends a PUBLISH for each existing and future track whose namespace matches the prefix, excluding tracks published by the subscriber.¶
SUBSCRIBE_NAMESPACE discovers namespaces, not tracks. The publisher responds with a NAMESPACE message for each matching namespace it knows, and sends NAMESPACE_DONE later if that namespace is withdrawn. NAMESPACE_DONE does not mark the end of a list.¶
A client uses SUBSCRIBE_TRACKS on its client scope (Section 3.2),
and MAY use it on the server-scope prefixes (mcp, <server-id>,
resources) and (mcp, <server-id>, skills) to follow public
resources and skills. A client MUST NOT send SUBSCRIBE_TRACKS for
(mcp, <server-id>) or (mcp, <server-id>, clients), which would
match other clients' tracks, and relays refuse it
(Section 9).¶
The following rules apply to prefix subscriptions:¶
Skipped tracks: if the publisher cannot create a subscription for a matching track, it sends PUBLISH_SKIPPED with that track's name and will not send a PUBLISH for it under this SUBSCRIBE_TRACKS. An MCP endpoint that receives PUBLISH_SKIPPED for a track it needs MUST send an explicit SUBSCRIBE for that track. Missing a request or response track would otherwise stall requests.¶
Large namespaces: a relay MAY refuse or close a prefix subscription with NAMESPACE_TOO_LARGE. An endpoint that receives it falls back to explicit SUBSCRIBE for the tracks listed in the catalog.¶
The catalog is authoritative for which tools, resources, resource templates, and prompts the server offers. When that set changes, the server publishes a new catalog group. It then starts publishing the tracks of any added resource, and ends the tracks of any removed resource with PUBLISH_DONE (TRACK_ENDED).¶
A client that subscribes to the catalog track receives each change
without polling, so it does not need subscriptions/listen or the
list-changed notifications of [MCP]. Changes to the content of a
resource are signaled by a new group on its resource track
(Section 2.7).¶
MOQT relays enable scalable MCP deployments through subscription aggregation and content caching.¶
When multiple subscribers subscribe to the same track, relays maintain a single upstream subscription and fan out content downstream, and a cached object serves later subscriptions and fetches. Server-scope tracks are shared by all clients of a server, so one copy of the catalog, a public resource, or a skill at a relay serves every client behind it. Client-scope tracks belong to one client; a cached object there serves only that client, for example in a fill fetch after reconnection (Section 3.4).¶
| Content Type | Scope | Cache Strategy |
|---|---|---|
| Catalog | Server | Latest group; replaced by each update |
| Public resources | Server | Latest group; a new group is a new version |
| Skill registry, metadata, instructions | Server | Latest group; replaced by each new version |
| Private resources, private catalog | Client | Latest group, for that client only |
| Responses, task state | Client | Kept only as long as needed for recovery (Section 3.4) |
A server MUST NOT publish content whose cacheScope is private in
the server scope.¶
A publisher controls how long relays may serve a track's objects from cache with the MAX_CACHE_DURATION Track Property [MOQT], a number of milliseconds after which a relay stops serving an object. Within that time, a relay that holds an object answers a FETCH or fill fetch for it directly, so the request does not travel to the server. This serves a client from the nearest relay when it reads the catalog, a resource, or a skill, joins late, or catches up after a reconnection.¶
Servers SHOULD set MAX_CACHE_DURATION on every track they publish:¶
On server-scope tracks, to the content's ttlMs [MCP], so that
relays stop serving content the server no longer considers fresh.¶
On response and task state tracks, to at least the period for which the server retains responses and task state (Section 3.4), so that a reconnecting client can recover them from the relay.¶
Clients MAY set MAX_CACHE_DURATION on their request tracks in the same way, so that a server that reconnects can FETCH a request, or a cancellation (Section 3.3.3), that it missed from the relay. Without MAX_CACHE_DURATION, relays can cache objects until they evict them, which can serve content after the server considers it stale.¶
A client normally learns a server ID out of band (Section 2.1). A relay operator MAY additionally offer a listing of the MCP servers behind a relay. Listing is optional and opt-in on both sides:¶
A server that wants to be listed advertises its server scope
(mcp, <server-id>) to the relay with PUBLISH_NAMESPACE. A relay
lists only servers that do so and that its operator allows to be
listed.¶
A client asks for the listing with SUBSCRIBE_NAMESPACE for (mcp).
A relay MUST refuse this request with REQUEST_ERROR (UNAUTHORIZED)
unless the client's token authorizes listing, and MAY limit the
listing to the servers that client is authorized to see.¶
The relay answers an authorized request with one NAMESPACE message for each listed server, and sends NAMESPACE_DONE when a server withdraws. The relay does not forward PUBLISH_NAMESPACE to clients.¶
Client Relay Server A Server B
| | | |
| | PUBLISH_NAMESPACE | |
| | (mcp, srv-a) | |
| |<---------------------| |
| | PUBLISH_NAMESPACE | |
| | (mcp, srv-b) |
| |<--------------------------------|
| | | |
| SUBSCRIBE_NAMESPACE | | |
| (mcp) | | |
|-------------------->| | |
| NAMESPACE (srv-a) | | |
|<--------------------| | |
| NAMESPACE (srv-b) | | |
|<--------------------| | |
| | | |
| ... later, Server B withdraws ... | |
| NAMESPACE_DONE | | |
| (srv-b) | | |
|<--------------------| | |
¶
NAMESPACE and NAMESPACE_DONE carry only the namespace suffix after the
subscribed prefix, here the server ID. The client then reads the
server's catalog and starts as in Section 3.2. A relay MUST NOT reveal client-scope namespaces, or any namespace other than a
listed server's (mcp, <server-id>), in answer to SUBSCRIBE_NAMESPACE.¶
Agent Skills are composed instructions that orchestrate multiple tools for complex tasks. Skills complement tools: tools are atomic and stateless, while skills are context-aware multi-step workflows.¶
This document supports two ways to run a skill:¶
Client-executed (the default): the client loads the skill's instructions and its model carries them out with ordinary MCP tool calls (Section 6.2). This needs no new MCP methods; the skill tracks only distribute the skill content.¶
Server-executed (OPTIONAL): the client asks the server to run the
skill with skills/invoke, and the server reports progress and the
result (Section 6.3). This adds JSON-RPC methods that
are not part of [MCP].¶
Support for Skills is OPTIONAL. A server that offers skills MUST
publish the registry track and the metadata and instructions tracks of
each skill, and MUST support FETCH on them. A server that also
supports server-executed skills marks those skills with
"serverExecution": true in the registry.¶
Skill content is published once in the server scope; server-executed skill requests are in the client scope.¶
| Track Namespace | Track Name | Publisher | Purpose |
|---|---|---|---|
(mcp, <server-id>, skills)
|
(registry)
|
MCP Server | List of skills with their metadata |
(mcp, <server-id>, skills, <skill-id>)
|
(metadata)
|
MCP Server | Skill metadata |
(mcp, <server-id>, skills, <skill-id>)
|
(instructions)
|
MCP Server | Full instructions |
(mcp, <server-id>, clients, <client-id>, skills, <skill-id>)
|
(request)
|
MCP Client |
skills/invoke requests (server-executed only) |
(mcp, <server-id>, clients, <client-id>, skills, <skill-id>)
|
(response)
|
MCP Server | Progress and result (server-executed only) |
Each skill version is a new group on the registry, metadata, and instructions tracks. Skills use progressive loading: a client reads the small metadata object first, and fetches the instructions only when it decides to use the skill. The metadata object contains:¶
id and version¶
description and triggers: when the skill applies¶
requiredTools: the tools the instructions call¶
instructionsHash: the SHA-256 hash of the instructions object,
base64url-encoded, so that a client can check the instructions it
fetched¶
Clients SHOULD cache skills by version.¶
Client Relay Server
| | |
| User types: "/commit" | |
| (match registry) | |
| | |
| FETCH skills, commit | |
| -- (metadata) | |
|----------------------->| [cached] |
| Object: metadata | |
|<-----------------------| |
| | |
| FETCH skills, commit | |
| -- (instructions) | |
|----------------------->| [cached] |
| Object: instructions | |
|<-----------------------| |
| | |
| (model follows the instructions) |
| | |
| Object on tools, | |
| git_status | |
| -- (request) | |
|----------------------->|------------------------>|
| Object on tools, | |
| git_status | |
| -- (response) | |
|<-----------------------|<------------------------|
| | |
| ... further tool calls, such as git_commit ... |
¶
The metadata and instructions are already-published, versioned objects in the server scope, so FETCH is appropriate and a relay that holds them answers from its cache, whichever client fetched them first (Section 5). Tool calls use the client's tool request/response tracks (Section 3.3).¶
The skill's request and response tracks are a request/response pair (Section 3.3). Each invocation is a new group on the request track, and the server publishes progress and the result in the group with the same Group ID on the response track. A server that runs the skill as a task returns a task handle and reports state on the task's state track (Section 2.8).¶
The request, published as Object 0 of a new group:¶
{
"jsonrpc": "2.0",
"id": "exec-001",
"method": "skills/invoke",
"params": {
"skillId": "commit",
"version": "1.2.0",
"arguments": { "push": false },
"_meta": { "progressToken": "exec-001" }
}
}
¶
Zero or more progress notifications follow on the response track, then the result as the last object of the group:¶
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "exec-001",
"progress": 1,
"total": 3,
"message": "Checking status..."
}
}
¶
{
"jsonrpc": "2.0",
"id": "exec-001",
"result": {
"resultType": "complete",
"output": { "commitSha": "abc123", "message": "Add auth flow" }
}
}
¶
A server reports skill failures as JSON-RPC errors on the skill's response track:¶
| Error Code | Description | Recovery Action |
|---|---|---|
| -33001 | Skill not found | Refresh the registry |
| -33002 | Required tool unavailable | Check that the tool's server is reachable |
| -33003 | Dependency resolution failed | Check the skill's requiredTools
|
| -33004 | Server-executed skill timed out | Retry, or run the skill client-side |
These codes are outside the JSON-RPC reserved range, as [MCP] requires for application-defined errors. A client that cannot run a skill can fall back to direct tool calls.¶
Each skill version is an immutable group in the server scope, so a relay can serve it to every client (Section 5). When a skill is updated, the server publishes a new group on the registry and on the skill's metadata and instructions tracks; clients with active subscriptions receive the new version without polling.¶
When a client uses multiple MCP servers, it has a separate, pairwise client ID and client scope at each:¶
Server A: (mcp, srv-a, resources) -- (file:///docs/index.md)
Server B: (mcp, srv-b, clients, <client-id-b>, tools, code_analysis)
-- (request)
Server C: (mcp, srv-c, clients, <client-id-c>, control) -- (request)
¶
The client coordinates across servers by:¶
Subscribing to related resource tracks from multiple servers and selecting sources based on freshness or performance¶
Passing tool results from one server as input to tools on another¶
Failing over to alternative servers when one becomes unavailable¶
No cross-server state sharing occurs at the protocol level; the client is responsible for coordination.¶
PACE [PACE] manages agent dialogs over MOQT: it supplies the dialog identifier, lifecycle, context propagation, identity, and authorization, and carries application protocols such as MCP as bindings. MCP over MOQT runs inside a PACE dialog with the same tracks and the same message handling as in standalone mode; only the namespaces change.¶
In a PACE dialog, each participant publishes under its own agent-id.
The leading fields of an MCP namespace,
(mcp, <server-id>, clients, <client-id>) in the client scope or
(mcp, <server-id>) in the server scope, are replaced by (pace, dialogs, <did>, <agent-id>,
mcp), where <did> is the dialog-id and <agent-id> is the agent-id
of the track's publisher. The rest of the namespace (the MCP category,
such as control, tools, prompts, resources, tasks or
skills, and any fields that follow it) and the track name stay the
same. For example, with cli and srv the agent-ids of the MCP
client and server:¶
Standalone:
(mcp, <server-id>, clients, <client-id>, tools, <tool-name>)
-- (request)
(mcp, <server-id>, clients, <client-id>, tools, <tool-name>)
-- (response)
(mcp, <server-id>, resources) -- (<resource-uri>)
(mcp, <server-id>) -- (catalog)
Within a PACE dialog:
(pace, dialogs, <did>, <cli>, mcp, tools, <tool-name>)
-- (request)
(pace, dialogs, <did>, <srv>, mcp, tools, <tool-name>)
-- (response)
(pace, dialogs, <did>, <srv>, mcp, resources) -- (<resource-uri>)
(pace, dialogs, <did>, <srv>, mcp) -- (catalog)
¶
As in standalone mode, the category and identifier stay separate
namespace fields, so tokens and relays can match on
(..., mcp, tools) by prefix, and identifiers that contain /, such
as resource URIs, remain unambiguous. The resulting Full Track Name
MUST NOT exceed the 4,096-byte and 32-field limits of [MOQT].¶
Everything else in this document applies unchanged inside a dialog: the object encoding, request and response tracks (Section 3.3), group and subgroup use, multi round-trip requests, tasks, skills, and relay caching. PACE takes over the following:¶
Scopes and client IDs: the dialog replaces both the server scope and the client scope, and PACE relay authorization keeps the dialog's tracks within the dialog. Client IDs are not used. Content that is public in standalone mode, such as the catalog, is published in each dialog; a client MAY instead read it from the server's standalone server scope, where relays share it across clients.¶
Getting started: MCP has no handshake. Once the dialog is active,
each participant sends SUBSCRIBE_TRACKS on the dialog prefix
(pace, dialogs, <did>), which delivers its peers' MCP tracks, and
the client starts publishing requests.¶
Dialog reference: each MCP request carries the PACE dialog reference
in _meta under the key org.ietf.agentproto/dialog-context, next
to the per-request fields of [MCP]. It is distinct from JSON-RPC
request IDs.¶
Cancellation: a PACE CANCEL with scope operation that names an MCP
request track and group has the same effect as
notifications/cancelled in that group (Section 3.3.3). The
client can also publish notifications/cancelled itself.¶
Identity and authorization: PACE supplies them. The MCP access rules
of Section 9 become PACE scopes over the
(..., <did>, <agent-id>, mcp, ...) namespaces.¶
Priorities: MCP tracks use the PACE tiers (Section 3.5).¶
Several bindings, for example A2A and MCP, can share one dialog, because each uses its own protocol field after the agent-id.¶
A deployment MAY use either mode. Standalone mode suits a client that talks directly to one or more MCP servers. PACE mode suits MCP tool access that is one part of a larger, multi-agent dialog.¶
The Personal Agent Protocol (Poppy) [POPPY] lets a person's own
agent, the Personal Agent, sign in to a Company and call the Company's
APIs, which the Company lists in its poppy.json discovery document.
Poppy Draft 0.3 reaches an API of type mcp over Streamable HTTP.
This informative section describes how a Company can offer the same
MCP server over MOQT. Poppy is a pre-1.0 draft, and this section
tracks Draft 0.3.¶
A Company lists its MCP over MOQT server in poppy.json, alongside or
instead of its Streamable HTTP server. Poppy ignores API entries
whose type it does not recognize, so the two can coexist:¶
{
"apis": [
{ "type": "mcp", "url": "https://acme.example/mcp" },
{ "type": "acme.example/mcp-moqt",
"url": "moqt://relay.acme.example",
"serverId": "acme-support" }
]
}
¶
The url member is the moqt:// URI of the relay or server to
connect to (Section 3.1), and serverId is the server ID
(Section 2.1). Poppy Draft 0.3 does not define a type for MCP over
MOQT, so the entry is a Poppy extension, named under a domain the
Company controls as Poppy's extension rules require.¶
Poppy issues access tokens bound to a resource with the resource
parameter of [RFC8707]. For an MCP over MOQT server, the resource is
the url of its apis entry. The Personal Agent presents the token
as Section 9 describes: in each request's _meta for the
server, and, where a relay enforces access, as a Common Access Token
with the DPoP binding of [MoQ-C4M] that authorizes the Personal
Agent's client scope.¶
Poppy sends a fresh Poppy-Session JWT with each request. It
identifies the session, the pairwise User ID, and the Personal Agent's
delegated key, and Poppy binds it to an HTTP request with the htm
and htu claims. Over MOQT, the Personal Agent puts the JWT in the
_meta of each JSON-RPC request it publishes, and replaces htm and
htu with an actx claim as defined by [MoQ-C4M]: type "moqt",
action "PUBLISH", and tns and tn naming the request track that
carries the object. The server verifies the JWT as Poppy specifies,
checking actx instead of htm and htu. As in Poppy, a server MAY
ignore the proof.¶
Note: Poppy does not yet support this use of the Poppy-Session JWT.¶
Poppy identifies a person to each Company only by a User ID that is
different at every Company. MCP over MOQT client IDs are pairwise in
the same way (Section 2.2), so they do not let two Companies link a
Personal Agent. Because _meta carries the Poppy-Session JWT and
the access token, the Personal Agent's request tracks SHOULD be
protected end to end with Secure Objects
[I-D.ietf-moq-secure-objects] when they cross a relay.¶
This document has no IANA actions. Future versions may request
registration of the mcp namespace prefix in an MOQT namespace
registry if one is established, and registration of the mcp binding
in a PACE protocol binding registry if [PACE] establishes one.¶
The examples use server ID srv-a and write the client's client ID as
<cid>. Except in Section 13.2, they omit the per-request _meta
fields of [MCP].¶
The client opens a QUIC connection to the relay, and each endpoint sends SETUP on its control stream.¶
The client derives its client ID <cid> for srv-a
(Section 2.2).¶
The client sends SUBSCRIBE_TRACKS for
(mcp, srv-a, clients, <cid>).¶
The client sends SUBSCRIBE for (mcp, srv-a) -- (catalog) with
FILL_PARAMETERS. It receives the current catalog, from the relay's
cache if another client has already read it, and then each
update.¶
The client sends PUBLISH for
(mcp, srv-a, clients, <cid>, tools, read_file) -- (request) and
publishes its first tools/call.¶
The client publishes the call as Object 0 of Group 7 on
(mcp, srv-a, clients, <cid>, tools, read_file) -- (request):¶
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/project/README.md" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
¶
The server publishes the result as Object 0 of Group 7 on
(mcp, srv-a, clients, <cid>, tools, read_file) -- (response):¶
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"content": [
{ "type": "text", "text": "# Project README\n..." }
]
}
}
¶
The server needs the user's confirmation before it deletes a file. All
four messages are on the delete_file tool tracks,
(mcp, srv-a, clients, <cid>, tools, delete_file).¶
The client publishes the call as Object 0 of Group 3 on the request track:¶
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "delete_file",
"arguments": { "path": "/project/old.log" }
}
}
¶
The server answers in Group 3 of the response track with an input-required result:¶
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"resultType": "input_required",
"inputRequests": {
"confirm": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Delete old.log?",
"requestedSchema": {
"type": "object",
"properties": { "ok": { "type": "boolean" } }
}
}
}
},
"requestState": "opaque-state"
}
}
¶
The client asks the user, then retries as a new request, with a new
id, in Group 4 of the request track:¶
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "delete_file",
"arguments": { "path": "/project/old.log" },
"inputResponses": {
"confirm": { "action": "accept", "content": { "ok": true } }
},
"requestState": "opaque-state"
}
}
¶
The server completes the call in Group 4 of the response track:¶
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"resultType": "complete",
"content": [ { "type": "text", "text": "Deleted" } ]
}
}
¶
The user types "/commit", and the client matches it against its cached skill registry.¶
The client FETCHes (mcp, srv-a, skills, commit) -- (metadata)
and learns the version, the required tools, and the instructions
hash.¶
The client FETCHes (mcp, srv-a, skills, commit) -- (instructions)
and checks it against the hash.¶
The model follows the instructions. For each tool call, such as
git_status, git_diff, and git_commit, the client publishes on
(mcp, srv-a, clients, <cid>, tools, <tool-name>) -- (request) and
reads the result from the matching response track.¶
The client returns the result to the user.¶
This document is a revision of draft-jennings-mcp-over-moqt available at https://datatracker.ietf.org/doc/draft-jennings-mcp-over-moqt/.¶