在本实操教程中,我们将使用纯Java基于模型上下文协议(Model Context Protocol,MCP) 构建一个SQL数据库代理(SQL Database Agent)。该代理能让大型语言模型(LLMs)通过结构化操作(如创建表、插入数据、查询数据等)以编程方式与SQL数据库交互,全程无需手动编写SQL语句。
我此前已撰写过多篇关于MCP、智能体间通信(Agent-to-Agent,A2A)及其对比的深度文章。为聚焦实操,本文不再重复这些概念,而是直接使用a2ajava库进行实现。
✅ 本教程非常适合希望通过Java,借助智能体(Intelligent Agents)将数据库与LLM集成的开发者。


模型上下文协议(MCP)的一大核心优势是能与工具无缝集成,但本文要实现的功能是将Java方法转换为工具:你只需编写常规的Java类(更推荐编写Spring Boot服务),a2a库会自动将其转换为可用于MCP或A2A(智能体间)任务的工具。
以下是使用a2ajava和Spring的真实案例,该代理可处理内存中的Apache Derby数据库的SQL操作:
package io.github.vishalmysore.service;import com.t4a.annotations.Action;import com.t4a.annotations.Agent;import com.t4a.detect.ActionCallback;import io.github.vishalmysore.a2a.domain.Task;import io.github.vishalmysore.a2a.domain.TaskState;import io.github.vishalmysore.data.*;import lombok.extern.java.Log;import org.springframework.stereotype.Service;import java.sql.*;import java.util.*;@Log@Service@Agent(groupName = "Database related actions") // 标注为数据库相关操作的智能体public class DerbyService { // Derby内存数据库的JDBC连接地址,若数据库不存在则自动创建 private static final String JDBC_URL = "jdbc:derby:memory:myDB;create=true"; private ActionCallback callback; // 启动数据库服务器的操作 @Action(description = "start database server") public String startServer(String serverName) { log.info("Derby server started."); return "为" + serverName + "启动Derby服务器"; } // 创建数据库的操作 @Action(description = "Create database") public String createDatabase(String databaseName) { // 将任务状态设置为“已完成”,并添加数据库创建信息 ((Task)callback.getContext()).setDetailedAndMessage(TaskState.COMPLETED, "Creating database: " + databaseName); try (Connection conn = DriverManager.getConnection(JDBC_URL)) { // 若连接成功则返回创建成功信息,否则返回失败信息 return conn != null ? "数据库创建成功。" : "数据库创建失败。"; } catch (SQLException e) { e.printStackTrace(); return "数据库创建失败。"; } } // 创建数据表的操作 @Action(description = "Create tables") public String createTables(TableData tableData) { // 【仅作演示用:实际使用前请务必对输入进行安全校验!】 StringBuilder createTableSQL = new StringBuilder("CREATE TABLE "); createTableSQL.append(tableData.getTableName()).append(" ("); // 遍历表的列信息,拼接SQL语句 for (ColumnData column : tableData.getHeaderList()) { createTableSQL.append(column.getColumnName()) .append(" ") .append(column.getSqlColumnType()) .append(", "); } // 移除SQL语句末尾多余的逗号和空格 createTableSQL.setLength(createTableSQL.length() - 2); createTableSQL.append(")"); try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { // 执行创建表的SQL语句 stmt.execute(createTableSQL.toString()); return tableData.getTableName() + "表创建成功。"; } catch (SQLException e) { e.printStackTrace(); return "创建表时出错:" + e.getMessage(); } } // 向数据表插入新数据的操作 @Action(description = "Insert new data in database table") public String insertDataInTable(TableData tableData) { StringBuilder insertSQL = new StringBuilder("INSERT INTO "); insertSQL.append(tableData.getTableName()).append(" ("); // 获取表的列信息(从第一行数据中提取) List<ColumnData> columns = tableData.getRowDataList().get(0).getColumnDataList(); for (ColumnData column : columns) { insertSQL.append(column.getColumnName()).append(", "); } // 移除末尾多余的逗号和空格,拼接VALUES子句 insertSQL.setLength(insertSQL.length() - 2); insertSQL.append(") VALUES ("); // 根据列数拼接占位符(?) insertSQL.append("?,".repeat(columns.size())); insertSQL.setLength(insertSQL.length() - 1); insertSQL.append(")"); try (Connection conn = DriverManager.getConnection(JDBC_URL); PreparedStatement pstmt = conn.prepareStatement(insertSQL.toString())) { // 批量处理每一行数据 for (RowData row : tableData.getRowDataList()) { int index = 1; for (ColumnData column : row.getColumnDataList()) { pstmt.setObject(index++, column.getColumnValue()); } pstmt.addBatch(); // 添加到批处理 } pstmt.executeBatch(); // 执行批处理插入 return "数据成功插入" + tableData.getTableName() + "表。"; } catch (SQLException e) { e.printStackTrace(); return "插入数据时出错:" + e.getMessage(); } } // 从数据表查询数据的操作 @Action(description = "Retrieve data from table") public List<Map<String, Object>> retrieveData(String sqlSelectQuery) { List<Map<String, Object>> result = new ArrayList<>(); try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sqlSelectQuery)) { // 获取查询结果的元数据(列信息) ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); // 遍历查询结果,将每行数据转换为Map(键:列名,值:列值) while (rs.next()) { Map<String, Object> row = new HashMap<>(); for (int i = 1; i <= columnCount; i++) { row.put(metaData.getColumnName(i), rs.getObject(i)); } result.add(row); } return result; } catch (SQLException e) { throw new RuntimeException(e); } }}
@Agent注解标记Java类,用@Action注解标记类中的方法;package io.github.vishalmysore;import io.github.vishalmysore.mcp.domain.*;import io.github.vishalmysore.mcp.server.MCPToolsController;import lombok.extern.java.Log;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.*;@Log@RestController@RequestMapping("/mcp") // 基础请求路径:/mcppublic class MCPController extends MCPToolsController { // 列出所有可用工具的接口(GET请求) @GetMapping("/list-tools") public ResponseEntity<Map<String, List<Tool>>> listTools() { Map<String, List<Tool>> response = new HashMap<>(); // 从父类MCPToolsController中获取工具列表 response.put("tools", super.getToolsResult().getTools()); return ResponseEntity.ok(response); } // 调用工具的接口(POST请求) @PostMapping("/call-tool") public ResponseEntity<CallToolResult> callTool(@RequestBody ToolCallRequest request) { // 调用父类MCPToolsController的工具调用方法 return super.callTool(request); }}
只需用@Action注解标记方法、用@Agent注解标记类,即可轻松将Java类或Spring Bean暴露为MCP工具或A2A任务。这些工具会被自动发现,并通过上述轻量级控制器访问。通过继承MCPToolsController,自定义的MCPController可提供如下端点:
/mcp/list-tools:列出所有可用工具;/mcp/call-tool:动态调用任意工具。这一设计能让后端服务无缝集成到任何AI驱动的工作流或前端界面中。