---
title: "用 TypeScript 写第一个 MCP Server"
wiki: mcp
category: "服务端开发"
slug: build-server-typescript
url: https://learnagent.wiki/mcp/cards/build-server-typescript
tags: ["MCP", "TypeScript SDK", "McpServer", "zod", "Server 开发"]
last_updated: 2026-04-11
reading_time: 10
---

> 如果你想用 TypeScript 写第一个 MCP Server，当前最稳的路线不是盲目跟 `typescript-sdk` 仓库 `main` 分支走，而是**先按官方 quickstart 使用 v1 这条稳定线**。

# 用 TypeScript 写第一个 MCP Server

## 基础概念

如果你想用 TypeScript 写第一个 MCP Server，当前最稳的路线不是盲目跟 `typescript-sdk` 仓库 `main` 分支走，而是**先按官方 quickstart 使用 v1 这条稳定线**。

原因很简单：

- MCP 官方 quickstart 里 TypeScript 示例仍然使用 `@modelcontextprotocol/sdk`
- TypeScript SDK 官方 `main` README 又明确说明：`main` 是 **v2 pre-alpha**，生产仍推荐 **v1.x**

所以今天要写“第一个可用的 TypeScript MCP Server”，最合理的做法是：

1. 按官方 quickstart 用 `@modelcontextprotocol/sdk`
2. 用 `zod` 定义输入 schema
3. 先走 `stdio` 路线接 Claude Desktop
4. 把“我是不是要追 v2”留到后面再处理

### 核心要素

| 要素 | 作用 |
|------|------|
| **`@modelcontextprotocol/sdk`** | 官方 quickstart 当前仍使用的 v1 生产线包 |
| **`zod`** | 用来声明工具参数 schema，README 和 quickstart 都围绕它展开 |
| **`McpServer`** | TypeScript 路线上最核心的服务端对象 |
| **`StdioServerTransport`** | 本地集成最直接的传输方式，适合桌面客户端和开发期调试 |
| **构建产物** | TypeScript 不能直接给 Claude Desktop 跑源码，最终要跑的是编译后的 `build/index.js` |

### 从 TS 项目到 MCP Server 的链路

```mermaid
graph LR
    A["初始化 npm 项目"] --> B["安装 @modelcontextprotocol/sdk + zod"]
    B --> C["写 McpServer + tools"]
    C --> D["用 tsc 编译到 build/"]
    D --> E["Claude Desktop 通过 node build/index.js 启动"]
```

### 为什么 TypeScript 路线一定要记住“先 build”

```mermaid
graph TD
    A["写了 src/index.ts"] --> B{"Claude Desktop 能直接运行 TS 吗？"}
    B -->|"不能"| C["先 npm run build"]
    C --> D["得到 build/index.js"]
    D --> E["客户端实际运行 node build/index.js"]
```

这一步是很多人第一次写 TS MCP Server 最容易漏掉的地方。

## 基础用法

### 第一步：创建项目

官方 quickstart 的 TypeScript 起手式是：

```bash
mkdir weather
cd weather
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
mkdir src
touch src/index.ts
```

官方 quickstart 对 Node 的要求写的是：

- **Node.js 16 或更高**

但你如果后面想往 v2 主线迁移，当前 npm registry 上 `@modelcontextprotocol/server` / `client` 的 alpha 包已经要求 **Node >=20**。所以从长期角度看，**本机环境直接准备 Node 20+ 会更稳。**

### 第二步：补 `package.json`

官方 quickstart 还要求你把 `package.json` 至少补成这样：

```json
{
  "type": "module",
  "bin": {
    "weather": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js"
  },
  "files": [
    "build"
  ]
}
```

这里最关键的不是 `bin`，而是：

- `type: "module"`
- 有一个稳定的 `build` 输出目录

### 第三步：写 `tsconfig.json`

官方 quickstart 示例是：

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

### 第四步：写一个最小 Server

下面这段结构，基本就是官方 quickstart 的最小核心：

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

const server = new McpServer({
  name: "demo",
  version: "1.0.0",
  capabilities: {
    resources: {},
    tools: {},
  },
});

server.tool(
  "add",
  "Add two numbers",
  {
    a: z.number(),
    b: z.number(),
  },
  async ({ a, b }) => {
    return {
      content: [
        {
          type: "text",
          text: String(a + b),
        },
      ],
    };
  },
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Demo MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});
```

这里面最重要的几个点：

- `McpServer` 是服务端主体
- `server.tool(...)` 注册工具
- `zod` 负责参数 schema
- `StdioServerTransport` 负责本地 stdio 传输

### 第五步：编译

这一步官方 quickstart 强调得非常明确：

```bash
npm run build
```

如果你没做这一步，Claude Desktop 后面根本没有现成的 `build/index.js` 可跑。

### 第六步：接入 Claude Desktop

官方 quickstart 给出的配置是：

```json
{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": [
        "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"
      ]
    }
  }
}
```

这里的关键不是名字，而是：

- 用 `node`
- 跑的是 **绝对路径**
- 跑的是编译后的 `build/index.js`

### 第七步：什么时候再看 v2

如果你这时已经把第一版跑通了，才值得回去看 TypeScript SDK 的 v2 主线。因为那时你已经有了：

- MCP Server 结构感
- tool schema 感
- transport 感
- 客户端配置感

再看拆包后的 `@modelcontextprotocol/server` / `client`，理解成本会低很多。

## 同类工具对比

如果目标是“用 TS 写第一个 MCP Server”，几种常见路径可以这样看：

| 维度 | 官方 quickstart v1 路线 | 直接追 v2 main | Python FastMCP |
|------|-------------------------|----------------|----------------|
| 当前稳定性 | **更稳** | 预发布 | 稳定 |
| 入门资料一致性 | 高，quickstart 直给 | 中，得自己分辨版本线 | 高 |
| 适合谁 | 现在就要写可用 TS Server 的人 | 想预研未来方向的人 | Python 团队 |
| 主要抽象 | `McpServer` + `zod` + stdio | server/client split packages | `FastMCP` |
| 踩坑点 | build 步骤、绝对路径、ESM 配置 | 版本与包名混淆 | Python 环境和依赖管理 |

核心区别：

- **TS v1 quickstart**：适合立刻上手、立刻做出能跑的东西。
- **TS v2**：适合进一步演进和预研，不是第一步。
- **Python FastMCP**：更偏“高层开发体验优先”。

## 常见误区

| 误区 | 准确理解 |
|------|----------|
| 以为写完 `src/index.ts` 就能直接接客户端 | 不行。官方 quickstart 明确要求先编译成 `build/index.js` |
| 以为现在 TS 官方主线就该直接用 v2 | 不对。官方 `main` README 自己写了 v2 还是 pre-alpha，生产推荐 v1.x |
| 以为 `zod` 只是可选项 | 在官方 quickstart 和 v1 README 里，它是核心 schema 工具 |
| 以为相对路径传给 Claude Desktop 就够了 | 官方 quickstart 明确要求绝对路径 |
| 以为写 TS 路线就不需要理解 transport | 不对。哪怕最小 demo 也得知道 stdio 是怎么接上的 |
| 以为 TypeScript 版一定比 Python 版更“现代”所以更适合首发 | 不一定。当前哪条路线更适合你，取决于团队语言栈和版本稳定性要求 |

## 优劣势分析

| 优势 | 劣势 |
|------|------|
| **前端 / Node 团队接入自然**：语言栈统一，容易复用现有工程能力 | **构建步骤更明显**：TS 不是写完就能直接给客户端跑 |
| **类型与 schema 配合好**：`zod` 很适合把工具参数写清楚 | **当前版本格局更复杂**：v1 和 v2 并存，容易看混 |
| **stdio 路线清楚**：quickstart 足够直接，第一版很快能跑起来 | **工程配置项不少**：`package.json`、`tsconfig.json`、ESM、build 目录都得弄对 |
| **后续可平滑接 Node 侧更多能力**：HTTP、框架、中间件生态强 | **对环境版本更敏感**：Node 版本、依赖版本、模块系统都可能踩坑 |
| **和 TS SDK 文档生态衔接自然**：后面可继续进入 server/client docs | **相比 Python FastMCP，抽象层略低**：入门心智负担稍重 |

## 思考题

<details>
<summary>初级：为什么官方 TypeScript quickstart 把“编译”当成关键步骤，而 Python 快速入门看起来就没这么强调？</summary>

**参考答案：**

因为 TypeScript 本身不是直接执行环境，客户端最终运行的是 JavaScript。

Python 路线很多时候可以直接 `python server.py` 或 `uv run ...`，但 TypeScript 路线如果你不先编译，就没有一个稳定的 `build/index.js` 给 Claude Desktop 或其他宿主去启动。

所以在 TS 路线里，“build”不是附加步骤，而是交付链路的一部分。

</details>

<details>
<summary>中级：为什么现在写第一个 TypeScript MCP Server 时，更建议你先走 v1 quickstart，而不是一上来就扑到 v2？</summary>

**参考答案：**

因为第一个项目最重要的是建立完整闭环，而不是抢先踩未来接口。

你第一版真正要学会的是：

- 工具怎么注册
- schema 怎么写
- transport 怎么接
- 客户端怎么配
- 产物怎么跑

这些用 v1 quickstart 已经足够，而且官方当前还明确把它放在生产推荐线上。等你先把完整闭环跑通，再研究 v2 的拆包、middleware、运行时支持，会更合理。

</details>

<details>
<summary>进阶：如果你的团队已经决定未来要迁到 TypeScript SDK v2，为什么仍然值得先写一版 v1 的最小可用 Server？</summary>

**参考答案：**

因为你真正需要迁移的是“理解和能力模型”，不是单纯代码文本。

先写一版 v1 最小 Server，可以让团队先统一：

- Tool / Resource / Prompt 的认知
- 宿主客户端配置方式
- 本地调试路径
- 构建与发布链路

这些经验未来迁 v2 仍然有效。相反，如果你一开始就把学习成本和版本不确定性叠在一起，往往会导致团队既没学会 MCP，也没真正吃透新包结构。

</details>

## 参考资料

1. MCP 官方 quickstart：Server Developer Guide（Node 部分）：<https://modelcontextprotocol.io/quickstart/server>（查询日期 2026-04-11）
2. MCP TypeScript SDK `v1.x` README：<https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/README.md>（查询日期 2026-04-11）
3. MCP TypeScript SDK `v1.x` server 文档：<https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/docs/server.md>（查询日期 2026-04-11）
4. MCP TypeScript SDK 概览：<https://modelcontextprotocol.io>（查询日期 2026-04-11）

---
*Source: https://learnagent.wiki/mcp/cards/build-server-typescript*
*Markdown mirror of https://learnagent.wiki, served as text/markdown for LLM ingestion.*