juneau-bean-mcp-v20250618
The juneau-bean-mcp-v20250618 module provides Java beans modelling the Model Context Protocol (MCP) 2025-06-18 wire format. It's paired with a separate, revision-neutral module, juneau-bean-jsonrpc, which carries the underlying JSON-RPC 2.0 envelope, and with a 2026-07-28 sibling, juneau-bean-mcp-v20260728. See Choosing your MCP setup for why the two revisions are separate adapter modules and why they're maintained differently going forward.
Overview
MCP is a JSON-RPC 2.0 protocol used by AI assistants and agents to interact with external tools, prompts, and resources. Two bean modules cover the wire format:
- juneau-bean-jsonrpc (package
org.apache.juneau.bean.jsonrpc) — the revision-neutral JSON-RPC 2.0 envelope (JsonRpcRequest,JsonRpcResponse,JsonRpcError,McpException). It carries no MCP-specific knowledge and depends only onjuneau-marshall. See juneau-bean-jsonrpc for full coverage. juneau-bean-mcp-v20250618(packageorg.apache.juneau.bean.mcp.v20250618) — the MCP2025-06-18wire beans covered by this page: tool/prompt/resource descriptors, capability beans, and the polymorphic content types. Renamed from the earlierjuneau-bean-mcp/org.apache.juneau.bean.mcpas part of a re-layering that split the JSON-RPC envelope out into its own module — the name change leaves room for a future sibling module under the sameorg.apache.juneau.bean.mcpnamespace if a later MCP protocol revision needs one. This module also depends only onjuneau-marshall.
Both modules ship Juneau @Marshalled-annotated POJOs, so you can build MCP servers and clients using any Juneau serializer/parser (JSON, JSON5, MessagePack, CBOR, YAML, and more).
Neither module ships transport or dispatch logic — no HTTP plumbing. For a stateless JSON-RPC endpoint built on juneau-rest-server and these beans, see juneau-rest-server-mcp, which is itself split into a revision-neutral core plus a 2025-06-18 adapter.
Key Features
- Round-trip serialization — Every bean parses back from its serialized form into an identical object across JSON, JSON5, MessagePack, CBOR, YAML, and other Juneau formats.
- Polymorphic content blocks —
ContentandResourceContentsuse@Marshalled(typePropertyName="type", dictionary={...})to discriminate subtype on the wire. - Fluent setters — Every bean uses
setX(...)returningthisfor ergonomic construction. - No external dependencies — Both modules depend only on
juneau-marshall(juneau-bean-mcp-v20250618additionally has a test-scope dependency onjuneau-bean-jsonrpc, to exercise the envelope types its own round-trip tests still reference). - Zero protocol logic — Pure DTOs; no validation, no state machines, no dispatch.
Coverage
The JSON-RPC 2.0 envelope beans (JsonRpcRequest, JsonRpcResponse, JsonRpcError, McpException) that carry MCP traffic on the wire live in the separate juneau-bean-jsonrpc module — see that page for full coverage of the envelope, including the JsonRpcResponse static factories and error-mapping helpers.
Initialization & capabilities
| Bean | Purpose |
|---|---|
InitializeRequest / InitializeResult | Handshake request/response. |
Implementation | Server / client identity (name, version). |
ClientCapabilities / ServerCapabilities | Capability advertisement structures. |
ToolCapability / PromptCapability / ResourceCapability | Per-feature capability beans. |
LoggingCapability / RootsCapability | Optional capabilities. |
Tools
| Bean | Purpose |
|---|---|
Tool | Tool descriptor: name, description, inputSchema, outputSchema. |
ListToolsResult | Result of tools/list. |
CallToolRequest / CallToolResult | tools/call request/response. CallToolResult carries content, isError, structuredContent (JSON object in this revision). |
JsonSchema | Six-keyword schema bean (type, properties, required, additionalProperties, items, $defs) used by both Tool.inputSchema and Tool.outputSchema. |
Prompts
| Bean | Purpose |
|---|---|
Prompt | Prompt descriptor. |
PromptArgument | Declared prompt argument. |
ListPromptsResult | Result of prompts/list. |
GetPromptRequest / GetPromptResult | prompts/get request/response. |
PromptMessage | A rendered prompt message. |
Role | Enum of user, assistant, system, tool. |
Resources
| Bean | Purpose |
|---|---|
Resource | Resource descriptor (uri, name, title, description, mimeType, size). |
ListResourcesResult | Result of resources/list. |
ReadResourceRequest / ReadResourceResult | resources/read request/response. |
ResourceContents | Polymorphic interface for the body of a resource. |
TextResourceContents (type: "resourceText") | UTF-8 text body. |
BlobResourceContents (type: "resourceBlob") | Base64-encoded binary body. |
ResourceTemplate | Resource-template descriptor: uriTemplate, name, title, description, mimeType. Backfilled in 10.0.0 — see below. |
ListResourceTemplatesResult | Result of resources/templates/list: resourceTemplates, nextCursor. Backfilled in 10.0.0 — see below. |
resources/templates/list backfill (10.0.0). This revision originally shipped with no resource-template beans or list method at all — 2026-07-28 had them from the start (see the sibling module section below). 10.0.0 closes that gap: ResourceTemplate and ListResourceTemplatesResult have the same five/two-field shape as their 2026-07-28 counterparts, but ListResourceTemplatesResult carries no cache fields (cache hints are a 2026-07-28-only concept). McpMethods.RESOURCES_TEMPLATES_LIST ("resources/templates/list") is the new method-name constant. A resource template itself is only listed by this bean — making it readable through ordinary resources/read is a juneau-rest-server-mcp handler-registration concern; see juneau-rest-server-mcp for the server side.
Polymorphic content blocks
Content is the discriminated interface returned in tool results and prompt messages. Subtypes:
| Bean | Discriminator |
|---|---|
TextContent | type: "text" |
ImageContent | type: "image" |
EmbeddedResourceContent | type: "resource" |
Completions (completion/complete, new in 10.0.0)
Both dated bean modules — this one and juneau-bean-mcp-v20260728 — gained an equivalent, independently-typed completion wire-bean family in 10.0.0. There's no shared completion module: each revision has its own copy of these nine simple names, following the same duplication-over-abstraction convention as the rest of this dated-bean pair.
| Bean | Purpose |
|---|---|
CompleteRequest | Parameters for completion/complete: ref, argument, optional context. |
CompletionReference | Closed polymorphic interface for the completion target. Exactly two subtypes — no fallback/Map<String,Object> carrier. |
PromptReference (type: "ref/prompt") | Targets a declared prompt by name. |
ResourceTemplateReference (type: "ref/resource") | Targets a registered resource template by its exact uri — the registered template string (e.g. file:///{name}), not a concrete expanded URI. |
CompletionArgument | The argument/variable being completed: name, value (the current partial value). |
CompletionContext | Optional already-resolved values: arguments (Map<String,String>). |
CompleteResult | Result payload: completion. |
Completion | values (List<String>, at most 100), optional total, optional hasMore. A lossless wire carrier — it does not itself enforce the 100-value cap; that's a juneau-rest-server-mcp dispatch responsibility. |
CompletionCapability | Empty marker bean advertised as ServerCapabilities.completions when at least one completer is registered. Serializes as {}. |
CompletionReference's two subtypes use the same @Marshalled(typePropertyName="type", dictionary={...}) discriminator pattern as Content/ResourceContents above — enable addBeanTypes to serialize the type field:
import org.apache.juneau.bean.mcp.v20250618.*;
import org.apache.juneau.marshall.json.*;
JsonSerializer ser = JsonSerializer.create().addBeanTypes().build();
CompleteRequest req = new CompleteRequest()
.setRef(new ResourceTemplateReference().setUri("file:///{name}"))
.setArgument(new CompletionArgument().setName("name").setValue("re"));
String wire = ser.write(req);
// {"ref":{"type":"ref/resource","uri":"file:///{name}"},"argument":{"name":"name","value":"re"}}
A successful-but-empty completion (unknown/uncompletable target — not an error) is {"completion":{"values":[]}}.
Prompt.arguments and PromptArgument (name, description, required — see Prompts above) are unchanged by this addition. A prompt argument may be paired with a completer on the server side, but that association is server behavior only and is never reflected as a new field on the wire PromptArgument bean.
Constants
McpMethods— JSON-RPC method names:INITIALIZE,PING,TOOLS_LIST,TOOLS_CALL,PROMPTS_LIST,PROMPTS_GET,RESOURCES_LIST,RESOURCES_READ,RESOURCES_TEMPLATES_LIST,COMPLETION_COMPLETE.McpProtocol— Protocol literals:JSON_RPC_2_0("2.0") andVERSION_2025_06_18.
Polymorphic Serialization
Content and ResourceContents declare @Marshalled(typePropertyName="type", dictionary={...}), so the parser automatically discovers the right subtype when reading. For serialization, enable addBeanTypes on the serializer so the type discriminator is actually written out:
import org.apache.juneau.bean.mcp.v20250618.*;
import org.apache.juneau.marshall.json.*;
JsonSerializer ser = JsonSerializer.create().addBeanTypes().build();
CallToolResult result = new CallToolResult().setContent(List.of(
new TextContent().setText("hello")
));
String wire = ser.write(result);
// {"content":[{"type":"text","text":"hello"}]}
CallToolResult back = JsonParser.DEFAULT.read(wire, CallToolResult.class);
When MCP responses are sent through juneau-rest-server-mcp, the servlet sets addBeanTypes="true" for you via @SerializerConfig.
Basic Usage
Building a JSON-RPC request
JsonRpcRequest lives in the neutral juneau-bean-jsonrpc module; McpMethods/McpProtocol stay in this module, since they're MCP-specific:
import org.apache.juneau.bean.jsonrpc.*;
import org.apache.juneau.bean.mcp.v20250618.*;
import org.apache.juneau.marshall.collections.*;
import org.apache.juneau.marshall.json.*;
JsonRpcRequest req = new JsonRpcRequest()
.setJsonrpc(McpProtocol.JSON_RPC_2_0)
.setId(1)
.setMethod(McpMethods.TOOLS_CALL)
.setParams(JsonMap.of(
"name", "echo",
"arguments", JsonMap.of("text", "hello")
));
String wire = JsonSerializer.DEFAULT.write(req);
JsonRpcRequest back = JsonParser.DEFAULT.read(wire, JsonRpcRequest.class);
Building a tool descriptor
Tool echo = new Tool()
.setName("echo")
.setDescription("Echoes the input text back.")
.setInputSchema(new JsonSchema()
.setType("object")
.setProperties(Map.of(
"text", new JsonSchema().setType("string")
))
.setRequired(List.of("text"))
);
ListToolsResult listed = new ListToolsResult().setTools(List.of(echo));
Building a tool with structured output
MCP 2025-06-18 introduced structured tool output (Tool.outputSchema, CallToolResult.structuredContent), constrained to object-shaped schemas and a JSON-object structuredContent value; MCP 2026-07-28 broadens this to arbitrary JSON Schema Draft 2020-12 shapes and any JSON value.
Tool weather = new Tool()
.setName("weather")
.setDescription("Looks up the current weather for a city.")
.setOutputSchema(new JsonSchema()
.setType("object")
.setProperties(Map.of(
"summary", new JsonSchema().setType("string"),
"temp", new JsonSchema().setType("number")
))
);
CallToolResult result = new CallToolResult()
.setStructuredContent(JsonMap.of("summary", "Sunny", "temp", 23.5));
Building a tool result with mixed content
CallToolResult result = new CallToolResult().setContent(List.of(
new TextContent().setText("Operation complete."),
new ImageContent()
.setMimeType("image/png")
.setData("iVBORw0KGgoAAAANSUhEUgAA...")
));
JsonSerializer ser = JsonSerializer.create().addBeanTypes().build();
String wire = ser.write(result);
// {"content":[{"type":"text","text":"Operation complete."},{"type":"image","mimeType":"image/png","data":"..."}]}
Building a resource read result
ReadResourceResult readResult = new ReadResourceResult().setContents(List.of(
new TextResourceContents()
.setUri("file://readme.md")
.setMimeType("text/markdown")
.setText("# Hello\n\nReadme contents."),
new BlobResourceContents()
.setUri("file://logo.png")
.setMimeType("image/png")
.setBlob("iVBORw0KGgoAAAANSUhEUgAA...")
));
JsonSerializer ser = JsonSerializer.create().addBeanTypes().build();
String wire = ser.write(readResult);
// contents[0].type = "resourceText", contents[1].type = "resourceBlob"
Mapping handler errors to JSON-RPC errors
McpException and the JsonRpcResponse error factories live in juneau-bean-jsonrpc — see that page for how to convert a thrown McpException into a JsonRpcError/JsonRpcResponse.
Multiple Wire Formats
Because the beans are pure POJOs with @Marshalled annotations, every Juneau serializer/parser works:
import org.apache.juneau.marshall.cbor.*;
import org.apache.juneau.marshall.marshaller.*;
import org.apache.juneau.marshall.msgpack.*;
CallToolResult r = new CallToolResult().setContent(List.of(new TextContent().setText("hi")));
byte[] cbor = Cbor.of(r);
byte[] msgpack = MsgPackSerializer.DEFAULT.write(r);
String yaml = Yaml.of(r);
Set addBeanTypes and the discriminator property names on the corresponding builders to round-trip polymorphic content correctly.
MCP 2026-07-28: Cache Hints, Resource Templates
The sibling juneau-bean-mcp-v20260728 module (package org.apache.juneau.bean.mcp.v20260728) adds SEP-2549 cache hints and a resources/templates/list result to its own revision of the wire beans covered above. Both are opt-in, top-level, nullable fields/beans — omitted entirely from the wire when unset.
| Bean | Purpose |
|---|---|
McpCacheScope | Enum with exactly two wire values: public, private. |
CacheableResult<T> | Abstract CRTP base contributing inherited, nullable, top-level ttlMs (Integer) and cacheScope (McpCacheScope) properties to a result bean. ListToolsResult, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, and ReadResourceResult all extend it. |
ResourceTemplate | Resource-template descriptor: uriTemplate, name, title, description, mimeType. |
ListResourceTemplatesResult | Result of resources/templates/list: resourceTemplates, nextCursor, plus the inherited cache fields. |
A compact example showing both features on the same result:
{"resourceTemplates":[{"uriTemplate":"file:///{name}","name":"files"}],"ttlMs":0,"cacheScope":"private"}
ttlMs: 0 means the result is immediately stale (a valid, explicit hint — distinct from omitting the field entirely). An absent cacheScope is interpreted by a consumer as public; Juneau never writes a default scope onto the wire — the property is only serialized when explicitly set. These fields describe the whole result, not individual list items.
McpMethods.RESOURCES_TEMPLATES_LIST ("resources/templates/list") is the JSON-RPC method name for the new endpoint. See juneau-rest-server-mcp for how a server configures cache hints and registers resource templates.
2026-07-28 completions (completion/complete, new in 10.0.0)
juneau-bean-mcp-v20260728 gained the same nine-bean completion family described above for 2025-06-18 — CompleteRequest, CompletionReference (with PromptReference/ResourceTemplateReference subtypes, ref/prompt/ref/resource discriminators), CompletionArgument, CompletionContext, CompleteResult, Completion, and CompletionCapability — as an independent copy in org.apache.juneau.bean.mcp.v20260728, with McpMethods.COMPLETION_COMPLETE ("completion/complete"). Field shapes, discriminators, and the 100-value/hasMore cap convention are identical between the two revisions. Like the other three concrete 2026-07-28 params beans, CompleteRequest extends the shared RequestParams<T> base (see "Metadata and Trace-Context" below), so it carries the same per-request _meta every 2026-07-28 request carries under params._meta — there is no separate envelope-level _meta to duplicate; the neutral JSON-RPC envelope itself has none (see juneau-bean-jsonrpc). See juneau-rest-server-mcp for how a server registers completers and dispatches completion/complete on both revisions.
MCP 2026-07-28 Metadata and Trace-Context (_meta)
10.0.0 also brings juneau-bean-mcp-v20260728's per-request/per-result metadata into exact alignment with the MCP 2026-07-28 schema. This is a correction to the wire shape (an unreleased-10.0.0 fix, not a compatibility mode — there's no old-shape fallback) and is 2026-07-28-only; 2025-06-18 has no _meta concept and is untouched by it.
The neutral JSON-RPC envelope beans (JsonRpcRequest/JsonRpcResponse in juneau-bean-jsonrpc) carry no _meta property. Per-request metadata instead lives one level down, under params._meta; per-result metadata lives under result._meta.
| Bean | Purpose |
|---|---|
RequestParams<T> | Abstract CRTP base for the four concrete request-params beans (CallToolRequest, GetPromptRequest, ReadResourceRequest, CompleteRequest), contributing the optional _meta (RequestMeta) property. |
RequestParamsOnly | Concrete params bean for the methods whose only member is _meta — */list, ping, server/discover. |
RequestMeta | Per-request metadata: required io.modelcontextprotocol/protocolVersion, optional io.modelcontextprotocol/clientInfo (an Implementation), required io.modelcontextprotocol/clientCapabilities, deprecated-but-accepted io.modelcontextprotocol/logLevel, and bare (unprefixed) W3C trace-context keys traceparent/tracestate/baggage. Declares the wire-key names as public static final String constants (KEY_PROTOCOL_VERSION, KEY_CLIENT_INFO, KEY_CLIENT_CAPABILITIES, KEY_LOG_LEVEL, KEY_TRACEPARENT, KEY_TRACESTATE, KEY_BAGGAGE). Unrecognized _meta members round-trip losslessly through an extension map. |
Result<T> | Abstract CRTP base for every successful result, contributing resultType (an open string; defaults to "complete") and optional _meta (ResultMeta). CacheableResult<T> (see above) extends it, so the five cacheable results inherit it transitively; CallToolResult, GetPromptResult, CompleteResult, PingResult, and ServerDiscoverResult all extend it directly or transitively. |
ResultMeta | Per-result metadata: io.modelcontextprotocol/serverInfo (an Implementation, the server's own identity) plus bare traceparent/tracestate/baggage, populated when an active tracer echoes the request's trace context back to the caller. Declares KEY_SERVER_INFO as its wire-key constant; same extension-map shape as RequestMeta. |
PingResult | Typed, empty success result for ping (previously an untyped JsonMap). |
Icon | One entry of Implementation.icons: required src, optional mimeType, ordered sizes, and theme (Icon.Theme: light/dark). |
Implementation gains optional title, description, and ordered icons (List<Icon>) alongside its existing name/version. ClientCapabilities and ServerCapabilities both gain a schema-defined extensions map (with a put*Extensions(name, value) convenience setter on each) — distinct from the pre-existing free-form experimental bag, which stays client-side only.
ServerDiscoverResult (the server/discover result) now extends CacheableResult<ServerDiscoverResult>, inheriting resultType/_meta/cache hints, and adds required supportedVersions (["2026-07-28"]), capabilities, and optional instructions. Server identity moved off a top-level serverInfo field onto the inherited _meta (ResultMeta.serverInfo) — the same place every other result now carries it.
A representative tools/call round trip showing the full _meta shape on both sides:
// Request
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","arguments":{"text":"hello"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"my-client","version":"1.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}
// Response
{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"my-server","version":"1.0.0"}},"content":[{"type":"text","text":"hello"}]}}
Trace-context propagation (the traceparent/tracestate/baggage keys above) is a REST-server-adapter behavior, not a bean-level one — see Trace-Context Propagation on the juneau-rest-server-mcp topic and Observability for the full mechanism.
MCP 2026-07-28: Multi-Round-Trip Requests
juneau-bean-mcp-v20260728 adds the wire beans for MCP 2026-07-28 SEP-2322 Multi-Round-Trip Requests: a tools/call, prompts/get, or resources/read handler can pause to ask the caller for more input, and the caller resumes by re-sending the same request with its answers. These beans model only the wire shape — the server-side pause/resume dispatch, the sealing of requestState, and capability gating are juneau-rest-server-mcp-v20260728 concerns, covered in juneau-rest-server-mcp. None of these beans exist in the 2025-06-18 revision; this is a 2026-07-28-only, additive addition.
Pause result
| Bean | Purpose |
|---|---|
InputRequiredResult | A Result<InputRequiredResult> subtype whose constructor pins resultType to "input_required". Carries a server-assigned-id-keyed inputRequests (Map<String,JsonMap>) and/or an opaque requestState (String). Its validate() method enforces the pinned-schema invariant that at least one of the two is present. |
inputRequests values are free-form and lossless: each entry is a raw JsonMap sub-request carried to the wire byte-for-byte, with no synthetic {type, payload} envelope. The concrete sub-request shapes are owned by their eventual consumers — elicitation's shape (ElicitRequest) is covered below in MCP 2026-07-28: Elicitation; sampling's is a separate, fully-supported mechanism (see MCP Sampling further down). There is deliberately no dedicated per-entry bean here in InputRequiredResult itself; the map value is a bare JsonMap, mirroring the equally free-form request-side inputResponses. requestState is opaque ciphertext to a client — meaningful only to the server codec that sealed it — and must be echoed back verbatim on the resume call.
Resume fields
CallToolRequest, GetPromptRequest, and ReadResourceRequest each gain two optional, nullable fields for the resume leg:
| Field | Type | Purpose |
|---|---|---|
requestState | String | The opaque token echoed back from a prior InputRequiredResult, identifying the paused operation to the server. |
inputResponses | Map<String,Object> | The caller's collected answers, keyed by the same ids the pause's inputRequests used. Free-form, mirroring inputRequests. |
Both are omitted from the wire entirely when unset, so an ordinary one-shot call is byte-identical to before this addition.
Capability
| Bean | Purpose |
|---|---|
ElicitationCapability | An empty marker bean (serializes as {}), exposed as the new nullable elicitation field on ClientCapabilities (getElicitation()/setElicitation(...)), matching SamplingCapability's presence-checkable shape. |
There is no dedicated input_required capability flag. Gating reuses the existing elicitation client capability: a client that advertises clientCapabilities.elicitation is signalling it can handle an input_required pause. This is a deliberate divergence from a separate per-feature flag — the pinned 2026-07-28 schema defines no such flag, so none is modelled here.
MCP 2026-07-28: Elicitation
juneau-bean-mcp-v20260728 adds the wire beans for MCP 2026-07-28 SEP-2322 elicitation: a handler can ask the end user a schema-shaped question mid-call, riding the same Multi-Round-Trip Requests pause/resume loop described above with zero changes to that mechanism. These beans model only the wire shape and the schema-building helper — the server-side placement/parsing helpers are a juneau-rest-server-mcp-v20260728 concern, and the client-side typed accessors are a juneau-rest-client-mcp-v20260728 concern; see juneau-rest-server-mcp and juneau-rest-client-mcp.
| Bean | Purpose |
|---|---|
ElicitRequest | One question: message (the prompt shown to the end user) and requestedSchema (a restricted JSON Schema JsonMap built via ElicitSchema, below). Placed into an input_required pause's inputRequests map under a server-assigned id. |
ElicitResult | The end user's answer: action (ElicitAction) and content (a dynamic Map<String,Object>, present only when action is ACCEPT — its shape is whatever requestedSchema described, so it is deliberately left untyped). Echoed back keyed by the same id in the resume call's inputResponses. |
ElicitAction | A closed three-value wire enum: accept / decline / cancel. |
ElicitSchema: the restricted-schema builder
Elicitation's requested-answer schema is deliberately restricted to primitive-typed, non-nested top-level properties — no objects, arrays, or $ref. ElicitSchema enforces this by construction: there is no objectField/arrayField method at all, so a nested schema is a compile-time impossibility, not a runtime-rejected one.
JsonMap schema = ElicitSchema.create()
.stringField("email").title("Email").format("email").minLength(3).maxLength(120)
.integerField("age").min(0).max(150)
.enumField("plan", "free", "pro").enumNames("Free", "Pro")
.required("email", "plan")
.build();
// {"type":"object","properties":{...},"required":["email","plan"]}
Each xxxField(name) call (stringField/numberField/integerField/booleanField/enumField) starts a new property and becomes the target of every subsequent per-field modifier (title, description, format, minLength/maxLength — string fields only — min/max, defaultValue, enumNames) until the next xxxField(...) call. required(String...) is different: it's a builder-level, not per-field, call — like every other modifier it returns this, so chaining continues normally — that marks one or more field names required, contributing a top-level "required":[...] array (in call order) to the built schema. It may be called more than once (accumulating names each time) and in any order relative to the xxxField(...) calls that add those names — a name may be marked required before the field it names even exists, and a later redeclaration of that field does not discard the marking — since an unknown name is validated only at build() time. build() validates structurally-invalid combinations — format/minLength/maxLength on a non-string field, minLength/maxLength on an enumField (a closed-choice field, where a length constraint is meaningless), a negative or inverted minLength/maxLength pair, min/max on a non-numeric field, enumNames with no matching enum, a required(...) name that was never added as a field, or an empty schema with no fields at all — and throws IllegalStateException immediately, rather than deferring the error to a wire-level schema-validation failure discovered by the client much later. Argument-level problems — a null/blank field name (including on enumField), a null title/description/format/min/max, null/empty enumField values or a null value among them, null/empty/length-mismatched enumNames or a null name among them, or null/empty required(...) names or a null name among them — are instead rejected immediately as IllegalArgumentException, at the modifier call itself.
MCP Sampling
MCP SEP-2577 deprecates the Sampling capability (along with Roots and Logging) at the protocol level in favor of newer interaction patterns — elicitation (above) and the Multi-Round-Trip Requests loop it rides are this codebase's newer, actively-recommended mechanism for interactive mid-call input. Juneau continues to fully support Sampling, though: juneau-bean-mcp-v20260728 ships full-fidelity typed beans for sampling/createMessage, since the beans are useful today wherever a caller needs to hand-drive a sampling exchange over the existing duplex seam (see juneau-rest-client-mcp) — for example a test harness, or a client author who wants typed decode/encode without the framework performing any dispatch itself. None of these beans extend RequestParams<T>/Result<T>/CacheableResult<T>: sampling/createMessage is a server-to-client duplex request that never touches McpRevision.dispatch(...), so it carries no _meta/resultType envelope.
| Bean | Purpose |
|---|---|
CreateMessageRequest | sampling/createMessage's parameters: messages (List<SamplingMessage>), modelPreferences, systemPrompt, includeContext, temperature, maxTokens, stopSequences, metadata. |
CreateMessageResult | The result: role (Role), content (Content), model, stopReason. |
SamplingMessage | One conversation turn: role (Role, reusing the existing enum) and content (Content — a single block, not a list). |
ModelPreferences | Model-selection hints: hints (List<ModelHint>), costPriority/speedPriority/intelligencePriority (each Double, 0.0-1.0). |
ModelHint | A single fuzzy model-family/name hint (name). |
SamplingCapability | An empty marker bean (serializes as {}), the typed value of ClientCapabilities.getSampling()/setSampling(...) (replacing the prior opaque Map<String,Object>). |
A hand-driven sampling exchange needs no framework changes — build a JsonRpcRequest with method = McpMethods.SAMPLING_CREATE_MESSAGE and params set to a serialized CreateMessageRequest, dispatch it via the existing McpDuplexDispatcher/McpServerRequestHandler seam, and decode the handler's typed CreateMessageResult return value. See juneau-rest-client-mcp for the duplex mechanism itself, which is entirely unchanged by this addition.
Content gains AudioContent
Content's polymorphic dictionary (TextContent/ImageContent/EmbeddedResourceContent) gains a fourth member, AudioContent (type: "audio", data/mimeType, mirroring ImageContent's exact shape) — completing genuine text/image/audio/resource fidelity for SamplingMessage.content/CreateMessageResult.content against the real MCP sampling schema. Like the other three members of the Content dictionary, AudioContent is general-purpose Content vocabulary usable anywhere a Content is accepted, not only in sampling contexts — merely completed here because sampling was the first feature in this codebase to need it for real wire fidelity.
SEP-2577 and the Roots/Logging capabilities
SEP-2577 groups three capabilities together — Sampling, Roots, and Logging — but Sampling is the new surface introduced above; only the other two were already shipping. RootsCapability (and ClientCapabilities.getRoots()/setRoots(...)) and LoggingCapability (and ServerCapabilities.getLogging()/setLogging(...)) remain ordinary, fully-supported beans — SEP-2577 deprecates these capabilities at the protocol level, but Juneau does not mark the corresponding Java types/members @Deprecated, since neither the MCP spec nor this project has scheduled their removal.
Related Modules
- juneau-bean-jsonrpc — The revision-neutral JSON-RPC 2.0 envelope beans that carry MCP traffic on the wire.
- juneau-rest-server-mcp — The revision-neutral MCP REST-server core plus the
2025-06-18adapter that builds an MCP JSON-RPC HTTP endpoint on top of these beans.