This is an automated email from the ASF dual-hosted git repository. jamesbognar pushed a commit to branch docs in repository https://gitbox.apache.org/repos/asf/juneau.git
commit 157a4a5ccd13a36033d98ae7b67e838d566b539d Author: james.bognar <[email protected]> AuthorDate: Sun May 3 08:18:56 2026 -0400 Doc updates. --- pages/release-notes/9.2.1.md | 157 +++++++++++++- pages/topics/04.08.JuneauBeanMcp.md | 225 ++++++++++++++++++++ pages/topics/10.03.06.LifecycleHooks.md | 9 +- pages/topics/10.04.03.JavaMethodParameters.md | 7 +- pages/topics/10.04.04.JavaMethodReturnTypes.md | 7 +- pages/topics/10.06.Marshalling.md | 24 +-- pages/topics/10.12.ConfigurationFiles.md | 18 +- pages/topics/10.13.SvlVariables.md | 23 +- pages/topics/10.16.08.SwaggerModels.md | 16 +- pages/topics/10.23.RestContext.md | 70 ++---- pages/topics/10.24.RestOpContext.md | 73 ++----- pages/topics/10a.01.JuneauRestServerMcpBasics.md | 257 +++++++++++++++++++++++ pages/topics/23.01.V9.5-migration-guide.md | 34 +++ sidebars.ts | 29 +++ 14 files changed, 796 insertions(+), 153 deletions(-) diff --git a/pages/release-notes/9.2.1.md b/pages/release-notes/9.2.1.md index 01be36dfd5..e8b399ead9 100644 --- a/pages/release-notes/9.2.1.md +++ b/pages/release-notes/9.2.1.md @@ -6,7 +6,7 @@ title: "Release 9.2.1" **Date:** TBD -Juneau 9.2.1 is a minor release with native TOML and YAML support, BSON (Binary JSON) support for MongoDB-interoperable binary serialization, CBOR (Concise Binary Object Representation) per RFC 8949 for IoT and constrained environments, full CSV serializer/parser support, JCS (JSON Canonicalization Scheme) per RFC 8785 for deterministic hashing and signing, RDF/THRIFT and RDF/PROTO binary format support, native serialization support for lazy-evaluated sequence types, large-dataset stream [...] +Juneau 9.2.1 is a minor release with native TOML and YAML support, BSON (Binary JSON) support for MongoDB-interoperable binary serialization, CBOR (Concise Binary Object Representation) per RFC 8949 for IoT and constrained environments, full CSV serializer/parser support, JCS (JSON Canonicalization Scheme) per RFC 8785 for deterministic hashing and signing, RDF/THRIFT and RDF/PROTO binary format support, native serialization support for lazy-evaluated sequence types, large-dataset stream [...] ### juneau-marshall @@ -866,6 +866,34 @@ The legacy `ParamInfo` type in the Swagger v2 module has been fully replaced wit ### juneau-rest-common +#### HTTP Annotations Moved from `juneau-marshall` + +The HTTP parameter annotations (`@Header`, `@Query`, `@Path`, `@Content`, `@FormData`, `@Request`, `@Response`, `@StatusCode`, `@PathRemainder`, `@HasQuery`, `@HasFormData`, `@Contact`, `@License`, `@Tag`) along with the constant classes (`CollectionFormatType`, `FormatType`, `ParameterType`) and the `httppart.bean` package (`RequestBeanMeta`, `RequestBeanPropertyMeta`, `ResponseBeanMeta`, `ResponseBeanPropertyMeta`, `MethodInfoUtils`) have been moved from `juneau-marshall` to `juneau-re [...] + +**Package names are unchanged** (`org.apache.juneau.http.annotation`, `org.apache.juneau.httppart.bean`), so import statements do not need updating. Maven resolves the classes from the new module location automatically. Existing compiled code will need recompilation. + +##### Removed Features + +- **`serializer()` and `parser()` attributes** removed from `@Query`, `@Header`, `@FormData`, `@Path`, `@PathRemainder`, `@Request`, `@Response`. Use the new `@HttpPartMarshalling` annotation (in `juneau-marshall`) to specify custom `HttpPartSerializer`/`HttpPartParser` classes. +- **`@Repeatable`** removed from all HTTP annotations. Dynamic annotation stacking via `on`/`onClass` is no longer supported. +- **`on()` and `onClass()` attributes** removed from all HTTP annotations. +- **`@ContextApply`** removed from all HTTP annotations. +- **14 XAnnotation companion classes** deleted (`ContentAnnotation`, `FormDataAnnotation`, `HasFormDataAnnotation`, `HasQueryAnnotation`, `HeaderAnnotation`, `PathAnnotation`, `PathRemainderAnnotation`, `QueryAnnotation`, `RequestAnnotation`, `StatusCodeAnnotation`). `ContactAnnotation`, `LicenseAnnotation`, `TagAnnotation`, and `ResponseAnnotation` remain for Swagger generation utilities. + +##### NG Duplicate Annotations Retired + +The next-generation duplicate annotations `org.apache.juneau.ng.http.remote.Body`, `Header`, `Path`, and `Query` have been deleted. `NgRemoteClient` now uses the standard `@Content`, `@Header`, `@Path`, and `@Query` annotations from `org.apache.juneau.http.annotation`. + +##### New `@HttpPartMarshalling` Annotation + +A new `@HttpPartMarshalling` annotation in `juneau-marshall` (`org.apache.juneau.httppart`) replaces the `serializer()` and `parser()` attributes that were removed from HTTP annotations. This decouples the HTTP annotations from `HttpPartSerializer`/`HttpPartParser` types. + +```java +@HttpPartMarshalling(serializer=UonSerializer.class, parser=UonParser.class) +@Query("name") +String name +``` + #### `RestSharedConstants` - **`RestSharedConstants`** — Home for static literals shared across REST modules (server, client, mock) so wire names and similar strings stay aligned. Serializer/parser session option wire names are **`HEADER_JuneauSerializerOptions`**, **`HEADER_JuneauParserOptions`**, **`QUERY_juneauSerializerOptions`**, and **`QUERY_juneauParserOptions`**. Add more `public static final` fields when other cross-module constants are needed. (This supersedes the earlier `RestSessionOptionWire` type.) @@ -977,6 +1005,20 @@ Programmatic **`RestContext.Builder`** / **`RestOpContext.Builder`** no longer e Generated Swagger (OpenAPI 2) for REST operations now includes optional documentation parameters for Juneau serializer/parser session options on the wire: headers `X-Juneau-Serializer-Options` and `X-Juneau-Parser-Options` (JSON5 object strings) and query parameters `juneauSerializerOptions` / `juneauParserOptions` (UON-encoded maps). Canonical names are the `HEADER_*` / `QUERY_*` fields on `RestSharedConstants` in **juneau-rest-common**. Existing explicit parameters with the same `in` + [...] +#### `RestContext.Builder` and `RestOpContext.Builder` removed + +`RestContext.Builder` and `RestOpContext.Builder` are no longer part of the public API. All resource-level and operation-level configuration that was previously expressed through fluent builder calls now flows exclusively through `@Rest(...)` / `@RestOp(...)` annotation attributes or `@RestInject`-annotated methods and fields. + +Key changes: + +- **`RestContext.Builder`** — entirely removed from public surface; the framework no longer publishes a builder instance to `@RestInit` hooks or the resource constructor. Replace each `builder.xxx(...)` call with the equivalent `@Rest(xxx=...)` attribute or an `@RestInject(name="xxx")`-annotated bean-supplier method. +- **`RestOpContext.Builder`** — same removal. Replace `builder.guards(...)`, `builder.converters(...)`, etc. with `@RestOp(guards=...)`, `@RestOp(converters=...)`, etc. +- **`RestContextInit` record** replaces the old `RestContext.create(resourceClass, parent, cfg).init(supplier).path(p).children(c).build()` factory chain. The six bootstrap fields (resource class, parent context, servlet config, resource supplier, path, children) plus an optional `Consumer<BasicBeanStore>` hook are bundled in one immutable record. The common top-level case becomes `new RestContext(new RestContextInit(MyResource.class, () -> new MyResource()))`. +- **`@RestInit(RestContext.Builder b)`** / **`@RestInit(RestOpContext.Builder b)`** injection hooks — removed. The supported `@RestInit` parameter shapes are now `ServletConfig`, `ServletContext`, the resource instance, `@RestInject`-supplied beans, and zero-arg. +- **Annotation memoizers** — every setting previously set by the builder is now computed lazily by a `findXxx()` method on `RestContext` / `RestOpContext` that walks the `@Rest` / `@RestOp` annotation chain, system properties, and the `@RestInject` bean store. Results are cached in a `Memoizer<T>` and invalidated by `RestContext.reset()`. + +See the [V9.5 Migration Guide](/docs/topics/V9.5-migration-guide) for a per-setting replacement table. + #### `RestResponse.setSerializer(Serializer)` - **`RestResponse.setSerializer(Serializer)`** — Forces the serializer used for the response body (via `setContent(Object)` or equivalent), bypassing `Accept` header negotiation. Pass `null` to clear the override. `Content-Type` is still set by the response processor from the serializer when appropriate; you can call `setContentType(String)` first if needed. @@ -1028,3 +1070,116 @@ Generated Swagger (OpenAPI 2) for REST operations now includes optional document #### Migration Notes - **RestClient `rootUrl`**: The `rootUrl` field is now stored as a `Supplier<String>` internally. Code that relies on reflection to access the private `rootUrl` field directly (unusual but possible) will now see a `Supplier<String>` instead of a `String`. + +### juneau-bean-mcp (new module) + +A new bean module, `juneau-bean-mcp`, models the [Model Context Protocol](https://modelcontextprotocol.io/) wire format as Juneau `@Bean` POJOs. The beans round-trip cleanly through any Juneau serializer/parser (JSON, JSON5, MessagePack, CBOR, YAML, etc.) and were the foundation for `juneau-rest-server-mcp`. + +#### Coverage + +- **JSON-RPC envelopes** — `JsonRpcRequest`, `JsonRpcResponse`, `JsonRpcError`, plus an `McpException` carrying JSON-RPC fields for handler-side propagation. +- **Initialization** — `InitializeRequest`, `InitializeResult`, `Implementation`, `ClientCapabilities`, `ServerCapabilities`, and the per-feature capability beans (`ToolCapability`, `PromptCapability`, `ResourceCapability`, `LoggingCapability`, `RootsCapability`). +- **Tools** — `Tool`, `CallToolRequest`, `CallToolResult`, `ListToolsResult`, plus the `JsonSchema` argument-schema bean. +- **Prompts** — `Prompt`, `PromptArgument`, `GetPromptRequest`, `GetPromptResult`, `ListPromptsResult`, `PromptMessage`, `Role`. +- **Resources** — `Resource`, `ReadResourceRequest`, `ReadResourceResult`, `ListResourcesResult`, plus polymorphic `ResourceContents` (`TextResourceContents`, `BlobResourceContents`). +- **Polymorphic content blocks** — `Content` interface with `TextContent`, `ImageContent`, `EmbeddedResourceContent` discriminated by a `type` property. +- **Constants** — `McpMethods` (JSON-RPC method names) and `McpProtocol` (version constants and `JSON_RPC_2_0` literal). + +#### Polymorphic Serialization + +`Content` and `ResourceContents` use `@Bean(typePropertyName="type", dictionary={...})` to discriminate at the wire level. Configure your serializer with `addBeanTypes()` to emit the discriminator on output: + +```java +JsonSerializer ser = JsonSerializer.create() + .addBeanTypes() + .typePropertyName(Content.class, "type") + .typePropertyName(ResourceContents.class, "type") + .build(); +``` + +#### Example + +```java +import org.apache.juneau.bean.mcp.*; +import org.apache.juneau.json.*; + +JsonRpcRequest req = new JsonRpcRequest() + .setJsonrpc(McpProtocol.JSON_RPC_2_0) + .setId(1) + .setMethod(McpMethods.TOOLS_LIST); + +String wire = JsonSerializer.DEFAULT.serialize(req); +JsonRpcRequest back = JsonParser.DEFAULT.parse(wire, JsonRpcRequest.class); +``` + +See [juneau-bean-mcp](/docs/topics/JuneauBeanMcp) for the full topic. + +### juneau-rest-server-mcp (new module) + +A new REST module, `juneau-rest-server-mcp`, exposes a stateless MCP JSON-RPC endpoint built on `juneau-rest-server` and the `juneau-bean-mcp` wire beans. The implementation is transport-agnostic at its core (a pure dispatcher seam) with two REST adapters: a drop-in servlet, and an interface mixin that mounts the endpoint on any existing `@Rest` resource. + +#### New Classes + +- **`McpDispatcher`** - Transport-agnostic JSON-RPC dispatcher. Routes every MCP method (`initialize`, `ping`, `tools/list|call`, `prompts/list|get`, `resources/list|read`), maps `McpException` to JSON-RPC errors, and silently suppresses responses for notifications (`id == null`). +- **`McpServerConfig`** - Aggregate registry of tools, prompts, resources, server identity, protocol version, instructions, capabilities, and pagination strategy. Typically registered as a bean in your `RestContext` bean store. +- **`McpRestServlet`** - Drop-in `BasicRestServlet` subclass exposing `POST /` as the MCP endpoint. Subclasses implement `createMcpConfig()` and the servlet handles dispatch + serialization (with `@SerializerConfig(addBeanTypes="true")` so polymorphic content carries its discriminator). +- **`McpEndpoint`** - Interface mixin with a default `@RestPost("/mcp")` method, letting users add an MCP endpoint to any existing `@Rest` class without subclassing `McpRestServlet`. +- **`Mcp`** - Static façade exposing `Mcp.handle(req, config, beanStore)` for embedders that want a one-line dispatch from inside their own REST methods. +- **`McpToolHandler`**, **`McpPromptHandler`**, **`McpResourceHandler`** - Raw `@FunctionalInterface` handlers that receive `Map<String, Object>` arguments and a per-request `BasicBeanStore`. +- **`McpTypedToolHandler<A,R>`**, **`McpTypedPromptHandler<A>`**, **`McpTypedHandlers`** - Optional sugar layer for binding incoming arguments into Juneau beans and wrapping non-`CallToolResult` returns as a single-`TextContent` result. +- **`McpCursor`**, **`McpPage<T>`** - Pagination strategy seam. Built-in implementations: `McpCursor.SINGLE_PAGE` (default, returns everything in one page) and `McpCursor.fixedSize(n)` (opaque integer-offset paging). + +#### Drop-in servlet + +```java +@Rest(path="/mcp") +public class MyMcpServlet extends McpRestServlet { + @Override + protected McpServerConfig createMcpConfig() { + return new McpServerConfig() + .setServerInfo(new Implementation().setName("my-server").setVersion("1.0.0")) + .addTool(new MyEchoTool()); + } +} +``` + +#### Mixin on an existing resource + +```java +@Rest(path="/api") +public class MyResource extends BasicRestServlet implements McpEndpoint { + @Override + public McpServerConfig getMcpConfig() { + return new McpServerConfig().addTool(new MyEchoTool()); + } +} +``` + +#### Typed handlers + +```java +public class WeatherArgs { String city; String unit; /* getters/setters */ } +public class WeatherResult { String summary; double temp; /* getters/setters */ } + +McpTypedToolHandler<WeatherArgs, WeatherResult> typed = new McpTypedToolHandler<>() { + @Override public Tool descriptor() { return new Tool().setName("weather"); } + @Override public Class<WeatherArgs> argumentType() { return WeatherArgs.class; } + @Override public WeatherResult call(WeatherArgs a, BasicBeanStore ctx) { /* ... */ } +}; + +config.addTool(McpTypedHandlers.adaptTool(typed)); +``` + +#### Pagination + +```java +config.setCursor(McpCursor.fixedSize(50)); +``` + +The cursor receives the full descriptor list and the inbound cursor token, and returns an `McpPage<T>` containing the slice plus an opaque `nextCursor`. Custom strategies plug in via the `McpCursor` functional interface. + +#### Notifications + +Requests without an `id` are treated as JSON-RPC notifications: handlers run, exceptions are swallowed, and the dispatcher returns `null`. The REST servlet writes an empty body so transports can map this to `204 No Content`. + +See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcpBasics) for the full topic. diff --git a/pages/topics/04.08.JuneauBeanMcp.md b/pages/topics/04.08.JuneauBeanMcp.md new file mode 100644 index 0000000000..92f160630e --- /dev/null +++ b/pages/topics/04.08.JuneauBeanMcp.md @@ -0,0 +1,225 @@ +--- +title: "juneau-bean-mcp" +slug: JuneauBeanMcp +--- + +# juneau-bean-mcp + +The `juneau-bean-mcp` module provides Java beans modelling the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) wire format. + +## Overview + +MCP is a JSON-RPC 2.0 protocol used by AI assistants and agents to interact with external tools, prompts, and resources. This module provides a complete set of Juneau `@Bean`-annotated POJOs covering the MCP HTTP wire surface, so you can build MCP servers and clients using any Juneau serializer/parser (JSON, JSON5, MessagePack, CBOR, YAML, and more). + +The module ships only the wire types — no transport, no dispatch logic, no HTTP plumbing. For a stateless JSON-RPC endpoint built on `juneau-rest-server` and these beans, see [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcpBasics). + +### 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** — `Content` and `ResourceContents` use `@Bean(typePropertyName="type", dictionary={...})` to discriminate subtype on the wire. +- **Fluent setters** — Every bean uses `setX(...)` returning `this` for ergonomic construction. +- **No external dependencies** — Only depends on `juneau-marshall`. +- **Zero protocol logic** — Pure DTOs; no validation, no state machines, no dispatch. + +## Coverage + +### JSON-RPC envelopes + +| Bean | Purpose | +|---|---| +| `JsonRpcRequest` | Inbound JSON-RPC request envelope (`jsonrpc`, `id`, `method`, `params`). | +| `JsonRpcResponse` | Outbound JSON-RPC response envelope (`jsonrpc`, `id`, `result`, `error`). | +| `JsonRpcError` | JSON-RPC error structure (`code`, `message`, `data`). | +| `McpException` | `RuntimeException` carrying JSON-RPC error fields for handler-side propagation. | + +### 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`). | +| `ListToolsResult` | Result of `tools/list`. | +| `CallToolRequest` / `CallToolResult` | `tools/call` request/response. | +| `JsonSchema` | Lightweight JSON Schema bean for tool argument schemas. | + +### 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`. | + +### Resources + +| Bean | Purpose | +|---|---| +| `Resource` | Resource descriptor (`uri`, `name`, `description`, `mimeType`). | +| `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. | + +### 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"` | + +### Constants + +- `McpMethods` — JSON-RPC method names: `INITIALIZE`, `PING`, `TOOLS_LIST`, `TOOLS_CALL`, `PROMPTS_LIST`, `PROMPTS_GET`, `RESOURCES_LIST`, `RESOURCES_READ`. +- `McpProtocol` — Protocol literals: `JSON_RPC_2_0` (`"2.0"`) and `VERSION_2025_06_18`. + +## Polymorphic Serialization + +`Content` and `ResourceContents` declare `@Bean(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: + +```java +import org.apache.juneau.bean.mcp.*; +import org.apache.juneau.json.*; + +JsonSerializer ser = JsonSerializer.create().addBeanTypes().build(); + +CallToolResult result = new CallToolResult().setContent(List.of( + new TextContent().setText("hello") +)); + +String wire = ser.serialize(result); +// {"content":[{"type":"text","text":"hello"}]} + +CallToolResult back = JsonParser.DEFAULT.parse(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 + +```java +import org.apache.juneau.bean.mcp.*; +import org.apache.juneau.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.serialize(req); +JsonRpcRequest back = JsonParser.DEFAULT.parse(wire, JsonRpcRequest.class); +``` + +### Building a tool descriptor + +```java +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 result with mixed content + +```java +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.serialize(result); +// {"content":[{"type":"text","text":"Operation complete."},{"type":"image","mimeType":"image/png","data":"..."}]} +``` + +### Building a resource read result + +```java +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.serialize(readResult); +// contents[0].type = "resourceText", contents[1].type = "resourceBlob" +``` + +### Mapping handler errors to JSON-RPC errors + +`McpException` is a `RuntimeException` carrying JSON-RPC `code`, `message`, and optional `data`. Handlers can throw it directly; calling code can convert via `toJsonRpcError()`: + +```java +try { + throw new McpException(-32602, "Invalid arguments", JsonMap.of("field", "name")); +} catch (McpException e) { + JsonRpcResponse resp = new JsonRpcResponse() + .setJsonrpc(McpProtocol.JSON_RPC_2_0) + .setId(1) + .setError(e.toJsonRpcError()); +} +``` + +## Multiple Wire Formats + +Because the beans are pure POJOs with `@Bean` annotations, every Juneau serializer/parser works: + +```java +import org.apache.juneau.cbor.*; +import org.apache.juneau.msgpack.*; +import org.apache.juneau.yaml.*; + +CallToolResult r = new CallToolResult().setContent(List.of(new TextContent().setText("hi"))); + +byte[] cbor = Cbor.of(r); +byte[] msgpack = MsgPackSerializer.DEFAULT.serialize(r); +String yaml = Yaml.of(r); +``` + +Set `addBeanTypes` and the discriminator property names on the corresponding builders to round-trip polymorphic content correctly. + +## Related Modules + +- **[juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcpBasics)** — Builds an MCP JSON-RPC HTTP endpoint on top of these beans plus `juneau-rest-server`. + +## Resources + +- [Model Context Protocol Specification](https://modelcontextprotocol.io/) +- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) diff --git a/pages/topics/10.03.06.LifecycleHooks.md b/pages/topics/10.03.06.LifecycleHooks.md index 27a62b6336..55f1ea5e72 100644 --- a/pages/topics/10.03.06.LifecycleHooks.md +++ b/pages/topics/10.03.06.LifecycleHooks.md @@ -15,12 +15,19 @@ public class MyResource extends BasicRestObject { private Map myDatabase; @RestInit - public void initMyDatabase(RestContext.Builder builder) throws Exception { + public void initMyDatabase() throws Exception { myDatabase = new LinkedHashMap(); } } ``` +:::note 9.5 change +Any lifecycle snippet using `RestContext.Builder` / `RestOpContext.Builder` parameters is pre-9.5 and +kept only for historical context. Builder injection was removed; migrate to `@Rest(...)` / `@RestOp(...)` +attributes and `@RestInject(name="...")` bean suppliers. See the +[v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide). +::: + Or if you want to intercept REST calls: ```java diff --git a/pages/topics/10.04.03.JavaMethodParameters.md b/pages/topics/10.04.03.JavaMethodParameters.md index a2e8f55a52..5f23278d3d 100644 --- a/pages/topics/10.04.03.JavaMethodParameters.md +++ b/pages/topics/10.04.03.JavaMethodParameters.md @@ -49,7 +49,7 @@ public String doGetExample1( ``` ::: -Additional parameter types can be defined via the annotation <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html#restOpArgs()" target="_blank">Rest.restOpArgs()</a> or by calling <a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#restOpArgs(java.lang.Class...)" target="_blank">restOpArgs()</a>. +Additional parameter types can be defined via the annotation <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html#restOpArgs()" target="_blank">Rest.restOpArgs()</a> or by supplying a named bean via <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" target="_blank">@RestInject</a>. :::tip Example ```java @@ -58,11 +58,6 @@ Additional parameter types can be defined via the annotation <a href="/site/apid ) public class MyResource extends BasicRestObject { - // Option #2 - Programmatically - @RestInit - public void init(RestContext.Builder builder) { - builder.restOpArgs(MyOpArg.class); - } } ``` ::: \ No newline at end of file diff --git a/pages/topics/10.04.04.JavaMethodReturnTypes.md b/pages/topics/10.04.04.JavaMethodReturnTypes.md index 6d3282fee3..a7425e068a 100644 --- a/pages/topics/10.04.04.JavaMethodReturnTypes.md +++ b/pages/topics/10.04.04.JavaMethodReturnTypes.md @@ -73,7 +73,7 @@ public void doGet2(RestResponse res, @Path("personId") UUID personId) { ``` ::: -Additional parameter types can be defined via the annotation <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html#responseProcessors()" target="_blank">Rest.responseProcessors()</a> or by calling <a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#responseProcessors(java.lang.Class...)" target="_blank">responseProcessors()</a>. +Additional return-processor types can be defined via the annotation <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html#responseProcessors()" target="_blank">Rest.responseProcessors()</a> or by supplying a named bean via <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" target="_blank">@RestInject</a>. :::tip Example ```java @@ -82,11 +82,6 @@ Additional parameter types can be defined via the annotation <a href="/site/apid ) public class MyResource extends BasicRestObject { - // Option #2 - Programmatically - @RestInit - public void init(RestContext.Builder builder) { - builder.responseProcessors(MyResponseProcessor.class); - } } ``` ::: \ No newline at end of file diff --git a/pages/topics/10.06.Marshalling.md b/pages/topics/10.06.Marshalling.md index fa3f9a7b17..987fd6b1d0 100644 --- a/pages/topics/10.06.Marshalling.md +++ b/pages/topics/10.06.Marshalling.md @@ -3,6 +3,12 @@ title: "Marshalling" slug: Marshalling --- +:::warning Outdated examples (9.5) +Examples on this page using `RestContext.Builder` is pre-9.5 and kept only for historical context. +Use `@Rest(...serializers/parsers...)`, `@RestOp(...)`, or `@RestInject(name="serializers"|"parsers")` +with fully built sets. See the [v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide). +::: + Juneau uses <a href="/site/apidocs/org/apache/juneau/parser/Parser.html" target="_blank">Parsers</a> and <a href="/site/apidocs/org/apache/juneau/serializer/Serializer.html" target="_blank">Serializers</a> for marshalling HTTP request and response bodies to POJOs using the `Content-Type` header to match the best parser and the `Accept` header to match the best serializer. Serializers and parsers can be associated with REST servlets using the following annotations: @@ -46,21 +52,11 @@ used as-is or augmented by child classes: <node-1><javac-interface><a href="/site/apidocs/org/apache/juneau/rest/config/BasicJsonConfig.html" target="_blank">BasicJsonConfig</a></javac-interface> <javac-interface><a href="/site/apidocs/org/apache/juneau/rest/config/BasicJsonHtmlConfig.html" target="_blank">BasicJsonHtmlConfig</a></javac-interface> <javac-interface><a href="/site/apidocs/org/apache/juneau/rest/config/BasicJson5Config.html" target="_blank">BasicJson5Config</a></javac-interface> <javac-interface><a href="/site/apid [...] </tree> -Serializers and parsers can also be defined programmatically using an INIT hook method like shown below: - -```java -@Rest -public class MyResource { - - @RestInit - public void init(RestContext.Builder builder) { - builder.serializers().add(JsonSerializer.class, HtmlSerializer.class); - builder.parsers().add(JsonParser.class, HtmlParser.class); - } -} -``` +Serializers and parsers can also be supplied programmatically using +<a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" target="_blank">@RestInject</a> +bean suppliers (for example, `name="serializers"` and `name="parsers"`). -They can also be defined through [custom REST contexts and builders](/docs/topics/RestContext). +They can also be defined through [custom REST contexts](/docs/topics/RestContext). Config annotations allow you to define serializer and parser properties using specialized annotations at either the class or operation levels: diff --git a/pages/topics/10.12.ConfigurationFiles.md b/pages/topics/10.12.ConfigurationFiles.md index 8628e93945..12a37aba55 100644 --- a/pages/topics/10.12.ConfigurationFiles.md +++ b/pages/topics/10.12.ConfigurationFiles.md @@ -3,6 +3,12 @@ title: "Configuration Files" slug: ConfigurationFiles --- +:::warning Outdated examples (9.5) +Examples on this page using `RestContext.Builder` is pre-9.5 and kept only for historical context. +Use `Config` parameter injection in `@RestOp` methods, `@RestInject` beans, or `RestContext.getConfig()` +from a supported `@RestInit` parameter. See the [v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide). +::: + The Server API provides methods for associating configuration files with REST servlets so that configuration properties can be defined in external files. @@ -80,7 +86,8 @@ Note that properties files are a subset of functionality of INI files (they're b default section). It's therefore possible to use INI-style syntax such as sections in your `application.properties` file. -Once a config file has been associated with a REST resource, it can be accessed through the <a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#config()" target="_blank">RestContext.Builder.config()</a> method. +Once a config file has been associated with a REST resource, it can be accessed through +<a href="/site/apidocs/org/apache/juneau/rest/RestContext.html#getConfig()" target="_blank">RestContext.getConfig()</a>. It can also be accessed by passing in a <a href="/site/apidocs/org/apache/juneau/config/Config.html" target="_blank">Config</a> bean to any of your REST OP methods. @@ -93,11 +100,12 @@ A common usage is to use this method to initialize fields in your servlet. ... ) public class MyResource { - private final String path; - private final File javaHome; + private String path; + private File javaHome; - public MyResource(RestContext.Builder builder) { - Config config = builder.getConfig(); + @RestInit + public void init(RestContext context) { + Config config = context.getConfig(); path = config.get("MyProperties/path").orElse("mypath"); javaHome = config.get("MyProperties/javaHome").as(File.class).orElse(null); } diff --git a/pages/topics/10.13.SvlVariables.md b/pages/topics/10.13.SvlVariables.md index 2ba5d3799d..0e9373f678 100644 --- a/pages/topics/10.13.SvlVariables.md +++ b/pages/topics/10.13.SvlVariables.md @@ -3,6 +3,13 @@ title: "SVL Variables" slug: RestServerSvlVariables --- +:::warning Outdated examples (9.5) +Examples on this page using `RestContext.Builder` / `builder.vars(...)` is pre-9.5 and kept only +for historical context. Register custom vars through `@RestInject(name="varResolver")` with a +`VarResolver` (or `VarResolver.Builder`) supplier. See the +[v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide). +::: + In the previous examples, there were several cases where embedded variables were contained within annotation values: ```java @@ -66,14 +73,8 @@ The following is the default list of supported variables: | | <a href="/site/apidocs/org/apache/juneau/rest/vars/UrlEncodeVar.html" target="_blank">UrlEncodeVar</a> | `$UE{uriPart}` | yes | yes | `$U{servlet:/foo?bar=$UE{$RA{bar}}}` | | | <a href="/site/apidocs/org/apache/juneau/rest/widget/Widget.html" target="_blank">Widget</a> | `$W{name}` | no | yes | `$W{MenuItemWidget}` | -Custom variables can be defined on resources via the following API: - -<tree> -<node-0><java-class><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html" target="_blank">RestContext.Builder</a></java-class></node-0> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#varResolver()" target="_blank">varResolver()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#vars(java.lang.Class...)" target="_blank">vars(Class...)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#vars(org.apache.juneau.svl.Var...)" target="_blank">vars(Var...)</a></java-method></node-1> -</tree> +Custom variables can be defined by supplying a named `varResolver` bean via +<a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" target="_blank">@RestInject</a>. :::tip Example ```java @@ -95,9 +96,9 @@ public class BracketVar extends SimpleVar { @Rest(...) public class MyResource extends BasicRestObject { - @RestInit - public void init(RestContext.Builder builder) { - builder.vars(BracketVar.class); + @RestInject(name="varResolver") + public static VarResolver varResolver(VarResolver.Builder b) { + return b.vars(BracketVar.class).build(); } } ``` diff --git a/pages/topics/10.16.08.SwaggerModels.md b/pages/topics/10.16.08.SwaggerModels.md index 8b4670b758..3e54cf5d3f 100644 --- a/pages/topics/10.16.08.SwaggerModels.md +++ b/pages/topics/10.16.08.SwaggerModels.md @@ -3,16 +3,24 @@ title: "Swagger Models" slug: SwaggerModels --- +:::warning Outdated examples (9.5) +Examples on this page using `@HookEvent(INIT)` or `RestContext.Builder` is pre-9.5 and kept only +for historical context. Configure schema generation through +`@RestInject(name="jsonSchemaGenerator")` with a fully built `JsonSchemaGenerator`. See the +[v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide). +::: + The <a href="/site/apidocs/org/apache/juneau/jsonschema/JsonSchemaGenerator.Builder.html#useBeanDefs()" target="_blank">JsonSchemaGenerator.Builder.useBeanDefs()</a> setting can be used to reduce the size of your generated Swagger JSON files by creating model definitions for beans and referencing those definitions through `$ref` attributes. -This setting is disabled by default but can be set on the <a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html" target="_blank">RestContext.Builder</a> object: +This setting is disabled by default but can be set by supplying a +`jsonSchemaGenerator` bean: ```java -@HookEvent(INIT) -public void init(RestContext.Builder builder) { - builder.jsonSchemaGenerator().useBeanDefs(); +@RestInject(name="jsonSchemaGenerator") +public static JsonSchemaGenerator jsonSchemaGenerator(JsonSchemaGenerator.Builder b) { + return b.useBeanDefs().build(); } ``` diff --git a/pages/topics/10.23.RestContext.md b/pages/topics/10.23.RestContext.md index 708fe3424b..5192ce3319 100644 --- a/pages/topics/10.23.RestContext.md +++ b/pages/topics/10.23.RestContext.md @@ -3,55 +3,40 @@ title: "RestContext" slug: RestContext --- +:::warning Outdated examples (9.5) +Examples on this page using `RestContext.Builder` (constructor injection or `@RestInit` parameter +injection) is pre-9.5 and kept only for historical context. The Builder-injection protocol was +removed; migrate to `@Rest(...)` / `@RestOp(...)` attributes, `@RestInject(name="...")` bean +suppliers, and `new RestContext(RestContextInit)` for programmatic bootstrap. See the +[v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide). +::: + The <a href="/site/apidocs/org/apache/juneau/rest/RestContext.html" target="_blank">RestContext</a> object is the workhorse class for all of the configuration of a single REST resource class. It's by-far the most important class in the REST API. Every class annotated with <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html" target="_blank">@Rest</a> ends up with an instance of this object. -The object itself is read-only and unchangeable and is initialized with all of the various annotations pulled from the -class and methods. -All functionality available through annotations have programmatic equivalents through the builder of this class. - -The <a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html" target="_blank">RestContext.Builder</a> class extends <a href="/site/apidocs/org/apache/juneau/BeanContext.Builder.html" target="_blank">BeanContext.Builder</a> allowing you to programmatically set any properties defined on that builder class. -It also implements <a href="https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta/servlet/ServletConfig.html" target="_blank">ServletConfig</a>. - -To access this object, simply pass it in as a constructor argument or in an INIT hook: - -```java -// Option #1 - Pass in through constructor. -public MyResource(RestContext.Builder builder) { - builder - .beanContext(x -> x.swaps(TemporalCalendarSwap.Rfc1123DateTime.class)) - .debugEnablement(CONDITIONAL); -} +The object itself is read-only and unchangeable and is initialized from the resource/method annotations plus injected +beans in the resource bean store. -// Option #2 - Use an init hook. -@RestInit -public void init(RestContext.Builder builder) throws Exception { - builder - .beanContext(x -> x.swaps(TemporalCalendarSwap.Rfc1123DateTime.class)) - .debugEnablement(CONDITIONAL); -} -``` +In 9.5+, configuration is annotation-driven (`@Rest(...)`, `@RestOp(...)`, `@RestGet(...)`, etc.) with optional +`@RestInject(name="...")` bean suppliers for programmatic overrides. This class is vast. Combined with <a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.html" target="_blank">RestOpContext</a> (which is the equivalent per-method context), these classes define the entire configuration and workflow of the REST API. -There are multiple ways to programmatically alter how `RestContext` behaves. -The most straightforward are the following builder methods which are direct equivalents to values defined on the <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html" target="_blank">@Rest</a> annotation: +For direct programmatic bootstrap, use <a href="/site/apidocs/org/apache/juneau/rest/RestContextInit.html" target="_blank">RestContextInit</a> with +<a href="/site/apidocs/org/apache/juneau/rest/RestContext.html#%3Cinit%3E(org.apache.juneau.rest.RestContextInit)" target="_blank">new RestContext(init)</a>. +This replaces the legacy `RestContext.create(...).init(...).build()` factory chain. -<tree> -<node-0><java-class><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html" target="_blank">RestContext.Builder</a></java-class></node-0> -<node-1><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#allowedHeaderParams(java.lang.String)" target="_blank">allowedHeaderParams</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#allowedMethodHeaders(java.lang.String)" target="_blank">allowedMethodHeaders</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#allowedMethodParams(java.lang.String)" ta [...] -</tree> +:::tip 9.5+ guidance +Prefer these configuration surfaces: -For more complex configurations, access to sub-builders is provided via the following methods: - -<tree> -<node-0><java-class><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html" target="_blank">RestContext.Builder</a></java-class></node-0> -<node-1><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#callLogger()" target="_blank">callLogger()</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#config()" target="_blank">config()</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#consumes()" target="_blank">consumes()</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/res [...] -</tree> +- `@Rest(...)` for resource-level settings. +- `@RestOp(...)` / verb annotations for operation-level settings. +- `@RestInject(name="...")` bean suppliers for programmatic overrides. +::: :::note The builders or built objects above can also be defined as injected beans defined in a Spring Configuration if you wish @@ -59,14 +44,5 @@ to do all your app configuration Spring-style. This is described in detail in the [juneau-rest-server-springboot](/docs/topics/JuneauRestServerSpringbootBasics) documentation. ::: -The programmatic equivalent to the annotated lifecycle methods are below: - -<tree> -<node-0><java-class><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html" target="_blank">RestContext.Builder</a></java-class></node-0> -<node-1><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#postInitMethods()" target="_blank">postInitMethods</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#postInitChildFirstMethods()" target="_blank">postInitChildFirstMethods</a></javac-method><javac-method><a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#startCallMethods()" target="_blank">startCallMethods</a></javac-method> [...] -</tree> - -:::note -It is also possible to override methods on the <a href="/site/apidocs/org/apache/juneau/rest/RestContext.html" target="_blank">RestContext</a> class -itself by providing your own specialized subclass via the <a href="/site/apidocs/org/apache/juneau/rest/RestContext.Builder.html#type(java.lang.Class)" target="_blank">RestContext.Builder.type(Class)</a> method. -::: \ No newline at end of file +The lifecycle methods are still annotation-driven via `@RestInit`, `@RestPreCall`, `@RestPostCall`, `@RestStartCall`, and +`@RestDestroy`. \ No newline at end of file diff --git a/pages/topics/10.24.RestOpContext.md b/pages/topics/10.24.RestOpContext.md index b37bc737e5..f8d189dbd2 100644 --- a/pages/topics/10.24.RestOpContext.md +++ b/pages/topics/10.24.RestOpContext.md @@ -10,62 +10,19 @@ Every class annotated with <a href="/site/apidocs/org/apache/juneau/rest/annotat Similar to <a href="/site/apidocs/org/apache/juneau/rest/RestContext.html" target="_blank">RestContext</a>, the object is read-only and unchangeable and is initialized with all of the various annotations pulled from the method. -All functionality available through annotations have programmatic equivalents through the builder of this class. - -To access the builder for these objects, simply implement the following init method that will be called for each <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestOp.html" target="_blank">RestOp</a>-annotated method. - -```java -// Use an init hook with RestOpContext.Builder as a parameter. -@RestInit -public void init(RestOpContext.Builder builder) throws Exception { - builder - .beanContext(x -> x.swaps(TemporalCalendarSwap.Rfc1123DateTime.class)) - .debugEnablement(CONDITIONAL); -} -``` - -There are multiple ways to programmatically alter how `RestOpContext` behaves. - -The most straightforward are the following builder methods which are direct equivalents to values defined on the <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestOp.html" target="_blank">RestOp</a> annotation: - -<tree> -<node-0><java-class><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html" target="_blank">RestOpContext.Builder</a></java-class></node-0> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#clientVersion(java.lang.String)" target="_blank">clientVersion(String)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#consumes(org.apache.juneau.MediaType...)" target="_blank">consumes(MediaType...)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#debug(org.apache.juneau.Enablement)" target="_blank">debug(Enablement)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultCharset(java.nio.charset.Charset)" target="_blank">defaultCharset(Charset)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#dotAll()" target="_blank">dotAll()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#httpMethod(java.lang.String)" target="_blank">httpMethod(String)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#maxInput(java.lang.String)" target="_blank">maxInput(String)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#path(java.lang.String...)" target="_blank">path(String...)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#produces(org.apache.juneau.MediaType...)" target="_blank">produces(MediaType...)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#roleGuard(java.lang.String)" target="_blank">roleGuard(String)</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#rolesDeclared(java.lang.String...)" target="_blank">rolesDeclared(String...)</a></java-method></node-1> -</tree> - -For more complex configurations, access to sub-builders is provided via the following methods: - -<tree> -<node-0><java-class><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html" target="_blank">RestOpContext.Builder</a></java-class></node-0> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#converters()" target="_blank">converters()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultClasses()" target="_blank">defaultClasses()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultRequestAttributes()" target="_blank">defaultRequestAttributes()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultRequestFormData()" target="_blank">defaultRequestFormData()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultRequestHeaders()" target="_blank">defaultRequestHeaders()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultRequestQueryData()" target="_blank">defaultRequestQueryData()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#defaultResponseHeaders()" target="_blank">defaultResponseHeaders()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#encoders()" target="_blank">encoders()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#guards()" target="_blank">guards()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#jsonSchemaGenerator()" target="_blank">jsonSchemaGenerator()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#matchers()" target="_blank">matchers()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#parsers()" target="_blank">parsers()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#partParser()" target="_blank">partParser()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#partSerializer()" target="_blank">partSerializer()</a></java-method></node-1> -<node-1><java-method><a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#serializers()" target="_blank">serializers()</a></java-method></node-1> -</tree> - -:::note -It is also possible to override methods on the <a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.html" target="_blank">RestOpContext</a> -class itself by providing your own specialized subclass via the -<a href="/site/apidocs/org/apache/juneau/rest/RestOpContext.Builder.html#type(java.lang.Class)" target="_blank">RestOpContext.Builder.type(Class)</a> method. +Per-operation configuration is annotation-driven via <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestOp.html" target="_blank">@RestOp</a> (and its method-specific cousins +<a href="/site/apidocs/org/apache/juneau/rest/annotation/RestGet.html" target="_blank">@RestGet</a>, <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestPut.html" target="_blank">@RestPut</a>, +<a href="/site/apidocs/org/apache/juneau/rest/annotation/RestPost.html" target="_blank">@RestPost</a>, <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestDelete.html" target="_blank">@RestDelete</a>, +<a href="/site/apidocs/org/apache/juneau/rest/annotation/RestPatch.html" target="_blank">@RestPatch</a>, <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestOptions.html" target="_blank">@RestOptions</a>). +Programmatic per-operation overrides are supplied via <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" target="_blank">@RestInject</a>-named beans +(use the `methodScope=` attribute to scope an override to specific operation methods). +Both Builder-injection protocols — per-op `@RestInit(RestOpContext.Builder)` and class-level `@RestInit(RestContext.Builder)` — +were removed in 9.5; see [the v9.5 Migration Guide](/docs/topics/V9.5MigrationGuide) for replacement recipes. + +:::tip 9.5+ configuration model +`RestOpContext` is now configured through: + +- `@RestOp(...)` / `@RestGet(...)` / `@RestPost(...)` / `@RestPut(...)` / `@RestPatch(...)` / `@RestDelete(...)` / `@RestOptions(...)` attributes. +- `@RestInject(name="...")` bean suppliers (optionally scoped with `methodScope=`). +- Resource-level defaults inherited from `@Rest(...)` (unless blocked by `noInherit`). ::: \ No newline at end of file diff --git a/pages/topics/10a.01.JuneauRestServerMcpBasics.md b/pages/topics/10a.01.JuneauRestServerMcpBasics.md new file mode 100644 index 0000000000..71b5a896b1 --- /dev/null +++ b/pages/topics/10a.01.JuneauRestServerMcpBasics.md @@ -0,0 +1,257 @@ +--- +title: "juneau-rest-server-mcp Basics" +slug: JuneauRestServerMcpBasics +--- + +# juneau-rest-server-mcp + +The `juneau-rest-server-mcp` module exposes a stateless [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) JSON-RPC endpoint built on `juneau-rest-server` and the [`juneau-bean-mcp`](/docs/topics/JuneauBeanMcp) wire beans. + +## Overview + +MCP is a JSON-RPC 2.0 protocol that lets AI assistants discover and invoke external **tools**, **prompts**, and **resources**. This module provides: + +- A transport-agnostic **dispatcher** that routes incoming JSON-RPC methods. +- A drop-in **servlet** (`McpRestServlet`) for the common case of "expose an MCP endpoint at `POST /mcp`". +- An **interface mixin** (`McpEndpoint`) for embedding an MCP endpoint inside an existing `@Rest` resource. +- Functional **handler interfaces** for tools/prompts/resources, plus a typed sugar layer that does argument binding and result wrapping for you. +- A pluggable **pagination** seam for `*\/list` methods. + +The implementation is stateless — every request is dispatched against an `McpServerConfig` registered in the REST `BeanStore`, and per-request state (such as the underlying `RestRequest`) is exposed to handlers through a `BasicBeanStore`. + +### Architecture + +``` ++------------------------+ +------------------+ +-----------------+ +| McpRestServlet -or- | ---> | McpDispatcher | ---> | Tool/Prompt/ | +| McpEndpoint mixin | | (pure JSON-RPC) | | Resource | +| (POST handler) | | | | Handlers | ++------------------------+ +------------------+ +-----------------+ + | | + v v + juneau-rest-server juneau-bean-mcp + (@Rest, @RestPost) (wire beans + JSON-RPC envelopes) +``` + +`McpDispatcher` is the single seam containing all protocol logic. The two REST adapters (`McpRestServlet` and `McpEndpoint`) only translate from `RestRequest` to a `BasicBeanStore` and call `Mcp.handle(...)`. + +## Getting Started + +### Add the dependency + +```xml +<dependency> + <groupId>org.apache.juneau</groupId> + <artifactId>juneau-rest-server-mcp</artifactId> + <version>9.2.1</version> +</dependency> +``` + +This module transitively pulls in `juneau-rest-server` and `juneau-bean-mcp`. + +### Drop-in servlet + +Subclass `McpRestServlet` and supply your config in `createMcpConfig()`. The base class wires up `@Rest`, `@SerializerConfig(addBeanTypes="true")`, and a `POST /` handler: + +```java +import org.apache.juneau.bean.mcp.*; +import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.mcp.*; + +@Rest(path="/mcp") +public class MyMcpServlet extends McpRestServlet { + + @Override + protected McpServerConfig createMcpConfig() { + return new McpServerConfig() + .setServerInfo(new Implementation() + .setName("my-server") + .setVersion("1.0.0")) + .addTool(new EchoTool()); + } +} +``` + +Mount the servlet through your microservice / Spring Boot config like any other `@Rest` resource. + +### Interface mixin + +If you already have a `@Rest` resource and want to expose MCP at `POST /mcp` next to your other endpoints, implement `McpEndpoint`: + +```java +@Rest(path="/api") +public class MyResource extends BasicRestServlet implements McpEndpoint { + + @Override + public McpServerConfig getMcpConfig() { + return new McpServerConfig().addTool(new EchoTool()); + } +} +``` + +The default `handleMcpRequest(...)` method on `McpEndpoint` (annotated `@RestPost("/mcp")`) takes care of dispatch. + +### Static façade + +For full custom routing, call `Mcp.handle(...)` from your own `@RestPost` method: + +```java +@RestPost(path="/custom-mcp") +public JsonRpcResponse mcp(@Content JsonRpcRequest req, RestRequest restReq) { + var bs = BasicBeanStore.of(restReq.getContext().getBeanStore()) + .addBean(RestRequest.class, restReq); + return Mcp.handle(req, getMcpConfig(), bs); +} +``` + +## Writing Handlers + +### Raw tool handler + +`McpToolHandler` is the lowest-level handler interface. It receives a parsed `Map<String, Object>` of arguments and a per-request `BasicBeanStore`: + +```java +import org.apache.juneau.bean.mcp.*; +import org.apache.juneau.cp.*; +import org.apache.juneau.rest.mcp.*; + +public class EchoTool implements McpToolHandler { + @Override + public Tool descriptor() { + return new Tool() + .setName("echo") + .setDescription("Echoes the supplied text back."); + } + + @Override + public CallToolResult call(Map<String, Object> arguments, BasicBeanStore ctx) { + String text = (String) arguments.getOrDefault("text", ""); + return new CallToolResult().setContent(List.of( + new TextContent().setText(text) + )); + } +} +``` + +### Typed tool handler (sugar) + +When you'd rather receive a Juneau bean for arguments and let the framework wrap your result, use `McpTypedToolHandler` and adapt it via `McpTypedHandlers.adaptTool(...)`: + +```java +public class WeatherArgs { + public String city; + public String unit = "C"; +} + +public class WeatherResult { + public String summary; + public double temp; +} + +public class WeatherTool implements McpTypedToolHandler<WeatherArgs, WeatherResult> { + @Override + public Tool descriptor() { + return new Tool().setName("weather"); + } + + @Override + public Class<WeatherArgs> argumentType() { return WeatherArgs.class; } + + @Override + public WeatherResult call(WeatherArgs args, BasicBeanStore ctx) { + WeatherResult r = new WeatherResult(); + r.summary = "Sunny"; + r.temp = 23.5; + return r; + } +} + +config.addTool(McpTypedHandlers.adaptTool(new WeatherTool())); +``` + +The adapter: + +1. JSON-roundtrips the inbound argument map into `argumentType()`. +2. Invokes your typed `call(...)`. +3. If the return value is a `CallToolResult`, passes it through unchanged. +4. Otherwise, JSON-serializes the return and wraps it in a single-`TextContent` `CallToolResult`. + +If binding fails, the dispatcher emits a JSON-RPC `-32602 Invalid params` error. + +### Prompts and resources + +`McpPromptHandler`, `McpResourceHandler`, and `McpTypedPromptHandler<A>` follow the same shape. Register them via `addPrompt(...)` and `addResource(...)` on `McpServerConfig`. + +### Reporting errors + +Throw `McpException` from any handler to surface a structured JSON-RPC error: + +```java +throw new McpException(McpDispatcher.CODE_INVALID_PARAMS, "Missing required argument 'city'"); +``` + +`McpException` carries the JSON-RPC `code`, `message`, and optional `data` fields. The dispatcher converts other unchecked exceptions into `-32603 Internal error` automatically. + +## Pagination + +`McpServerConfig.setCursor(McpCursor)` controls how `tools/list`, `prompts/list`, and `resources/list` paginate. + +### Built-in strategies + +```java +import org.apache.juneau.rest.mcp.*; + +config.setCursor(McpCursor.SINGLE_PAGE); // default - one page, no cursor +config.setCursor(McpCursor.fixedSize(50)); // opaque integer-offset paging, 50 per page +``` + +### Custom strategy + +`McpCursor` is a `@FunctionalInterface`. Implement it to plug in arbitrary pagination (database cursors, HMAC-signed tokens, etc.): + +```java +McpCursor myCursor = new McpCursor() { + @Override + public <T> McpPage<T> page(List<T> all, String cursor, BasicBeanStore ctx) { + // ... your logic ... + return new McpPage<>(items, nextOpaqueToken); + } +}; + +config.setCursor(myCursor); +``` + +`McpPage<T>` is a record holding the slice items and an opaque `nextCursor` (`null` to indicate end-of-results). + +## Notifications + +JSON-RPC requests with `id == null` are *notifications*. The dispatcher invokes the handler, **silently swallows any exception**, and returns `null`. The REST servlet renders this as an empty response body, so transports can map this to `204 No Content`. + +## Capabilities + +`McpServerConfig.setCapabilities(...)` lets you advertise an explicit `ServerCapabilities` bean. When omitted, `McpDispatcher` synthesizes one from the registered handler lists (advertising `tools`, `prompts`, and/or `resources` only when at least one matching handler is registered). + +## Server Info Defaults + +If `setServerInfo(...)` is not provided, the dispatcher reports: + +- `name` = `"juneau-rest-server-mcp"` +- `version` = `"unknown"` + +Always set your own `Implementation` so MCP clients can identify and version your service. + +## Polymorphic Wire Format + +MCP returns polymorphic content (`Content`, `ResourceContents`) discriminated by a `type` property. `McpRestServlet` enables `addBeanTypes` on its serializer via `@SerializerConfig(addBeanTypes="true")` so the discriminator is emitted on the wire. If you build your own `RestServlet`, apply the same configuration. + +See [juneau-bean-mcp](/docs/topics/JuneauBeanMcp) for the full wire-bean catalog. + +## Related Modules + +- **[juneau-bean-mcp](/docs/topics/JuneauBeanMcp)** — The MCP wire beans consumed by this module. +- **[juneau-rest-server](/docs/topics/JuneauRestServerBasics)** — The base REST server framework. + +## Resources + +- [Model Context Protocol Specification](https://modelcontextprotocol.io/) +- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) diff --git a/pages/topics/23.01.V9.5-migration-guide.md b/pages/topics/23.01.V9.5-migration-guide.md new file mode 100644 index 0000000000..03ba0ef1e1 --- /dev/null +++ b/pages/topics/23.01.V9.5-migration-guide.md @@ -0,0 +1,34 @@ +--- +title: "v9.5 Migration Guide" +slug: V9.5-migration-guide +--- + +The following guide can be used to help migrate your code to v9.5. +Note that you can also refer to the Release Notes for changes as well. + +This guide also captures breaking changes introduced during the 9.2.x line so that +teams jumping from 9.1 (or earlier) directly to 9.5 have a single reference. + +| Old | New | +|-----|-----| +| `@Rest(allowedHeaderParams="NONE")` / `@Rest(allowedMethodHeaders="NONE")` / `@Rest(allowedMethodParams="NONE")` — the literal string `"NONE"` was used to suppress inheriting the attribute from a parent class. | The `"NONE"` sentinel has been removed. Use the standard <a href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html#noInherit()" target="_blank">Rest.noInherit</a> array to suppress inheritance, e.g. `@Rest(noInherit={"allowedHeaderParams"})`. The same substitution appl [...] +| `RestContext.Builder` — large stateful builder with dozens of fluent setters (`allowedHeaderParams(String)`, `encoders(Class<?>...)`, `callLogger(Class<?>)`, etc.). | **Removed.** All configuration now flows through `@Rest(...)` annotation attributes or <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" target="_blank">RestInject</a>-annotated methods/fields supplying named beans to the REST bean store. See the per-setting migration table in the 9.5 release notes. | +| `RestOpContext.Builder` — large stateful builder with dozens of fluent setters. | **Removed.** Same replacement model as `RestContext.Builder` — `@RestOp(...)` / `@RestGet(...)` / `@RestPost(...)` annotation attributes and `@RestInject` beans. | +| Sub-builder chaining via `RestContext.Builder.encoders()`, `.parsers()`, `.serializers()`, etc. (returned mutable child builders that user code chained fluent calls on). | **Removed.** Compose by supplying a fully-built `EncoderSet` / `ParserSet` / `SerializerSet` bean via `@RestInject(name="encoders" / "parsers" / "serializers" / ...)`, or rely on the `@Rest(encoders=..., parsers=..., serializers=...)` class array attributes. | +| `static Optional<X> createXxx(BeanStore, Resource, Logger, ...)` magic-named static factory methods on the resource class — used to supply `EncoderSet`, `ParserSet`, `SerializerSet`, `BeanContext`, `CallLogger`, `DebugEnablement`, `StaticFiles`, `SwaggerProvider`, `HeaderList`, `NamedAttributeMap`, etc. The framework discovered these by name via `BeanCreateMethodFinder` reflection. | Annotate the same method with <a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html" [...] +| `RestContext.Builder.defaultAccept(String)` / `defaultContentType(String)` convenience setters. | Use the new `@Rest(defaultAccept="...", defaultContentType="...")` annotation attributes (also available on `@RestOp` / `@RestGet` / etc.). They expand internally into `Accept` / `Content-Type` entries on `defaultRequestHeaders`, preserving the semantic. | +| `RestOpContext.Builder.dotAll()` flag. | **Removed.** The flag is now inferred from the URL pattern itself — a path containing `**` or `/.*` implies `dotAll=true`. Delete the call; the URL pattern dictates behavior. | +| `RestOp(defaultCharset)` / `RestOp(maxInput)` resolved by falling back to `RestContext`-level builder values. | Both fall back to the resource class's `@Rest(defaultCharset)` / `@Rest(maxInput)` annotation values directly (walking the class hierarchy, governed by `@Rest(noInherit={...})`). The `RestContext`-level builder fields/getters have been removed; they were only ever populated by the now-deleted builder. | +| Lifecycle method getter return types — `RestContext.getStartCallMethods()`, `getEndCallMethods()`, `getPostCallMethods()` (and similar for pre-call etc.) returned `MethodInvoker[]` for some lists and `MethodList` for others. | All seven lifecycle method lists now return `MethodList` for consistency. Callers that iterated `MethodInvoker[]` should migrate to `MethodList`'s iteration API (or call `.toArray(new MethodInvoker[0])` if they truly need an array). | +| Reusable bean definitions had to be declared inline on each REST resource class via `@RestInject`-annotated methods/fields, with no first-class way to share them across multiple resources without using a full DI container like Spring. | New `@Rest(beans={MyConfig.class, ...})` attribute. The named classes are scanned for `@RestInject` members and their beans are contributed to the REST bean store, Spring-`@Configuration`-style. `@RestInject` on the resource class still works as before; [...] +| `RestContext.create(resourceClass, parentContext, servletConfig).init(supplier).path(p).children(c).build()` static-factory chain — used by mock REST clients, child-resource bootstrap, and `RestServlet.init(ServletConfig)`. | **Removed.** Use the new <a href="/site/apidocs/org/apache/juneau/rest/RestContext.html#%3Cinit%3E(org.apache.juneau.rest.RestContextInit)" target="_blank">`new RestContext(RestContextInit init)`</a> constructor with a <a href="/site/apidocs/org/apache/juneau/rest [...] +| Resource classes could expose a `public MyResource(RestContext.Builder builder) throws Exception { builder.path(...); builder.children(...); }` constructor and have the framework inject the in-flight builder so the resource could imperatively configure itself. | **Removed.** The Builder-injection protocol is gone. Resource classes must declare configuration declaratively via `@Rest(...)` annotation attributes and `@RestInject` members, or pass values through `RestContextInit` when cons [...] +| Per-operation `@RestInit public void init(RestOpContext.Builder b) { ... }` hook — the framework discovered every `@RestInit` method whose parameter list contained `RestOpContext.Builder` and invoked it once per `@RestOp`-annotated method, threading the in-flight per-op builder so the hook could imperatively customize a single operation's context. | **Removed.** The per-op `@RestInit(RestOpContext.Builder)` injection protocol is gone. All operation-level configuration is now expressed [...] +| `RestOpContext.create(java.lang.reflect.Method, RestContext)` static factory + the fluent `.beanStore(...).type(...).build()` chain — used internally by `RestContext` and (rarely) by user code building one-off `RestOpContext` instances. | **Removed.** The two internal callers in `RestContext.Builder.createRestOperations` migrated to direct constructor invocation: `new RestOpContext(method, context)` for the standard path and `new RrpcRestOpContext(method, context)` for the RRPC special [...] +| Class-level `@RestInit public void init(RestContext.Builder b) { ... }` hook — the framework added the in-flight `RestContext.Builder` to the resource's bean store so any `@RestInit` method that declared a `RestContext.Builder` parameter received it and could imperatively configure the resource-level context (`builder.path(...)`, `builder.children(...)`, `builder.encoders(...)`, etc.). | **Removed.** The class-level Builder-injection protocol is gone — `RestContext.Builder` is no longe [...] + +| Custom annotation appliers — user code that subclassed the internal `AnnotationApplier<Rest, RestContext.Builder>` (or `AnnotationApplier<RestOp, RestOpContext.Builder>`) to extend the annotation-processing pass (the `apply(AnnotationInfo<A>, B builder)` hook invoked once per annotation during context construction). | **Removed.** The builder-based apply-pass is gone; `RestAnnotation.Apply` (`RestContextApply`) is now a package-private nested class inside `RestContext` and is not exten [...] +| Custom `RestAnnotation.create(...)` / `RestOpAnnotation.create(...)` builder-of-builders patterns — programmatic construction of `@Rest` / `@RestOp` annotation proxies used to feed synthetic annotations into the builder apply-pass (common in test fixtures and extension libraries). | The annotation proxy builders still exist for test use (`RestAnnotation.create()` / `RestOpAnnotation.create()` are still available via annotation-test helpers), but they no longer feed into a builder apply [...] + +<!-- Additional rows will be populated as 9.5 breaking changes land. See todo/TODO-17 for the + ongoing 9.2.1 audit. --> diff --git a/sidebars.ts b/sidebars.ts index e5ca2e66ac..507daa3e3a 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -783,6 +783,11 @@ const sidebars: SidebarsConfig = { id: 'topics/04.07.JuneauBeanSwagger2', label: '4.7. juneau-bean-swagger-v2', }, + { + type: 'doc', + id: 'topics/04.08.JuneauBeanMcp', + label: '4.8. juneau-bean-mcp', + }, ], }, { @@ -1486,6 +1491,18 @@ const sidebars: SidebarsConfig = { }, ], }, + { + type: 'category', + label: '10a. juneau-rest-server-mcp', + collapsed: true, + items: [ + { + type: 'doc', + id: 'topics/10a.01.JuneauRestServerMcpBasics', + label: '10a.1. juneau-rest-server-mcp Basics', + }, + ], + }, { type: 'category', label: '11. juneau-rest-server-springboot', @@ -1964,6 +1981,18 @@ const sidebars: SidebarsConfig = { }, ], }, + { + type: 'category', + label: '23. V9.5 Migration Guide', + collapsed: true, + items: [ + { + type: 'doc', + id: 'topics/23.01.V9.5-migration-guide', + label: '23.1. V9.5 Migration Guide', + }, + ], + }, ], }, // Developer Info section
