AI编程技术专栏

提示词工程技巧

MCP 采用客户端-服务器架构,MCP 主机为每个 MCP 服务器创建一个 MCP 客户端。每个 MCP 客户端与其 MCP 服务器保持一对一的专用连接。

MCP组成

MCP Host:协调和管理一个或多个 MCP 客户端的 AI 应用程序(cursor,claude,vscode)

MCP 客户端:维护与 MCP 服务器的连接,并从 MCP 服务器获取上下文,以供 MCP 主机使用的组件(vscode 运行时会实例化一个 MCP 客户端对象,用于维护与 MCP 服务器的连接)

MCP 服务器:为 MCP 客户端提供上下文的程序。这里并非常规认知里的Server端接口。

特别说明:MCP 服务器指的是 提供上下文数据的程序,无论其运行在何处。MCP 服务器可以本地也可以远程。

启动文件系统服务器时,由于使用 STDIO 传输,且在同一台机器上运行,被称为“本地” MCP 服务器。

官方 Sentry MCP 服务器运行在 Sentry 平台上,并使用 Streamable HTTP 传输,被称为“远程” MCP 服务器。

一、核心内容

1.1 数据层(JSON-RPC 2.0)

定义基于 JSON-RPC 的客户端-服务器通信协议,包括生命周期管理、核心原语(如工具、资源、提示和通知)

JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议。 本规范主要定义了一些数据结构及其相关的处理规则。它允许运行在基于socket,http等诸多不同消息传输环境的同一进程中。其使用JSON(RFC 4627)作为数据格式。

核心组成
  • 生命周期管理:处理客户端和服务器之间的连接初始化、功能协商和连接终止

  • 服务器功能:服务器提供核心功能,包括 AI 操作的工具、上下文数据的资源和从客户端到交互模板的提示

  • 客户端功能:使服务器能够要求客户端从主机 LLM 进行采样,从用户那里获取输入,并将消息记录到客户端

  • 实用功能:支持附加功能,例如实时更新通知和长期运行操作的进度跟踪

Primitives(原语)

MCP 原语是 MCP 中最重要的概念。它们定义了客户端和服务器可以相互提供哪些功能。这些原语指定了可以与 AI 应用程序共享的上下文信息类型以及可以执行的操作范围。

服务器公开的三个核心原语
  • 工具:AI 应用程序可以调用以执行操作的可执行函数(文件操作、API 调用、数据库查询)

  • 资源:向 AI 应用程序提供上下文信息的数据源(文件内容、数据库记录、API 响应)

  • 提示:可重复使用的模板,有助于构建与语言模型的交互(系统提示、少量示例)

客户端公开的原语
  • 采样:允许服务器向客户端的 AI 应用程序请求语言模型补全。

  • 启发式:允许服务器向用户请求更多信息。当服务器开发者想要从用户那里获取更多信息,或者请求用户确认某个操作时,此功能非常有用。

  • 日志记录:使服务器能够向客户端发送日志消息以进行调试和监控。

1.2 传输层
  • Stdio 传输:使用标准输入/输出流在同一台机器上的本地进程之间进行直接进程通信,提供最佳性能且无网络开销。

  • 可流式传输的 HTTP 传输:使用 HTTP POST 协议发送客户端到服务器的消息,并可选用服务器发送事件来实现流式传输功能。

二、核心开发要点

2.1 核心步骤
  • 安装SDK: npm install @modelcontextprotocol/sdk
  • 创建服务器: 使用Server
  • 定义工具: 在tools数组中定义工具
  • 实现逻辑: 在toolImplementations中实现
  • 注册处理器: 使用setRequestHandler
  • 启动服务: 连接传输层并启动
2.2 常见错误
  • 切勿写入标准输出 (stdout),最佳实践是使用写入 stderr,即不使用console.log/print输出日志(干扰JSON-RPC),应该使用console.error
  • 违反单一职责,工具功能过于复杂
  • 缺少参数验证(安全风险)
  • 同步处理耗时操作(阻塞服务器)

三、关键实现步骤

3.1 项目初始化
# 创建项目结构
mkdir mcp-server
cd mcp-server

# 初始化package.json
npm init -y

# 安装MCP SDK
npm install @modelcontextprotocol/sdk
3.2 核心代码结构
3.2.1 服务器主结构 (src/server.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";

// 1. 定义工具列表
const tools = [
  {
    name: "get_weather", // 工具唯一标识
    description: "获取指定城市的天气信息", // 介绍描述
    inputSchema: {                       // 工具参数的JSON格式
      type: "object",

      // 系统操作,与本地系统交互的工具
      // properties: {
      //   command: { type: "string" },
      //   args: { type: "array", items: { type: "string" } }
      // },

      // 转换或分析数据的工具
      // properties: {
      //   filepath: { type: "string" },
      //   operations: {
      //     type: "array",
      //     items: {
      //       enum: ["sum", "average", "count"]
      //     }
      //   }
      // }

      // API 集成,包装外部 API 的工具
      properties: {                      // 工具指定参数
        city: {
          type: "string",
          description: "城市名称",
        },
      },
      required: ["city"],
    },
    // annotations?: {        // 工具注释
    //   title?: string;      // 工具注释人类可读标题
    //   readOnlyHint?: boolean;    // 如果为 true,则该工具不会修改其环境
    //   destructiveHint?: boolean; // 如果为 true,则该工具可能会执行破坏性更新
    //   idempotentHint?: boolean;  // 如果为 true,则使用相同参数重复调用不会产生额外效果
    //   openWorldHint?: boolean;   // 如果为 true,则工具会与外部实体交互
    // }
  },
];

// 2. 实现工具函数
const toolImplementations = {
  get_weather: async (args) => {
    const { city } = args;
    // 业务逻辑实现
    return {
      content: [
        {
          type: "text",
          text: `${city}的天气信息...`,
        },
      ],
    };
  },
};

// 3. 创建MCP服务器实例
const server = new Server(
  {
    name: "mcp-server-example",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 4. 注册请求处理器
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  
  if (!toolImplementations[name]) {
    throw new Error(`工具 '${name}' 不存在`);
  }

  try {
    const result = await toolImplementations[name](args);
    return result;
  } catch (error) {
    throw new Error(`执行工具 '${name}' 时出错: ${error.message}`);
  }
});

// 5. 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP服务器已启动");
}

main().catch((error) => {
  console.error("服务器启动失败:", error);
  process.exit(1);
});

3.2.2 mcp-inspector开发调试
  • 上面主题搭建完毕,如果上面没有问题,可以正常启动,那么我们可以进入调试

  • 调试阶段使用 mcp-inspector 工具,可以看下面截图

// package.json
"scripts": {
    "inspector": "mcp-inspector --config mcp-config.json --server mcp-server-example"
}

// --config mcp-config.json 读取根目录 mcp-config.json
{
  "mcpServers": {
    "mcp-server-example": {
      "command": "node",
      "args": ["src/server.js"],
      "env": {
        "NODE_ENV": "development"
      }
    }
  }
} 

// --server mcp-server-example 启动的就是通过 mcp-config.json 的 mcp-server-example 找到命令执行
  • 从上面初始化的内容,我们可以看到提供了2个工具,可以在inspector的web界面的tool查看到
  • 启动时提供了token内容,需要在inspector的web界面输入才能启动调试

  • 这里就能看到工具列表
  • 在左侧的配置里输入上面启动时token,点击链接才能正常链接

到这里基本上就完成初步服务开发结构和调试方式,此时就真正的跑起来了MCP,如果你只是完成天气这种简单工具MCP,那稍作维护和处理,即可发布第一个MCP。

四、开发最佳实践

4.1 工具设计原则
// 好的工具设计
const goodTool = {
  name: "calculate_math",
  description: "执行数学计算",
  inputSchema: {
    type: "object",
    properties: {
      expression: {
        type: "string",
        description: "数学表达式,如 '2 + 3 * 4'"
      }
    },
    required: ["expression"]
  }
};

// 坏的的设计
const badTool = {
  name: "do_everything", // 功能过于复杂
  description: "执行各种操作", // 描述不清晰
  inputSchema: {
    type: "object",
    properties: {
      data: {
        type: "string",
        description: "数据" // 描述过于简单
      }
    }
  }
};
4.2 错误处理
const safeToolImplementation = async (args) => {
  try {
    // 1. 参数验证
    if (!args.city) {
      throw new Error("城市参数是必需的");
    }

    // 2. 业务逻辑
    const result = await fetchWeatherData(args.city);
    
    // 3. 返回标准格式
    return {
      content: [
        {
          type: "text",
          text: result
        }
      ]
    };
  } catch (error) {
    // 4. 错误处理
    console.error("工具执行错误:", error);
    throw new Error(`天气查询失败: ${error.message}`);
  }
};
4.3 日志处理
// 正确的日志处理(STDIO传输)
import logging from 'logging';

// 使用stderr输出日志
console.error("服务器启动成功");

// 错误的日志处理
console.log("服务器启动成功"); // 会干扰JSON-RPC消息

五、扩展功能

5.1 添加新工具
// 步骤1: 定义工具
const newTool = {
  name: "translate_text",
  description: "翻译文本",
  inputSchema: {
    type: "object",
    properties: {
      text: {
        type: "string",
        description: "要翻译的文本"
      },
      target_language: {
        type: "string",
        description: "目标语言代码"
      }
    },
    required: ["text", "target_language"]
  }
};

// 步骤2: 实现工具逻辑
const translateTool = async (args) => {
  const { text, target_language } = args;
  
  // 调用翻译API
  const translated = await callTranslationAPI(text, target_language);
  
  return {
    content: [
      {
        type: "text",
        text: `翻译结果: ${translated}`
      }
    ]
  };
};

// 步骤3: 注册工具
tools.push(newTool);
toolImplementations.translate_text = translateTool;
5.2 自定义传输层
// WebSocket传输示例
import { WebSocketServerTransport } from "@modelcontextprotocol/sdk/server/websocket.js";

const transport = new WebSocketServerTransport({
  port: 8080,
  host: "localhost"
});

// HTTP传输示例
import { HttpServerTransport } from "@modelcontextprotocol/sdk/server/http.js";

const transport = new HttpServerTransport({
  port: 3000
});
5.3 资源管理
// 添加资源支持
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "file:///path/to/file.txt",
        name: "示例文件",
        description: "这是一个示例文件",
        mimeType: "text/plain"
      }
    ]
  };
});

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;
  
  // 读取资源内容
  const content = await readFile(uri);
  
  return {
    contents: [
      {
        uri,
        mimeType: "text/plain",
        text: content
      }
    ]
  };
});

六、部署配置

6.1 客户端配置
{
  "mcpServers": {
    "weather-server": {
      "command": "node",
      "args": ["/path/to/src/server.js"],
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}

附录参考资料

https://wiki.geekdream.com/Specification/json-rpc_2.0.html

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐