MCP Server 实战:构建自己的扩展服务

定义

MCP (Model Context Protocol) Server 是一种协议,允许大语言模型通过标准化的接口访问外部数据源和工具。MCP Server 作为中间层,为模型提供结构化的数据访问能力,比如文件系统操作、数据库查询、API调用等。

特点

  • 接收AI模型的请求
  • 将请求转换为对应工具/服务的API调用
  • 把结果返回给AI模型
  • 提供统一的安全和权限管理

实际应用举例

文件系统 MCP Server

arduino 复制代码
用户:帮我分析一下项目文件夹里的代码结构
AI → MCP Server → 本地文件系统
MCP Server返回文件列表和内容给AI进行分析

画个图来更直观地展示这个架构:

MCP Server架构图

举例

接下来我们自己实现一个文件读取的 mcp server,功能是列出指定目录下的所有文件和文件夹。

我们以 nodejs 为例,可以直接让 AI 帮助我们生成代码即可:

package.json

js 复制代码
{
  "name": "file-browser-mcp-server",
  "version": "0.1.0",
  "description": "一个用于浏览文件系统的 MCP Server",
  "type": "module",
  "main": "server.js",
  "bin": {
    "file-browser-mcp": "./server.js"
  },
  "scripts": {
    "start": "node server.js",
    "dev": "node --inspect server.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.4.0"
  },
  "keywords": [
    "mcp",
    "model-context-protocol",
    "file-browser",
    "filesystem"
  ],
  "author": "你的名字",
  "license": "MIT"
}

server.js

js 复制代码
#!/usr/bin/env node

import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
import fs from 'fs/promises'
import path from 'path'

class FileBrowserServer {
  constructor() {
    this.server = new Server(
      {
        name: 'file-browser-server',
        version: '0.1.0',
      },
      {
        capabilities: {
          tools: {},
        },
      },
    )

    this.setupToolHandlers()
  }

  setupToolHandlers() {
    // 注册工具列表处理器
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'list_directory',
            description: '列出指定目录下的所有文件和文件夹',
            inputSchema: {
              type: 'object',
              properties: {
                path: {
                  type: 'string',
                  description: '要浏览的目录路径',
                },
              },
              required: ['path'],
            },
          },
          {
            name: 'get_file_info',
            description: '获取文件或目录的详细信息',
            inputSchema: {
              type: 'object',
              properties: {
                path: {
                  type: 'string',
                  description: '文件或目录的路径',
                },
              },
              required: ['path'],
            },
          },
        ],
      }
    })

    // 注册工具调用处理器
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      switch (request.params.name) {
        case 'list_directory':
          return await this.listDirectory(request.params.arguments)
        case 'get_file_info':
          return await this.getFileInfo(request.params.arguments)
        default:
          throw new Error(`未知工具: ${request.params.name}`)
      }
    })
  }

  async listDirectory(args) {
    try {
      const dirPath = args.path

      // 检查路径是否存在
      await fs.access(dirPath)

      // 读取目录内容
      const items = await fs.readdir(dirPath)
      const itemsWithInfo = []

      for (const item of items) {
        const itemPath = path.join(dirPath, item)
        try {
          const stats = await fs.stat(itemPath)
          itemsWithInfo.push({
            name: item,
            type: stats.isDirectory() ? 'directory' : 'file',
            size: stats.isFile() ? stats.size : null,
            modified: stats.mtime.toISOString(),
            path: itemPath,
          })
        } catch (error) {
          // 如果无法获取文件信息,仍然列出文件名
          itemsWithInfo.push({
            name: item,
            type: 'unknown',
            error: '无法访问文件信息',
            path: itemPath,
          })
        }
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                directory: dirPath,
                items: itemsWithInfo,
                total: itemsWithInfo.length,
              },
              null,
              2,
            ),
          },
        ],
      }
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `错误: 无法读取目录 "${args.path}": ${error.message}`,
          },
        ],
        isError: true,
      }
    }
  }

  async getFileInfo(args) {
    try {
      const filePath = args.path
      const stats = await fs.stat(filePath)

      const fileInfo = {
        path: filePath,
        name: path.basename(filePath),
        type: stats.isDirectory()
          ? 'directory'
          : stats.isFile()
          ? 'file'
          : stats.isSymbolicLink()
          ? 'symlink'
          : 'other',
        size: stats.size,
        created: stats.birthtime.toISOString(),
        modified: stats.mtime.toISOString(),
        accessed: stats.atime.toISOString(),
        permissions: stats.mode.toString(8),
        isReadable: true, // 这里可以添加更详细的权限检查
        isWritable: true,
      }

      // 如果是文件,尝试获取扩展名
      if (stats.isFile()) {
        fileInfo.extension = path.extname(filePath)
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(fileInfo, null, 2),
          },
        ],
      }
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `错误: 无法获取 "${args.path}" 的信息: ${error.message}`,
          },
        ],
        isError: true,
      }
    }
  }

  async run() {
    const transport = new StdioServerTransport()
    await this.server.connect(transport)
    console.error('文件浏览 MCP Server 已启动')
  }
}

// 启动服务器
const server = new FileBrowserServer()
server.run().catch(console.error)

复制完代码执行:npm i 安装依赖即可

cline中使用

我们先以 cline 这个插件为例

在 cline 中配置 MCP Server

js 复制代码
{
  "mcpServers": {
    "file-browser": {
      "disabled": false,
      "timeout": 60,
      "type": "stdio",
      "command": "node",
      "args": ["D:/project_code/practice_project/mcp-test/server.js"],
      "env": {}
    }
  }
}

可以看待右侧的 switch 为绿色就代表 mcp 安装成功了,一共有两个 tool,也就是刚刚代码里注册的list_directory 和 get_file_info。

直接提问:帮我"D:/project_code/practice_project/mcp-test/server.js"看下这个路径下有哪些文件,

可以看到大模型调用了我们写的 MCP Server,list_directory,并且成功的输出了出来,大家可以把代码复制进去自己试一试。

cursor中使用

同样找到cursor setting,添加刚刚那段代码即可

提问试试:帮我"D:/project_code/practice_project/mcp-test/server.js"看看这个文件的详细信息

可以看到大模型调用了我们写的 MCP Server,get_file_info,并且成功的输出了出来。

总结

MCP Server通常包含:

  • 接口定义: 定义AI可以调用的功能
  • 参数验证: 确保传入参数的安全性
  • 权限检查: 控制哪些操作被允许
  • 错误处理: 优雅地处理异常情况
  • 日志记录: 追踪所有操作

比如一个简单的文件MCP Server可能提供这些接口:

scss 复制代码
- read_file(path) - 读取文件内容
- list_directory(path) - 列出目录内容  
- search_files(pattern) - 搜索文件

这样设计的好处是,AI 不需要直接访问你的文件系统,而是通过MCP Server这个受控的中介,既保证了功能性,又维护了安全性。

整个生态系统就像搭积木一样,你可以根据需要选择和组合不同的MCP Server,为AI构建出强大而安全的工具集。

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端