chore: Spring AI 重构

This commit is contained in:
abel533
2026-03-25 00:15:00 +08:00
parent a9c71002d2
commit 2afa4712cb
124 changed files with 11777 additions and 3530 deletions
+125 -81
View File
@@ -14,60 +14,83 @@ MCP servers expose:
- **Resources**: Data Claude can read (like files or database records)
- **Prompts**: Pre-built prompt templates
## Quick Start: Python MCP Server
## Quick Start: Java/Spring AI MCP Server
### 1. Project Setup
```bash
# Create project
# 使用 Spring Initializr 创建项目
# 或通过 Maven 手动创建
mkdir my-mcp-server && cd my-mcp-server
python3 -m venv venv && source venv/bin/activate
mvn archetype:generate -DgroupId=com.example -DartifactId=my-mcp-server \
-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
```
# Install MCP SDK
pip install mcp
`pom.xml` 中添加依赖:
```xml
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
```
### 2. Basic Server Template
```python
#!/usr/bin/env python3
"""my_server.py - A simple MCP server"""
```java
// src/main/java/com/example/McpServerApplication.java
package com.example;
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
# Create server instance
server = Server("my-server")
@SpringBootApplication
public class McpServerApplication {
# Define a tool
@server.tool()
async def hello(name: str) -> str:
"""Say hello to someone.
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
Args:
name: The name to greet
"""
return f"Hello, {name}!"
@Bean
public MyTools myTools() {
return new MyTools();
}
}
@server.tool()
async def add_numbers(a: int, b: int) -> str:
"""Add two numbers together.
// 定义工具类
class MyTools {
Args:
a: First number
b: Second number
"""
return str(a + b)
@Tool(description = "Say hello to someone")
public String hello(@ToolParam(description = "The name to greet") String name) {
return "Hello, " + name + "!";
}
# Run server
async def main():
async with stdio_server() as (read, write):
await server.run(read, write)
@Tool(description = "Add two numbers together")
public String addNumbers(
@ToolParam(description = "First number") int a,
@ToolParam(description = "Second number") int b) {
return String.valueOf(a + b);
}
}
```
if __name__ == "__main__":
import asyncio
asyncio.run(main())
配置 `application.properties`:
```properties
spring.ai.mcp.server.name=my-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
spring.main.web-application-type=none
spring.main.banner-mode=off
spring.ai.mcp.server.stdio=true
```
### 3. Register with Claude
@@ -77,8 +100,8 @@ Add to `~/.claude/mcp.json`:
{
"mcpServers": {
"my-server": {
"command": "python3",
"args": ["/path/to/my_server.py"]
"command": "java",
"args": ["-jar", "/path/to/my-mcp-server/target/my-mcp-server.jar"]
}
}
}
@@ -140,74 +163,95 @@ server.connect(transport);
### External API Integration
```python
import httpx
from mcp.server import Server
```java
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
server = Server("weather-server")
public class WeatherTools {
@server.tool()
async def get_weather(city: str) -> str:
"""Get current weather for a city."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.weatherapi.com/v1/current.json",
params={"key": "YOUR_API_KEY", "q": city}
)
data = resp.json()
return f"{city}: {data['current']['temp_c']}C, {data['current']['condition']['text']}"
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@Tool(description = "Get current weather for a city")
public String getWeather(@ToolParam(description = "City name") String city) {
String url = "https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=" + city;
String response = restTemplate.getForObject(url, String.class);
JsonNode data = objectMapper.readTree(response);
JsonNode current = data.get("current");
return String.format("%s: %s°C, %s",
city, current.get("temp_c"), current.get("condition").get("text").asText());
}
}
```
### Database Access
```python
import sqlite3
from mcp.server import Server
```java
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.jdbc.core.JdbcTemplate;
server = Server("db-server")
public class DatabaseTools {
@server.tool()
async def query_db(sql: str) -> str:
"""Execute a read-only SQL query."""
if not sql.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries allowed"
private final JdbcTemplate jdbcTemplate;
conn = sqlite3.connect("data.db")
cursor = conn.execute(sql)
rows = cursor.fetchall()
conn.close()
return str(rows)
public DatabaseTools(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Tool(description = "Execute a read-only SQL query")
public String queryDb(@ToolParam(description = "SQL query to execute") String sql) {
if (!sql.trim().toUpperCase().startsWith("SELECT")) {
return "Error: Only SELECT queries allowed";
}
var rows = jdbcTemplate.queryForList(sql);
return rows.toString();
}
}
```
### Resources (Read-only Data)
```python
@server.resource("config://settings")
async def get_settings() -> str:
"""Application settings."""
return open("settings.json").read()
```java
import org.springframework.ai.tool.annotation.Tool;
import java.nio.file.Files;
import java.nio.file.Path;
@server.resource("file://{path}")
async def read_file(path: str) -> str:
"""Read a file from the workspace."""
return open(path).read()
public class ResourceTools {
@Tool(description = "Read application settings")
public String getSettings() throws Exception {
return Files.readString(Path.of("settings.json"));
}
@Tool(description = "Read a file from the workspace")
public String readFile(@ToolParam(description = "Path to the file") String path) throws Exception {
return Files.readString(Path.of(path));
}
}
```
## Testing
```bash
# Build the project
mvn clean package -DskipTests
# Test with MCP Inspector
npx @anthropics/mcp-inspector python3 my_server.py
npx @anthropics/mcp-inspector java -jar target/my-mcp-server.jar
# Or send test messages directly
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python3 my_server.py
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | java -jar target/my-mcp-server.jar
```
## Best Practices
1. **Clear tool descriptions**: Claude uses these to decide when to call tools
2. **Input validation**: Always validate and sanitize inputs
3. **Error handling**: Return meaningful error messages
4. **Async by default**: Use async/await for I/O operations
1. **Clear tool descriptions**: Claude uses `@Tool(description=...)` to decide when to call tools
2. **Input validation**: Always validate and sanitize inputs in tool methods
3. **Error handling**: Return meaningful error messages, use proper exception handling
4. **Use Spring DI**: Leverage Spring's dependency injection for service wiring
5. **Security**: Never expose sensitive operations without auth
6. **Idempotency**: Tools should be safe to retry