ADR-0032 — Handler discovery recognizes meta-annotations¶
- Status: Accepted
- Date: 2026-07-31
Context¶
mocapi discovers handlers by scanning Spring beans for methods carrying
@McpTool / @McpPrompt / @McpResource / @McpResourceTemplate
(handlers design; ADR-0010/ADR-0011).
The scan (HandlerMethodsCache) and every attribute read
(HandlerKind, the *Handlers.build methods) used raw reflection —
Method.getAnnotation, Method.isAnnotationPresent,
MethodUtils.getMethodsListWithAnnotation — which only sees an
annotation when it is placed directly on the method.
The planned MCP Apps module (io.modelcontextprotocol/ui) wants an
ergonomic single annotation, @McpAppResource, that both registers a
ui:// resource and defaults its MIME type — i.e. a composed annotation
meta-annotated with @McpResource. Raw reflection cannot see
through a meta-annotation, so such a composed annotation would be
invisible to discovery. The alternatives were worse: a bespoke,
per-extension resource-registration SPI, or forcing authors to stack two
annotations. Making discovery meta-annotation aware is a single, general
capability that any future extension can reuse.
Decision¶
Detect and read handler annotations through Spring's merged-annotation model, and permit the handler annotations to be used as meta-annotations.
- Detection uses
MergedAnnotations.from(method).isPresent(type)inHandlerMethodsCache(both the bean prefilter and the per-method grouping) and inHandlerKind.of. Merged detection is a strict superset of raw detection, so directly-annotated methods are still found. - Attribute reads use
AnnotatedElementUtils.findMergedAnnotation(method, type)inHandlerKind.nameOf,CallToolHandlers.build,GetPromptHandlers.build,ReadResourceHandlers.build, andReadResourceTemplateHandlers.build. This resolves@AliasForattribute overrides, so a composed annotation can alias, e.g., the resourceuri. - The four handler annotations gain
ElementType.ANNOTATION_TYPEin their@Target(in addition toMETHOD) so they may legally be used as meta-annotations. This is additive — method placement is unchanged.
The scanned annotation-type list is unchanged: a composed annotation is
discovered under its meta-annotation (e.g. @McpAppResource is found
when scanning for @McpResource), so no registration SPI is required.
Consequences¶
Extensions can ship ergonomic composed annotations that register through
the existing scan with zero further core change — the Apps module needs
no resource-registration SPI. The capability is general (works for tool,
prompt, resource, and resource-template annotations alike) and is proven
end-to-end by a test that discovers a meta-annotated resource and
resolves an @AliasFor on uri.
Cost: discovery now depends on spring-core's annotation model (already
on the classpath) rather than Commons Lang MethodUtils, and the scan
iterates Class.getMethods() filtering by merged presence. This is
startup-only work with no hot-path impact. The change is behavior-
preserving for all existing directly-annotated handlers (the full
handler/discovery/observability suites pass unchanged).
Non-goal: this does not change which annotations are handler annotations, nor how handlers are built once discovered — only how the existing annotations are detected and read.
Code anchors:
mocapi-autoconfigure/src/main/java/com/callibrity/mocapi/server/autoconfigure/HandlerMethodsCache.javamocapi-server/src/main/java/com/callibrity/mocapi/server/handler/HandlerKind.javamocapi-server/src/main/java/com/callibrity/mocapi/server/{tools/CallToolHandlers,prompts/GetPromptHandlers,resources/ReadResourceHandlers,resources/ReadResourceTemplateHandlers}.javamocapi-api/src/main/java/com/callibrity/mocapi/api/{tools/McpTool,prompts/McpPrompt,resources/McpResource,resources/McpResourceTemplate}.java(@Target)mocapi-autoconfigure/src/test/java/com/callibrity/mocapi/server/autoconfigure/ResourceServiceAutoConfigurationTest.java(discovers_meta_annotated_resource_…)