MCP作为一个开放的协议,简化了我们使用语言大模型跟外部工具和工具的通信,提供了标准的上下文方式,那我们能在这个基础之上做哪些应用呢?我首先想到的是能否让大模型读取我们数据库的表结构,直接基于表进行代码生成,辅助我们开发,直接实践一下。
一、明确需求
使用Spring AI 开发连接数据库的MCP服务端,最好可以支持多种数据库,MCP提供可以访问数据库元数据的能力,包括数据表信息、表的列信息、表外键关联信息、索引信息等;可以辅助我们生成代码。
二、新建工程引入依赖
我们新建一个名为dbc-mcp-server
的Spring工程,引入依赖,这里为了方便我们扩展支持其他数据库依赖,我们引入阿里数据库连接池、Mysql驱动包、Sqlite驱动包等,支持通过配置文件修改连接不同的数据库。
这里我们使用SSE方式来调用MCP Server,所以引入webflu
相关的依赖,也引入mybaits方便我们使用SQL查询数据信息
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- Sqlite驱动包 -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<!-- SqlServer驱动包 -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<!-- PostgreSql驱动包 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- SpringBoot集成mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.starter.version}</version>
</dependency>
二、配置文件
启动在端口8081上,增加连接数据库的信息和mybaits的配置
properties
server.port=8081
spring.application.name=dbc-mcp-server
spring.main.banner-mode=off
logging.pattern.console=
# transport.mode=stdio
transport.mode=sse
logging.file.name=dbc-mcp-server.log
# AppProperties
app.dbUrl=127.0.0.1:3306
app.dbUser=root
app.dbPassword=your_pwd
app.dbDriver=com.mysql.cj.jdbc.Driver
app.dbName=your_db
app.dbType=mysql
mybatis.type-aliases-package=com.renne.ai.dbcmcp.domain
mybatis.mapper-locations=classpath:mybatis/**/*Mapper.xml
mybatis.config-location=classpath:mybatis/mybatis-config.xml
三、编写MCP Service方法
这里的Service就是获取数据库元数据的方法,包括数据库结构、表、表关联信息
java
package com.renne.ai.dbcmcp.service;
import com.renne.ai.dbcmcp.domain.SysTableColumnMeta;
import com.renne.ai.dbcmcp.domain.SysTableKeyColumnUsage;
import com.renne.ai.dbcmcp.domain.SysTableMeta;
import com.renne.ai.dbcmcp.domain.SysTableUniqueMeta;
import com.renne.ai.dbcmcp.mapper.DBQueryMapper;
import com.renne.ai.dbcmcp.properties.AppProperties;
import com.renne.ai.dbcmcp.utils.SpringUtils;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 查询数据库表结构的服务
*
* @author LiuYu
* @since 2025-03-31 19:01
*/@Service
public class DBTableStructureService {
@Resource
private AppProperties appProperties;
/**
* 查询数据库所有表结构并返回表名和注释信息。
* <p>
* 该函数通过调用数据库查询接口,获取指定数据库中的所有表名及其注释信息,并将结果以字符串形式返回。
*
* @return 返回包含所有表名和注释信息的字符串,格式为列表的字符串表示形式。
*/
@Tool(name = "getTableNameAndComment", description = """
查询数据库所有表结构并返回表名和注释信息。
该函数通过调用数据库查询接口,获取指定数据库中的所有表名及其注释信息,并将结果以字符串形式返回。 返回包含所有表名和注释信息的字符串,格式为列表的字符串表示形式。
""")
public String getTableNameAndComment() {
// 获取当前应用的数据库名称
String dbName = appProperties.getDbName();
// 通过SpringUtils获取DBQueryMapper的实例,并调用其方法查询表名和注释信息
List<SysTableMeta> tableNameAndComment = SpringUtils.getBean(DBQueryMapper.class).getTableNameAndComment(dbName);
// 将查询结果转换为字符串并返回
return tableNameAndComment.toString();
}
/**
* 查询数据库表字段的详细信息。
* <p>
* 该函数通过指定的表名,查询数据库中该表的字段信息,并返回字段的元数据列表。
*
* @param tableName 表名,用于指定要查询的数据库表。
* @return 返回包含表字段元数据的字符串表示形式。
*/
@Tool(name = "getTableColumnDetail", description = """
查询数据库表字段的详细信息。
该函数通过指定的表名,查询数据库中该表的字段信息,并返回字段的元数据列表。 返回包含表字段元数据的字符串表示形式。
""")
public String getTableColumnDetail(@ToolParam(description = "表名,用于指定要查询的数据库表。") String tableName) {
// 获取当前应用的数据库名称
String dbName = appProperties.getDbName();
// 通过DBQueryMapper查询指定表的字段元数据
List<SysTableColumnMeta> tableNameAndComment = SpringUtils.getBean(DBQueryMapper.class).getTableColumnDetail(dbName, tableName);
// 将字段元数据列表转换为字符串并返回
return tableNameAndComment.toString();
}
/**
* 查询指定数据库表的外键信息。
* <p>
* 该函数通过调用数据库查询接口,获取指定表的外键信息,并将其以字符串形式返回。
*
* @param tableName 需要查询外键信息的表名。
* @return 返回包含外键信息的字符串,格式为列表的字符串表示。
*/
@Tool(name = "getTableForeignKeys", description = """
查询指定数据库表的外键信息。
该函数通过调用数据库查询接口,获取指定表的外键信息,并将其以字符串形式返回。 返回包含外键信息的字符串,格式为列表的字符串表示。
""")
public String getTableForeignKeys(@ToolParam(description = "需要查询外键信息的表名。") String tableName) {
// 获取当前应用的数据库名称
String dbName = appProperties.getDbName();
// 通过DBQueryMapper查询指定表的外键信息
List<SysTableKeyColumnUsage> foreignKeys = SpringUtils.getBean(DBQueryMapper.class).getTableForeignKeys(dbName, tableName);
// 将外键信息列表转换为字符串并返回
return foreignKeys.toString();
}
/**
* 查询指定表的索引信息。
*
* 该函数通过调用数据库查询接口,获取指定表的索引信息,并将结果以字符串形式返回。
*
* @param tableName 表名,用于指定需要查询索引信息的表。
* @return 返回指定表的索引信息,以字符串形式表示。
*/
@Tool(name = "getTableIndexes", description = """
查询指定表的索引信息。
该函数通过调用数据库查询接口,获取指定表的索引信息,并将结果以字符串形式返回。 返回指定表的索引信息,以字符串形式表示。
""")
public String getTableIndexes(@ToolParam(description = "表名,用于指定需要查询索引信息的表。") String tableName) {
// 获取当前应用的数据库名称
String dbName = appProperties.getDbName();
// 调用数据库查询接口,获取指定表的索引信息
List<SysTableUniqueMeta> indexes = SpringUtils.getBean(DBQueryMapper.class).getTableIndexes(dbName, tableName);
// 将索引信息转换为字符串并返回
return indexes.toString();
}
/**
* 查询指定表的行数。
*
* 该函数通过调用数据库查询接口,获取指定表的行数,并返回包含行数的字符串信息。
*
* @param tableName 需要查询行数的表名,不能为空。
* @return 返回一个字符串,格式为 "表 {tableName} 的行数为: {rowCount}",其中 {tableName} 是传入的表名,{rowCount} 是查询到的行数。
*/
@Tool(name = "getTableRowCount", description = """
查询指定表的行数。
该函数通过调用数据库查询接口,获取指定表的行数,并返回包含行数的字符串信息。 返回一个字符串,格式为 "表 {tableName} 的行数为: {rowCount}",其中 {tableName} 是传入的表名,{rowCount} 是查询到的行数。
""")
public String getTableRowCount(@ToolParam(description = "需要查询行数的表名。") String tableName) {
// 通过 SpringUtils 获取 DBQueryMapper 的实例,并调用其 getTableRowCount 方法查询表的行数
int rowCount = SpringUtils.getBean(DBQueryMapper.class).getTableRowCount(tableName);
// 返回包含表名和行数的字符串
return "表 " + tableName + " 的行数为: " + rowCount;
}
/**
* 查询数据库的存储引擎。
*
* 该函数通过应用程序配置获取数据库名称,并使用该名称查询数据库的存储引擎。
* 最终返回一个包含数据库名称和存储引擎信息的字符串。
*
* @return 返回一个字符串,格式为 "数据库 [数据库名称] 的存储引擎为: [存储引擎]"
*/ @Tool(name = "getDatabaseEngine", description = """
查询数据库的存储引擎。
该函数通过应用程序配置获取数据库名称,并使用该名称查询数据库的存储引擎,最终返回一个包含数据库名称和存储引擎信息的字符串。 返回一个字符串,格式为 "数据库 [数据库名称] 的存储引擎为: [存储引擎]"
""")
public String getDatabaseEngine() {
// 从应用程序配置中获取数据库名称
String dbName = appProperties.getDbName();
// 通过Spring工具类获取DBQueryMapper的实例,并查询数据库的存储引擎
String engine = SpringUtils.getBean(DBQueryMapper.class).getDatabaseEngine(dbName);
// 返回包含数据库名称和存储引擎信息的字符串
return "数据库 " + dbName + " 的存储引擎为: " + engine;
}
}
编写一个生成代码的角色提示词Service,让模型生成代码时候先获取提示词作为上下文
java
package com.renne.ai.dbcmcp.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
/**
* 内置角色提示词服务
*
* @author LiuYu
* @since 2025-04-01 14:26
*/@Service
public class RolePromptService {
/**
* 获取RuoYi Vue3代码生成角色提示词
*
* @return 角色提示词
*/
@Tool(name = "getRuoYiVue3CodeGenRolePrompt", description = "获取RuoYi Vue3代码生成角色提示词")
public String getRuoYiVue3CodeGenRolePrompt() {
String prompt = """
- Role: 前端开发工程师和代码生成专家
- Background: 用户需要快速生成Vue项目的列表页面、编辑页面和详情页面,期望通过输入字段名来自动化生成代码,以提高开发效率,减少重复劳动。
- Profile: 你是一位精通Vue.js框架的前端开发工程师,对Vue组件的开发有着丰富的经验,能够根据查询到的字段信息,快速生成符合规范的页面代码。
- Skills: 你具备Vue.js项目开发、代码自动化生成、前端界面设计、数据结构解析等关键能力,能够根据字段名、字段类型和字段含义,自动生成对应的页面代码。
- Goals: 根据查询到的字段名(支持中英文混合,带类型或不带类型),快速生成列表页面、编辑页面和详情页面的Vue代码。
- Constrains: 生成的代码应遵循Vue.js的最佳实践,代码结构清晰,易于维护和扩展,确保字段名的正确解析和字段类型的准确应用。
- OutputFormat: 生成的代码应包含完整的Vue组件结构,包括模板、脚本和样式,支持字段名的自动翻译和字段类型的自动识别。
- Workflow: 1. 解析查询到的字段名,识别中英文字段名、字段类型(如果存在)。
2. 根据字段信息生成列表页面的代码,包括表格展示和分页功能。
3. 根据字段信息生成编辑页面的代码,根据字段类型生成对应的表单项。
4. 根据字段信息生成详情页面的代码,展示字段的详细信息。
""";
return prompt;
}
}
其他具体代码,看仓库地址吧,我会放在文末。
四、在Cursor中配置服务
在Cursor的mcp.json文件中增加配置,我们可以看到Cursor已经读取到我们写的方法
json
{
"mcpServers": {
"dbc-mcp-server": {
"url": "http://localhost:8081/sse"
}
}
}
配置好之后,我们在Cursor的chat中提问,数据库中有哪些表
提问,岗位信息表中有哪些字段,也能正确回答
继续提问,帮我生成岗位信息表的RuoYi Vue3部分的代码,这里也能完成代码生成,列表字段也都正确列出来
这样我们就完成了一个可以辅助代码生成的连接数据库MCP服务端了。
这里是我Gitee仓库地址dbc-mcp-server,代码里目前只实现了mysql数据库的连接,后面会补全其他数据库的连接