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

相关推荐
younuo36555 分钟前
广州网站搭建费用明细:域名、服务器与开发成本全解析
服务器·前端·github
恋猫de小郭13 分钟前
Flutter A2UI 深度解析,它是怎么提供动态生产力的,然后为什么 A2UI 不只是 Flutter
android·前端·flutter
snow@li15 分钟前
服务器运维:Alibaba Cloud Linux 4 LTS・Vue前端 Java 后端 K3S 部署 CICD 目录规范清单
linux·前端·vue.js
百变梦仔19 分钟前
Codex 启动回复合格后,我会用三类证据验收前端改动
前端·github
the局外人22 分钟前
别让 Codex 一口气写完整个前端:5 组 Skills,把页面、逻辑、测试和构建拆清楚
前端·人工智能·agent
honkun624 分钟前
vue 表格组件 vxe-table 实实现专业记账凭证编辑表格与自动汇总
前端·javascript·vue.js
前端snow31 分钟前
ai agent ---nest+langchain实现SSE推送信息
前端
IMPYLH33 分钟前
HTML 的 <nav> 元素
前端·html
IT_陈寒42 分钟前
Vue的computed属性差点让我加班到凌晨
前端·人工智能·后端
涛涛ing1 小时前
一天写1000行被开除,一周写100行反被升职:2026前端的“工作量陷阱”
前端