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构建出强大而安全的工具集。

相关推荐
二哈喇子!17 分钟前
Vue3 组合式API
前端·javascript·vue.js
二哈喇子!2 小时前
Vue 组件化开发
前端·javascript·vue.js
chxii2 小时前
2.9 插槽
前端·javascript·vue.js
姑苏洛言3 小时前
扫码点餐小程序产品需求分析与功能梳理
前端·javascript·后端
Freedom风间3 小时前
前端必学-完美组件封装原则
前端·javascript·设计模式
江城开朗的豌豆3 小时前
React表单控制秘籍:受控组件这样玩就对了!
前端·javascript·react.js
一枚前端小能手3 小时前
📋 代码片段管理大师 - 5个让你的代码复用率翻倍的管理技巧
前端·javascript
国家不保护废物3 小时前
Web Worker 多线程魔法:告别卡顿,轻松实现图片压缩!😎
前端·javascript·面试
接着奏乐接着舞。4 小时前
如何在Vue中使用拓扑图功能
前端·javascript·vue.js
阳先森4 小时前
Vue3 Proxy 为何不直接返回target[key],选用Reflect
前端·vue.js