ADR-0025 — Typed progress emitters and the MrtrContext super-interface¶
- Status: Accepted
- Date: 2026-06-16
Context¶
mocapi's only progress API is McpToolContext.sendProgress(long, long).
The MCP 2026-07-28 progress notification rules are broader than it
exposes:
- "The progress value MUST increase with each notification, even if the
total is unknown." —
sendProgressenforces nothing; monotonicity is the caller's burden and a non-increasing value ships silently. - "The progress and the total values MAY be floating point." —
sendProgressis integer-only and cannot express0.5, ratios, or fractional percents. - "The message field SHOULD provide relevant human-readable progress
information." —
sendProgresshas nomessageparameter; the field is hard-codednull.
The wire model is already correct
(ProgressNotificationParams(ValueNode progressToken, double progress,
Double total, String message)); only the public surface is narrow.
Progress is also tool-only. sendProgress lives on McpToolContext;
prompt and resource handlers receive just McpElicitor
(ADR-0024) and cannot report progress. The
Streamable HTTP transport's JSON-vs-SSE choice is method-agnostic
(ADR-0004), so the sole reason
prompts/get and resources/read never stream is that they have no way
to emit a pre-final notification. The spec scopes progressToken to any
request, so progress on prompts/resources is legitimate — and the spec's
InputRequiredResult table names exactly the same three methods
(tools/call, prompts/get, resources/read) that already share the
elicitation surface.
ADR-0024 deferred this explicitly ("no progress API for
prompts/resources … revisit on demand") and chose not to mint per-kind
context types because their only member would have been elicit. Adding
a progress surface changes that calculus: there is now a second
mid-execution capability to carry, and a shared super-interface for the
three MRTR-capable contexts becomes worth its weight.
Decision¶
Replace sendProgress(long, long) with a family of stateful, type-safe
progress emitters, exposed through a new McpProgressSource factory, and
introduce an MrtrContext super-interface that unifies elicitation and
progress for the three MRTR-capable handler kinds.
// com.callibrity.mocapi.api.progress
public interface McpProgressSource {
DoubleProgressEmitter doubleProgress(Double total); // null = unknown
LongProgressEmitter longProgress(Long total); // null = unknown
CountingProgressEmitter countingProgress(Long total); // auto +1 per emit
PercentageCompleteProgressEmitter percentProgress(); // wire total = 1.0
}
public interface DoubleProgressEmitter {
void emit(double progress, String message);
void emit(double progress);
}
public interface LongProgressEmitter {
void emit(long progress, String message);
void emit(long progress);
}
public interface CountingProgressEmitter {
void emit(String message); // advances progress by one (1, 2, 3, …)
void emit();
}
public interface PercentageCompleteProgressEmitter {
void complete(double fraction, String message); // 0.0–1.0
void complete(double fraction);
}
// com.callibrity.mocapi.api.context
public interface MrtrContext extends McpElicitor, McpProgressSource {
String handlerName();
}
Rules:
McpToolContext,McpPromptContext, andMcpResourceContexteachextend MrtrContext. The leaves are empty today; they exist so each handler kind can grow type-specific members later without disturbing the others.handlerName()moves up fromMcpToolContexttoMrtrContext.sendProgress(long, long)is removed fromMcpToolContext. The emitters supersede it (they add floats, themessagefield, and the monotonic guard). This is a breaking change, taken cleanly per the pre-1.0 / 2026-07-28 posture (ADR-0019).- Each emitter captures its
totaland binds the request's progress token at creation and tracks the last-emitted value. Anulltotal means "unknown" and is omitted on the wire. - Every
progress(...)/complete(...)call must be strictly greater than the previous value or the emitter throwsIllegalArgumentException.percentProgress()additionally bounds the fraction to[0.0, 1.0]. Validation runs regardless of whether a progress token was supplied — the token gates only the network send, so a non-increasing bug surfaces the same way for every client. - The server binds an
MrtrContext(and resolvesMcpToolContext/McpPromptContext/McpResourceContextparameters) around dispatch of the three MRTR-capable methods only, reusing the seams of ADR-0021.McpElicitorparameters keep resolving as before.
Code anchors: mocapi-api/.../progress/McpProgressSource.java (+
DoubleProgressEmitter, LongProgressEmitter, CountingProgressEmitter,
PercentageCompleteProgressEmitter), mocapi-api/.../context/MrtrContext.java,
mocapi-api/.../tools/McpToolContext.java,
mocapi-api/.../prompts/McpPromptContext.java,
mocapi-api/.../resources/McpResourceContext.java,
mocapi-server/.../progress/DefaultMcpProgressSource.java (+
ProgressChannel and the four emitter impls),
mocapi-server/.../context/AbstractMrtrContext.java,
mocapi-server/.../tools/DefaultMcpToolContext.java,
mocapi-server/.../prompts/DefaultMcpPromptContext.java,
mocapi-server/.../resources/DefaultMcpResourceContext.java, and the
McpPromptContextResolver / McpResourceContextResolver. Full design and
implementation plan:
docs/plans/2026-06-16-progress-emitters-design.md.
Consequences¶
What this buys us. The progress API matches what the spec permits:
floating-point progress/total, the human-readable message, and a
monotonic-increase guarantee the type system enforces by construction.
Prompts and resources gain progress — and, because the transport is
method-agnostic, SSE streaming — for free. MrtrContext turns the
spec's "these three methods only" boundary into a compile-time fact and
gives the two mid-execution capabilities (elicit, progress) one home.
Costs. A breaking change: every ctx.sendProgress(p, t) call site
must migrate to ctx.longProgress(t).emit(p) (mechanical). The API
surface grows by one factory interface, three emitter interfaces, one
super-interface, and two leaf context interfaces. Progress now shares the
MRTR idempotency hazard: a replayed handler re-emits the progress it
produced before its last elicit(...) (documented, ADR-0021).
Non-goals. No per-call total (it is fixed at emitter creation; make
a new emitter if it changes). No sampling/roots progress equivalents
(removed features, ADR-0022).
No change to the transport, the MRTR engine, or the elicitation schema.
Supersedes the progress non-goal of
ADR-0024, which deferred a prompt/resource
progress API and declined per-kind context types. Both are now adopted;
ADR-0024's elicitation decision otherwise stands, with McpElicitor
becoming a super-interface of MrtrContext.