KeelFramework

Keel Framework / Keel Framework/MCP / Java

Reference Implementation

mcp-petstore-sample — A real application implementing the full Keel MCP Architecture stack, designed as a reference for developers and architects building MCP servers with Spring AI. The repository materializes the architectural principles, layers, components and conventions defined by Keel MCP into a functional implementation

What this sample demonstrates

  • ✅ 3 MCP Tools calling a real external REST API (findPet, listPets, registerPet)
  • ✅ 2 MCP Prompts guiding the LLM through common flows (lookup by ID, guided data collection)
  • ✅ 1 MCP Resource documenting the available tools for the LLM
  • ✅ JWT authentication (Streamable HTTP), validated against a public OIDC demo IdP
  • ✅ Structured JSON logging (technical, functional, security) via Keel Observability
  • ✅ Resilient JSON deserialization against a publicly writable backend with years of inconsistent data

No Keycloak, no API keys, no signup — everything runs against public demo services.

Prerequisites

  • Java 25
  • Maven 3.8+
  • An internet connection (both backends used are public, hosted services)

Backends used (public, no setup required)

PurposeServiceURL
REST dataSwagger Petstore demo APIhttps://petstore3.swagger.io/api/v3
JWT / OIDCDuende IdentityServer public demohttps://demo.duendesoftware.com

Both are public, shared demo services. Don't rely on them for anything beyond trying out this sample — data may be reset, and other people's test data (with inconsistent shapes) is exactly why this project's DTOs are written defensively (see below).

Running locally

git clone https://github.com/Keel-Framework/keel-mcp-petstore-sample.git
cd keel-mcp-petstore-sample/petstore-sample-boot
mvn clean install
mvn spring-boot:run

The server starts on http://localhost:8080, MCP endpoint at POST /mcp.

Getting a JWT to authenticate requests

This sample validates JWTs against the public Duende demo IdP using the client_credentials grant (machine-to-machine, no user login needed):

curl -X POST "https://demo.duendesoftware.com/connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=m2m&client_secret=secret&grant_type=client_credentials&scope=api"

Copy the access_token from the response and use it as a Bearer token in the Authorization header of every request to /mcp.

Connecting with MCP Inspector (recommended)

npx @modelcontextprotocol/inspector@latest

In the Inspector UI:

  1. Transport Type: Streamable HTTP
  2. URL: http://localhost:8080/mcp
  3. Authorization header: Bearer <your-token>
  4. Click Connect

You should see 3 tools, 2 prompts, and 1 resource listed.

Tools

ToolDescriptionBackend call
findPet(petId)Look up a pet by its numeric IDGET /pet/{petId}
listPets(status)List pets filtered by status (available, pending, sold)GET /pet/findByStatus?status=...
registerPet(name, category, status)Register a new petPOST /pet

Prompts

PromptPurpose
petstore-find-petTemplate for looking up a pet by ID
petstore-register-petGuides the model to collect name, category and status before calling registerPet, via a two-message (USER + ASSISTANT) example

Resources

ResourceURIPurpose
Tools guidepetstore://tools-guidePlain-text guide describing when and how to use each tool

Handling inconsistent public data

petstore3.swagger.io is a publicly writable demo used by countless developers over the years. Some existing entries have data shapes that don't match the OpenAPI spec exactly (e.g. a tags entry that's a plain string instead of {"id": ..., "name": ...}). This sample's DTOs handle that gracefully:

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static Tag fromString(String name) {
    return new Tag(null, name);
}

If you're building against your own backend with clean, controlled data, you likely won't need this kind of defensive deserialization — it's included here specifically because this sample targets a public, shared sandbox.

Project structure

petstore-sample/
├── petstore-sample-model/     # DTOs (PetstoreDTO), error message constants
├── petstore-sample-mcp/       # Tools, Prompts, Resources
└── petstore-sample-boot/      # Spring Boot application, configuration

Configuration

Local configuration lives in petstore-sample-boot/src/main/resources/application-local.yml:

keel:
  adapters:
    rest-client:
      services:
        petstore:
          base-url: https://petstore3.swagger.io/api/v3
          log-requests: true
  mcp:
    transport:
      auth:
        enabled: true
        jwt:
          issuer: https://demo.duendesoftware.com
          jwks-uri: https://demo.duendesoftware.com/.well-known/openid-configuration/jwks

Built with Keel Framework

This sample is powered by Keel Framework — an architecture for building production-ready MCP Servers on Spring AI, with JWT auth, session management, observability, and REST adapters handled for you.

License

Apache License 2.0 — see LICENSE.