Spring Boot 4.1Java 21Spring AI 2.0 MCPApache-2.0

Your REST API,
ready for AI agents.

An honest, production-shaped Spring Boot REST starter. Selected service methods are also exposed as MCP tools, so Claude Code, Claude Desktop and Cursor can call your business API with the same permissions, validation and audit trail as REST.

Only JDK 21 needed: ./mvnw spring-boot:run. No database, Redis or Docker.

The AgentREST beaver mascot
~/AgentREST
$ claude mcp add --transport http agentrest \
    http://localhost:8080/mcp --header "X-API-Key: ar_…"
$ claude -p "Search AgentREST for articles about MCP…"
● agentrest - searchArticles(query: "MCP")
  ⎿ 1 result · "Exposing Spring services as MCP tools"
● agentrest - getArticle(id: 2)
GET /api/audit-logs · one trail for REST and MCP
RESTaliceARTICLE_CREATESUCCESS
MCPeditorsearchArticlesSUCCESS
MCPeditorgetArticleSUCCESS
MCPalicecreateArticleDraftDENIED

The problem

Agents need your business API, safely

Teams want AI agents to look up orders, read the knowledge base and draft content. The hard part is not “can it call the API” but “does it follow the rules when it does”.

Option 1

Let the agent hit the database

Bypasses every business rule: no permissions, no ownership checks, no validation, and no record of who did what.

Option 2

Build a separate agent API

Two sets of permissions, validation and auditing that drift apart over time, and the drift is where the holes are.

AgentREST

Agents use the same service layer

MCP tools are thin adapters over the same services as REST: @PreAuthorize, ownership, @Validated and @Audited are all reused.

How it works

One service layer, two front doors

REST clients use JWTs; AI agents use a personal API key on /mcp (Streamable HTTP). Both pass the same Spring Security filter chain and land in the same database and the same audit table.

Architecture: REST clients and AI agents pass the Spring Security filter chain (JWT / API key, RBAC), reach REST controllers or MCP tools, call the same service layer and write to the database and audit log
Errors are RFC 9457 ProblemDetails (REST) or isError: true plus one readable sentence (MCP). Never a stack trace.

MCP in 3 steps

Let Claude Code call your API

The dev profile ships a demo API key bound to editor. Create your own with POST /api/api-keys, read-only recommended.

Start the app

In-memory H2 with demo data; the MCP endpoint is /mcp.

$ ./mvnw spring-boot:run

Register the MCP server

Claude Code speaks HTTP natively; pass the key as a header.

$ claude mcp add --transport http agentrest \
  http://localhost:8080/mcp \
  --header "X-API-Key: ar_dev_editor_0123456789abcdefghijklmnop"

Just ask

The agent picks the tools; every call lands in the audit log (channel=MCP).

> Search AgentREST for articles about MCP, then read the most relevant one and summarize it in two sentences.
A real Claude Code session calling the agentrest searchArticles and getArticle tools and summarizing the result
A real claude -p run (its stream-json transcript laid out as a terminal, content unchanged). Claude Desktop works through mcp-remote; the MCP Inspector CLI is verified too.

What's inside

Everything listed exists and is tested

Not included, so not claimed: caching / Redis, file upload, multi-tenancy, an OAuth2 authorization server, MFA. Add them when you need them.

Auth auth

BCrypt login, JWT access tokens (HS256, 15 min), opaque refresh tokens: hashed, rotated on every use, reuse detection revokes the family. Per-IP rate limit on login / refresh / register (429 + Retry-After).

API keys

ar_ prefix (legacy lr_ keys from 2.0 keep working), stored as SHA-256 only, shown once, revocable, optionally read-only; accepted on /mcp only.

RBAC user

Roles plus fine-grained permissions (article:write, audit:read, mcp:write …), @PreAuthorize method security, admin user management, first admin bootstrapped from env vars.

Example domain article

CRUD, Jakarta Validation, paging + whitelisted sorting, safely escaped search, ownership checks (others' drafts 404, editing others' articles 403), optimistic locking, publishing. Delete it in one go.

Audit audit

An @Audited aspect records who, what, outcome (SUCCESS / DENIED / FAILURE), channel (REST / MCP), requestId, IP and duration, persisted in its own transaction and queryable by admins.

MCP mcp

Streamable HTTP endpoint /mcp: searchArticles, getArticle, createArticleDraft, with JSON schemas and hints like readOnlyHint. Authentication required.

Errors & tracing

RFC 9457 application/problem+json with code, requestId and field-level errors; X-Request-Id in the log MDC.

Persistence

Spring Data JPA + Flyway migrations (Hibernate only validates). dev = H2 + demo data; prod = MySQL 8 or PostgreSQL, refusing to start without secrets.

Ops & delivery

OpenAPI 3 + Swagger UI, Actuator (only health public), Prometheus, graceful shutdown, virtual threads; multi-stage Dockerfile, docker-compose, GitHub Actions CI. 70 tests + Testcontainers.

Security model

An agent can do exactly what its key's owner can do

Authentication required

/mcp accepts X-API-Key: ar_… (or Bearer ar_…) as well as normal JWTs; anything else gets a 401 ProblemDetail. Read-only keys keep only *:read permissions.

Write tools: three gates

If any gate is closed, nothing is written. Agents can only create drafts; publishing always takes a human (REST only).

Every call audited

Each tool call records an MCP_TOOL_CALL entry (argument summary, outcome, duration); the business actions inside are recorded with channel=MCP. Tool errors go back to the model as one readable sentence, with no internals.

01 · switchwrite-tools-enabled

off by default in prod; when off, the tool is not even in tools/list

02 · keymcp:write

and not a read-only key

03 · servicearticle:write

the very same check as REST

Keep in mind: registering a tool exposes it; whatever an agent reads enters the model context; user-written content can carry prompt injection aimed at agents, which is why publishing stays human. Not covered yet: MFA, OAuth for MCP, TLS (terminate it at your reverse proxy).

Quick start

One command to run it

Local dev (JDK 21 only)

$ git clone https://github.com/melonlee/AgentREST.git
$ cd AgentREST
$ ./mvnw spring-boot:run

Swagger UI: http://localhost:8080/swagger-ui.html · MCP: http://localhost:8080/mcp

admin / admin123everything: users, audit, Actuator
editor / editor123write + publish articles, MCP write tools
alice / alice123read articles, write own drafts

Demo users and the demo key exist in the dev profile only.

Docker Compose (MySQL 8.4)

$ cp .env.example .env
# set JWT_SECRET, database and admin passwords
$ docker compose up --build

Runs the prod profile: Flyway creates the schema, the first start creates the admin from AGENTREST_ADMIN_*, and a missing required variable stops startup with a message saying which one. For PostgreSQL just change DB_URL.

As a template: click Use this template, change the coordinates and package, delete the article example module, add your domain.

Screenshots

All taken from the running app

Swagger UI shots are real browser captures; terminal shots are real command output, only laid out.

Swagger UI Authorize dialog with the accessToken from login
Swagger UI · authorize with the accessToken from login
Swagger UI executing GET /api/articles with a real response
Try it out · real response of GET /api/articles
curl session: login, create draft, search, validation failure as ProblemDetail, admin audit query
curl · a validation failure as RFC 9457 ProblemDetail, then the audit query
MCP tool calls in the audit log, including one denied write
Audit · every agent tool call; a read-only key trying to write is DENIED
MCP Inspector CLI output for tools/list and tools/call
MCP Inspector CLI · tools/list and tools/call

FAQ

Questions

Can I run several replicas?

The REST side is stateless, so yes. MCP Streamable HTTP sessions live in memory: use sticky sessions with several replicas, or switch to protocol: STATELESS (and swap the transport bean in McpServerConfig for the stateless variant).

Does MCP support OAuth?

Not yet. Authentication is a personal API key (or a normal JWT). OAuth 2.1 for MCP is on the roadmap; until there is an authorization server, AgentREST deliberately does not advertise OAuth metadata in WWW-Authenticate, so clients don't start a login flow that cannot succeed.

Is the rate limit accurate across instances?

No. The login / refresh / register limiter is in-memory per instance, so each replica counts on its own. Swap in Bucket4j + Redis when you need a global limit.

Is there caching / Redis?

No. Nothing in the example needs it, so v2 does not claim it.

Is production safe by default?

With the prod profile, a missing JWT_SECRET or database config stops startup, the dev secret is rejected, MCP write tools, self-registration and Swagger UI are off by default, and Actuator exposes only health.

Can I use PostgreSQL?

Yes. Set DB_URL to jdbc:postgresql://host:5432/agentrest and Flyway picks db/migration/postgresql. CI runs the full flow on MySQL 8.4 and PostgreSQL 17 with Testcontainers.

You write the business logic. The beaver wires your API to AI.

One command to run it, and your endpoints are MCP tools too. The beaver is AgentREST’s mascot, part of the FantasyAI Lab mascot family with 喵想.