第8篇:《白嫖社区生态:一行配置接入GitHub的MCP Server,AI直接读我的代码仓库》

1 阅读8分钟

承上:上一篇我们把自己的工具升级成了MCP协议,三种传输方式都玩了一遍。但你有没有想过——连我们自己都知道把工具做成MCP Server,GitHub、文件系统、数据库这些常用工具,社区是不是早就有人写好了?今天,我们不写一行工具代码,直接接入开源MCP Server,让AI读文件、查数据库、操作GitHub。

1. 核心认知:MCP最大的价值不是“写”,是“接”

回顾一下我们上篇做的事情:

自己写WeatherService → 用@Tool暴露 → 启动MCP ServerClient连接

这条路走通了,但换个角度想:

如果你的AI需要读本地文件,你要自己写文件读取逻辑吗?
如果你的AI需要查数据库,你要自己写JDBC查询吗?
如果你的AI需要操作GitHub Issue,你要自己调GitHub API吗?

不用。社区已经有现成的MCP Server了。

这就是MCP协议真正的威力——不是让你多写代码,而是让你少写代码。

2. 社区MCP Server生态一览

在动手之前,先看看社区有哪些“轮子”可以直接用:

MCP Server功能官方/社区
@anthropic/mcp-server-filesystem文件系统读写官方
@anthropic/mcp-server-githubGitHub Issue/PR/仓库操作官方
@anthropic/mcp-server-postgresPostgreSQL数据库查询官方
@anthropic/mcp-server-sqliteSQLite数据库查询官方
@anthropic/mcp-server-puppeteer浏览器自动化官方
@anthropic/mcp-server-brave-searchBrave搜索引擎官方
社区还有更多...Slack、Jira、Notion等社区

这些Server大部分是Node.js/Python写的。你不需要懂这些语言,只需要知道怎么启动它们,然后用Spring AI的MCP Client去连接。

3. 实战一:接入文件系统MCP Server

3.1 场景

让AI能读取你本地的代码、文档、配置文件,然后回答诸如:

  • “我这个项目的pom.xml里用了哪些依赖?”
  • “帮我找一下项目里所有用了@Tool注解的类”
  • “application.yml里配置了多少个MCP连接?”

3.2 安装文件系统MCP Server

# 需要Node.js环境
npm install -g @modelcontextprotocol/server-filesystem

3.3 Spring AI Client配置

spring:
  ai:
    mcp:
      client:
        stdio:
          servers-configuration: classpath:mcp-servers-config.json

classpath:mcp-servers-config.json

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/yunxi/IdeaProjects/yunxi-spring-ai/chapter-07-mcp-client"]
    }
  }
}

注意:路径必须是绝对路径。~ 波浪号可能不被识别,建议写完整路径。

3.4 Client代码

package com.yunxi.ai.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class ChatService {

    private final ChatClient chatClient;

    public ChatService(ChatClient.Builder builder, ToolCallbackProvider tools) {
        this.chatClient = builder
                .defaultToolCallbacks(tools)
                .build();
    }

    public String chat(String message) {
        return chatClient.prompt()
                .system("你是一个专业的助手,可以读取文件系统")
                .user(message)
                .call()
                .content();
    }

}

3.5 测试

2026-07-25T19:57:28.338+08:00 DEBUG 62860 --- [nio-9999-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.yunxi.ai.controller.ChatController#chat(String)
2026-07-25T19:57:28.356+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.client.DefaultRestClient         : Writing [ChatCompletionRequest[messages=[ChatCompletionMessage[rawContent=你是一个专业的助手,可以读取文件系统, role=SYSTEM, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=写一个测试文件hello world, role=USER, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null]], model=deepseek-v4-pro, store=null, metadata=null, frequencyPenalty=null, logitBias=null, logprobs=null, topLogprobs=null, maxTokens=null, maxCompletionTokens=null, n=null, outputModalities=null, audioParameters=null, presencePenalty=null, responseFormat=null, seed=null, serviceTier=null, stop=null, stream=false, streamOptions=null, temperature=0.7, topP=null, tools=[org.springframework.ai.openai.api.OpenAiApi$FunctionTool@7fd90769, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@26a98958, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@1a88f6c0, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@357a5804, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@22ff38d, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@68a649d9, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@35459cfb, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@5218582f, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@2f566a7a, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@756406c8, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@6cdffedb, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@7c161a4f, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@608fba0d, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@3508a292, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@2632bbce], toolChoice=null, parallelToolCalls=null, user=null, reasoningEffort=null, webSearchOptions=null, verbosity=null, promptCacheKey=null, safetyIdentifier=null, extraBody={}]] as "application/json" with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
2026-07-25T19:57:30.569+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.client.DefaultRestClient         : Reading to [org.springframework.ai.openai.api.OpenAiApi$ChatCompletion]
2026-07-25T19:57:30.571+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.a.m.tool.DefaultToolCallingManager   : Executing tool call: write_file
2026-07-25T19:57:30.588+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.client.DefaultRestClient         : Writing [ChatCompletionRequest[messages=[ChatCompletionMessage[rawContent=你是一个专业的助手,可以读取文件系统, role=SYSTEM, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=写一个测试文件hello world, role=USER, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=我来创建一个 `hello_world` 测试文件。, role=ASSISTANT, name=null, toolCallId=null, toolCalls=[ToolCall[index=null, id=call_00_VAuDtPo7fV0dndXT4LsR0717, type=function, function=ChatCompletionFunction[name=write_file, arguments={"path": "hello_world.txt", "content": "Hello World!\n这是一个测试文件。\n"}]]], refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=[{"text":"Successfully wrote to hello_world.txt"}], role=TOOL, name=write_file, toolCallId=call_00_VAuDtPo7fV0dndXT4LsR0717, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null]], model=deepseek-v4-pro, store=null, metadata=null, frequencyPenalty=null, logitBias=null, logprobs=null, topLogprobs=null, maxTokens=null, maxCompletionTokens=null, n=null, outputModalities=null, audioParameters=null, presencePenalty=null, responseFormat=null, seed=null, serviceTier=null, stop=null, stream=false, streamOptions=null, temperature=0.7, topP=null, tools=[org.springframework.ai.openai.api.OpenAiApi$FunctionTool@6aba0925, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@77f899ad, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@714a9f2, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@34cb8e15, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@e025a88, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@3d049f14, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@1ffdfa7f, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@772e74b3, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@6be4a6c4, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@72017893, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@25f4aae, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@6503dba, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@484c6932, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@31705548, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@490263f2], toolChoice=null, parallelToolCalls=null, user=null, reasoningEffort=null, webSearchOptions=null, verbosity=null, promptCacheKey=null, safetyIdentifier=null, extraBody={}]] as "application/json" with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
2026-07-25T19:57:32.141+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.client.DefaultRestClient         : Reading to [org.springframework.ai.openai.api.OpenAiApi$ChatCompletion]
2026-07-25T19:57:32.143+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.a.m.tool.DefaultToolCallingManager   : Executing tool call: read_text_file
2026-07-25T19:57:32.169+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.client.DefaultRestClient         : Writing [ChatCompletionRequest[messages=[ChatCompletionMessage[rawContent=你是一个专业的助手,可以读取文件系统, role=SYSTEM, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=写一个测试文件hello world, role=USER, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=我来创建一个 `hello_world` 测试文件。, role=ASSISTANT, name=null, toolCallId=null, toolCalls=[ToolCall[index=null, id=call_00_VAuDtPo7fV0dndXT4LsR0717, type=function, function=ChatCompletionFunction[name=write_file, arguments={"path": "hello_world.txt", "content": "Hello World!\n这是一个测试文件。\n"}]]], refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=[{"text":"Successfully wrote to hello_world.txt"}], role=TOOL, name=write_file, toolCallId=call_00_VAuDtPo7fV0dndXT4LsR0717, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=文件已成功创建!让我确认一下内容:, role=ASSISTANT, name=null, toolCallId=null, toolCalls=[ToolCall[index=null, id=call_00_WewfrWtoSgjnOiJDT5Op7208, type=function, function=ChatCompletionFunction[name=read_text_file, arguments={"path": "hello_world.txt"}]]], refusal=null, audioOutput=null, annotations=null, reasoningContent=null], ChatCompletionMessage[rawContent=[{"text":"Hello World!\n这是一个测试文件。\n"}], role=TOOL, name=read_text_file, toolCallId=call_00_WewfrWtoSgjnOiJDT5Op7208, toolCalls=null, refusal=null, audioOutput=null, annotations=null, reasoningContent=null]], model=deepseek-v4-pro, store=null, metadata=null, frequencyPenalty=null, logitBias=null, logprobs=null, topLogprobs=null, maxTokens=null, maxCompletionTokens=null, n=null, outputModalities=null, audioParameters=null, presencePenalty=null, responseFormat=null, seed=null, serviceTier=null, stop=null, stream=false, streamOptions=null, temperature=0.7, topP=null, tools=[org.springframework.ai.openai.api.OpenAiApi$FunctionTool@66132625, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@3b5dc607, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@69243810, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@4aa1bd72, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@357c880d, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@60441d5b, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@28684dfb, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@6fe53fa9, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@3e08eeb8, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@7ee692e7, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@140b1f55, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@5255720a, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@2bd12bd6, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@616ae5e1, org.springframework.ai.openai.api.OpenAiApi$FunctionTool@2dd7af2e], toolChoice=null, parallelToolCalls=null, user=null, reasoningEffort=null, webSearchOptions=null, verbosity=null, promptCacheKey=null, safetyIdentifier=null, extraBody={}]] as "application/json" with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
2026-07-25T19:57:33.410+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.client.DefaultRestClient         : Reading to [org.springframework.ai.openai.api.OpenAiApi$ChatCompletion]
2026-07-25T19:57:33.415+08:00 DEBUG 62860 --- [nio-9999-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/plain', given [*/*] and supported [text/plain, */*, application/json, application/*+json, application/yaml]
2026-07-25T19:57:33.416+08:00 DEBUG 62860 --- [nio-9999-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Writing ["✅ 测试文件 `hello_world.txt` 已创建完成,内容如下:<EOL><EOL>```<EOL>Hello World!<EOL>这是一个测试文件。<EOL>```"]
2026-07-25T19:57:33.419+08:00 DEBUG 62860 --- [nio-9999-exec-3] o.s.web.servlet.DispatcherServlet        : Completed 200 OK 画一下这个交互

你一行文件读取代码都没写。 这就是白嫖的快乐。

4. 实战二:接入Gitee MCP Server

4.1 场景

让AI能操作你的Gitee仓库:

  • “帮我查一下最近一周的Issue”
  • “创建一个PR,标题是'修复订单查询Bug'”
  • “看看最近的commit记录”

4.2 安装Gitee MCP Server

npm install -g @gitee/mcp-gitee@latest

4.3 创建Gitee Personal Access Token

  1. 打开 gitee.com/profile/per…
  2. 点击 "生成新令牌"
  3. 勾选权限:user_info projects pull_requests issues notes keys hook groups gists enterprises emails
  4. 生成后复制Token(只显示一次)

4.4 Spring AI Client配置

{
  "mcpServers": {
    "gitee": {
      "command": "npx",
      "args": [ "-y", "@gitee/mcp-gitee@latest" ],
      "env": {
        "GITEE_API_BASE": "https://gitee.com/api/v5",
        "GITEE_ACCESS_TOKEN": "e474ac8baf0******a5260decebab4"
      }
    }
  }
}

4.5 测试

5. 实战三:同时接入多个MCP Server

把文件系统和Gitee一起接进来:

跨系统协作,只靠一个 McpClient

6. 如何发现更多社区MCP Server?

资源地址说明
MCP官方仓库github.com/modelcontex…官方维护的Server列表
npm搜索npm search mcp-serverNode.js生态
GitHub搜索mcp-server topic社区开源项目
Spring AI文档docs.spring.io/spring-ai/r…Java生态
MCP广场www.modelscope.cn/mcp魔塔社区

7. 避坑指南

坑1:Node.js环境没装

大部分社区MCP Server是Node.js写的,需要先装Node.js

node -v   # 确认版本 >= 18
npm -v

坑2:文件系统MCP Server权限太大

mcp-server-filesystem 默认能读传入目录下的所有文件,包括 .env.git/config 等敏感文件。

建议:创建一个专门给AI访问的目录,不要直接给项目根目录。

坑3:STDIO方式的日志看不到

STDIO方式如果Server启动失败,日志不会直接输出到控制台。排查时可以先手动运行命令确认Server能启动。

坑4:版本兼容问题

社区MCP Server更新频繁,和Spring AI的MCP Client可能有兼容问题。优先使用官方维护的 @anthropic/ 开头的Server。

八、本篇小结

这一篇我们没有写一行工具代码,全靠白嫖社区生态:

接入的Server能力
文件系统读代码、读配置、读文档
GiteeIssue、PR、Commit操作
(未来更多)数据库、Slack、Jira...

核心心法

第一阶段:自己写@Tool → 知道原理
第二阶段:升级成MCP Server → 标准化
第三阶段:接入社区MCP Server → 白嫖  ← 你现在在这里

MCP协议真正的价值,不在于你写了多少Server,而在于你能接入多少现成的生态。

MCP协议真正的价值,不在于你写了多少Server,而在于你能接入多少现成的生态。

现在我们的AI工具能力已经拉满了——能调接口、能读文件、能操作GitHub。但每次对话都像在跟一个失忆的人聊天,上一句刚说完我叫张三,下一句它就问“您好,请问怎么称呼?”

下一篇,我们要解决这个问题——给AI装上"海马体",让它记住你们聊过什么。


本文与DeepSeek协作完成