Java中使用Spring Boot+Ollama实现本地AI的MCP接入

目录结构

在我的上一篇文章搭建好本地的聊天机器人后,准备接入MCP进一步增强AI的能力,以实现类似手机AI的功能

参考的是第二篇文章链接其内容比较精炼,有些细节被忽略了,导致踩坑不少,可能是因为版本原因,最终我没能使用他的方案运行成功,转而使用了另一个方案,原文连接

为什么使用Qwen3

完善spring boot

pom.xml添加依赖

在实际添加过程中,第一个依赖我用的不一样,原文给的我无法加载,第二个依赖我并不能成功加入,我使用的依赖如下

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>
    <groupId>com.example</groupId>
    <artifactId>Qwen3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Qwen3</name>
    <description>Qwen3</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.1</version>
    </parent>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
            <version>1.0.0-M6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
            <version>1.0.0-M6</version>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <url>https://repo.spring.io/milestone</url>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.example.qwen3.Qwen3Application</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

MCP客户端和服务端分别作用

我两者都添加了

application.yml

yml完全改掉了,毕竟

yml 复制代码
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        enabled: true
        # 聊天模型
        model: qwen3:4b
        options:
          temperature: 0.7
          top_p: 0.9
          num_predict: 5120   # 单次回复最大长度,根据自己电脑性能来定
      embedding:
        enabled: true
        # 向量模型(通常用小点的模型效率高)
        model: qwen3:4b
        options:
          num-batch: 5120
    mcp:
      client:
        enabled: true
        name: mcp-client
        version: 1.0
        type: SYNC
        request-timeout: 30s
        stdio:
          servers-configuration: classpath:/mcp-server-settings.json

logging:
  level:
    org.springframework.ai.mcp.tool: DEBUG


server:
  port: 8181

MCP 工具配置 mcp-servers.json

我想使用本地文件操作以实现

它需要安装Node.js 和 npm

自测一下确实安装过了

并且

在红框路径新建mcp-servers.json文件,注意红色下划线处要改成自己的桌面路径

json 复制代码
{
  "mcpServers": {
    "filesystem": {
      "command": "F:\\Environment\\nodejs\\npx.cmd",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\lenovo\\Desktop\\temp"
      ]
    }
  }
}

"filesystem" 是 MCP server 的名字

"command": "npx.cmd" → Windows 下用 npx 启动(Linux/macOS 下就是 "npx")

"@modelcontextprotocol/server-filesystem" 是官方提供的文件系统 MCP server

"C:\Users\lenovo\Desktop\temp" 表示允许 AI 访问的目录范围

配置类

java 复制代码
package com.example.qwen3.config;

import io.modelcontextprotocol.client.McpSyncClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class McpConfig {

    @Bean
    public SyncMcpToolCallbackProvider syncMcpToolCallbackProvider(List<McpSyncClient> mcpSyncClients) {
        return new SyncMcpToolCallbackProvider(mcpSyncClients);
    }

}

编写API

java 复制代码
package com.example.qwen3.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.Duration;


@RequestMapping("/mcp")
@RestController
public class McpController {
    private final OllamaChatModel chatModel;
    private final SyncMcpToolCallbackProvider syncMcpToolCallbackProvider;

    public McpController(OllamaChatModel chatModel, SyncMcpToolCallbackProvider syncMcpToolCallbackProvider) {
        this.chatModel = chatModel;
        this.syncMcpToolCallbackProvider = syncMcpToolCallbackProvider;
    }

    @GetMapping("/mcpChat")
    public String generate(@RequestParam(value = "prompt") String prompt) {
        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultTools(syncMcpToolCallbackProvider.getToolCallbacks())
                .build();
        return chatClient.prompt().user(prompt).call().content();
    }

    @GetMapping(value = "/mcpChatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ChatResponse> generateStream(@RequestParam(value = "prompt") String prompt) {
        // 先执行一次完整的调用以确保工具被执行
        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultTools(syncMcpToolCallbackProvider.getToolCallbacks())
                .defaultOptions(OllamaOptions.builder()
                        .model("qwen3:4b")
                        .build())
                .build();

        // 执行工具调用(非流式)
        String result = chatClient.prompt().user(prompt).call().content();

        // 然后返回流式响应(基于已执行的结果)
        return chatClient.prompt()
                .user(prompt + "\n\n基于上述操作,请总结执行结果:")
                .stream()
                .chatResponse();
    }


    // 方案5:简化版本 - 直接使用工作版本的逻辑
    @GetMapping(value = "/mcpChatStreamFixed", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> mcpChatStreamFixed(@RequestParam("prompt") String prompt) {
        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultTools(syncMcpToolCallbackProvider.getToolCallbacks())
                .build();

        // 使用已知工作的逻辑,但改为流式输出
        return Mono.fromCallable(() -> {
                    // 先执行完整的对话(包括工具调用)
                    return chatClient.prompt().user(prompt).call().content();
                })
                .flatMapMany(result -> {
                    // 然后将结果分块流式返回
                    return Flux.fromArray(result.split(""))
                            .delayElements(Duration.ofMillis(50)); // 每个字符延迟50ms
                })
                .subscribeOn(Schedulers.boundedElastic());
    }

    @GetMapping("/test")
    public String test(@RequestParam(value = "prompt") String prompt) {
        return "input=" + prompt;
    }
}

我在想要实现流式问答部分卡了很久,原因是在流式响应中,工具调用和工具执行的时机与非流式不同,导致工具调用被跳过或执行顺序有问题。

所以要么就是一次性输出内容,比如

复制代码
http://localhost:8181/mcp/mcpChat?在temp目录下创建文件e1.txt,内容为我是mcpChat创建的文件

要么是先执行操作,再流式输出对于上一次操作的总结

复制代码
http://localhost:8181/mcp/mcpChatStream?在temp目录下创建文件e2.txt,内容为我是mcpChat创建的文件

或者单纯流式

本地模型速度还是太慢了,实测大概需要5分钟才能创建好一个文件,基本没有实用性可言

相关推荐
毕设源码-郭学长9 分钟前
【开题答辩全过程】以 J2EE在电信行业的应用研究为例,包含答辩的问题和答案
java·java-ee
Aevget9 分钟前
「Java EE开发指南」如何用MyEclipse开发Java EE企业应用程序?(二)
java·ide·java-ee·开发·myeclipse
不爱编程的小九九36 分钟前
小九源码-springboot048-基于spring boot心理健康服务系统
java·spring boot·后端
龙茶清欢39 分钟前
Spring Boot 应用启动组件加载顺序与优先级详解
java·spring boot·后端·微服务
吃饭睡觉发paper44 分钟前
High precision single-photon object detection via deep neural networks,OE2024
人工智能·目标检测·计算机视觉
青云交1 小时前
Java 大视界 -- Java 大数据在智能公交调度优化与准点率提升中的应用实践(416)
java·动态规划·flink cep·spark mllib·智能公交调度·杭州公交案例·准点率提升
RainbowSea1 小时前
4. ChatClient 的初始,快速使用上手
java·spring·ai编程
RainbowSea1 小时前
3. Ollama 安装,流式输出,多模态,思考模型
java·spring·ai编程
醉方休1 小时前
TensorFlow.js高级功能
javascript·人工智能·tensorflow
云宏信息1 小时前
赛迪顾问《2025中国虚拟化市场研究报告》解读丨虚拟化市场迈向“多元算力架构”,国产化与AI驱动成关键变量
网络·人工智能·ai·容器·性能优化·架构·云计算