Spring Boot 4.1Java 21Spring AI 2.0 MCPApache-2.0

你的业务 API,
让 AI Agent 安全调用。

一个诚实的、生产级形态的 Spring Boot REST 脚手架。选定的 Service 方法同时暴露为 MCP 工具,Claude Code、Claude Desktop、Cursor 可以直接调用 —— 和 REST 同样的权限、同样的校验、同样的审计。

只需 JDK 21:./mvnw spring-boot:run,不需要数据库、Redis 或 Docker。

LazyREST 吉祥物河狸
~/LazyREST
$ claude mcp add --transport http lazyrest \
    http://localhost:8080/mcp --header "X-API-Key: lr_…"
$ claude -p "Search LazyREST for articles about MCP…"
● lazyrest - searchArticles(query: "MCP")
  ⎿ 1 result · "Exposing Spring services as MCP tools"
● lazyrest - getArticle(id: 2)
GET /api/audit-logs · REST 与 MCP 同一条审计链
RESTaliceARTICLE_CREATESUCCESS
MCPeditorsearchArticlesSUCCESS
MCPeditorgetArticleSUCCESS
MCPalicecreateArticleDraftDENIED

问题

Agent 需要你的业务 API —— 但必须安全

越来越多团队希望 AI Agent 能查订单、读知识库、起草内容。真正难的不是“能不能调”,而是“调的时候守不守规矩”。

做法一

让 Agent 直连数据库

绕过所有业务规则:没有权限、没有所有权校验、没有参数校验,出了事也查不到是谁做的。

做法二

再写一套 Agent 专用接口

两套权限、两套校验、两套审计,迟早不一致 —— 而不一致的地方就是漏洞。

LazyREST

Agent 走同一个 Service 层

MCP 工具只是薄薄的适配器,调用和 REST 相同的 Service:@PreAuthorize、所有权、@Validated、@Audited 全部复用。

原理

一个 Service 层,两扇门

REST 客户端用 JWT,AI Agent 用个人 API Key 走 /mcp(Streamable HTTP)。两者经过同一条 Spring Security 过滤链,落到同一个数据库和同一张审计表。

架构图:REST 客户端与 AI Agent 经过 Spring Security 过滤链(JWT / API Key、RBAC),进入 REST 控制器或 MCP 工具,再调用同一个 Service 层,最终写入数据库与审计日志
错误统一为 RFC 9457 ProblemDetail(REST)或 isError: true + 一句可读的话(MCP),永不返回堆栈。

MCP 接入

三步,让 Claude Code 调用你的接口

dev 环境自带一个演示 API Key(绑定 editor)。自己的 Key 用 POST /api/api-keys 创建,推荐只读。

启动应用

H2 内存库 + 演示数据,MCP 端点在 /mcp。

$ ./mvnw spring-boot:run

注册 MCP 服务器

Claude Code 原生支持 HTTP 传输,用请求头带上 Key。

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

直接提问

Agent 会自己选择工具;每次调用都进入审计日志(channel=MCP)。

> 搜索关于 MCP 的文章,读最相关的那篇,用两句话总结。
真实的 Claude Code 会话:调用 lazyrest 的 searchArticles 和 getArticle 工具并给出总结
真实的 claude -p 运行记录(stream-json 原文排版成终端样式,未改动内容)。Claude Desktop 可通过 mcp-remote 桥接,MCP Inspector CLI 也已验证。

里面有什么

每一项都已实现,并有测试覆盖

不写就不吹:没有缓存 / Redis、文件上传、多租户、OAuth2 授权服务器、MFA。需要时再加。

认证 auth

BCrypt 登录,JWT 访问令牌(HS256,15 分钟),不透明刷新令牌:哈希存储、每次轮换、重用检测吊销整组。登录 / 刷新 / 注册按 IP 限流(429 + Retry-After)。

API Key

lr_ 前缀,只存 SHA-256,明文只显示一次,可吊销,可设为只读;只在 /mcp 上被接受。

RBAC user

角色 + 细粒度权限(article:write、audit:read、mcp:write …),@PreAuthorize 方法级安全,管理员用户管理,首个管理员由环境变量引导。

示例领域 article

CRUD、Jakarta Validation、分页 + 白名单排序、安全转义的搜索、所有权校验(他人草稿 404、改他人文章 403)、乐观锁、发布流程。可整体删除。

审计 audit

@Audited 切面记录谁、做了什么、结果(SUCCESS / DENIED / FAILURE)、渠道(REST / MCP)、requestId、IP、耗时;独立事务持久化,管理员可查询。

MCP mcp

Streamable HTTP 端点 /mcp:searchArticles、getArticle、createArticleDraft。带 JSON Schema 和 readOnlyHint 等提示,必须认证。

错误与追踪

RFC 9457 application/problem+json(含 code、requestId、字段级 errors),X-Request-Id 写入日志 MDC。

持久化

Spring Data JPA + Flyway 迁移(Hibernate 只校验)。dev = H2 + 演示数据;prod = MySQL 8 或 PostgreSQL,缺少密钥直接拒绝启动。

运维与交付

OpenAPI 3 + Swagger UI、Actuator(仅 health 公开)、Prometheus、优雅停机、虚拟线程;多阶段 Dockerfile、docker-compose、GitHub Actions CI。68 个测试 + Testcontainers。

安全模型

Agent 的权限 = Key 主人的权限,一分不多

必须认证

/mcp 接受 X-API-Key: lr_…(或 Bearer lr_…),也接受普通 JWT;未认证返回 401 ProblemDetail。只读 Key 只保留 *:read 权限。

写工具三重门控

任何一道门关着,写操作都不会发生。Agent 只能创建草稿,发布永远需要人(仅 REST)。

每次调用都审计

每次工具调用记录一条 MCP_TOOL_CALL(参数摘要、结果、耗时),内部业务操作也以 channel=MCP 记录。工具错误以一句可读的话返回给模型,不泄露内部信息。

01 · 开关write-tools-enabled

prod 默认关闭,关闭时工具不出现在 tools/list

02 · Keymcp:write

且不是只读 Key

03 · Servicearticle:write

和 REST 同一个检查

仍需注意:注册一个工具就等于把它开放出去;Agent 读到的数据会进入模型上下文;用户写入的内容可能包含针对 Agent 的提示注入 —— 所以发布必须由人完成。尚未覆盖:MFA、MCP 的 OAuth 授权、TLS(请在反向代理终止)。

快速开始

一条命令跑起来

本地开发(只需 JDK 21)

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

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

admin / admin123全部权限:用户管理、审计、Actuator
editor / editor123写 + 发布文章,可用 MCP 写工具
alice / alice123读文章、写自己的草稿

演示账号与演示 Key 只在 dev profile 存在。

Docker Compose(MySQL 8.4)

$ cp .env.example .env
# 填入 JWT_SECRET、数据库密码、管理员密码
$ docker compose up --build

使用 prod profile:Flyway 自动建表,首次启动按 LAZYREST_ADMIN_* 创建管理员;缺少必需的环境变量时直接拒绝启动并指出缺了什么。换 PostgreSQL 只需改 DB_URL。

作为模板:点 Use this template,改坐标和包名,删掉 article 示例模块,加上你的领域。

截图

全部来自运行中的应用

Swagger UI 是浏览器实拍;终端截图是真实命令的原样输出,只做了排版。

Swagger UI 的 Authorize 对话框,粘贴登录得到的 accessToken
Swagger UI · 用登录拿到的 accessToken 完成 Authorize
Swagger UI 中执行 GET /api/articles 的真实响应
Try it out · GET /api/articles 的真实响应
curl 会话:登录、创建草稿、搜索、校验失败返回 ProblemDetail、管理员查询审计
curl · 校验失败返回 RFC 9457 ProblemDetail,随后查询审计
审计日志中的 MCP 工具调用,包括一次被拒绝的写操作
审计 · Agent 的每次工具调用;只读 Key 尝试写入被记为 DENIED
MCP Inspector CLI 的 tools/list 与 tools/call 输出
MCP Inspector CLI · tools/list 与 tools/call

FAQ

常见问题

能部署多个副本吗?

REST 部分完全无状态,可以。MCP 的 Streamable HTTP 会话保存在内存里:多副本时请开启粘性会话,或者切换到 protocol: STATELESS(同时把 McpServerConfig 的传输 Bean 换成无状态版本)。

MCP 支持 OAuth 吗?

还不支持。目前用个人 API Key(或普通 JWT)认证。MCP 的 OAuth 2.1 授权在路线图上;在有授权服务器之前,LazyREST 刻意不在 WWW-Authenticate 里声明 OAuth 元数据,以免客户端尝试一个无法完成的登录流程。

限流在多实例下准吗?

不准 —— 登录 / 刷新 / 注册的限流是单实例内存实现,每个实例单独计数。需要全局限流时,可换成 Bucket4j + Redis。

有缓存 / Redis 吗?

没有。示例里没有任何东西需要它,所以 v2 不声称有。

生产环境默认安全吗?

prod profile 下:缺少 JWT_SECRET / 数据库配置直接拒绝启动,dev 假密钥被拒绝,MCP 写工具、自助注册和 Swagger UI 默认关闭,Actuator 只公开 health。

能用 PostgreSQL 吗?

可以。把 DB_URL 换成 jdbc:postgresql://host:5432/lazyrest,Flyway 会自动选择 db/migration/postgresql。CI 用 Testcontainers 在 MySQL 8.4 和 PostgreSQL 17 上跑全流程。

你负责偷懒,河狸负责把接口接给 AI。

一条命令跑起来,接口同时就是 MCP 工具。河狸是 LazyREST 的吉祥物,和 喵想 同属 FantasyAI Lab 吉祥物家族。