内容
# Sergiu-AI MCP 服务器
一个使用 **模型上下文协议(MCP)** 的 Spring Boot 服务器,向任何与 MCP 兼容的 AI 客户端公开一组 SailPoint 身份治理知识工具。
该服务器使用单个 `/mcp` 端点通过 **可流式 HTTP** 协议进行通信,通过 API 密钥过滤器进行保护,并在 **AWS EC2** 上使用 **nginx HTTPS 反向代理** 运行,API 密钥存储在 **AWS Secrets Manager** 中,日志发送到 **AWS CloudWatch**。
---
## 目录
- [项目介绍](#项目介绍)
- [架构](#架构)
- [MCP 工具](#mcp-工具)
- [技术栈](#技术栈)
- [配置](#配置)
- [安全](#安全)
- [AWS 云集成](#aws-云集成)
- [密钥 - AWS Secrets Manager](#密钥---aws-secrets-manager)
- [日志 - AWS CloudWatch](#日志---aws-cloudwatch)
- [持续集成和交付(GitHub Actions)](#持续集成和交付github-actions)
- [测试和部署阶段](#测试和部署阶段)
- [第一阶段 - 普通 HTTP(仅限应用程序)](#第一阶段---普通-http仅限应用程序)
- [第二阶段 - 通过 nginx 反向代理使用 HTTPS](#第二阶段---通过-nginx-反向代理使用-https)
- [第三阶段 - 在 AWS EC2 上生产](#第三阶段---在-aws-ec2-上生产)
- [监控(执行器)](#监控执行器)
- [项目布局](#项目布局)
- [技术总结](#技术总结)
- [进一步部署](#进一步部署)
---
## 项目介绍
该应用程序是一个 **MCP 服务器**。MCP 是一个开放协议,允许 AI 助手调用外部“工具”(函数)来获取数据或执行操作。该服务器注册了几个只读工具,返回有关 **SailPoint**(身份安全/IGA)、其 **IdentityIQ 认证类型** 和 **工作流类型** 的精选知识。与服务器连接的 AI 客户端可以调用这些工具,以准确回答用户问题,而无需依赖模型的记忆。
工具返回的所有内容都是静态的、精选的文本,存储在内存中 - 没有数据库,也没有对 SailPoint 的外部调用。该价值在于通过 MCP 合同公开这些知识,以便助手可以按需检索。
---
## 架构
```
生产环境(AWS EC2,HTTPS)
MCP 客户端 ──HTTPS :443──▶ nginx ──HTTP :8080(内部)──▶ Spring Boot 应用程序
(TLS, ├─ ApiKeyFilter
速率限制, ├─ MCP /mcp 端点
代理) └─ 执行器
│
┌──────────────────────────────────────────┤
▼ ▼
AWS Secrets Manager AWS CloudWatch
(API 密钥,在启动时读取 (OS + 应用程序日志通过
通过 EC2 实例角色) CloudWatch 代理)
```
- **Spring Boot 应用程序** - 在 `/mcp` 端点托管 MCP 端点、工具组件、API 密钥过滤器和 Spring Boot 执行器。在 **8080 端口** 监听。
- **nginx** - 在 **443 端口** 终止 TLS,重定向 **80 → 443**,限制 `/mcp` 的速率,并代理到应用程序。应用程序 **不公开**;nginx 是唯一的公开入口。
- **AWS Secrets Manager** - 持有 API 密钥;应用程序在启动时使用 EC2 实例的 IAM 角色获取它(没有静态凭证存储在任何地方)。
- **AWS CloudWatch** - CloudWatch 代理将 OS 日志和应用程序的结构化 JSON 日志从实例发送到查询/警报。
---
## MCP 工具
三个 `@Component` 类通过 Spring AI 的 `@McpTool` 注解注册工具。
### SailPoint 工具(`SailPointTool`)
| 工具名称 | 用途 |
|-----------|---------|
| `sailpoint-overview` | SailPoint 和其主要产品(Identity Security Cloud, IdentityIQ)的概述。 |
| `sailpoint-features` | SailPoint 的关键功能列表,每个功能一行。可选 `limit` 参数。 |
| `sailpoint-feature-details` | 按 ID(例如 `access-certifications`)获取某个功能的完整描述。 |
| `sailpoint-connectors` | SailPoint 集成的系统类别和示例连接器。 |
### IdentityIQ 认证工具(`CertificationTool`)
| 工具名称 | 用途 |
|-----------|---------|
| `identityiq-certification-types` | IdentityIQ 认证类型列表,每个类型一行。可选 `limit` 参数。 |
| `identityiq-certification-details` | 按 ID(例如 `manager`、`targeted`)获取某个认证类型的完整描述。 |
### 工作流工具(`WorkflowTool`)
| 工具名称 | 用途 |
|-----------|---------|
| `workflows-overview` | SailPoint 的关键工作流类型列表(例如 `Policy Violation`、`LCM Provisioning`、`Identity Lifecycle`),每个类型一行。可选 `limit` 参数。 |
每个工具都会记录其调用情况,因此调用情况在应用程序日志中可见(并且在生产环境中,可在 CloudWatch Logs Insights 中查询)。
---
## 技术栈
- **Java 21**
- **Spring Boot 4.1.0** (`spring-boot-starter-webmvc`、`spring-boot-starter-actuator`)
- **Spring AI 2.0.0** - `spring-ai-starter-mcp-server-webmvc`(MCP 服务器,可流式协议)
- **AWS SDK for Java 2.x** - `software.amazon.awssdk:secretsmanager`
- **Maven**(包含包装器:`mvnw` / `mvnw.cmd`)
- **Docker**(多阶段构建)+ **Docker Compose**
- **nginx 1.27-alpine**(反向代理)
- **AWS** - EC2(Amazon Linux 2023),Secrets Manager,CloudWatch,IAM 实例角色
- **GitHub Actions** - CI(构建、测试)+ Docker 镜像发布到 GHCR
---
## 配置
关键设置位于 `src/main/resources/application.yaml`:
```yaml
spring:
ai:
mcp:
server:
name: Sergiu-AI MCP 服务器
version: 0.1.1
protocol: streamable # MCP 流式 HTTP
stdio: false # HTTP 传输,非 stdio
type: sync
mcp:
security:
api-key: ${MCP_API_KEY:dev-local-key} # 本地/CI 备用密钥
secret-name: ${MCP_SECRET_NAME:} # 在 EC2 上设置 -> 从 Secrets Manager 获取密钥
secret-region: ${MCP_SECRET_REGION:} # 可选;否则使用 AWS_REGION / ~/.aws / EC2 实例元数据
logging:
structured:
format:
file: ecs # 当配置日志文件时使用 ECS JSON
management:
endpoints:
web:
exposure:
include: health, info, metrics, env, loggers, mappings, beans
endpoint:
health:
show-details: always
info:
env:
enabled: true
```
JAR 是 **环境无关的** - 没有硬编码的环境特定值。每个环境通过环境变量提供其所需的内容:
| 环境变量 | 含义 |
|---------|---------|
| `MCP_API_KEY` | 当 Secrets Manager 未使用时作为备用 API 密钥。默认为本地/CI 的 `dev-local-key`。 |
| `MCP_SECRET_NAME` | Secrets Manager 密钥的名称(例如 `mcp/api-key`)。**设置后,应用程序从 Secrets Manager 获取密钥**,并忽略 `MCP_API_KEY`。 |
| `MCP_SECRET_REGION` | 密钥的区域。可选 - 默认为 `AWS_REGION` / `~/.aws` / EC2 实例元数据。 |
| `LOGGING_FILE_NAME` | 应用程序日志文件(例如 `/var/log/mcp/app.log`)的路径。设置后,Spring Boot 以 ECS 格式 JSON 编写日志;未设置则仅输出到控制台。 |
---
## 安全
- **通过 AWS Secrets Manager 的 API 密钥** - 在生产环境中,密钥永远不会存储在文件中。`SecretsManagerConfig` 使用 EC2 实例的 IAM 角色在启动时从 Secrets Manager 获取密钥,因此代码或配置中 **没有静态 AWS 凭证**。解析路径:`SecretsManagerConfig` → `ApiKeyHolder` → `ApiKeyFilter`。
- **API 密钥过滤器(`ApiKeyFilter`)** - 一个 `OncePerRequestFilter`,仅保护 `/mcp`。它通过 `X-API-Key:` 或 `Authorization: Bearer <key>` 接受密钥,并使用恒定时间检查 (`MessageDigest.isEqual`) 进行比较。缺少/错误的密钥 → `401 {"error":"unauthorized"}`。`/actuator/health` 故意保持开放以进行健康检查。
- **TLS 终止** - nginx 加密传输中的所有内容,因此 API 密钥永远不会明文传输。
- **速率限制** - nginx 将 `/mcp` 限制为每个客户端 IP 10 req/s(突发 20)。
- **不直接暴露应用程序** - 应用程序只能通过 nginx 访问;端口 8080 不公开。
---
## AWS 云集成
### 密钥 - AWS Secrets Manager
在 EC2 上,MCP API 密钥存储在 Secrets Manager 密钥(`mcp/api-key`)中。启动时,`SecretsManagerConfig` 读取 `MCP_SECRET_NAME`;如果设置了它,它会调用 `GetSecretValue` 并将值交给 `ApiKeyHolder`,后者由 `ApiKeyFilter` 使用。身份验证到 AWS 由 **EC2 实例角色**(`mcp-ec2-role`,授予 `secretsmanager:GetSecretValue`)通过默认凭证链处理 - 不会在磁盘上存储任何内容。
在本地和 CI 中,`MCP_SECRET_NAME` 未设置,因此不会进行 AWS 调用,应用程序会回退到 `MCP_API_KEY` / `dev-local-key`。
> **轮换注意:** 密钥在启动时获取一次,因此轮换密钥需要重启服务以获取新值。
### 日志 - AWS CloudWatch
当设置 `LOGGING_FILE_NAME` 时(通过 EC2 上的 systemd 单元),Spring Boot 以 **ECS 格式 JSON** 将日志写入该文件,同时也以纯文本形式记录到控制台/日志记录器。**CloudWatch 代理** 监视日志文件并将它们发送到 CloudWatch 日志组:
| 文件 | 日志组 |
|------|-----------|
| `/var/log/messages` | `/ec2/mcp/messages` |
| `/var/log/secure` | `/ec2/mcp/secure` |
| `/var/log/cloud-init-output.log` | `/ec2/mcp/cloud-init` |
| `/var/log/mcp/app.log` (ECS JSON) | `/ec2/mcp/app` |
因为应用程序日志是 JSON,因此可以在 **CloudWatch Logs Insights** 中查询工具的使用情况,例如:
```
fields message
| filter message like /Tool invoked/
| parse message "Tool invoked: *" as tool
| stats count(*) as calls by tool
| sort calls desc
```
完整的代理设置(IAM 策略、安装、配置、AL2023 `rsyslog` 注意)位于
[`deploy/CLOUDWATCH_LOGS.md`](deploy/CLOUDWATCH_LOGS.md)。
---
## 持续集成和交付(GitHub Actions)
[`.github/workflows/build.yml`](.github/workflows/build.yml) 有两个作业:
**`build`** - 检出代码,设置 **JDK 21**(Temurin)并使用 Maven 缓存,运行 `./mvnw clean package`(**构建 JAR 并运行测试**),并将 JAR 作为可下载的工件上传。无需 AWS - 使用 `MCP_SECRET_NAME` 未设置,Spring 上下文使用 `dev-local-key` 备用密钥加载。
**`docker`** - 在 `build` 成功后运行。从多阶段的 `Dockerfile` 构建容器镜像,并在默认分支或版本标签上,将其推送到 **GitHub 容器注册表(GHCR)**,并使用提交 SHA、git 标签(在 `v*` 推送时)和 `latest`(在默认分支上)进行标记:
```bash
docker pull ghcr.io/mergeasergiu/springbootmcpserver:latest
```
**何时运行:**
| 事件 | 构建 + 测试 | 镜像构建 | 发布到 GHCR |
|-------|:---:|:---:|:---:|
| 拉取请求 | ✅ | ✅ | ❌ |
| 推送到 `master` | ✅ | ✅ | ✅ |
| 推送标签 `v*` | ✅ | ✅ | ✅ |
| 文档更改(**.md`,`docs/**) | ❌ | ❌ | ❌ |
特性分支通过其 PR 进行验证;只有 `master` 和版本标签发布镜像。
---
## 测试和部署阶段
项目分为三个阶段运行。从项目根目录运行本地命令(`demo/`)。
### 第一阶段 - 普通 HTTP(仅限应用程序)
证明应用程序及其 MCP 工具在 **没有 TLS 和没有代理** 的情况下工作 - 应用程序直接监听端口 8080。
```bash
./mvnw clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar # http://localhost:8080
```
验证:
```bash
curl http://localhost:8080/actuator/health # {"status":"UP"}
curl -i http://localhost:8080/mcp # 401 unauthorized
curl -i http://localhost:8080/mcp -H "X-API-Key: dev-local-key" # 通过过滤器
```
然后指向一个 MCP 客户端 `http://localhost:8080/mcp` 并使用密钥调用工具(例如 `sailpoint-overview`)。
> 流量未加密并且端口是公开的 - 适合本地开发,不适用于网络。阶段 2 修复了这一点。
### 第二阶段 - 通过 nginx 反向代理使用 HTTPS
在应用程序前面添加 **nginx 反向代理并使用 HTTPS** 使用 Docker Compose:
```
client ──HTTPS :443──▶ nginx ──HTTP :8080(仅限内部)──▶ Spring Boot 应用程序
```
生成自签名证书,然后启动堆栈:```bash
mkdir -p nginx/certs
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout nginx/certs/key.pem -out nginx/certs/cert.pem \
-days 365 -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
docker compose up --build # add -d to run detached
```
验证 (`-k` 接受自签名证书):
```bash
curl -k https://localhost/actuator/health # {"status":"UP"}
curl -kI http://localhost/actuator/health # 301 -> https
curl -k -i https://localhost/mcp # 401 unauthorized
curl -k -i https://localhost/mcp -H "X-API-Key: dev-local-key"
curl http://localhost:8080/actuator/health # FAILS (app not public) — good
```
完整演练: [`deploy/LOCAL_NGINX.md`](deploy/LOCAL_NGINX.md).
#### 为什么 nginx 配置看起来如此
- **`proxy_buffering off;` + `proxy_read_timeout 3600s;`** 在 `/mcp` 上 - MCP 流式协议使用长寿命的流式响应;缓冲代理或短超时将破坏流式传输。
- **`limit_req` 在 `/mcp` 上** - 公共端点的廉价保护。
- **`X-Forwarded-*` 头部** - 将真实的客户端 IP 和方案传递给应用程序。
### 阶段 3 - 在 AWS EC2 上生产
应用程序在 EC2 实例(Amazon Linux 2023,区域 `us-east-1`)上作为 **systemd 服务** 运行(`/opt/mcp/app.jar`, 单元文件位于
[`deploy/mcp.service`](deploy/mcp.service)),在 443 端口后面使用 nginx。
- API 密钥来自 **Secrets Manager** (`MCP_SECRET_NAME=mcp/api-key`) via 实例角色 - 参见 [AWS 云集成](#aws-cloud-integration).
- 日志流入 **CloudWatch** (`LOGGING_FILE_NAME=/var/log/mcp/app.log` + 代理)。
**重新部署 JAR:**
```bash
./mvnw clean package
scp -i <key.pem> target/demo-0.0.1-SNAPSHOT.jar ec2-user@<host>:/home/ec2-user/
ssh -i <key.pem> ec2-user@<host>
sudo mv /home/ec2-user/demo-0.0.1-SNAPSHOT.jar /opt/mcp/app.jar
sudo systemctl restart mcp
sudo systemctl status mcp --no-pager
```
EC2 systemd + 端口设置文档位于
[`deploy/DEPLOY.md`](deploy/DEPLOY.md);CloudWatch 代理位于
[`deploy/CLOUDWATCH_LOGS.md`](deploy/CLOUDWATCH_LOGS.md).
---
## 监控(执行器)
启用了 Spring Boot 执行器。公开的端点由 `management.endpoints.web.exposure.include` 在 `application.yaml` 中控制
(`health, info, metrics, env, loggers, mappings, beans`)。
通过 nginx,执行器访问由 `location /actuator/` 块控制。为了访问 `/actuator/health` 之外的端点,该块必须是 **前缀** 匹配
(`location /actuator/`,而不是精确匹配 `location = /actuator/health`),并且它被限制在受信任的网络中:
```nginx
location /actuator/ {
allow 172.16.0.0/12; # Docker internal network
allow 127.0.0.1; # localhost
deny all; # block everyone else
proxy_pass http://app;
}
```
> **安全注意事项:** `env`、`beans` 和 `mappings` 可以泄漏配置内部信息。将其保留在允许列表中,或如果您不需要它们,则将其从 `include:` 列表中删除。在生产环境中,这些数据也会出现在发送到 CloudWatch 的应用程序日志中,因此请相应地对待两者。
---
## 项目布局
```
demo/
├── .github/workflows/build.yml # CI: build + test, upload jar artifact
├── src/main/java/dev/sergiu/demo/
│ ├── DemoApplication.java # Spring Boot entrypoint
│ ├── SailPointComponent/SailPointTool.java # SailPoint MCP tools
│ ├── CertificationsType/CertificationTool.java# IdentityIQ certification MCP tools
│ ├── Workflows/WorkflowTool.java # SailPoint workflow MCP tools
│ └── security/
│ ├── ApiKeyFilter.java # API-key filter guarding /mcp
│ ├── ApiKeyHolder.java # holds the resolved API key
│ └── SecretsManagerConfig.java # fetches the key from AWS Secrets Manager
├── src/main/resources/application.yaml # app + MCP + actuator + logging config
├── Dockerfile # multi-stage build (Maven -> JRE 21)
├── docker-compose.yml # app + nginx orchestration
├── nginx/
│ ├── nginx.conf # reverse proxy config
│ └── certs/ # cert.pem + key.pem (generated)
├── deploy/
│ ├── DEPLOY.md # EC2: run the JAR as a systemd service
│ ├── LOCAL_NGINX.md # Local HTTPS via nginx (Docker Compose)
│ ├── CLOUDWATCH_LOGS.md # CloudWatch agent setup (OS + app logs)
│ └── mcp.service # systemd unit (Secrets Manager + logging env)
└── README.md
```
---
## 技术总结
一个 Java 21 / Spring Boot + Spring AI MCP 服务器,使用 Docker 容器化,通过 Docker Compose 构建可复制的镜像,在 AWS EC2 上部署在 nginx HTTPS 后面,其秘密存储在 AWS Secrets Manager(通过 IAM 实例角色读取),日志记录在 AWS CloudWatch 中,并在 GitHub Actions 上进行 CI/CD,发布 Docker 镜像到 GHCR。
**一句总结:** 一个 Java 21 / Spring Boot + Spring AI MCP 服务器,使用 Docker 容器化,通过 Docker Compose 构建可复制的镜像,在 AWS EC2 上部署在 nginx HTTPS 后面,其秘密存储在 AWS Secrets Manager(通过 IAM 实例角色读取),日志记录在 AWS CloudWatch 中,并在 GitHub Actions 上进行 CI/CD,发布 Docker 镜像到 GHCR。
---
## 进一步部署
- **[`deploy/DEPLOY.md`](deploy/DEPLOY.md)** - 在 EC2 上将 JAR 作为 systemd 服务运行(Amazon Linux 2023)。
- **[`deploy/LOCAL_NGINX.md`](deploy/LOCAL_NGINX.md)** - 使用 Docker Compose 的本地 HTTPS 反向代理演练。
- **[`deploy/CLOUDWATCH_LOGS.md`](deploy/CLOUDWATCH_LOGS.md)** - 使用 CloudWatch 代理将 OS 和应用程序日志发送到 CloudWatch。
将本地 nginx 设置推广到真正的服务器意味着指向实例的域名,用 Let's Encrypt 证书替换自签名证书(自动续期,删除浏览器警告),并关闭端口 8080,以便只有 443 端口是公开的。`nginx.conf` 结构保持不变。
### 可能的下一步
- 将工具知识从硬编码的 Java 导出到数据文件中。
- 为工具添加单元/集成测试,并将它们集成到 CI 中。
- 将 CI 扩展到 CI/CD - 自动将构建的 JAR 部署到 EC2(例如,通过 SSM)。
- nginx 上的真实 HTTPS(ACM + ALB)或 Let's Encrypt。
```
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器