Skip to content

Releases: callibrity/mocapi

0.17.0

07 May 13:39

Choose a tag to compare

Changed

  • Tool input/output schemas now mark fields as required by default. When a tool is declared with @McpToolParams on a record (or any Jackson-deserializable type), every component is now listed in the generated input schema's required array unless it is explicitly opted out. The same applies to record-shaped output schemas. The previous behavior emitted no required array at all for these shapes, which was misleading: tool authors writing record HelloRequest(String firstName, String lastName) clearly meant both fields to be required, but the schema told clients they were optional. The new policy mirrors the per-parameter path that has always been required-by-default.

    Opt-outs (any one of these makes a field optional):

    • declare the type as Optional<T>,
    • annotate with @jakarta.annotation.Nullable, or
    • annotate with @io.swagger.v3.oas.annotations.media.Schema(requiredMode = NOT_REQUIRED).

    Migration. If you have a record component that was implicitly optional under the old behavior, switch its type to Optional<T> (the most idiomatic option), or add @Nullable / @Schema(requiredMode = NOT_REQUIRED). Clients that were sending requests missing such fields will otherwise be rejected at schema validation time once you upgrade.

0.16.0

07 May 13:14

Choose a tag to compare

Changed

  • DELETE /mcp now returns 200 OK instead of 204 No Content. The MCP Streamable HTTP spec only constrains the 405 "I refuse" case for client-initiated session termination; any 2xx is conformant. The official TypeScript SDK returns 200, and at least one widely used client (the MCP Inspector's proxy) mishandles 204 because its underlying fetch implementation rejects the null-body status when the proxy attaches a non-null body. Switching to 200 matches the reference implementation and unblocks those clients without changing semantics — the body is still empty and terminate() still runs.

0.15.0

07 May 11:31

Choose a tag to compare

Added

  • Startup banner. A new MocapiStartupBanner logs an ASCII-art
    mocapi block plus a one-shot summary of the running configuration
    when the application becomes ready — registered tool/prompt/resource
    counts, the active transport (with endpoint path), the session-store
    backend, OAuth2 state, and any observability modules. Discovery is
    bean-presence driven via runtime class lookup so the banner has no
    hard classpath dependency on optional starters; each line gracefully
    collapses to a sensible default when the backing module is absent.
    Gated by mocapi.banner.enabled (default true); set to false to
    suppress.

Changed

  • Strict tool return-type contract (breaking). @McpTool method
    return types are now checked at handler-build time and classified
    once into one of four permitted shapes; the old runtime type switch
    in McpToolsService.toCallToolResult is gone, and every handler
    carries a pre-selected ResultMapper. Permitted shapes (applied to
    the effective type after recursively peeling any number of
    CompletionStage layers): (1) void/Void → empty CallToolResult;
    (2) CallToolResult → passthrough, author owns the result; (3)
    CharSequence (including String) → single text content block, no
    structured content; (4) a record/POJO whose Jackson-derived schema is
    type: "object" with declared properties → advertised as
    outputSchema, serialized into structuredContent. Everything else
    (List<T>, Map<K,V>, primitives, arrays, Optional<T>,
    JsonNode/ObjectNode, raw/wildcard CompletionStage, empty
    records, …) fails to register with a message naming the offending
    type and suggesting a fix. Previously,
    non-object returns silently advertised a misleading outputSchema
    and/or shipped structuredContent = null in violation of the MCP
    spec; the new contract makes those mistakes impossible.

Added

  • CompletionStage<T> / CompletableFuture<T> tool return types.
    Mocapi awaits the future on an innermost
    CompletionStageAwaitingInterceptor and applies the same mapping
    rules to the awaited value. The interceptor loops, so any depth of
    nesting (CompletionStage<CompletionStage<X>> etc.) is unwrapped
    inside-out by a single instance. CompletionException is unwrapped
    so domain exceptions surface with their original type. Raw and
    wildcard CompletionStage declarations are rejected at registration
    (no concrete inner type to derive a schema from).
  • CharSequence return type shortcut. Tools that want to return
    plain text can declare String (or any CharSequence) and mocapi
    produces a text-only CallToolResult — no need to hand-build a
    CallToolResult or invent a single-field record.
  • Public ResultMapper API. ResultMapper is a sealed interface
    with four impls: VoidResultMapper, PassthroughResultMapper,
    TextContentResultMapper, StructuredResultMapper. Users building
    CallToolHandlers directly (outside the @McpTool auto-detection
    path) can select a mapper explicitly.
  • McpToolException for structured tool errors. A new public
    exception type in mocapi-api lets tool authors signal execution
    failures that carry a structured payload and/or extra content blocks,
    not just a text message. Throwing it (or a subclass) produces a
    CallToolResult with isError: true, the exception message as the
    first text block, getAdditionalContent() blocks appended after it,
    and getStructuredContent() serialized into structuredContent (the
    payload must serialize to a JSON object per MCP spec). Handled
    identically when thrown synchronously or from a failed
    CompletionStage. Authors are encouraged to subclass for reusable,
    domain-specific error shapes; mocapi catches the parent type. No
    "error code" field — per the MCP spec, tool-execution errors have no
    protocol-level code slot; put any code inside structuredContent.

0.14.0

22 Apr 21:38

Choose a tag to compare

Added

  • Opt-in output schema validation for tool calls. A new mocapi.tools.validate-output property (default false) installs an OutputSchemaValidatingInterceptor next to the existing input validator on every tool handler. When on, each tool's return value is validated against its derived output schema and a mismatch fails loudly with JSON-RPC -32603, surfacing schema/reality drift at test time instead of as an opaque gateway 500 in production. The interceptor switches on the result shape: CallToolResult validates structuredContent (skipped when null), JsonNode validates directly, null is skipped, anything else is serialized via valueToTree and validated. Flip it on in @SpringBootTest runs to catch drift before clients do.

Changed

  • mocapi-server tests aligned to draft_2020_12. Production has defaulted mocapi.tools.schema-version to draft_2020_12 for a while, but three mocapi-server tests were still instantiating DefaultMethodSchemaGenerator with SchemaVersion.DRAFT_7. Flipping them to match the production default is a correctness fix, not cosmetic — and it also cut mocapi-server's test-suite wall time from ~15.7s to ~5.7s.

Documentation

  • Removed two obsolete example ITs (PostgresqlStarterAutoConfigurationIT, RedisStarterAutoConfigurationIT) that spun up Testcontainers only to verify Substrate's own Postgres/Redis auto-configurations. They didn't exercise a single mocapi type and referred to mocapi-specific "starters" that no longer exist. Saves ~22s of Docker per CI run.

0.13.0

21 Apr 17:23

Choose a tag to compare

Highlights

  • /actuator/mcp now shows handler internals. Each tool / prompt / resource / resource-template entry carries a handler block with kind, declaring class, method name, and the outer-to-inner toString sequence of every interceptor wrapping the reflective call. Reading the list top-to-bottom reconstructs the stratum chain — useful for "why is Jakarta validation firing before authorization?"–class questions. Actuator counts and lists are now operator-view (every registered handler, no guard filtering); filtering still applies to the MCP protocol's tools/list.
  • Interceptor strata API. Customizers no longer negotiate @Order around a generic interceptor(...) bucket. Each *HandlerConfig gains five typed add methods — correlationInterceptor, observationInterceptor, auditInterceptor, validationInterceptor, invocationInterceptor — and the handler builder assembles CORRELATION → OBSERVATION → AUDIT → AUTHORIZATION (guards) → VALIDATION (JSON schema + semantic) → INVOCATION in a stable outer-to-inner order. Fixes the "Jakarta Bean Validation runs before the authorization gate" smell from earlier versions.
  • mocapi-oauth2 restructured. Filter chain split into separate metadata and MCP beans; RFC 9728 metadata document now populated by five independently-overridable facet customizers; McpTokenStrategy SPI abstracts JWT vs. opaque-token validation. Package split: token strategies in .oauth2.token, metadata customizers in .oauth2.metadata.
  • McpToolContext cleanup. Logging concerns moved to McpLogger. ctx.log(...) and ctx.isEnabled(...) are gone; use ctx.logger(name).info("...") and ctx.logger(name).isDebugEnabled(). McpLogger itself compressed to two abstract methods with default per-level shortcuts.
  • mocapi-bom re-exports methodical-bom. Consumers get the Methodical version mocapi was built against transitively, preventing NoSuchMethodError from stale methodical-core pins.

Breaking changes

McpToolContext lost log(...) and isEnabled(...)

Migrate:

// Before
ctx.log(LoggingLevel.INFO, "weather", "Processing " + city);
if (ctx.isEnabled(LoggingLevel.DEBUG)) ctx.log(LoggingLevel.DEBUG, "weather", expensiveFormat());

// After
ctx.logger("weather").info("Processing {}", city);
var log = ctx.logger("weather");
if (log.isDebugEnabled()) log.debug(expensiveFormat());

handlerName() and logger(String) are now abstract — custom McpToolContext test doubles must implement them. The runtime DefaultMcpToolContext wires both correctly.

*HandlerConfig.interceptor(...) removed

Customizer code that added a generic interceptor must pick a stratum:

// Before
config.interceptor(new MyTimingInterceptor());

// After — pick the stratum that matches intent
config.observationInterceptor(new MyTimingInterceptor());

Mapping:

Old call New call
MDC / request-id stamping correlationInterceptor
spans / metrics observationInterceptor
audit / outcome recording auditInterceptor
schema / bean validation validationInterceptor
retry / timeout wrappers invocationInterceptor

Guards are still added via config.guard(Guard) — they're the AUTHORIZATION stratum.

Service accessor semantics

McpToolsService.allToolDescriptors() / McpPromptsService.allDescriptors() / McpResourcesService.allResourceDescriptors() / allResourceTemplateDescriptors() now return every registered descriptor, not the per-caller-visible subset. Per-caller guard filtering still applies at the MCP list endpoints (listTools, listPrompts, listResources, listResourceTemplates). The visibilityFilter() hook on PaginatedService is gone; subclasses pass a Predicate<T> to the new paginate(Predicate, params, resultCtor) overload.

mocapi-oauth2 reshape

  • Single combined SecurityFilterChain split into mcpMetadataFilterChain (permit-all, RFC 9728 path) and mcpFilterChain (authenticated, MCP endpoint).
  • OAuth2ProtectedResourceMetadataCustomizerMcpMetadataCustomizer (moved to com.callibrity.mocapi.oauth2.metadata).
  • McpSecurityFilterChainCustomizerMcpFilterChainCustomizer; new McpMetadataFilterChainCustomizer SPI for the metadata chain.
  • McpSecurityFilterChainBuilder deleted; use the static factories McpFilterChains.createMcpMetadataFilterChain(...) / createMcpFilterChain(...).
  • Token strategy abstracted: register a @Primary McpTokenStrategy bean to swap JWT/opaque wholesale; the built-ins auto-select via @ConditionalOnBean on JwtDecoder / OpaqueTokenIntrospector.
  • McpMetadataCustomizers.of(...) helper removed — five baseline facet beans (ResourceMetadataCustomizer, AuthorizationServersMetadataCustomizer, ScopesSupportedMetadataCustomizer, ResourceNameMetadataCustomizer, ClaimsMetadataCustomizer) replace it.
  • Packages split: tokens in .oauth2.token, metadata customizers in .oauth2.metadata.

Requirements

Methodical 0.8.0 → 0.9.2. mocapi-bom re-exports methodical-bom so importers get the right version transitively.

0.12.1

20 Apr 20:27

Choose a tag to compare

[0.12.1] - 2026-04-20

Fixed

  • mocapi-otel added to mocapi-bom. The module was added to the reactor
    in 0.12.0 but missed in the BOM's <dependencyManagement> block, so users
    importing the BOM had to specify <version> explicitly on mocapi-otel.
    Now BOM-managed like every other published module. Pure BOM fix — no code
    changes from 0.12.0.

0.12.0

20 Apr 20:13

Choose a tag to compare

[0.12.0] - 2026-04-20

Added

  • Two-layer OpenTelemetry trace hierarchy with MCP / GenAI / JSON-RPC
    semantic conventions.
    Traces now carry two nested spans per handler
    invocation:

    • Outer jsonrpc.server — emitted by ripcurl's new ripcurl-o11y
      module via a per-@JsonRpcMethod interceptor. Carries OpenTelemetry
      RPC / JSON-RPC semconv attrs: rpc.system.name=jsonrpc,
      jsonrpc.protocol.version=2.0, jsonrpc.request.id (from ripcurl's new
      JsonRpcDispatcher.CURRENT_REQUEST ScopedValue), rpc.response.status_code
      ("OK" on success, numeric JSON-RPC error code on failure), error.type.
      mocapi-o11y enriches each one via a new McpObservationFilter that adds
      mcp.method.name, mcp.session.id, mcp.protocol.version from the bound
      McpSession / request envelope.
    • Inner mcp.handler.execution — emitted by mocapi-o11y's new
      McpHandlerObservationInterceptor for every tool / prompt / resource /
      resource-template invocation. Carries mcp.handler.kind and the
      kind-specific GenAI / MCP attrs: gen_ai.operation.name=execute_tool +
      gen_ai.tool.name for tools; gen_ai.prompt.name for prompts;
      mcp.resource.uri for resources. Only fires for methods that route
      through a mocapi handler — dispatch-only methods (tools/list,
      initialize, notifications) emit only the outer span.

    Attributes align with the OTel MCP semconv
    (https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/) and JSON-RPC
    semconv (https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/).
    Known gaps: span kind is the default INTERNAL on both spans rather than
    SERVER (the ambient HTTP span already carries SERVER via Spring MVC);
    tool_error for CallToolResult.isError=true is not yet emitted.

  • mocapi-otel module. Source-less dependency bundle pulling mocapi-o11y
    plus spring-boot-starter-opentelemetry (Spring Boot 4's OTel SDK +
    Micrometer Observation → OTel tracing bridge + autoconfig). Add this module
    plus the exporter for your target backend — opentelemetry-exporter-otlp
    for Jaeger / Tempo / Grafana, spring-cloud-azure-starter-monitor for
    Azure App Insights, Datadog, etc. — and traces flow end-to-end. No default
    properties are set: choices like management.otlp.metrics.export.enabled
    are backend-specific and left to the consumer. Same source-less pattern as
    mocapi-jakarta-validation. See docs/otel.md.

  • HandlerKind enum (com.callibrity.mocapi.server.handler.HandlerKind).
    Replaces the scattered string constants "tool" / "prompt" / "resource"
    / "resource_template" used across mocapi-audit, mocapi-logging, and
    mocapi-o11y as MDC values, metric labels, and span tag values. Lives in
    mocapi-server (not mocapi-api) since it's an internal classification
    consumed by cross-cutting feature modules, not something users writing
    @McpTool / @McpPrompt / @McpResource / @McpResourceTemplate reference
    in their code. Each enum value has a tag() method returning the stable
    string form.

Changed

  • Feature modules are now self-sufficient. Adding a mocapi-* feature
    module to a Spring Boot app is sufficient to activate the feature — no more
    "also add spring-boot-starter-X" footnotes. Specifically:
    • mocapi-o11y now transitively pulls spring-boot-micrometer-observation,
      so an ObservationRegistry bean is always present when mocapi-o11y is on
      the classpath (previously the autoconfig silently no-op'd unless the user
      separately added Actuator or a tracing starter).
    • mocapi-actuator now depends on spring-boot-starter-actuator (the
      starter) instead of just spring-boot-actuator (the library), so the
      /actuator/mcp endpoint actually serves over HTTP without additional
      wiring.
    • mocapi-oauth2 drops its unused spring-boot-starter-validation
      dependency (@Validated on MocapiOAuth2Properties had no constraint
      annotations — dead weight).
  • MocapiJakartaValidationAutoConfiguration no longer declares a shared
    JakartaValidationInterceptor @Bean.
    The interceptor is stateless; each
    per-handler customizer now takes Validator directly and constructs a
    local JakartaValidationInterceptor inline. One less bean in the context,
    nothing functionally changes.

Breaking changes

  • mocapi-o11y / mocapi-audit / mocapi-logging interceptor constructors take
    HandlerKind instead of String kind.
    Users constructing
    McpHandlerObservationInterceptor, McpMdcInterceptor, or
    AuditLoggingInterceptor directly (rare — normally attached via the
    per-kind customizer beans in the matching autoconfig) must pass a
    HandlerKind enum value (HandlerKind.TOOL, etc.) instead of the raw
    string. Emitted tag / MDC / audit-field string values are unchanged.
  • McpObservationInterceptor removed. Replaced by the split
    JsonRpcObservationInterceptor in ripcurl-o11y (JSON-RPC layer) plus
    McpHandlerObservationInterceptor and McpObservationFilter in
    mocapi-o11y (MCP layer). Direct consumers (rare) migrate to the new
    types.
  • Old HandlerKinds holder class removed from mocapi-api. Replaced by
    the HandlerKind enum in mocapi-server. No known user-facing callers
    since HandlerKinds was an internal helper.

Requirements

  • ripcurl 2.11.0. Adds:
    • JsonRpcDispatcher.CURRENT_REQUEST ScopedValue bound by the dispatcher
      during each call / notification — makes the full envelope (method, id,
      version, params) readable from per-method interceptors and downstream
      code.
    • New ripcurl-o11y module with JsonRpcObservationInterceptor +
      RipCurlObservationAutoConfiguration. Emits jsonrpc.server
      observations carrying OTel JSON-RPC semconv attrs. Activates when
      Micrometer's ObservationRegistry is present on the classpath.
      mocapi-o11y's autoconfig now composes on top of ripcurl-o11y's observation:
      the outer JSON-RPC span comes from ripcurl, mocapi adds MCP-specific
      enrichment and the inner handler-layer span.

Fixed

  • GraalVM native-image AOT failure caused by static-final SLF4J
    loggers.
    Spring Boot AOT pulls @AutoConfiguration-annotated
    classes (and, transitively, classes their bean-factory methods
    return) into GraalVM's build-time class-initialization pass. The
    first LoggerFactory.getLogger(...) call inside any such class's
    <clinit> runs before Logback's SLF4JServiceProvider has been
    registered with Substrate's service-loader system, so SLF4J
    falls back to NOP_FallbackServiceProvider and caches that
    decision forever. The NOP instance then gets baked into the image
    heap, and GraalVM rejects it because its class is
    initialize-at-run-time. Converted every static-final Logger in
    main sources — both explicit private static final Logger fields
    and the ones Lombok generated via @Slf4j — to non-static
    instance fields (or removed entirely, for the SSE writer chain
    where the log lines were low-value trace noise on a hot path).
    Constructor execution happens at runtime after Logback is fully
    wired, so the NOP race no longer fires. Affects:
    MocapiServer{Tools,Prompts,Resources}AutoConfiguration,
    MocapiAudit/Logging/O11y/JakartaValidation/SpringSecurityGuards
    AutoConfiguration, DefaultMcpServer,
    McpSessionService, McpPromptsService, McpResourcesService,
    McpToolsService, McpLifecycleService, StdioServer,
    StreamableHttpTransport, AuditLoggingInterceptor, and the
    three streamable-HTTP message writers (Direct, Sse, Closed
    — the writers lose their loggers entirely).

0.11.0

20 Apr 04:40

Choose a tag to compare

[0.11.0] - 2026-04-20

Breaking changes

  • Bean-level MethodInterceptor autowiring removed from handler
    autoconfigs.
    The three handler autoconfigs
    (MocapiServerToolsAutoConfiguration,
    MocapiServerPromptsAutoConfiguration,
    MocapiServerResourcesAutoConfiguration) no longer pick up bare
    MethodInterceptor<? super X> beans from the context and apply
    them to every handler of that kind. Interceptors now attach
    exclusively via the *HandlerCustomizer SPI. Migration: wrap
    each former MethodInterceptor bean in a customizer, e.g.
    @Bean CallToolHandlerCustomizer c() { return cfg -> cfg.interceptor(new MyInterceptor()); }.
    The same applies for GetPromptHandlerCustomizer,
    ReadResourceHandlerCustomizer, and
    ReadResourceTemplateHandlerCustomizer.

  • Artifact layout consolidated around mocapi-autoconfigure.
    All feature-level -spring-boot-starter modules have been
    collapsed into a single mocapi-autoconfigure module containing
    every autoconfig (server, transports, OAuth2, logging,
    observability, Jakarta Validation, and the /actuator/mcp
    endpoint). Each autoconfig is @ConditionalOnClass-gated on a
    class from its feature code module, so users light up a feature
    by adding the feature module to their classpath — no
    mocapi-specific starter name to remember. Old-to-new artifact
    mapping:

    Old artifact New artifact
    mocapi-oauth2-spring-boot-starter mocapi-oauth2
    mocapi-jakarta-validation-spring-boot-starter mocapi-jakarta-validation
    mocapi-logging-spring-boot-starter mocapi-logging
    mocapi-o11y-spring-boot-starter mocapi-o11y
    mocapi-actuator-spring-boot-starter mocapi-actuator (pair with spring-boot-starter-actuator to activate MocapiActuatorAutoConfiguration in mocapi-autoconfigure)

    The two transport starters
    (mocapi-streamable-http-spring-boot-starter,
    mocapi-stdio-spring-boot-starter) remain user-facing and now
    pull in mocapi-autoconfigure + their transport module.
    mocapi-jakarta-validation is a new code module bundling the
    same runtime deps the old starter packaged (no source of its own).
    This is pre-1.0 — no compat shims. Migration is a one-line
    artifactId rename in each consumer pom.

Changed

  • mocapi-actuator extracted from mocapi-autoconfigure. The
    actuator endpoint code (McpActuatorEndpoint, McpActuatorSnapshot,
    McpActuatorSnapshots) now lives in its own mocapi-actuator module
    — matching the pattern used by every other feature (mocapi-oauth2,
    mocapi-logging, mocapi-o11y, mocapi-jakarta-validation):
    feature code in a dedicated module, feature autoconfig in
    mocapi-autoconfigure. Activation is unchanged in spirit but now
    requires both mocapi-actuator and spring-boot-starter-actuator
    on the classpath. Side benefit: consumers who want only the
    snapshot-building machinery for a custom admin endpoint can depend
    on mocapi-actuator without pulling in the autoconfig.

Added

  • mocapi-audit module — structured audit logging for every
    handler invocation.
    Drop in mocapi-audit and every MCP tool /
    prompt / resource / resource-template call emits one INFO event
    on the mocapi.audit SLF4J logger carrying caller identity,
    session id, handler kind/name, outcome
    (success/forbidden/invalid_params/error), duration, and
    (opt-in via mocapi.audit.hash-arguments=true) a SHA-256 hash
    of the arguments. Caller identity is extracted by a pluggable
    AuditCallerIdentityProvider; the default reads Spring Security's
    SecurityContextHolder when present and returns anonymous
    otherwise. No bundled encoder — users route the mocapi.audit
    logger to their SIEM / file sink with standard Logback config.
    Audit attaches at @Order(200); mocapi-logging's MDC
    customizers now pin to @Order(100) and mocapi-o11y's
    observation customizers to @Order(300) so the in-flight
    interceptor chain runs MDC → audit → o11y → user → guard →
    validation → method. See Audit.

  • Custom ParameterResolvers via the customizer SPI. Each
    *HandlerConfig (tool, prompt, resource, resource-template)
    gains a resolver(ParameterResolver<? super X>) mutator that
    slots alongside the existing interceptor(...) and guard(...)
    entry points. Customizer beans (e.g. CallToolHandlerCustomizer)
    can now register bespoke resolvers — for patterns like
    @CurrentTenant String tenant pulled from the session — without
    forking the autoconfig. User resolvers are placed ahead of the
    structural catch-all (Jackson for tools, StringMapArgResolver
    for prompts and resource templates), so a specific supports()
    check always wins over the generic fallback. Builder signatures
    changed: CallToolHandlers.build(...) takes an ObjectMapper
    (the structural resolvers are constructed internally);
    GetPromptHandlers.build(...) and
    ReadResourceTemplateHandlers.build(...) take a
    ConversionService; none of the builders accept a resolver list
    any longer. See
    docs/tools-guide.md ("Custom Parameter
    Resolvers") and docs/prompts-guide.md.

  • New module mocapi-spring-security-guards. First real Guard
    implementation mocapi ships. Two method-level annotations —
    @RequiresScope(String[]) (all scopes required, AND semantics) and
    @RequiresRole(String[]) (any role grants access, OR semantics) —
    placed on user @McpTool / @McpPrompt / @McpResource /
    @McpResourceTemplate methods cause the module's autoconfig
    (MocapiSpringSecurityGuardsAutoConfiguration, in
    mocapi-autoconfigure) to attach a ScopeGuard / RoleGuard to
    that handler via the customizer SPI. Both guards read
    SecurityContextHolder.getContext().getAuthentication() at check
    time; denial hides the handler from list operations and returns
    JSON-RPC -32003 with a descriptive reason
    (unauthenticated / missing scope(s): ... / insufficient role).
    Role values may be bare (ADMIN) or prefixed (ROLE_ADMIN). The
    autoconfig is doubly @ConditionalOnClass-gated: on the feature
    module's ScopeGuard class and on Spring Security's
    Authentication class, so it stays dormant when either is absent.
    See docs/guards.md and
    docs/authorization.md ("Per-handler
    authorization" section).

  • Guard SPI (com.callibrity.mocapi.server.guards). Plugins attach
    per-handler Guards via the existing *HandlerCustomizer beans; a
    guard's check() returns Allow or Deny(reason). Evaluation is AND
    with short-circuit on first Deny. A denied guard both hides the
    handler from list operations (tools/list, prompts/list,
    resources/list, resources/templates/list) and rejects invocation
    with JSON-RPC code -32003 (JsonRpcErrorCodes.FORBIDDEN) and message
    "Forbidden: <reason>". The SPI carries no framework coupling — guard
    implementations reach into their own auth model (Spring Security,
    McpSession.CURRENT, a plain ScopedValue, …). See
    docs/guards.md; the reference Spring Security
    implementation ships separately as mocapi-spring-security-guards.

  • New module mocapi-actuator-spring-boot-starter: a Spring Boot
    Actuator endpoint at /actuator/mcp that returns a read-only
    inventory of every MCP tool / prompt / resource / resource-template
    registered on the node. The response is a JSON document with a
    server block (name, version, protocolVersion), a counts
    block, and per-kind descriptor lists. Tool entries carry
    inputSchemaDigest / outputSchemaDigest — SHA-256 hex prefixed
    sha256: — rather than full schema bodies (the full schemas are
    still available via MCP tools/list). Activation follows standard
    actuator rules: the autoconfig is guarded by
    @ConditionalOnAvailableEndpoint, so users opt in via
    management.endpoints.web.exposure.include=mcp. BuildProperties
    is optional; if absent the server.version field is simply
    omitted. No session state, no metrics fold-in — session
    introspection is out of scope for a per-node snapshot, and metrics
    already have /actuator/metrics + /actuator/prometheus.

  • New module pair mocapi-o11y + mocapi-o11y-spring-boot-starter:
    single-interceptor observability for MCP handler invocations via
    the Micrometer Observation API. One McpObservationInterceptor
    wraps every @McpTool / @McpPrompt / @McpResource /
    @McpResourceTemplate call in an Observation, so the same code
    produces both metrics (when a MeterObservationHandler is
    registered) and distributed-tracing spans (when a
    TracingObservationHandler is registered) — whichever observation
    handlers the user has on their classpath participate automatically.

    Observation names: mcp.tool, mcp.prompt, mcp.resource,
    mcp.resource_template. Low-cardinality tags:
    mcp.handler.kind (tool / prompt / resource /
    resource_template) and mcp.handler.name (tool / prompt name, or
    resource URI / URI template). The standard outcome tag is added
    automatically by DefaultMeterObservationHandler on exception.

    The starter autoconfig activates only when an ObservationRegistry
    bean is present. Spring Boot 3+ auto-creates one whenever
    spring-boot-starter-actuator or any Micrometer Observation
    autoconfiguration is on the classpath, so adding this starter
    alongside e.g. micrometer-registry-prometheus or
    micrometer-tracing-bridge-otel just works. Stdio-only apps with
    no metrics / tracing stack on the classpath get no registry and no
    customizers wire — pure no-op.

    Wiring uses the per-handler customizer SPI (spec 180): four
    *HandlerCustomizer beans, one per handler kind, each closing over
    the descriptor's name / uri / uriTemplate at startup so the hot
    path does zero reflection.

  • Per-handler customizer SPI in `mocapi-...

Read more

0.10.0

18 Apr 20:47

Choose a tag to compare

Added

  • Auto-derivation of mocapi.oauth2.resource from the Spring Boot
    spring.security.oauth2.resourceserver.jwt.audiences property.
    When audiences has exactly one entry and mocapi.oauth2.resource
    is unset, mocapi publishes that single audience as the RFC 9728
    protected-resource metadata resource field. Common case for
    Auth0 / Okta / Keycloak / Entra setups with one logical API.
    Removes the duplicate-string-in-two-properties ceremony that the
    0.9.0 configuration required.

Changed

  • Startup-time invariant: mocapi.oauth2.resource (whether
    explicitly set or auto-derived) must be a member of the configured
    spring.security.oauth2.resourceserver.jwt.audiences list. A
    mismatch fails at application start with a descriptive error
    naming both the rejected resource and the accepted audiences.
    Rationale: clients that follow the protected-resource metadata
    document request tokens bound to the advertised resource
    identifier; if that identifier isn't in the server's accepted
    audiences, every token the client obtains would be rejected
    during validation. Catching that at startup is cheaper than a
    silently-broken deployment where every MCP request returns 401.

Notes

  • @NotBlank is no longer enforced on mocapi.oauth2.resource
    the property becomes optional with the auto-derivation handling
    the common case. Applications that were already setting the
    property with a matching audience keep working unchanged.
  • Documentation (docs/authorization.md) updated with the
    minimum-configuration example (two Spring Boot properties, no
    mocapi.oauth2.* needed), the recommended jwk-set-uri pattern
    to skip Spring's OIDC discovery HTTP call at startup, and an
    explicit note about the resource-must-be-in-audiences invariant.

0.9.0

18 Apr 19:31

Choose a tag to compare

Added

  • New mocapi-jakarta-validation-spring-boot-starter pom-only starter
    that turns on Jakarta Bean Validation across mocapi's reflective-
    dispatch surface. Bundles spring-boot-starter-validation,
    methodical-jakarta-validation, and ripcurl-jakarta-validation.
    Once on the classpath, @NotBlank/@Size/@Pattern/etc. on user
    @ToolMethod / @PromptMethod / @ResourceTemplateMethod
    parameters surface as MCP-spec-idiomatic errors per handler type:
    tools/call violations produce CallToolResult.isError=true (the
    spec's "Input validation errors" path for LLM self-correction),
    while prompts/get and resources/read violations produce JSON-RPC
    -32602 Invalid params with per-violation {field, message} detail
    in the response's data field. Mocapi's internal protocol handlers
    deliberately keep their hand-rolled checks so mocapi's own contract
    is always enforced regardless of consumer classpath — this starter
    is user-code-only. See docs/validation.md for setup and
    examples/jakarta-validation for a runnable app.
  • New mocapi-oauth2 module and mocapi-oauth2-spring-boot-starter
    that wire OAuth2 resource-server protection for the Streamable HTTP
    transport per MCP 2025-11-25 authorization. Adds bearer-token
    validation on the MCP endpoint, a 401 WWW-Authenticate challenge
    with resource_metadata="..." (RFC 8707), and an RFC 9728
    protected-resource metadata document served at
    /.well-known/oauth-protected-resource. Token validation (decoder,
    issuer, audience) uses the standard Spring Boot
    spring.security.oauth2.resourceserver.jwt.* properties; mocapi
    fills in the metadata document from mocapi.oauth2.* with
    fall-back to the configured issuer-uri. Two extension hooks —
    OAuth2ProtectedResourceMetadataCustomizer and
    MocapiOAuth2SecurityFilterChainCustomizer — let apps append
    claims or layer authorization rules without redeclaring the chain.
    End-to-end integration test bundles Spring Authorization Server
    in-process to verify the full client_credentials → bearer → /mcp
    flow. See docs/authorization.md for setup.
  • Opaque (non-JWT) access token support in mocapi-oauth2. When
    spring.security.oauth2.resourceserver.opaquetoken.* is configured
    instead of jwt.*, the chain switches to RFC 7662 introspection.
    Mocapi wraps Spring's OpaqueTokenIntrospector with an
    audience-enforcing decorator so the MCP-mandated aud check still
    runs (Spring's opaque path does not include an audience validator
    by default). JWT and opaque modes are mutually exclusive; JWT wins
    if both happen to be configured.