MCP实践-搭建基础的MCP服务

1为什么需要MCP

1.1大模型的局限与扩展

大模型虽然功能强大,但它也有自己的局限性:
●一旦训练完成,它的能力就基本固定了
●面对实时数据和本地数据,它往往无能为力
●对于超出它知识范围的功能需求,也无法满足 要训练这样一个大模型,成本非常高昂:
○需要大量的显卡同时工作
○耗时耗力

1.2MCP的作用

MCP协议正是为了解决大模型的局限性提出的一种的插件系统

2 什么是MCP协议

MCP协议:大模型的「万能插头」系统

正如一个优质插座能兼容多种彩色插头,MCP协议就是大模型的通用接口标准 让各类服务像「即插即用」的插头一样,轻松扩展大模型能力

2.1核心组件解析

组件

对应物

作用

MCP协议

插座接口

定义统一的通信规则 📜

服务器插件

功能插头

提供具体能力(如数据分析⚙️)

大模型

电器主机

通过插头获取新技能 💪

2.2三大核心功能

1️⃣ 实时数据处理插件 📊💨
●实时流式计算
●数据清洗/聚合
●事件驱动响应
2️⃣ 本地文件操作插件 📁🔧
●文件读写加密
●跨平台兼容
●版本控制管理
3️⃣ 个性化功能扩展插件 🎨✨
●用户习惯学习
●多语言支持
●定制化UI生成
💡 优势对比 传统集成需要「重新布线」,而MCP协议实现「即插即用」,开发效率提升💯

3MCP 协议概述

MCP (Model Context Protocol) 是 Anthropic 为 Claude 开发并开源的一个协议
允许 AI 模型与外部服务进行通信,扩展其能力范围
目前只有Claude AI模型提供MCP支持, MCP的相关服务构成了Claude的生态.
1Claude AI:使用 MCP 协议与外部服务通信
2OpenAI 模型(如 GPT-4):使用 Function Calling API 或 Plugin 系统
3Google 模型(如 Gemini):使用 Extension API

官方文档:Introduction - Model Context Protocol

官方Github:Model Context Protocol · GitHub

希望国产大模型快速推出自己的类似服务!
 

3.1MCP 的核心思想

MCP 的核心思想

是让 AI模型 能够通过标准化的接口访问外部工具、数据和服务

3.2MCP 的主要特点

1工具和资源访问:允许 AI 使用外部工具和访问外部资源
2标准化通信:定义了 AI 与服务器之间的通信格式
3安全边界:维持 AI 与外部系统之间的安全隔离
4能力扩展:使 AI 能够执行原本无法完成的任务

4MCP 工作流程

MCP 的工作流程可以概括为以下步骤:

1用户向 Claude 提出需要使用外部工具或数据的请求
2Claude 识别需要使用 MCP 服务,通过 MCP 协议发送请求
3MCP 服务器接收请求,处理并调用相应的外部服务或 API
4外部服务返回结果给 MCP 服务器
5MCP 服务器将结果格式化后返回给 Claude
6Claude 使用这些信息回答用户的问题

4.1MCP 服务器组件

一个基本的 MCP 服务器包含以下核心组件:
1通信层:处理与 Claude 的通信,通常使用标准输入/输出 (stdio)
2请求处理器:解析和处理来自 Claude 的请求
3工具实现:实现各种工具功能的代码
4资源提供者:提供对外部资源的访问
5错误处理:处理可能出现的错误并返回适当的响应

4.2搭建 MCP 服务器的思路

4.2.1环境准备

首先,我们需要准备开发环境:

安装 Node.js(推荐使用 v18 或更高版本)
●创建项目目录
●初始化 npm 项目
●安装 MCP SDK:npm install @modelcontextprotocol/sdk

4.2.2创建基本 MCP 服务器

创建 MCP 服务器的思路

4.2.3配置 Claude 使用 MCP 服务器

最后,配置 Claude 使用我们的 MCP 服务器:

5具体实现

5.1开发环境

5.1.1MCP客户端

首先我们要准备一个支持MCP的客户端, 具体大家可以参考下面的链接

https://modelcontextprotocol.io/llms-full.txt

image.png

这里, 我选择使用的是Cursor 0.46.11


5.2项目初始化

创建一个项目文件mcp
初始化

npm init -y

安装依赖

npm install @modelcontextprotocol/sdk zod
{
  "name": "mcp",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.1",
    "zod": "^3.24.2"
  }
}

5.3编写服务器

// 导入MCP SDK
const {
  McpServer,
  ResourceTemplate,
} = require('@modelcontextprotocol/sdk/server/mcp.js')
const {
  StdioServerTransport,
} = require('@modelcontextprotocol/sdk/server/stdio.js')
const { z } = require('zod')

// 导入牛排数据
const steakData = require('./steakData')

// 创建MCP服务器
const server = new McpServer({
  name: 'SteakInfoServer',
  version: '1.0.0',
  description: '提供牛排相关信息的MCP服务器',
})

// 添加牛排资源
server.resource(
  'steak',
  new ResourceTemplate('steak://{id}', { list: 'steak://list' }),
  async (uri, { id }) => {
    // 如果请求的是列表
    if (uri.href === 'steak://list') {
      return {
        contents: [
          {
            uri: 'steak://list',
            text:
              '可用的牛排信息:\n' +
              steakData
                .filter((item) => item.type !== 'tips')
                .map(
                  (steak) => `${steak.id}. ${steak.name} (${steak.englishName})`
                )
                .join('\n'),
          },
        ],
      }
    }

    // 查找特定ID的牛排
    const steak = steakData.find((s) => s.id === parseInt(id))
    if (!steak) {
      return {
        contents: [
          {
            uri: uri.href,
            text: `未找到ID为${id}的牛排信息。`,
          },
        ],
      }
    }

    // 返回牛排信息
    let text = ''
    if (steak.type === 'tips') {
      text =
        `${steak.name}:\n` +
        steak.tips.map((tip, index) => `${index + 1}. ${tip}`).join('\n')
    } else {
      text =
        `${steak.name} (${steak.englishName})\n\n` +
        `描述: ${steak.description}\n\n` +
        `烹饪方法: ${steak.cookingMethods.join(', ')}\n` +
        `建议熟度: ${steak.recommendedDoneness}\n\n` +
        `营养成分:\n` +
        `- 热量: ${steak.nutritionFacts.calories}\n` +
        `- 蛋白质: ${steak.nutritionFacts.protein}\n` +
        `- 脂肪: ${steak.nutritionFacts.fat}`
    }

    return {
      contents: [
        {
          uri: uri.href,
          text: text,
        },
      ],
    }
  }
)

// 添加获取牛排推荐的工具
server.tool(
  'getSteakRecommendation',
  {
    preference: z.enum(['嫩', '多汁', '风味浓郁', '低脂肪']).optional(),
    cookingMethod: z.enum(['煎', '烤', '低温慢煮', '炭烤']).optional(),
  },
  async ({ preference, cookingMethod }) => {
    let recommendedSteaks = [
      ...steakData.filter((item) => item.type !== 'tips'),
    ]

    // 根据偏好筛选
    if (preference) {
      switch (preference) {
        case '嫩':
          recommendedSteaks = recommendedSteaks.filter(
            (s) => s.name === '菲力牛排' || s.description.includes('嫩')
          )
          break
        case '多汁':
          recommendedSteaks = recommendedSteaks.filter((s) =>
            s.description.includes('多汁')
          )
          break
        case '风味浓郁':
          recommendedSteaks = recommendedSteaks.filter((s) =>
            s.description.includes('风味浓郁')
          )
          break
        case '低脂肪':
          recommendedSteaks = recommendedSteaks.filter(
            (s) => s.description.includes('没有脂肪') || s.name === '菲力牛排'
          )
          break
      }
    }

    // 根据烹饪方法筛选
    if (cookingMethod) {
      recommendedSteaks = recommendedSteaks.filter((s) =>
        s.cookingMethods.includes(cookingMethod)
      )
    }

    // 如果没有匹配的牛排,返回所有牛排
    if (recommendedSteaks.length === 0) {
      recommendedSteaks = steakData.filter((item) => item.type !== 'tips')
    }

    // 构建推荐文本
    let responseText = '根据您的偏好,我推荐以下牛排:\n\n'

    recommendedSteaks.forEach((steak) => {
      responseText += `${steak.name} (${steak.englishName}):\n`
      responseText += `${steak.description}\n`
      responseText += `建议烹饪方法: ${steak.cookingMethods.join(', ')}\n`
      responseText += `建议熟度: ${steak.recommendedDoneness}\n\n`
    })

    // 添加烹饪小技巧
    const tips = steakData.find((item) => item.type === 'tips')
    if (tips) {
      responseText +=
        `${tips.name}:\n` +
        tips.tips.map((tip, index) => `${index + 1}. ${tip}`).join('\n')
    }

    return {
      content: [{ type: 'text', text: responseText }],
    }
  }
)

// 添加获取牛排烹饪方法的工具
server.tool(
  'getSteakCookingMethod',
  {
    steakType: z.enum(['菲力牛排', '肋眼牛排', '纽约客牛排', 'T骨牛排']),
    doneness: z
      .enum(['一分熟', '三分熟', '五分熟', '七分熟', '全熟'])
      .optional(),
  },
  async ({ steakType, doneness }) => {
    const steak = steakData.find((s) => s.name === steakType)
    if (!steak) {
      return {
        content: [{ type: 'text', text: `未找到${steakType}的信息。` }],
      }
    }

    let cookingTime
    switch (doneness) {
      case '一分熟':
        cookingTime = '每面约1分钟'
        break
      case '三分熟':
        cookingTime = '每面约2-3分钟'
        break
      case '五分熟':
        cookingTime = '每面约3-4分钟'
        break
      case '七分熟':
        cookingTime = '每面约4-5分钟'
        break
      case '全熟':
        cookingTime = '每面约5-6分钟'
        break
      default:
        cookingTime = steak.recommendedDoneness
    }

    const tips = steakData.find((item) => item.type === 'tips')

    let responseText = `${steakType}的烹饪方法:\n\n`
    responseText += `推荐烹饪方法: ${steak.cookingMethods.join(', ')}\n`
    responseText += `建议熟度: ${steak.recommendedDoneness}\n`
    responseText += `烹饪时间: ${cookingTime}\n\n`

    if (tips) {
      responseText +=
        `烹饪小技巧:\n` +
        tips.tips.map((tip, index) => `${index + 1}. ${tip}`).join('\n')
    }

    return {
      content: [{ type: 'text', text: responseText }],
    }
  }
)

// 启动服务器
async function startServer() {
  try {
    console.log('启动牛排信息MCP服务器...')
    const transport = new StdioServerTransport()
    await server.connect(transport)
    console.log('MCP服务器已连接')
  } catch (error) {
    console.error('启动MCP服务器时出错:', error)
  }
}

startServer()

// 牛排数据
const steakData = [
  {
    id: 1,
    name: '菲力牛排',
    englishName: 'Filet Mignon',
    description:
      '菲力牛排是从牛里脊中最嫩的部分切出来的,质地极其柔软多汁,几乎没有脂肪。它是最受欢迎的高档牛排之一,通常价格较高。',
    cookingMethods: ['煎', '烤', '低温慢煮'],
    recommendedDoneness: '三分熟到五分熟',
    nutritionFacts: {
      calories: '约200-250卡路里/100克',
      protein: '约26-28克/100克',
      fat: '约15-18克/100克',
    },
  },
  {
    id: 2,
    name: '肋眼牛排',
    englishName: 'Ribeye Steak',
    description:
      '肋眼牛排来自牛的肋骨部位,肉质鲜嫩多汁,带有丰富的大理石纹理(脂肪分布),风味浓郁。它是许多牛排爱好者的首选。',
    cookingMethods: ['煎', '烤', '炭烤'],
    recommendedDoneness: '四分熟到七分熟',
    nutritionFacts: {
      calories: '约290-320卡路里/100克',
      protein: '约24-26克/100克',
      fat: '约22-25克/100克',
    },
  },
  {
    id: 3,
    name: '纽约客牛排',
    englishName: 'New York Strip Steak',
    description:
      '纽约客牛排(也称为西冷牛排)来自牛的短腰部位,肉质较为紧实但仍然嫩滑,风味浓郁,脂肪含量适中。',
    cookingMethods: ['煎', '烤', '炭烤'],
    recommendedDoneness: '四分熟到六分熟',
    nutritionFacts: {
      calories: '约260-290卡路里/100克',
      protein: '约25-27克/100克',
      fat: '约19-22克/100克',
    },
  },
  {
    id: 4,
    name: 'T骨牛排',
    englishName: 'T-Bone Steak',
    description:
      'T骨牛排因其T形骨而得名,一侧是菲力牛排,另一侧是纽约客牛排,提供两种不同口感的牛肉。适合喜欢多种口感的食客。',
    cookingMethods: ['煎', '烤'],
    recommendedDoneness: '四分熟到六分熟',
    nutritionFacts: {
      calories: '约250-280卡路里/100克',
      protein: '约25-27克/100克',
      fat: '约18-21克/100克',
    },
  },
  {
    id: 5,
    name: '牛排煎制小技巧',
    type: 'tips',
    tips: [
      '煎牛排前,将牛排从冰箱取出,放置至室温(约20-30分钟)',
      '煎制前用厨房纸巾擦干牛排表面的水分',
      '热锅冷油,确保锅温足够高再放入牛排',
      '煎制过程中不要频繁翻动牛排',
      '煎好后让牛排静置3-5分钟再切,可以保持肉汁',
    ],
  },
]

module.exports = steakData

5.4配置客户端

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值