当前位置: 代码网 > it编程>编程语言>Java > 使用 Java 开发 MCP 服务并发布到 Maven 中央仓库实践指南

使用 Java 开发 MCP 服务并发布到 Maven 中央仓库实践指南

2026年04月06日 Java 我要评论
什么是 mcpmodel context protocol (mcp) 是一个开放协议,用于标准化大语言模型与外部数据源和工具之间的交互。它允许开发者将应用程序、数据源或 ai 功能无缝集成到任何使用

什么是 mcp

model context protocol (mcp) 是一个开放协议,用于标准化大语言模型与外部数据源和工具之间的交互。它允许开发者将应用程序、数据源或 ai 功能无缝集成到任何使用 mcp 的 llm 客户端中。

mcp 的核心优势

  • 标准化接口:统一的工具调用规范
  • 松耦合架构:服务与 llm 客户端独立部署
  • 易于扩展:快速添加新的工具和能力
  • 跨平台支持:支持多种编程语言和运行环境

mcp 通信模式

mcp 支持多种通信方式:

  • stdio:基于标准输入输出的本地进程通信(本文重点)
  • http/sse:基于 http 的服务器发送事件
  • websocket:双向实时通信

本文将以中国天气查询服务为例,详细介绍如何使用 java 开发 mcp 服务,并发布到 maven 中央仓库,最终通过 jbang 和 stdio 方式集成到大模型工具中。

项目架构与技术选型

技术栈

  • java 17:lts 版本,提供优秀的性能和新特性
  • spring boot 3.5.13:快速应用开发框架
  • spring ai mcp:spring ai 提供的 mcp 服务器支持
  • maven:项目构建和依赖管理
  • jbang:java 脚本执行工具,用于运行 jar

项目结构

cn-weather-mcp/
├── src/main/java/cn/wubo/cn/weather/mcp/
│   ├── weatherapplication.java    # 主启动类
│   └── weatherservice.java        # 天气服务实现
├── src/main/resources/
│   └── application.yml            # 配置文件
├── pom.xml                        # maven 配置
└── .github/workflows/
    └── publish.yml                # ci/cd 发布流程

开发环境准备

必需软件

  • jdk 17+
  • java -version
    
  • maven 3.6+
  • mvn -version
    
  • git
  • git --version
    
  • gpg(用于签名发布到中央仓库)
gpg --version

gpg 密钥生成

发布到 maven 中央仓库需要 gpg 签名:

# 生成 gpg 密钥
gpg --full-generate-key
# 查看生成的密钥
gpg --list-keys
# 将公钥上传到密钥服务器
gpg --keyserver keyserver.ubuntu.com --send-keys your_key_id

创建 spring boot 项目

1. 创建 pom.xml

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xsi:schemalocation="http://maven.apache.org/pom/4.0.0 
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>
    
    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>3.5.13</version>
        <relativepath/>
    </parent>
    
    <groupid>io.github.your-username</groupid>
    <artifactid>cn-weather-mcp</artifactid>
    <version>0.0.1-snapshot</version>
    <name>cn-weather-mcp</name>
    <description>中国天气 mcp 服务 - 提供中国城市天气查询服务</description>
    
    <!-- 项目元信息 -->
    <url>https://github.com/your-username/cn-weather-mcp</url>
    
    <developers>
        <developer>
            <id>your-id</id>
            <name>your name</name>
            <email>your-email@example.com</email>
        </developer>
    </developers>
    
    <licenses>
        <license>
            <name>the apache software license, version 2.0</name>
            <url>https://www.apache.org/licenses/license-2.0.txt</url>
        </license>
    </licenses>
    
    <scm>
        <connection>scm:git:https://github.com/your-username/cn-weather-mcp.git</connection>
        <developerconnection>scm:git:git@github.com:your-username/cn-weather-mcp.git</developerconnection>
        <url>https://github.com/your-username/cn-weather-mcp</url>
    </scm>
    
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.1.3</spring-ai.version>
    </properties>
    
    <!-- 依赖管理 -->
    <dependencymanagement>
        <dependencies>
            <dependency>
                <groupid>org.springframework.ai</groupid>
                <artifactid>spring-ai-bom</artifactid>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencymanagement>
    
    <dependencies>
        <!-- spring ai mcp server -->
        <dependency>
            <groupid>org.springframework.ai</groupid>
            <artifactid>spring-ai-starter-mcp-server</artifactid>
        </dependency>
        
        <!-- spring web (用于 http 请求) -->
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-web</artifactid>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <!-- spring boot maven 插件 -->
            <plugin>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-maven-plugin</artifactid>
            </plugin>
            
            <!-- 源码插件 -->
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-source-plugin</artifactid>
                <version>3.3.0</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            
            <!-- javadoc 插件 -->
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-javadoc-plugin</artifactid>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            
            <!-- gpg 签名插件 -->
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-gpg-plugin</artifactid>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <id>sign-artifacts</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                        <configuration>
                            <gpgarguments>
                                <arg>--pinentry-mode</arg>
                                <arg>loopback</arg>
                            </gpgarguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            
            <!-- central publishing 插件 -->
            <plugin>
                <groupid>org.sonatype.central</groupid>
                <artifactid>central-publishing-maven-plugin</artifactid>
                <version>0.9.0</version>
                <extensions>true</extensions>
                <configuration>
                    <publishingserverid>central</publishingserverid>
                    <autopublish>true</autopublish>
                    <waituntil>published</waituntil>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2. 创建配置文件 application.yml

spring:
  main:
    web-application-type: none  # 非 web 应用
    banner-mode: off            # 关闭启动横幅
  ai:
    mcp:
      server:
        name: cn-weather-mcp    # mcp 服务名称
        version: 1.0.0          # mcp 服务版本
logging:
  file:
    name: ./mcp/cn-weather-mcp.log  # 日志文件路径

实现 mcp 服务核心逻辑

1. 创建服务类 weatherservice

这是 mcp 服务的核心,包含所有工具方法的实现:

package cn.wubo.cn.weather.mcp;
import com.fasterxml.jackson.annotation.jsonignoreproperties;
import com.fasterxml.jackson.annotation.jsonproperty;
import com.fasterxml.jackson.core.jsonprocessingexception;
import com.fasterxml.jackson.databind.objectmapper;
import org.springframework.ai.tool.annotation.tool;
import org.springframework.ai.tool.annotation.toolparam;
import org.springframework.http.responseentity;
import org.springframework.stereotype.service;
import org.springframework.web.client.restclient;
import org.springframework.web.client.restclientexception;
import java.nio.charset.standardcharsets;
import java.util.arrays;
import java.util.list;
import java.util.map;
import java.util.stream.collectors;
@service
public class weatherservice {
    private static final string base_url = "http://t.weather.itboy.net/api/weather/city/";
    private final restclient restclient;
    private final objectmapper objectmapper;
    private final map<string, citycodeinfo> citycodemap;
    public weatherservice() {
        this.restclient = restclient.builder()
                .baseurl(base_url)
                .build();
        this.objectmapper = new objectmapper();
        this.citycodemap = loadcitycodes();
    }
    // 加载城市代码数据
    private static map<string, citycodeinfo> loadcitycodes() {
        list<citycodeinfo> citycodes = arrays.aslist(
            new citycodeinfo(1, "北京", "北京", "101010100"),
            new citycodeinfo(23, "天津市", "天津", "101030100"),
            new citycodeinfo(36, "上海", "上海", "101020100"),
            // ... 更多城市数据
        );
        return citycodes.stream()
            .collect(collectors.tomap(citycodeinfo::citycode, info -> info));
    }
    // 数据记录类
    @jsonignoreproperties(ignoreunknown = true)
    public record weatherresponse(
            @jsonproperty("status") integer status,
            @jsonproperty("message") string message,
            @jsonproperty("cityinfo") cityinfo cityinfo,
            @jsonproperty("data") weatherdatawrapper data
    ) {}
    @jsonignoreproperties(ignoreunknown = true)
    public record cityinfo(
            @jsonproperty("city") string city,
            @jsonproperty("citykey") string citykey,
            @jsonproperty("parent") string parent,
            @jsonproperty("updatetime") string updatetime
    ) {}
    @jsonignoreproperties(ignoreunknown = true)
    public record weatherdatawrapper(
            @jsonproperty("shidu") string humidity,
            @jsonproperty("pm25") double pm25,
            @jsonproperty("pm10") double pm10,
            @jsonproperty("quality") string quality,
            @jsonproperty("wendu") string temperature,
            @jsonproperty("ganmao") string healthtip,
            @jsonproperty("forecast") list<forecast> forecast
    ) {}
    @jsonignoreproperties(ignoreunknown = true)
    public record forecast(
            @jsonproperty("date") string date,
            @jsonproperty("high") string hightemp,
            @jsonproperty("low") string lowtemp,
            @jsonproperty("ymd") string ymd,
            @jsonproperty("week") string week,
            @jsonproperty("fx") string winddirection,
            @jsonproperty("fl") string windlevel,
            @jsonproperty("type") string weathertype,
            @jsonproperty("notice") string notice
    ) {}
    public record citycodeinfo(
        integer id,
        string province,
        string city,
        string citycode
    ) {}
    /**
     * 工具方法 1:获取当前天气
     */
    @tool(description = "get current weather for a chinese city. input is city code (e.g., 101010100 for beijing)")
    public string getcurrentweather(
        @toolparam(description = "city code (e.g., 101010100 for beijing, 101020100 for shanghai)") 
        string citycode
    ) {
        responseentity<byte[]> responseentity = restclient.get()
                .uri("{citycode}", citycode)
                .retrieve()
                .toentity(byte[].class);
        byte[] body = responseentity.getbody();
        if (body == null) {
            throw new runtimeexception("empty response from weather api");
        }
        string response = new string(body, standardcharsets.utf_8);
        weatherresponse weatherresponse;
        try {
            weatherresponse = objectmapper.readvalue(response, weatherresponse.class);
        } catch (jsonprocessingexception e) {
            throw new runtimeexception("failed to parse weather data: " + e.getmessage());
        }
        if (200 != weatherresponse.status()) {
            throw new runtimeexception("weather api returned error: " + weatherresponse.message());
        }
        weatherdatawrapper data = weatherresponse.data();
        cityinfo cityinfo = weatherresponse.cityinfo();
        return string.format("""
                        城市:%s (%s)
                        温度:%s°c
                        湿度:%s
                        空气质量:%s (pm2.5: %s)
                        风向:%s %s
                        温馨提示:%s
                        更新时间:%s
                        """,
                cityinfo.city(),
                cityinfo.parent(),
                data.temperature(),
                data.humidity(),
                data.quality(),
                data.pm25(),
                data.forecast().get(0).winddirection(),
                data.forecast().get(0).windlevel(),
                data.healthtip(),
                cityinfo.updatetime());
    }
    /**
     * 工具方法 2:搜索城市代码
     */
    @tool(description = "search chinese city codes by city name. returns list of cities with their codes for weather queries")
    public string searchcitycode(
        @toolparam(description = "city name to search (e.g., '北京', '上海', '广州')") 
        string cityname
    ) {
        if (cityname == null || cityname.trim().isempty()) {
            return "请输入要查询的城市名称";
        }
        string searchname = cityname.trim();
        list<citycodeinfo> results = citycodemap.values().stream()
            .filter(info -> info.city().contains(searchname) ||
                    info.province().contains(searchname))
            .limit(20)
            .tolist();
        if (results.isempty()) {
            return string.format("未找到包含 '%s' 的城市,请检查输入后重试", searchname);
        }
        stringbuilder sb = new stringbuilder();
        sb.append(string.format("找到 %d 个匹配的城市:\n\n", results.size()));
        sb.append(string.format("%-4s %-10s %-10s %-12s%n", "编号", "省份", "城市", "城市编码"));
        sb.append("-".repeat(40)).append("\n");
        for (citycodeinfo info : results) {
            sb.append(string.format("%-4d %-10s %-10s %-12s%n",
                info.id(), info.province(), info.city(), info.citycode()));
        }
        sb.append("\n提示:使用城市编码可以查询具体天气");
        return sb.tostring();
    }
}

关键注解说明

  • @service:标记为 spring 服务组件
  • @tool:spring ai 的工具注解,标记该方法为 mcp 工具
  • @toolparam:标注工具方法的参数及其描述

配置 mcp server

创建启动类 weatherapplication

package cn.wubo.cn.weather.mcp;
import org.springframework.ai.tool.toolcallbackprovider;
import org.springframework.ai.tool.method.methodtoolcallbackprovider;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
import org.springframework.context.annotation.bean;
@springbootapplication
public class weatherapplication {
    public static void main(string[] args) {
        springapplication.run(weatherapplication.class, args);
    }
    @bean
    public toolcallbackprovider weathertools(weatherservice weatherservice) {
        return methodtoolcallbackprovider.builder()
                .toolobjects(weatherservice)
                .build();
    }
}

配置说明

  • toolcallbackprovider:提供工具回调的 bean
  • methodtoolcallbackprovider:基于方法注解的工具提供者
  • .toolobjects(weatherservice):注册包含 @tool 注解的服务对象

发布到 maven 中央仓库

1. 在 sonatype central 创建账户

访问 sonatype central 注册账户并完成验证。

2. 配置认证信息

本地测试(~/.m2/settings.xml)

<settings>
  <servers>
    <server>
      <id>central</id>
      <username>your-username</username>
      <password>your-token</password>
    </server>
  </servers>
</settings>

github secrets 配置

在 github 仓库设置中添加以下 secrets:

  • central_username:sonatype central 用户名
  • central_token:sonatype central token
  • gpg_private_key:gpg 私钥
  • gpg_passphrase:gpg 私钥密码

3. 创建 github actions 发布流程

创建 .github/workflows/publish.yml

name: publish to sonatype central
on:
  release:
    types: [created]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v4
      - name: setup java
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: maven
      - name: create maven settings.xml
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << eof
          <settings xmlns="http://maven.apache.org/settings/1.0.0"
                    xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
                    xsi:schemalocation="http://maven.apache.org/settings/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
            <servers>
              <server>
                <id>central</id>
                <username>\${central_username}</username>
                <password>\${central_token}</password>
              </server>
            </servers>
          </settings>
          eof
        env:
          central_username: \${{ secrets.central_username }}
          central_token: \${{ secrets.central_token }}
      - name: import gpg key
        uses: crazy-max/ghaction-import-gpg@v6
        with:
          gpg_private_key: \${{ secrets.gpg_private_key }}
          passphrase: \${{ secrets.gpg_passphrase }}
          git_user_signingkey: false
          git_commit_gpgsign: false
      - name: extract version from tag
        id: version
        run: echo "version=\${github_ref#refs/tags/v}" >> \$github_output
      - name: set version
        run: mvn versions:set -dnewversion=\${{ steps.version.outputs.version }} -dgeneratebackuppoms=false -b
      - name: publish to central
        run: mvn -b clean deploy
        env:
          gpg_passphrase: \${{ secrets.gpg_passphrase }}

4. 发布流程

提交代码到 git

git add .
git commit -m "initial release"
git push origin main

创建 git release

# 打标签
git tag v1.0.0
git push origin v1.0.0
# 或在 github ui 创建 release
  • 自动发布
    • 创建 release 后,github actions 会自动触发
    • 等待 workflow 完成
    • sonatype central 查看发布状态
    • 通常 15-30 分钟后同步到 maven central

5. 验证发布

发布成功后,可以在以下地址查看:

  • sonatype central: https://central.sonatype.com/artifact/io.github.wb04307201/cn-weather-mcp
  • maven central: https://repo.maven.apache.org/maven2/io/github/wb04307201/cn-weather-mcp/

使用 jbang 通过 stdio 集成到大模型

什么是 jbang

jbang 是一个允许你无需安装 jdk 或配置项目即可运行 java 代码的工具。它非常适合快速原型开发和脚本编写。

1. 安装 jbang

windows (powershell)

iex "& { $(iwr https://ps.jbang.dev) } app setup"

linux / macos

curl -ls https://sh.jbang.dev | bash -s - app setup

2. 验证安装

jbang --version

3. mcp client 配置

以大模型工具的 mcp 配置为例,创建配置文件:

claude desktop 配置

编辑 claude_desktop_config.json

{
  "mcpservers": {
    "cn-weather-mcp": {
      "command": "jbang",
      "args": [
        "io.github.wb04307201:cn-weather-mcp:1.0.0"
      ]
    }
  }
}

配置说明

  • command: jbang - 使用 jbang 运行
  • args: maven 坐标 - jbang 会自动从 maven central 下载并运行

4. stdio 工作原理

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│  llm client │◄───────►│  jbang       │◄───────►│  mcp server │
│  (claude)   │  json   │  (runner)    │  stdio  │  (your jar) │
└─────────────┘  rpc    └──────────────┘  io     └─────────────┘

通信流程

  1. llm client 发送 json-rpc 请求到 jbang
  2. jbang 启动 java 进程,通过 stdin/stdout 与 mcp server 通信
  3. mcp server 处理请求并返回结果
  4. 结果沿原路返回给 llm client

5. 测试 mcp 服务

本地测试

# 直接使用 jbang 运行
jbang io.github.wb04307201:cn-weather-mcp:1.0.0

在 ide 中测试

运行 weatherapplication.main() 方法,然后通过 mcp 客户端工具连接。

6. 在大模型中使用

配置完成后,在大模型对话中可以直接使用:

示例对话

用户:北京今天天气怎么样?

助手:[调用 mcp 工具 searchcitycode("北京")]
      [获取城市代码 101010100]
      [调用 mcp 工具 getcurrentweather("101010100")]
      
      北京今天的天气情况如下:
      - 温度:25°c
      - 湿度:60%
      - 空气质量:良 (pm2.5: 35)
      - 风向:东南风 2 级
      - 温馨提示:天气舒适,适合户外活动

完整示例代码清单

项目关键文件

  1. pom.xml - maven 配置(见前文)
  2. application.yml - 应用配置(见前文)
  3. weatherapplication.java - 启动类(见前文)
  4. weatherservice.java - 服务实现(见前文)
  5. publish.yml - github actions(见前文)

运行命令

# 编译项目
mvn clean package
# 本地测试
mvn spring-boot:run
# 发布到 maven central
mvn clean deploy
# 使用 jbang 运行
jbang io.github.wb04307201:cn-weather-mcp:1.0.0

常见问题与解决方案

q1: gpg 签名失败

错误信息gpg: signing failed: no secret key

解决方案

# 确认 gpg 密钥存在
gpg --list-secret-keys
# 重新生成密钥
gpg --full-generate-key

q2: maven 部署被拒绝

原因:缺少必要的元数据或签名

解决方案

  • 确保 pom.xml 包含所有必需信息(developers, licenses, scm)
  • 确保生成了 source 和 javadoc jars
  • 确保 gpg 签名正确配置

q3: jbang 无法下载 jar

错误信息failed to resolve artifact

解决方案

  • 等待 maven central 同步完成(发布后约 15-30 分钟)
  • 检查 maven 坐标是否正确
  • 清除 jbang 缓存:jbang cache clear

q4: mcp 工具无法被识别

原因@tool 注解未正确配置

解决方案

  • 确保方法有 @tool 注解
  • 确保方法参数有 @toolparam 注解
  • 确保 toolcallbackprovider bean 正确注册

最佳实践建议

1. 代码规范

  • 为所有工具方法提供详细的文档注释
  • 使用清晰的参数命名和描述
  • 提供完善的错误处理和异常信息

2. 安全性

  • 避免在代码中硬编码敏感信息(api keys、密码等)
  • 使用环境变量或配置中心管理敏感配置
  • 对输入参数进行验证和清理

3. 性能优化

  • 使用连接池管理 http 连接
  • 实现适当的缓存策略减少重复请求
  • 异步处理耗时操作

4. 可维护性

  • 保持单一职责原则,每个工具方法功能明确
  • 编写单元测试覆盖核心逻辑
  • 使用日志记录关键操作和错误信息

总结

本文详细介绍了如何使用 java 开发 mcp 服务并发布到 maven 中央仓库的完整流程:

核心步骤回顾

  1. 环境准备:安装 jdk、maven、gpg
  2. 项目搭建:创建 spring boot 项目,配置 maven pom
  3. 服务开发:实现 @tool 注解的业务方法
  4. mcp 配置:配置 toolcallbackprovider bean
  5. 发布准备:配置 gpg 签名和 sonatype central
  6. 自动化发布:使用 github actions 自动发布
  7. 集成使用:通过 jbang 和 stdio 集成到大模型

技术优势

  • 快速开发:基于 spring ai,几行代码即可暴露 mcp 工具
  • 标准化:遵循 mcp 协议,兼容所有 mcp 客户端
  • 易部署:通过 jbang 无需安装,直接运行 jar
  • 可扩展:轻松添加新的工具和方法

应用场景

  • 企业工具集成:将内部系统封装为 mcp 工具供 ai 调用
  • saas 服务封装:将第三方 api 包装为标准化工具
  • 个人项目分享:发布开源工具到 maven central
  • 微服务治理:统一管理 ai 可调用的服务接口

后续学习资源

  • spring ai 官方文档:https://docs.spring.io/spring-ai/reference/
  • mcp 协议规范:https://modelcontextprotocol.io/
  • sonatype central 发布指南:https://central.sonatype.org/publish/
  • jbang 使用手册:https://www.jbang.dev/documentation/

通过本文的学习,你已经掌握了从零开始开发、发布和使用 mcp 服务的完整技能树。现在就开始创建你自己的 mcp 服务,让大模型能够调用你的代码吧!

项目源码参考:https://github.com/wb04307201/cn-weather-mcp

到此这篇关于使用 java 开发 mcp 服务并发布到 maven 中央仓库完整指南的文章就介绍到这了,更多相关java mcp服务发布maven 中央仓库内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com