返回文章列表
服务器

MCP 是什么?Model Context Protocol 核心概念 + TypeScript 实现

柒柒
2025-12-24
5小时前
MCP 是什么?Model Context Protocol 核心概念 + TypeScript 实现

说实话,我关注MCP不是因为它火,而是因为我真的受够了。

受够了什么?受够了那些所谓的"集成代码"——一堆临时拼凑的脚本,看似聪明,实则脆弱得一碰就碎。每次同事问我:"能不能让AI安全地读取内部数据,但不用重写半个后端?",我就知道又要开始一场艰难的架构讨论。

直到我真正理解了MCP,才发现这不是什么时髦的新框架,而是一个早就该存在的抽象层。就像REST让服务和服务对话,MCP让模型和系统对话——这个类比一旦想通,其他问题就迎刃而解了。

这篇文章不谈理论,不讲故事,只讲怎么用TypeScript搭建你的第一个MCP服务器,以及我踩过的所有坑

先搞清楚一件事:MCP不是什么

在动手之前,咱们得澄清一个误区。

MCP(Model Context Protocol)不是:

  • 又一个需要你供起来的框架
  • 什么神奇的AI服务器
  • 你现有后端的替代品

MCP是一份契约——一份非常固执己见的契约。

它定义了工具(Tools)、资源(Resources)和提示词(Prompts)如何以可预测、可检查、可审计的方式暴露给模型。

打个比方:REST让微服务之间能说话,MCP让AI模型和你的业务系统能说话。

如果这个概念还不清楚,建议先去看官方规范(https://modelcontextprotocol.io),看一遍就够了。看完回来,我们接着聊。

为什么TypeScript是最优选?

能用Python写MCP服务器吗?当然。

能用Rust写吗?没问题。

但为什么我强烈推荐TypeScript?

理由很简单:

  1. 你的技术栈里很可能已经有Node了
  2. JSON是MCP的原生语言,TypeScript天然亲和
  3. 类型系统能在模型开始胡编乱造之前就把错误拦住
  4. 工具链成熟且稳定(这是夸奖,不是讽刺)

更重要的是,大部分MCP服务器都是集成密集型,而非计算密集型。TypeScript在这种场景下如鱼得水。

必须理解的心智模型(否则你会很痛苦)

在写代码之前,你必须搞懂这个核心概念:

一个MCP服务器暴露三样东西:

┌─────────────────────────────────────┐│         MCP Server 核心             │├─────────────────────────────────────┤│                                     ││  Resources  ← 只读数据              ││             (查询配置、读取文档)     ││                                     ││  Tools      ← 有副作用的动作        ││             (创建记录、发送请求)     ││                                     ││  Prompts    ← 结构化的推理引导      ││             (摘要模板、分析框架)     ││                                     │└─────────────────────────────────────┘

就这三样,别的没了。

如果你试图模糊这三者的边界,你的服务器会变成一团乱麻。

记住这个原则:

  • 如果它会改变状态 → 它是Tool
  • 如果它获取数据 → 它是Resource
  • 如果它塑造推理 → 它是Prompt

大声说出来,这很重要。

搭建第一个服务器(无聊但必要的部分)

创建项目

mkdir my-first-mcp-servercd my-first-mcp-servernpm init -ynpm install @modelcontextprotocol/sdk zod

对,安装zod。等会你就知道为什么了。

创建 index.ts

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

初始化服务器

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

传输层很关键

对于本地开发和大多数工具场景:

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

就这样,你的服务器起来了。

没有HTTP,没有Express,没有废话。

添加第一个Resource(从简单开始,保持清醒)

咱们暴露点无聊的东西——这是故意的。

server.resource(  "config",  "config://app",  async () => ({    name: "Demo App",    environment: "development",  }));

这个Resource:

  • 有稳定的URI
  • 返回JSON
  • 安全可读

模型喜欢无聊、可预测的数据。你也应该喜欢。

Tools:大多数人翻车的地方

Tools很强大,这意味着它们很危险。

来看一个好的Tool示例:

import { z } from"zod";server.tool("create_note",  {    title: z.string(),    body: z.string(),  },async ({ title, body }) => {    // 这里写入数据库    return { success: true };  });

为什么这个Tool设计得好?

  1. 输入有验证(用了zod schema)
  2. 输出简单无聊
  3. 副作用明确清晰

现实警告:

如果你的Tool:

  • 接受没有schema的原始字符串
  • 一次干五件事
  • 依赖隐藏的全局状态

……那你就是在造脚射神器

一个开发者熟悉的场景

假设你在做一个内部工具,需要让AI助手能够查询公司的Jira工单。

错误做法:

// ❌ 危险!没有任何输入验证server.tool(  "query_jira",  {},  async (params: any) => {    // 直接拼接SQL或者API查询,等着被注入吧    return await jiraApi.search(params.query);  });

正确做法:

// ✅ 安全且可靠server.tool("query_jira",  {    project: z.string().max(20),    status: z.enum(["open", "in_progress", "done"]),    assignee: z.string().email().optional(),  },async ({ project, status, assignee }) => {    // 类型安全的查询,有明确的参数范围    returnawait jiraApi.search({      jql: `project = ${project} AND status = ${status}${        assignee ? ` AND assignee = ${assignee}` : ""      }`,    });  });

看到区别了吗?Schema就是你的防火墙。

Prompts不是花哨的字符串

这是新手最容易低估MCP的地方。

Prompts是接口,不是文本块。

server.prompt(  "summarize_notes",  {    notes: z.string(),  },({ notes }) => ({    messages: [      {        role: "system",        content: "你是一位精准的技术写作专家。",      },      {        role: "user",        content: `请总结以下笔记:\n${notes}`,      },    ],  }));

你不是在告诉模型该想什么,你是在给它设置护栏

一个实际应用场景

假设你在做一个代码审查助手,需要让AI按照团队规范检查代码:

server.prompt(  "code_review",  {    code: z.string(),    language: z.enum(["typescript", "javascript", "python"]),    focus: z.enum(["performance", "security", "style"]).optional(),  },({ code, language, focus = "style" }) => ({    messages: [      {        role: "system",        content: `你是一位经验丰富的${language}代码审查专家。审查重点:${focus}- 如果是performance,关注性能瓶颈和优化机会- 如果是security,关注安全漏洞和潜在风险- 如果是style,关注代码风格和最佳实践请给出具体的改进建议,并标注严重程度(critical/major/minor)。`,      },      {        role: "user",        content: `\`\`\`${language}\n${code}\n\`\`\``,      },    ],  }));

这样设计的好处:

  1. 审查重点可配置
  2. 输出格式标准化
  3. 容易集成到CI/CD流程

测试你的服务器(别像其他人一样跳过这步)

如果你不测试MCP服务器,模型会替你测试。

而且它们毫不留情

使用官方的MCP inspector: https://modelcontextprotocol.io/docs/tools/inspector

检查这些:

  • Schema验证是否生效
  • Tool命名是否清晰
  • Resource是否可被发现

如果你自己都觉得困惑,模型会更困惑。

简单的测试工作流

┌──────────────┐│ 1. 启动服务器 │└──────┬───────┘       │       ▼┌──────────────────┐│ 2. 用inspector   ││    连接并检查     │└──────┬───────────┘       │       ▼┌──────────────────┐│ 3. 调用每个Tool  ││    尝试边界输入   │└──────┬───────────┘       │       ▼┌──────────────────┐│ 4. 检查错误处理  ││    和返回格式     │└──────────────────┘

安全性:大家都摆手的部分(大错特错)

MCP不会替你保护数据。

你必须:

  • 严格限定Tool范围
  • 避免暴露原始凭证
  • 记录Tool使用日志
  • 假设模型会尝试各种奇怪的输入

MCP让访问变得结构化,但不会让访问默认安全。这是你的活。

一个真实的安全案例

假设你在为团队搭建一个能访问数据库的AI助手:

❌ 危险做法:

// 千万别这么干!server.tool(  "execute_sql",  {    query: z.string(),  },  async ({ query }) => {    // 直接执行任意SQL?这是在找死    return await db.raw(query);  });

✅ 安全做法:

// 预定义的安全查询const ALLOWED_QUERIES = {  user_count: "SELECT COUNT(*) FROM users WHERE active = true",  recent_orders: "SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days' LIMIT 100",  product_stats: "SELECT category, COUNT(*) FROM products GROUP BY category",} asconst;server.tool("execute_predefined_query",  {    query_name: z.enum(Object.keys(ALLOWED_QUERIES) as [string, ...string[]]),  },async ({ query_name }) => {    // 只能执行预定义的查询    returnawait db.raw(ALLOWED_QUERIES[query_name]);  });

关键原则:永远不要相信模型的输入,哪怕它看起来很"智能"。

什么时候MCP是错误的选择

说实话,别盲目用MCP。

不要用MCP如果:

  • 你只需要一个定时任务
  • 你在暴露公共API
  • 根本没有模型会碰这个系统

MCP适合的场景:

  • 模型需要受控访问
  • 你需要可审计性
  • 你在乎长期的可维护性

真正的收益(不是炒作,是杠杆)

在我们团队上线第一个MCP服务器之后:

✅ 集成变得无聊了(这是好事)✅ Tool行为变得可预测✅ Prompt混乱大幅下降✅ Debug不再像占卜

这才是真正的价值。不是炒作,是杠杆。

一个团队的实际案例

某电商团队用MCP搭建了一个订单管理助手:

之前的做法:

  • 各种API散落在不同服务
  • 每次接入新功能都要写大量胶水代码
  • AI经常因为接口不一致而出错
  • 调试全靠猜

用MCP重构后:

订单查询 → MCP Resource (只读,安全)订单创建 → MCP Tool (有验证,有日志)订单分析 → MCP Prompt (标准化输出)

结果:

  • 新功能接入时间从2天降到2小时
  • AI错误率下降70%
  • 代码量减少40%
  • 团队终于能好好睡觉了

最后(以及一点点个人看法)

MCP不会让你的产品变魔法。

但它会让你的AI集成少犯蠢

说实话?这就是大多数团队现在需要跨越的门槛。

本文内容仅供参考,不构成任何专业建议。使用本文提供的信息时,请自行判断并承担相应风险。

分享文章
合作伙伴

本站所有广告均是第三方投放,详情请查询本站用户协议