【快速入门到精通】MCP Typescript SDK核心概念和快速开发你的MCP

本文转自Github开源项目,github.com/modelcontex... ,由dogstar整理翻译成中文分享,方便更多开发者快速掌握开发MCP的技术。已经学会的请跳过。

关于MCP TS SDK

是官方 TypeScript SDK,用于开发 Model Context Protocol(MCP)的服务端与客户端交互。

以下是本文的知识点:

概述

模型上下文协议(MCP)允许应用程序以标准化方式为LLM提供上下文,将上下文提供与实际的LLM交互分离。此TypeScript SDK完整实现了MCP规范,使您可以:

  • 构建可连接到任何MCP服务器的客户端
  • 创建暴露资源和工具的MCP服务器
  • 使用标准传输协议(如标准输入输出和可流式传输的HTTP)
  • 处理所有MCP协议消息和生命周期事件

安装

bash 复制代码
npm install @modelcontextprotocol/sdk

快速入门

让我们创建一个简单的MCP服务器,暴露计算器工具和一些数据:

typescript 复制代码
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建MCP服务器
const server = new McpServer({
  name: "demo-server",
  version: "1.0.0"
});

// 添加加法工具
server.registerTool("add",
  {
    title: "加法工具",
    description: "将两个数字相加",
    inputSchema: { a: z.number(), b: z.number() }
  },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }]
  })
);

// 添加动态问候资源
server.registerResource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  { 
    title: "问候资源",
    description: "动态问候生成器"
  },
  async (uri, { name }) => ({
    contents: [{
      uri: uri.href,
      text: `你好,${name}!`
    }]
  })
);

// 通过标准输入输出接收和发送消息
const transport = new StdioServerTransport();
await server.connect(transport);

什么是MCP?

模型上下文协议(MCP)是允许您构建以安全、标准化方式向LLM应用程序提供数据和功能的服务器。可以将其视为专门为LLM交互设计的Web API。MCP服务器能够:

  • 通过资源暴露数据(类似于REST API的GET端点;用于加载信息到LLM上下文)
  • 通过工具提供功能(类似于POST端点;用于执行代码或产生副作用)
  • 通过提示定义交互模式(LLM交互的可重用模板)
  • 以及更多功能!

核心概念

Server服务端

McpServer 是您与MCP协议交互的核心接口。它处理连接管理、协议合规性和消息路由:

typescript 复制代码
const server = new McpServer({
  name: "my-app",
  version: "1.0.0"
});

Resources资源

资源是您向LLMs暴露数据的方式。它们类似于REST API中的GET端点------提供数据但不应执行重大计算或有副作用:

typescript 复制代码
// 静态资源
server.registerResource(
  "config",
  "config://app",
  {
    title: "应用程序配置",
    description: "应用程序配置数据",
    mimeType: "text/plain"
  },
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// 动态资源与参数
server.registerResource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  {
    title: "用户资料",
    description: "用户个人信息"
  },
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);

// 带上下文感知补全的资源
server.registerResource(
  "repository",
  new ResourceTemplate("github://repos/{owner}/{repo}", {
    list: undefined,
    complete: {
      // 基于已解析参数提供智能补全
      repo: (value, context) => {
        if (context?.arguments?.["owner"] === "org1") {
          return ["project1", "project2", "project3"].filter(r => r.startsWith(value));
        }
        return ["default-repo"].filter(r => r.startsWith(value));
      }
    }
  }),
  {
    title: "GitHub仓库",
    description: "仓库信息"
  },
  async (uri, { owner, repo }) => ({
    contents: [{
      uri: uri.href,
      text: `Repository: ${owner}/${repo}`
    }]
  })
);

Tools工具

工具允许LLMs通过您的服务器采取行动。与资源不同,工具预期会执行计算并产生副作用:

typescript 复制代码
// 带参数的简单工具
server.registerTool(
  "calculate-bmi",
  {
    title: "BMI计算器",
    description: "计算身体质量指数",
    inputSchema: {
      weightKg: z.number(),
      heightM: z.number()
    }
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

// 异步工具与外部API调用
server.registerTool(
  "fetch-weather",
  {
    title: "天气查询器",
    description: "获取城市的天气数据",
    inputSchema: { 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 }]
    };
  }
);

// 返回ResourceLink的工具
server.registerTool(
  "list-files",
  {
    title: "列出文件",
    description: "列出项目文件",
    inputSchema: { pattern: z.string() }
  },
  async ({ pattern }) => ({
    content: [
      { type: "text", text: `找到匹配"${pattern}"的文件:` },
      // ResourceLink允许工具返回引用而无须嵌入完整内容
      {
        type: "resource_link",
        uri: "file:///project/README.md",
        name: "README.md",
        mimeType: "text/markdown",
        description: '自述文件'
      },
      {
        type: "resource_link",
        uri: "file:///project/src/index.ts",
        name: "index.ts",
        mimeType: "text/typescript",
        description: '索引文件'
      }
    ]
  })
);

ResourceLink资源链接

工具可以通过返回 ResourceLink 对象引用资源而无需嵌入完整内容。这对于处理大文件或大量资源时的性能至关重要------客户端可以选择性地通过提供的URI读取所需资源。

Prompts提示词

Prompts 是可复用的模板,帮助 LLM 有效与您的服务器交互:

typescript 复制代码
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";

// 注册代码审查提示
server.registerPrompt(
  "review-code",
  {
    title: "代码审查",
    description: "检查代码的最佳实践和潜在问题",
    argsSchema: { code: z.string() }
  },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `请审查以下代码:\n\n${code}`
      }
    }]
  })
);

// 带上下文感知补全的提示
server.registerPrompt(
  "team-greeting",
  {
    title: "团队问候",
    description: "为团队成员生成问候语",
    argsSchema: {
      department: completable(z.string(), (value) => {
        // 部门建议
        return ["工程部", "销售部", "市场部", "支持部"].filter(d => d.startsWith(value));
      }),
      name: completable(z.string(), (value, context) => {
        // 基于所选部门的姓名建议
        const department = context?.arguments?.["department"];
        if (department === "工程部") {
          return ["爱丽丝", "鲍勃", "查理"].filter(n => n.startsWith(value));
        } else if (department === "销售部") {
          return ["大卫", "夏娃", "弗兰克"].filter(n => n.startsWith(value));
        } else if (department === "市场部") {
          return ["格蕾丝", "亨利", "艾里斯"].filter(n => n.startsWith(value));
        }
        return ["访客"].filter(n => n.startsWith(value));
      })
    }
  },
  ({ department, name }) => ({
    messages: [{
      role: "assistant",
      content: {
        type: "text",
        text: `你好 ${name},欢迎加入${department}团队!`
      }
    }]
  })
);

自动补全(Completions)

MCP支持参数补全功能,帮助用户填写提示参数和资源模板参数。具体用法可参考资源补全

和提示补全的示例。

客户端用法

typescript 复制代码
// 请求任何参数的补全
const result = await client.complete({
  ref: {
    type: "ref/prompt",  // 或 "ref/resource"
    name: "example"      // 或 uri: "template://..."
  },
  argument: {
    name: "argumentName",
    value: "partial"     // 用户已输入的内容
  },
  context: {             // 可选:包含已解析的先前参数
    arguments: {
      previousArg: "value"
    }
  }
});

显示名称与元数据(Metadata)

所有资源、工具和提示均支持可选的title字段以优化UI展示。title作为显示名称,name仍保持唯一标识符。

注意:新代码推荐使用register*方法(如:registerTool、registerPrompt、registerResource),旧方法(tool、prompt、resource)仍保留以兼容旧版。

工具的标题优先级

对于工具,可通过两种方式指定标题:

  • 工具配置中的title字段
  • 使用旧版tool()方法时的annotations.title字段

优先级顺序:title → annotations.title → name

typescript 复制代码
// 使用registerTool(推荐)
server.registerTool("my_tool", {
  title: "我的工具",              // 优先显示
  annotations: {
    title: "注解标题"    // 若title存在则忽略
  }
}, handler);

// 使用带注解的旧版API
server.tool("my_tool", "描述", {
  title: "注解标题"      // 此处生效
}, handler);

客户端可通过工具函数获取规范显示名称:

typescript 复制代码
import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js";

// 自动处理优先级:title → annotations.title → name
const displayName = getDisplayName(tool);

运行你的MCP服务器

stdio标准输入输出

MCP支持通过标准输入输出(STDIO)传输消息,这是最简单的本地开发方式。使用 StdioServerTransport 即可快速启动,适用于命令行工具和直接集成:

typescript 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "示例服务器",
  version: "1.0.0"
});

// ... 配置服务器资源、工具和提示 ...

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

可流式传输的HTTP

适用于远程服务器,需配置支持客户端请求和服务器通知的流式HTTP传输层:

带会话管理(有状态)

通过会话管理实现状态保持:

typescript 复制代码
import express from "express";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";

const app = express();
app.use(express.json());

// 会话ID与传输层映射表
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

// 处理客户端请求
app.post('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  let transport: StreamableHTTPServerTransport;

  if (sessionId && transports[sessionId]) {
    // 复用现有会话
    transport = transports[sessionId];
  } else if (!sessionId && isInitializeRequest(req.body)) {
    // 新建会话
    transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (sessionId) => {
        transports[sessionId] = transport;
      }
    });

    // 清理会话
    transport.onclose = () => {
      if (transport.sessionId) delete transports[transport.sessionId];
    };

    const server = new McpServer({
      name: "示例服务器",
      version: "1.0.0"
    });

    // ... 配置服务器资源、工具和提示 ...

    await server.connect(transport);
  } else {
    res.status(400).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: '错误请求:无效的会话ID'
      }
    });
    return;
  }

  await transport.handleRequest(req, res, req.body);
});

// 处理GET/DELETE请求
const handleSessionRequest = async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  if (!sessionId || !transports[sessionId]) {
    res.status(400).send('无效或缺失的会话ID');
    return;
  }
  await transports[sessionId].handleRequest(req, res);
};

app.get('/mcp', handleSessionRequest);
app.delete('/mcp', handleSessionRequest);

app.listen(3000);

无会话管理(无状态)

无状态方法适用于以下场景:

简单API包装器

适用于无需状态管理的轻量级服务封装,例如将现有REST API快速转换为MCP服务。

RESTful独立请求场景

每个请求包含完整上下文信息,无需依赖会话状态,符合REST无状态设计原则无共享会话状态的。

水平扩展

通过负载均衡实现横向扩展,每个请求独立处理,避免会话数据同步问题。

适用于简单场景的示例:

typescript 复制代码
const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
  try {
    const server = getServer();
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined
    });
    
    res.on('close', () => {
      transport.close();
      server.close();
    });
    
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    res.status(500).json({
      jsonrpc: '2.0',
      error: {
        code: -32603,
        message: '内部服务器错误'
      }
    });
  }
});

// 处理不支持的HTTP方法
app.get('/mcp', async (req, res) => {
  res.status(405).json({
    jsonrpc: "2.0",
    error: {
      code: -32000,
      message: "方法不允许"
    }
  });
});

app.listen(3000, () => {
  console.log(`无状态MCP服务器运行在端口3000`);
});

适用场景对比

特性 有状态会话模式 无状态模式
会话保持 ✅ 支持跨请求上下文关联 ❌ 每个请求独立处理
资源消耗 ❌ 较高(需维护会话状态) ✅ 较低
横向扩展能力 ❌ 受会话存储限制 ✅ 支持负载均衡
典型用例 长时交互的复杂任务流程 简单API调用/微服务集成

测试与调试

使用 @modelcontextprotocol/sdk/testing 包中的工具可以轻松模拟MCP客户端并与服务器交互:

typescript 复制代码
import { createTestClient } from "@modelcontextprotocol/sdk/testing.js";

const client = createTestClient(server);
const response = await client.sendMessage({
  type: "tool_call",
  toolName: "add",
  args: { a: 2, b: 3 }
});
console.log(response.content); // 输出: [{ type: "text", text: "5" }]

一些MCP示例

简单的Echo输出 服务配置示例

以下是一个简单的MCP服务器示例,它会将客户端发送的消息原样返回:

typescript 复制代码
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

// 创建MCP服务器实例
const server = new McpServer({
  name: "echo-server",  // 服务器名称
  version: "1.0.0"      // 版本号
});

// 注册回声资源
server.registerResource(
  "echo",  // 资源标识符
  new ResourceTemplate("echo://{message}", { list: undefined }),  // 资源URI模板
  {
    title: "回声资源",  // 显示名称
    description: "通过资源形式回显消息"  // 功能描述
  },
  async (uri, { message }) => ({
    contents: [{
      uri: uri.href,  // 资源URI
      text: `资源回显: ${message}`  // 响应内容
    }]
  })
);

// 注册回声工具
server.registerTool(
  "echo",  // 工具标识符
  {
    title: "回声工具",  // 显示名称
    description: "通过工具形式回显消息",  // 功能描述
    inputSchema: { message: z.string() }  // 输入验证规则
  },
  async ({ message }) => ({
    content: [{ type: "text", text: `工具回显: ${message}` }]  // 响应内容
  })
);

// 注册回声提示模板
server.registerPrompt(
  "echo",  // 提示标识符
  {
    title: "回声提示模板",  // 显示名称
    description: "创建消息处理提示模板",  // 功能描述
    argsSchema: { message: z.string() }  // 参数验证规则
  },
  ({ message }) => ({
    messages: [{
      role: "user",  // 角色类型
      content: {
        type: "text",  // 内容类型
        text: `请处理消息: ${message}`  // 提示内容
      }
    }]
  })
);

SQLite数据库操作

以下是一个暴露SQLite数据库查询功能的MCP服务器示例:

typescript 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";

// 创建MCP服务器实例
const server = new McpServer({
  name: "sqlite-explorer",  // 服务器名称
  version: "1.0.0"          // 版本号
});

// 数据库连接辅助函数
const getDb = () => {
  const db = new sqlite3.Database("database.db");
  return {
    all: promisify<string, any[]>(db.all.bind(db)),  // 将回调转换为Promise
    close: promisify(db.close.bind(db))
  };
};

// 注册数据库Schema资源
server.registerResource(
  "schema",  // 资源标识符
  "schema://main",  // 资源URI模板
  {
    title: "数据库Schema",  // 显示名称
    description: "SQLite数据库结构定义",  // 功能描述
    mimeType: "text/plain"  // 内容类型
  },
  async (uri) => {
    const db = getDb();
    try {
      // 查询所有表结构
      const tables = await db.all(
        "SELECT sql FROM sqlite_master WHERE type='table'"
      );
      return {
        contents: [{
          uri: uri.href,  // 资源URI
          text: tables.map((t: {sql: string}) => t.sql).join("\n")  // 合并表结构
        }]
      };
    } finally {
      await db.close();  // 确保连接关闭
    }
  }
);

// 注册SQL查询工具
server.registerTool(
  "query",  // 工具标识符
  {
    title: "SQL查询工具",  // 显示名称
    description: "执行任意SQL语句并返回结果",  // 功能描述
    inputSchema: { sql: z.string() }  // 输入验证规则
  },
  async ({ sql }) => {
    const db = getDb();
    try {
      // 执行用户输入的SQL
      const results = await db.all(sql);
      return {
        content: [{
          type: "text",  // 内容类型
          text: JSON.stringify(results, null, 2)  // 格式化输出
        }]
      };
    } catch (err: unknown) {
      const error = err as Error;
      return {
        content: [{
          type: "text",  // 错误信息类型
          text: `错误: ${error.message}`  // 错误提示
        }],
        isError: true  // 标记错误状态
      };
    } finally {
      await db.close();  // 确保连接关闭
    }
  }
);

高级用法

动态服务器

动态服务器允许根据运行时配置(如环境变量、数据库状态)动态注册资源和工具。适用于需要灵活扩展的场景:

typescript 复制代码
const server = new McpServer({ name: "dynamic-server", version: "1.0.0" });

// 根据环境变量动态注册工具
if (process.env.NODE_ENV === "production") {
  server.registerTool("prod-analytics",
    { /* 工具配置 */ },
    async () => { /* 生产环境逻辑 */ }
  );
} else {
  server.registerTool("dev-debug",
    { /* 工具配置 */ },
    async () => { /* 开发环境逻辑 */ }
  );
}

低级服务器

对于需要完全控制协议细节的场景,可以使用低级服务器API直接处理原始消息:

typescript 复制代码
import { LowLevelServer } from "@modelcontextprotocol/sdk/server/low-level.js";

const server = new LowLevelServer({
  onConnect: (connection) => {
    console.log("新客户端连接");
  },
  onMessage: async (connection, message) => {
    switch (message.type) {
      case "tool_call":
        const result = await handleToolCall(message);
        connection.send({ type: "tool_response", id: message.id, result });
        break;
      // 处理其他消息类型...
    }
  }
});

编写MCP客户端

您可以使用官方提供的 @modelcontextprotocol/sdk/client 包构建自定义客户端,或直接通过HTTP/STDIO与服务器交互:

typescript 复制代码
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

// 创建传输层实例
const transport = new StdioClientTransport({
  command: "node",  // 执行命令
  args: ["server.js"] // 命令参数
});

// 初始化客户端
const client = new Client(
  {
    name: "示例客户端",  // 客户端名称
    version: "1.0.0"      // 版本号
  }
);

// 建立连接
await client.connect(transport);

// 列出提示模板
const prompts = await client.listPrompts();

// 获取指定提示模板
const prompt = await client.getPrompt({
  name: "示例提示模板",  // 提示模板名称
  arguments: {
    arg1: "参数值"  // 输入参数
  }
});

// 列出资源模板
const resources = await client.listResources();

// 读取指定资源
const resource = await client.readResource({
  uri: "file:///示例.txt"  // 资源URI
});

// 调用工具
const result = await client.callTool({
  name: "示例工具",  // 工具名称
  arguments: {
    arg1: "参数值"  // 输入参数
  }
});

开发要求

  • Node.js ≥ 18.0.0
  • npm ≥ 9.0.0
  • TypeScript ≥ 5.0.0
相关推荐
tommyrunner2 小时前
换个角度认识 MCP
ai编程·cursor·mcp
loong_XL4 小时前
fastmcp MCPConfig多服务器使用案例;sse、stdio、streamable-http使用
服务器·agent·mcp
小鬼难缠z4 小时前
HomeLab的反代之神,还得是香香的IPv6
程序员
redreamSo4 小时前
AI Daily | AI日报:摩尔线程冲刺国产GPU第一股; KAIST公布HBM4关键特性及长期路线图; 北京AIGC创投会推动文旅与AI融合
程序员·aigc·资讯
Young55664 小时前
MCP实践:MCP Client 开发保姆教程(附全部代码)
mcp
快起来别睡了4 小时前
大模型工作流解析:从输入到输出的详细拆解
程序员
后端小肥肠4 小时前
让Mermaid听懂人话:用Coze空间+MCP一句话搞定所有业务图
人工智能·coze·mcp
Q_Q5110082856 小时前
python+uniapp基于微信小程序健康管理系统
spring boot·python·微信小程序·django·flask·uni-app·node.js
老周聊大模型7 小时前
并行性能提升300%!LangGraph如何重塑大模型任务编排
人工智能·程序员