Internet-Draft MCP over MOQT November 2026
Jennings, et al. Expires 5 May 2027 [Page]
Workgroup:
Agent Communication Protocols
Internet-Draft:
draft-jennings-agentproto-mcp-over-moqt-01
Published:
Intended Status:
Experimental
Expires:
Authors:
C. Jennings
Cisco
I. Swett
Google
J. Rosenberg
Five9
S. Nandakumar
Cisco

Model Context Protocol and Agent Skills over Media over QUIC Transport

Abstract

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).

About This Document

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.

Status of This Memo

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.

▲

Table of Contents

1. Introduction

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:

MOQT provides:

1.1. Relationship to PACE

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.

1.2. Terminology

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).

1.3. Protocol Overview

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).

1.3.1. System Components

+-----------------+    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.

2. MOQT Tracks for MCP

2.1. Server and Client Scopes

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.

2.2. Client ID

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.

2.3. Catalog Track

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.

2.4. Control Tracks

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)

Table 1: Control Tracks
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.

Table 2: Requests on the Control Tracks
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

2.5. Tool Tracks

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>)

Table 3: Tool Tracks
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).

2.6. Prompt Tracks

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>)

Table 4: Prompt Tracks
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).

2.7. Resource Tracks

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.

2.8. Task Tracks

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>)

Table 5: Tracks in a Task Scope
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.

3. Protocol Operation

3.1. MOQT Session Establishment

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.

3.2. Getting Started

A client that has established a MOQT session with the server, or with a relay in front of it:

  1. Derives its client ID for the server (Section 2.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.

  3. Subscribes to the catalog (mcp, <server-id>) -- (catalog) with FILL_PARAMETERS, so that it receives the current catalog and then each update.

  4. 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.

3.3. Request and Response Exchanges

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.

3.3.1. Response Layout and Continuity

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.

3.3.2. Multi Round-Trip Requests

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.

3.3.3. Cancellation

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).

3.3.4. Delivering Requests and Responses

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.

3.4. Reconnection

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.

3.5. Priority Management

MOQT subscriber priorities (0-255, lower is higher priority) optimize MCP message delivery:

Table 6: Standalone Priorities
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:

Table 7: Priorities within PACE
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

3.6. Error Handling

3.6.1. Transport-Level Errors

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):

Table 8: MOQT Request Errors
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:

Table 9: PUBLISH_DONE Status Codes
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:

Table 10: Stream Reset Codes
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.

3.6.2. Subscription Lifecycle

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).

3.6.3. MCP-Level Errors

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.

3.6.4. Error Recovery

  1. Transient errors: Retry with exponential backoff

  2. Unsupported protocol version: Retry with a version from the error's supported list

  3. Resource errors: Invalidate cache and re-fetch

  4. Skill errors: See Section 6.4

4. Capability Discovery

4.1. Catalog Contents

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.

4.2. Track Discovery by Prefix

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.

4.3. Dynamic Updates

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).

5. Relay Support

MOQT relays enable scalable MCP deployments through subscription aggregation and content caching.

5.1. Subscription Aggregation and 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).

Table 11: Relay Cache Strategy
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.

5.1.1. Cache Duration

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.

5.2. Finding Servers Through a Relay

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.

6. Agent Skills

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:

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.

6.1. Skills Track Structure

Skill content is published once in the server scope; server-executed skill requests are in the client scope.

Table 12: Skills Tracks
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.

6.2. Client-Executed Skills

    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).

6.3. Server-Executed Skills

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" }
  }
}

6.4. Skills Errors

A server reports skill failures as JSON-RPC errors on the skill's response track:

Table 13: Skills Error Codes
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.

6.5. Relay Caching for Skills

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.

7. Multi-Server Operation

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:

No cross-server state sharing occurs at the protocol level; the client is responsible for coordination.

8. Carrying MCP inside a PACE Dialog

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.

8.1. Namespace Mapping

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].

8.2. What PACE Replaces

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.

8.3. Choosing a Mode

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.

9. Authorization

Implementations SHOULD use MOQT's authorization token mechanism to control access to individual tracks. Tokens SHOULD be scoped to specific track namespaces or patterns (e.g., a token granting access to mcp/<server-id>/clients/<client-id>/tools/* but not mcp/<server-id>/clients/<client-id>/skills/*). Tokens MUST have bounded lifetimes; servers MUST reject expired tokens with EXPIRED_AUTH_TOKEN.

Relays MUST authorize the scopes of Section 2.1 as follows:

Within a client scope, the client and server publish under the same namespaces (for example, both publish tracks in (mcp, <server-id>, clients, <client-id>, tools, <tool-name>)), and only the track name tells the request track from the response track. A namespace-scoped grant to publish therefore does not stop one peer from publishing a track that belongs to the other. Tokens that authorize PUBLISH MUST be scoped to Full Track Names, or to namespace patterns combined with the track names that the holder publishes as defined in Section 2. A relay MUST reject a PUBLISH for a track outside the publisher's token scope. Subscribe-only tokens MAY remain namespace-scoped.

Section 9.20.2 of [MOQT] forbids copying the AUTHORIZATION TOKEN parameter from a SUBSCRIBE_TRACKS into the PUBLISH messages that result from it. A relay that forwards a PUBLISH because of a SUBSCRIBE_TRACKS MUST therefore check that each forwarded track falls within the scope of the token presented on that SUBSCRIBE_TRACKS. For example, a token scoped to mcp/<server-id>/clients/<client-id>/tools/* must not result in the relay forwarding a PUBLISH for a skills or tasks track.

9.1. Mapping MCP Authorization onto MOQT

The MCP authorization framework [MCP] treats the MCP server as an OAuth 2.1 protected resource. It uses OAuth 2.0 Protected Resource Metadata discovery ([RFC9728]), OpenID Connect Discovery for the authorization server, incremental scope consent signaled via WWW-Authenticate, and OAuth Client ID Metadata Documents for client registration.

Because MOQT supplies transport over QUIC rather than HTTP, this framework maps onto MOQT as follows:

  • Resource discovery: the protected-resource and authorization-server metadata are obtained out of band (e.g., from the server's HTTPS origin or an MCP registry entry) before the MOQT session is established. The resulting access token is then presented to MOQT.

  • Token presentation: the OAuth access token (or a token derived from it) is carried in MOQT's AUTHORIZATION TOKEN Setup Option and in the AUTHORIZATION TOKEN message parameter on individual requests, rather than in an HTTP Authorization header. Setup Options are hop-by-hop, so a token in SETUP authorizes the session with the first relay. Relays aggregate subscriptions, so a token in a request parameter does not in general reach the MCP server either. When the path includes relays, credentials that the MCP server itself must verify travel in each request's _meta.

  • Proof of possession: HTTP DPoP [RFC9449] binds a proof to a request with the htm and htu claims, which MOQT has no equivalent for. Deployments that need sender-constrained tokens use the DPoP binding of [MoQ-C4M]. There, each proof carries an Authorization Context (actx) naming the MOQT action, Track Namespace and Track Name in place of htm and htu.

  • Relay-level authorization: where a relay enforces namespace access, deployments SHOULD use MOQT Common Access Tokens [MoQ-C4M], whose scopes map to the (mcp, <server-id>, ...) track namespaces. A C4M token can be minted from the OAuth grant so that the relay authorizes subscribe/publish without seeing the upstream OAuth token.

  • Incremental consent: when a server requires additional scope for a particular tool or resource, it rejects the PUBLISH, SUBSCRIBE or FETCH with REQUEST_ERROR (UNAUTHORIZED), and the client obtains a token with the broader scope, mirroring the WWW-Authenticate incremental-consent flow. EXPIRED_AUTH_TOKEN is used only when the presented token has expired.

10. Use with the Personal Agent Protocol

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.

10.1. Advertising the Server

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.

10.2. Access Tokens

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.

10.3. Session Proofs

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.

10.4. Privacy

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.

11. Security Considerations

TODO

12. IANA Considerations

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.

13. Examples

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].

13.1. Starting to Use a Server

  1. The client opens a QUIC connection to the relay, and each endpoint sends SETUP on its control stream.

  2. The client derives its client ID <cid> for srv-a (Section 2.2).

  3. The client sends SUBSCRIBE_TRACKS for (mcp, srv-a, clients, <cid>).

  4. 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.

  5. The client sends PUBLISH for (mcp, srv-a, clients, <cid>, tools, read_file) -- (request) and publishes its first tools/call.

13.2. Tool Execution

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..." }
    ]
  }
}

13.3. Multi Round-Trip Request

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" } ]
  }
}

13.4. Skill Invocation

  1. The user types "/commit", and the client matches it against its cached skill registry.

  2. The client FETCHes (mcp, srv-a, skills, commit) -- (metadata) and learns the version, the required tools, and the instructions hash.

  3. The client FETCHes (mcp, srv-a, skills, commit) -- (instructions) and checks it against the hash.

  4. 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.

  5. The client returns the result to the user.

14. References

14.1. Normative References

[MCP]
Linux Foundation, "Model Context Protocol Specification", Version 2026-07-28, , <https://modelcontextprotocol.io/specification/2026-07-28>.
[MCP-TASKS]
Linux Foundation, "MCP Tasks Extension", , <https://github.com/modelcontextprotocol/ext-tasks>.
[MOQT]
Nandakumar, S., Vasiliev, V., Swett, I., and A. Frindell, "Media over QUIC Transport", Work in Progress, Internet-Draft, draft-ietf-moq-transport-22, , <https://datatracker.ietf.org/doc/draft-ietf-moq-transport/>.
[QUIC]
Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based Multiplexed and Secure Transport", RFC 9000, DOI 10.17487/RFC9000, , <https://www.rfc-editor.org/rfc/rfc9000>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.

14.2. Informative References

[I-D.ietf-moq-secure-objects]
Jennings, C. F., Nandakumar, S., and R. Barnes, "End-to-End Secure Objects for Media over QUIC Transport", Work in Progress, Internet-Draft, draft-ietf-moq-secure-objects-01, , <https://datatracker.ietf.org/doc/html/draft-ietf-moq-secure-objects-01>.
[MoQ-C4M]
Law, W., Lemmons, C., Simon, G., and S. Nandakumar, "Authorization scheme for MOQT using Common Access Tokens", Work in Progress, Internet-Draft, draft-ietf-moq-c4m, , <https://datatracker.ietf.org/doc/draft-ietf-moq-c4m/>.
[PACE]
Nandakumar, S. and C. Jennings, "PACE: Protocol for Agent Communication Exchange", Work in Progress, Internet-Draft, draft-nandakumar-agentproto-pace, , <https://datatracker.ietf.org/doc/draft-nandakumar-agentproto-pace/>.
[POPPY]
Sierra and Meta, "Personal Agent Protocol (Poppy) Specification", Version Draft 0.3, , <https://personalagentprotocol.org/docs/0.3/spec>.
[RFC8707]
Campbell, B., Bradley, J., and H. Tschofenig, "Resource Indicators for OAuth 2.0", RFC 8707, DOI 10.17487/RFC8707, , <https://www.rfc-editor.org/rfc/rfc8707>.
[RFC9449]
Fett, D., Campbell, B., Bradley, J., Lodderstedt, T., Jones, M., and D. Waite, "OAuth 2.0 Demonstrating Proof of Possession (DPoP)", RFC 9449, DOI 10.17487/RFC9449, , <https://www.rfc-editor.org/rfc/rfc9449>.
[RFC9728]
Jones, M.B., Hunt, P., and A. Parecki, "OAuth 2.0 Protected Resource Metadata", RFC 9728, DOI 10.17487/RFC9728, , <https://www.rfc-editor.org/rfc/rfc9728>.
[WebTransport]
Kinnear, E. and V. Vasiliev, "The WebTransport Protocol Framework", Work in Progress, Internet-Draft, draft-ietf-webtrans-overview-13, , <https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview-13>.

Appendix A. Acknowledgments

This document is a revision of draft-jennings-mcp-over-moqt available at https://datatracker.ietf.org/doc/draft-jennings-mcp-over-moqt/.

Authors' Addresses

Cullen Jennings
Cisco
Ian Swett
Google
Jonathan Rosenberg
Five9
Suhas Nandakumar
Cisco