This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
infobip-openapi-mcp is an open-source Java framework for exposing OpenAPI-documented HTTP APIs as MCP (Model Context Protocol) servers. It is built on Spring AI and Spring Boot, and is published to Maven Central.
Project uses Java 21. Make sure that local tooling supports this version. DO NOT attempt to downgrade the source code to earlier Java versions. Look for newer JKD instead, for example using the SDKMAN version manager.
# Build the project (runs tests + spotless format check)
mvn verify
# Build skipping tests
mvn package -DskipTests
# Run all tests
mvn test
# Run tests for a specific module
mvn test -pl infobip-openapi-mcp-core
mvn test -pl infobip-openapi-mcp-spring-boot-starter -am
# Run a single test class
mvn test -pl infobip-openapi-mcp-core -Dtest=DiscriminatorFlattenerTest
mvn test -pl infobip-openapi-mcp-spring-boot-starter -am -Dtest=ToolCallIntegrationTest
# Apply code formatting (Palantir Java Format via Spotless)
mvn spotless:apply
# Check formatting without applying
mvn spotless:check
# Install git hooks (runs spotless:apply on pre-commit)
mvn install -Pgit-hook
# Check version of Java that is used by maven
mvn --version
# Check available Java versions with sdkman
PAGER=cat sdk list java | grep -E 'installed|local'
# Pick identifier of installed Java version equal or greater than 21 (for example 25-tem) and enable it with sdkman
sdk use java <identifier>ALWAYS do this after completing any coding task:
- Run
mvn spotless:applyas the final step before presenting results. - Update
CHANGELOG.mdunder the[Unreleased]section using the Keep a Changelog format (Added,Changed,Fixed,Removed). Write entries from a user perspective — describe the feature and its value, not the classes or internal mechanics behind it. It is fine to mention configuration properties needed to enable or customize a feature, or java interfaces that users can implement such asOpenApiFilterorApiRequestEnricher. Avoid class names, method names, test names, and other implementation details. - If you added or changed an external configuration property, add or update its row in the properties table in
README.md. - Check and update
CLAUDE.mdto reflect the new state of the project.
This is a two-module Maven project:
infobip-openapi-mcp-core— Framework core logic. No Spring Boot autoconfiguration, suitable as a library dependency.infobip-openapi-mcp-spring-boot-starter— Spring Boot autoconfiguration that wires the core beans. This is what users add to theirpom.xml.
The framework follows this startup flow:
OpenApiRegistryreads and caches the OpenAPI spec frominfobip.openapi.mcp.open-api-urlOpenApiFilterChainappliesOpenApiFilterbeans (e.g.,DiscriminatorFlattener,PatternPropertyRemover) to transform the specToolRegistryconverts each API operation into aRegisteredToolusingInputSchemaComposer,InputExampleComposer,ToolAnnotationResolver, and the configuredNamingStrategy- Tools are registered with the Spring AI MCP server (SSE, Streamable HTTP, Stateless HTTP, or stdio transport)
Runtime tool call flow:
ToolSpecBuilder → ToolCallFilterChain (ordered ToolCallFilter beans) → RegisteredTool (lowest precedence, makes
HTTP call via ToolHandler) → optional JsonDoubleSerializationCorrector retry logic
| Interface | Purpose |
|---|---|
OpenApiFilter |
Transform the OpenAPI spec before tool metadata is built; disable via infobip.openapi.mcp.filters.[filter-name]: false |
ApiRequestEnricher |
Modify HTTP requests to the downstream API (headers, metadata); failures are swallowed |
ToolCallFilter |
Intercept tool calls; can abort the chain unlike enrichers |
NamingStrategy |
Custom tool name generation; replace the default bean |
ErrorModelProvider |
Custom error response format returned to MCP clients |
Important: filters, enrichers, strategies and providers can be implemented by application code, which is outside the framework. You will not see those implementations in this project's source code. This is the supported way to extend and customize framework functionality. Take extra care to maintain backwards compatibility with unseen application code.
Authentication is handled by InitialAuthenticationFilter. When infobip.openapi.mcp.security.auth.enabled: true,
every MCP interaction delegates credential validation to the endpoint at infobip.openapi.mcp.security.auth.auth-url.
OAuth support (OAuthConfiguration, OAuthController) proxies /.well-known endpoints to the authorization server.
InputSchemaComposer merges OpenAPI path/query parameters and request body into a single MCP tool input JSON schema.
When both exist, parameters are wrapped under _params key and the request body under _body (configurable).
InputExampleComposer extracts request examples from OpenAPI parameters and request bodies (using precedence:
examples map > example field > schema.example) and composes them into a single example object matching
InputSchemaComposer's combination rules. ToolRegistry appends the result as a Markdown JSON code block to tool
descriptions.
DiscriminatorFlattener resolves OpenAPI discriminator patterns into JSON Schema–compatible oneOf/allOf structures
since MCP does not support OpenAPI discriminators natively.
ToolAnnotationResolver infers MCP tool annotations (readOnlyHint, destructiveHint, idempotentHint,
openWorldHint) from HTTP method semantics, then merges overrides from x-mcp-annotations vendor
extension on the Operation and from YAML config properties (infobip.openapi.mcp.tools.annotations.<tool-name>.*).
- Formatter: Palantir Java Format (enforced by Spotless on every build and pre-commit hook, run
mvn spotless:applyafter code changes!) - Null annotations: JSpecify (
@Nullable,@NonNull) — seepackage-info.javafiles in enricher package (for every new package you create putpackage-info.javainto it and annotate the package with@NullMarked) - Java version: 21 (records, pattern matching, text blocks are in use throughout)
- Async server is explicitly blocked: The autoconfiguration throws
BeanCreationNotAllowedExceptionifspring.ai.mcp.server.type=ASYNCto enforce sync-only usage - Comments: Javadoc is welcome on public classes and public methods. Inside test method bodies,
// Given,// When,// Thensection markers are fine. Do not use decorative separator blocks between methods (e.g.// ---... SectionName ...---); let the method names speak for themselves - swagger-models fluent API: Prefer the fluent builder pattern over setters when constructing OpenAPI model objects.
All swagger-models 2.x classes expose property-named fluent methods that return
this— use them instead ofsetXxx(...)calls. For example:new OpenAPI().specVersion(V31).info(...).components(...).paths(...)instead ofopenApi.setSpecVersion(V31); openApi.setInfo(...) - List access: Prefer
list.getFirst()overlist.get(0)when accessing the first element of a list. - Collection literals: Prefer
Map.of(...)andList.of(...)over creating a mutable instance and callingput/addrepeatedly, when all elements are known upfront. Do not use these when: (a) the collection must remain mutable after construction, (b) insertion order must be preserved (Map.ofis unordered — useLinkedHashMapinstead), or (c) elements are accumulated in a loop/stream. - Assertion style: When validating an object or a list of objects in tests, construct the expected instance(s) and
assert using
then(actual).usingRecursiveComparison().isEqualTo(expected)instead of asserting each property individually. For a collections prefer AssertJ fluent assertions, such ascontainsExactly. - String manipulation: Within a single class, use one consistent approach — either
+concatenation orStringBuilder— do not mix both. Choose whichever fits the class's dominant use case: preferStringBuilderwhen the class contains any method that builds strings conditionally or in a loop; prefer+concatenation in classes where all string building is simple and unconditional. Assigning a plain variable (x = someString) does not count as string manipulation and does not influence the choice.
- Unit tests live in
infobip-openapi-mcp-core/src/test/ - Integration tests live in
infobip-openapi-mcp-spring-boot-starter/src/test/under theintegrationpackage - Integration tests use WireMock to stub the downstream API and the OpenAPI spec endpoint
IntegrationTestBase(stateful) and transport-specific subclasses wire up a full Spring Boot context withTestApplication- Test OpenAPI specs are in
src/test/resources/openapi/(both modules) - Integration test profiles (
application-integration.yml,application-test-http.yml, etc.) configure which transport and OpenAPI spec to use per test class @DirtiesContextis used on integration tests that reload tools to ensure a clean Spring context per test- Tests use given-when-then structure, and follow F.I.R.S.T. principles (fast, isolated, repeatable, self-validating, thorough)
- In case a lot of setup code is repeated between test methods use parameterized tests
The examples/ directory contains two standalone Spring Boot applications that use this framework:
infobip-sms-mock-mcp/— Mock SMS MCP server using Infobip API specopen-meteo-mcp/— Real MCP server calling the Open-Meteo weather API
These are not part of the main Maven build (not listed as modules in the root pom.xml).