前端也需要了解的 mcp 知识

MCP 是什么?

MCP 是 Model Context Protocol 由 Anthropic 开发的开源协议,使(如 Cursor、Cline、Claude Desktop) AI 系统能够安全地连接各种数据源。它通过客户端-服务器架构为 AI 助手提供了访问外部数据、工具和提示的通用标准。

一句话总结: MCP 为 AI 代理连接到不同的服务提供了一种标准化的方式

一张你在很多地方看到过的图 这个应该原文章地址

MCP 可以干什么?

相比于 MCP 是什么我其实更想知道 MCP 能干什么?

AI 有一个缺点: AI 只知道过去发生的事情,却无法实时从外界获取最新的信息 比如今天的天气情况、今天的热点新闻等。因为训练 AI 的数据集始终是滞后的

在 MCP 之前我们可以通过 Function Call 来给 AI 增加类似网页搜索的功能让其可以获取到实时数据,但没有一个标准,同样的功能换一个 AI 可能还需要重新实现一下

就像上边的那张图片 MCP 提供一种标准的接入协议,只要是按照 MCP 开发的服务端就可以在 MCP 客户端中接入(这他么不是废话吗!)

MCP 可以定义一系列的能力比如 GitHub MCP 服务

提供了比如搜索仓库、issuer、回复、用户,创建仓库,提交代码等功能

你可以通过自然语言让 AI 来执行这些操作

然后还有 filesystem 可以让 AI 操作文件

MCP 不只是提供 Tool(工具) 还可以提供 Prompt(主要是作为 AI 提示词 模板)、Resource(客户端可以读取的数据(如 API 响应或文件内容),这里不再展开感兴趣的自行前往官网探索

然后要说的是: 使用一些 MCP 服务的时候要注意 安全 防止恶意代码被执行造成损失!

如何实现一个 MCP 服务?

这里实现两个简单的 MCP 服务一个时间时间工具 和 简单的 GitHub 搜索,把官网 MCP 服务端的两种写法都实践一些

首先做一些准备工作, 为了方便使用 monorepo 的方式组织仓库 猛击直达

实现 time MCP 服务

这里假设你已经准备好了一个与上边类似的仓库,然后进入到 time 文件夹

如果你是一个单仓库,创建一个空的文件夹直接跟着做

执行 npm init -y 初始化 package.json 文件

安装需要的依赖

bash 复制代码
pnpm install @modelcontextprotocol/sdk zod dayjs
pnpm install -D @types/node typescript shx

创建 index.ts 文件

创建 tsconfig.json 写入如下内容 (仓库中是继承了根目录下的配置文件)

json 复制代码
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["packages/**/*"],
  "exclude": ["node_modules"]
}

修改 package.json 文件

json 复制代码
{
  "name": "time",
  "version": "0.0.1",
  "bin": {
    "mcp-server-time": "dist/index.js"
  },
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "npm run build",
    "watch": "tsc --watch"
  },
  "files": [
    "dist"
  ],
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.8.0",
    "dayjs": "^1.11.13",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^22.14.0",
    "shx": "^0.3.4",
    "typescript": "^5.6.2"
  }
}

index.ts 中写入如下代码

ts 复制代码
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js'
import timezone from 'dayjs/plugin/timezone.js'
import packageJson from './package.json'

// 扩展 dayjs 以支持 UTC 和时区
dayjs.extend(utc)
dayjs.extend(timezone)

// 创建 MCP 服务器实例
const server = new McpServer({
  name: 'mcp-server-time',
  version: packageJson.version
})

注意 !/usr/bin/env node (解释器指示符) 意思是需要使用 node 去执行代码

增加获取当前时间的工具

ts 复制代码
// 获取当前时间的工具
server.tool(
  'get_current_time', // 工具名称
  '获取当前时间', // 工具描述
  { 
    // 工具参数
    timezone: z.string().optional(),
  },
  // 工具实现
  async ({ timezone }) => {
    // 获取当前时间
    const tz = timezone || process.env.LOCAL_TIMEZONE || 'Asia/Shanghai';
    // 格式化当前时间
    const currentTime = dayjs().tz(tz).format('YYYY-MM-DD HH:mm:ss');

    // 返回数据 - 下边的格式是固定的
    return {
      content: [{ type: "text", text: JSON.stringify({ currentTime }, null, 2) }],
    };
  }
)

增加时间转换工具

ts 复制代码
// 时间转换工具
server.tool(
  'convert_time', // 工具名称
  '在时区之间转换时间', // 工具描述
  {
    // 工具参数
    source_timezone: z.string(),
    time: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, 'Invalid time format, expected HH:MM'),
    target_timezone: z.string(),
  },
  // 工具实现
  async ({ source_timezone, time, target_timezone }) => {
    const sourceTime = dayjs.tz(`${dayjs().format('YYYY-MM-DD')} ${time}`, source_timezone);
    const convertedTime = sourceTime.clone().tz(target_timezone).format();
    return {
      content: [{ type: "text", text: JSON.stringify({ convertedTime }, null, 2) }],
    };
  }
)

启动 MCP 服务器

ts 复制代码
// 启动服务器
async function runServer() {
  // 这两行代码应该算是固定写法
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('获取当前时间和时区转换的 MCP 服务器已在 stdio 上启动')
}

runServer().catch((error) => {
  console.error('启动服务器时出错:', error)
  process.exit(1)
})

这里与上边的引入有一点差别使用 Server 而不是 McpServer 来实现服务

ts 复制代码
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import packageJson from './package.json'
import {CallToolRequestSchema, ListToolsRequestSchema,} from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from 'zod-to-json-schema';

const server = new Server(
  {
    name: "github-search-mcp-server",
    version: packageJson.version,
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

这里会统一定义工具列表

ts 复制代码
// 定义 GitHub 搜索工具的输入参数
const SearchParamsSchema = z.object({
  query: z.string().describe('搜索关键词,用于匹配 GitHub 中的内容'),
  page: z.number().optional().default(1).describe('当前页码,用于分页查询'),
  perPage: z.number().optional().default(30).describe('每页返回的搜索结果数量'),
  type: z.enum(['repositories', 'code', 'issues', 'users'])
    .optional()
    .describe('搜索类型,可选值为 repositories、code、issues 或 users')
});

// 定义获取 GitHub 用户信息的输入参数
const UserInfoParamsSchema = z.object({
  username: z.string().describe('GitHub 的用户名')
});

// 定义工具列表及输入参数
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "github_search",
        description: "在 GitHub 上搜索仓库、代码、Issues 或用户",
        inputSchema: zodToJsonSchema(SearchParamsSchema),
      },
      {
        name: "get_github_user",
        description: "通过用户名查询 GitHub 用户信息",
        inputSchema: zodToJsonSchema(UserInfoParamsSchema),
      },
    ]
  }
})

实现对应的工具

ts 复制代码
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    if (!request.params.arguments) {
      throw new Error("Arguments are required");
    }

    switch (request.params.name) {
      // GitHub 搜索工具
      case "search_github": {
        const { query, page, perPage, type } = SearchParamsSchema.parse(request.params.arguments);

        const url = `https://api.github.com/search/${type}?q=${encodeURIComponent(query)}&page=${page}&per_page=${perPage}`
        const res = await fetch(url, {
          headers: {
            Accept: 'application/vnd.github.v3+json',
            Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
          }
        })

        if (!res.ok) {
          throw new Error(`GitHub API 错误: ${res.status} ${res.statusText}`)
        }

        const data = await res.json()

        return {
          content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
        };
      }

      // 获取 GitHub 用户信息工具
      case "get_github_user": {
        const { username } = UserInfoParamsSchema.parse(request.params.arguments);
        const url = `https://api.github.com/users/${encodeURIComponent(username)}`
        const res = await fetch(url, {
          headers: {
            Accept: 'application/vnd.github.v3+json',
            Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
          }
        })

        if (!res.ok) {
          throw new Error(`获取用户信息失败: ${res.status} ${res.statusText}`)
        }

        const data = await res.json()

        return {
          content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
        };
      }

      default:
        throw new Error(`Unknown tool: ${request.params.name}`);
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
    }
    throw error;
  }
});

这里需要注意的是我们使用了一个 process.env.GITHUB_TOKEN (调用 GitHub 的 token 可以在 github 中生成) 环境变量

启动 MCP 服务器,这里和上边一样

ts 复制代码
async function runServer() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('GitHub 搜索 MCP 服务器已在 stdio 上启动')
}

runServer().catch((error) => {
  console.error('启动服务器时出错:', error)
  process.exit(1)
})

测试 MCP 服务

官方提供了一个测试工具猛击直达

在调试之前需要先执行 pnpm run build 执行打包

测试 time MCP 服务

执行 npx @modelcontextprotocol/inspector node dist/index.js

访问 http://127.0.0.1:6274

获取当前时间

根据时区转换时间

执行 npx @modelcontextprotocol/inspector node dist/index.js

访问 http://127.0.0.1:6274

这里因为使用了 process.env.GITHUB_TOKEN 环境变量,启动的时候我们需要进行设置,否则 GitHub api 调用会 401

根据用户名获取 GitHub 的用户的信息

github_search 之 查询多个 GitHub 用户

按照要求返回两个符合条件的用户信息

如何使用 ?

在 vscode 中

vscode 版本更新后增加了 MCP 的相关功能 我的版本是 1.99.2

打开配置面板 (⌘, on Mac or Ctrl+, on Windows/Linux)

搜索 chat.agent.enabled

创建 .vscode/mcp.json 官方文档 写入如下内容

json 复制代码
{
  "inputs": [
    {
      "type": "promptString",
      "id": "GITHUB_TOKEN",
      "description": "GitHub token",
      "password": true
    }
  ],
  "servers": {
    "github-search": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@vaebe/server-github-search"],
      "env": {
        "GITHUB_TOKEN": "${input:GITHUB_TOKEN}"
      }
    },
    "get_current_time": {
      "type": "stdio",
      "command": "node",
      "args": ["xxxx/packages/time/dist/index.js"],
    },
  }
}

npx 是一个允许你直接运行 npm 包中命令、无需全局安装的工具

get_current_time 这个配置中 "xxxx/packages/time/dist/index.js" 是你 mcp 文件的绝对路径

细心的你可能发现了 还有一种 docker 的调用方式,这里没有写!

打开 AI 对话面板-连接 MCP

填写参数

MCP 工具列表

时间 MCP 服务调用

GitHub 搜索 MCP服务调用

在 nextjs 中使用 ai sdk 调用

具体示例可以在 ai-chat 仓库中查看

首先 ai 的版本要大于 4.2 因为 mcp clent 是 4.2 更新的内容

这个仓库是一个 AI 应用还在开发中,代码可能会发生变化

app/api/ai/chat/mcp/index.ts 写入 连接 MCP 服务的逻辑

ts 复制代码
import { experimental_createMCPClient as createMCPClient } from 'ai'
import { Experimental_StdioMCPTransport as StdioMCPTransport } from 'ai/mcp-stdio'

/**
 * 一个 MCP clent 只能连接一个 MCP server
 * 所以这里我们后边可以创建多个 MCP server 函数然后导出
 */
export async function createGithubSearchMcpServer() {
  // 创建 MCP client
  const client = await createMCPClient({
    transport: new StdioMCPTransport({
      command: 'npx',
      args: ['-y', '@vaebe/server-github-search'],
      env: {
        GITHUB_TOKEN: process.env.GITHUB_TOKEN ?? ''
      }
    })
  })

  // 获取 MCP server 的工具列表
  const tools = await client.tools()

  return {
    client,
    tools
  }
}

然后在调用 AI 的地方引入使用

ts 复制代码
import { createGithubSearchMcpServer } from './mcp'
// 省略其他代码

const githubSearchMcp = await createGithubSearchMcpServer()

const result = streamText({
    model: openai('qwen-turbo-latest'), // 模型名称
    system: '你是一个通用的智能 AI 可以根据用户的输入回答问题',
    messages, // 传入用户消息历史
    tools: {
      ...githubSearchMcp.tools
    },
    maxSteps: 5
})

目前看还是 MCP 服务当成 Function Call 来用了

总结

这就是我一个周的实践过程

回答了我最初的两个问题: MCP 是什么? MCP 可以干什么?

实现了两个简单的 MCP 服务, 一个用来获取当前时间、转换时区, 另一个是用于检索 GitHub 信息

做了相关测试,在 vscode 、nextjs、 ai/sdk 中使用 MCP 服务

在动手实践的过程中更容易理解 MCP 这个概念

MCP 潜力绝不止如此

参考

modelcontextprotocol.io

How to build MCP servers with TypeScript SDK

What is Model Context Protocol (MCP)? How it simplifies AI integrations compared to APIs

MCP从理解到实现

mcp.so

相关推荐
db_lnn_202141 分钟前
【vue】全局组件及组件模块抽离
前端·javascript·vue.js
Qin_jiangshan1 小时前
vue实现进度条带指针
前端·javascript·vue.js
菜鸟una1 小时前
【layout组件 与 路由镶嵌】vue3 后台管理系统
前端·vue.js·elementui·typescript
小张快跑。1 小时前
【Vue3】使用vite创建Vue3工程、Vue3基本语法讲解
前端·前端框架·vue3·vite
Zhen (Evan) Wang1 小时前
.NET 8 API 实现websocket,并在前端angular实现调用
前端·websocket·.net
星空寻流年1 小时前
css3响应式布局
前端·css·css3
带刺的坐椅2 小时前
jFinal 使用 SolonMCP 开发 MCP(拥抱新潮流)
java·ai·solon·jfinal·mcp
Rverdoser2 小时前
代理服务器运行速度慢是什么原因
开发语言·前端·php
是代码侠呀2 小时前
从前端视角看网络协议的演进
leetcode·开源·github·github star·github 加星
航Hang*2 小时前
前端项目2-01:个人简介页面
前端·经验分享·html·css3·html5·webstorm