mcp的使用

模型上下文协议(mcp)允许应用程序以标准化方式为 LLM 提供上下文,从而将提供上下文的关注点与实际的 LLM 交互分开。此 TypeScript SDK 实现了完整的 MCP 规范,使其易于:

  • Build MCP clients that can connect to any MCP server 构建可连接到任何 MCP 服务器的 MCP 客户端
  • Create MCP servers that expose resources, prompts and tools 创建公开资源、提示和工具的 MCP 服务器
  • Use standard transports like stdio and Streamable HTTP 使用标准传输方式,如 stdio 和 Streamable HTTP
  • Handle all MCP protocol messages and lifecycle events 处理所有 MCP 协议消息和生命周期事件

McpServer

MCP 协议的核心接口。它处理连接管理、协议合规性和消息路由。

const server = new McpServer({
  name: "My App",
  version: "1.0.0"
});

Resources 资源

资源是向 LLM 公开数据的方式。它们类似于 REST API 中的 GET 端点 - 它们提供数据,但不应执行大量计算或具有副作用:

入参:

  • 资源类型标识符
  • url 相当于请求路径
  • callback 返回体
// Static resource
server.resource(
  "config",
  "config://app",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// 动态url
server.resource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);

ResourceTemplate

动态url

import { Server, ResourceTemplate } from '@modelcontextprotocol/sdk/server/index.js';

const server = new Server(/*...*/);

// 方式1: 简单字符串URI(静态)
server.resource(
  "config",
  "config://app",  // 固定URI
  async (uri) => ({
    contents: [{ uri: uri.href, text: "Static config" }]
  })
);

// 方式2: ResourceTemplate(动态)
server.resource(
  "user-profile",
  new ResourceTemplate(
    "users://{userId}/profile",     // 动态URI模板
    { 
      list: undefined               // 不支持列表
    }
  ),
  async (uri, { userId }) => {     // 注意:回调函数多了第二个参数
    // 模拟用户数据
    const users = {
      '123': { name: 'Alice', email: '[email protected]', role: 'admin' },
      '456': { name: 'Bob', email: '[email protected]', role: 'user' },
      '789': { name: 'Charlie', email: '[email protected]', role: 'user' }
    };
    
    const user = users[userId];
    
    if (!user) {
      throw new Error(`用户不存在: ${userId}`);
    }
    
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify({
          userId: userId,
          profile: user,
          lastUpdated: new Date().toISOString()
        }, null, 2)
      }]
    };
  }
);

// 方式3: ResourceTemplate 支持列表
server.resource(
  "user-posts",
  new ResourceTemplate(
    "users://{userId}/posts",
    { 
      list: async () => {
        // 返回所有可用的用户ID
        return [
          { uri: "users://123/posts", name: "Alice的文章" },
          { uri: "users://456/posts", name: "Bob的文章" },
          { uri: "users://789/posts", name: "Charlie的文章" }
        ];
      }
    }
  ),
  async (uri, { userId }) => {
    const posts = {
      '123': [
        { id: 1, title: 'Hello World', content: 'My first post' },
        { id: 2, title: 'Learning MCP', content: 'MCP is awesome!' }
      ],
      '456': [
        { id: 3, title: 'JavaScript Tips', content: 'Some useful tips' }
      ],
      '789': []
    };
    
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify({
          userId: userId,
          posts: posts[userId] || [],
          totalCount: (posts[userId] || []).length
        }, null, 2)
      }]
    };
  }
);

// 更复杂的多参数模板
server.resource(
  "file-content",
  new ResourceTemplate(
    "files://{projectId}/{fileName}",
    { list: undefined }
  ),
  async (uri, { projectId, fileName }) => {
    console.log(`请求项目 ${projectId} 中的文件 ${fileName}`);
    
    // 模拟文件内容
    const fileContent = `
// 项目: ${projectId}
// 文件: ${fileName}

export default function ${fileName.replace('.js', '')}() {
  console.log('Hello from ${fileName}');
}
    `;
    
    return {
      contents: [{
        uri: uri.href,
        mimeType: "text/javascript",
        text: fileContent.trim()
      }]
    };
  }
);

Tools 工具

工具允许 LLM 通过服务器执行操作。与资源不同,工具需要执行计算并具有副作用:

入参:

  • 资源类型标识符
  • callback函数入参的类型约束
  • callback 返回体

server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

// Async tool with external API call
server.tool(
  "fetch-weather",
  { city: z.string() },
  async ({ city }) => {
    const response = await fetch(`https://api.weather.com/${city}`);
    const data = await response.text();
    return {
      content: [{ type: "text", text: data }]
    };
  }
);

Prompts 提示

提示是可重用的模板,可帮助 LLM 有效地与服务器交互:

入参:

  • 资源类型标识符
  • callback函数入参的类型约束
  • callback 返回体
server.prompt(
  "review-code",
  { code: z.string() },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please review this code:\n\n${code}`
      }
    }]
  })
);

运行服务器

StdioServerTransport​ 是MCP服务器的标准输入输出传输层,它负责:

  • 建立通信通道:通过stdin/stdout与MCP客户端进行双向通信
  • 消息序列化:将JavaScript对象转换为JSON消息格式
  • 协议处理:处理MCP协议的消息交换

工作流程:

  1. 客户端发送JSON消息到服务器的stdin
  2. StdioServerTransport接收并解析消息
  3. 服务器处理请求并返回响应
  4. StdioServerTransport将响应写入stdout发送给客户端
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const transport = new StdioServerTransport();
await server.connect(transport);