【初学】使用 node 编写 MCP Server

初始项目

bash 复制代码
npm init -y
npm install @modelcontextprotocol/sdk typescript ts-node @types/node
npm install -D typescript @types/node

修改 package.json

json 复制代码
{
  "name": "mcp-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.18.1",
    "ts-node": "^10.9.2"
  },
  "devDependencies": {
    "@types/node": "^24.5.2",
    "typescript": "^5.9.2"
  }
}

添加 tsconfig.json

json 复制代码
{
    "compilerOptions": {
      "target": "ES2022",
      "module": "ESNext",
      "moduleResolution": "node",
      "outDir": "./dist",
      "rootDir": "./src",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "resolveJsonModule": true,
      "declaration": true,
      "sourceMap": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist"]
  }

编写 src/server.ts

官方文档: https://github.com/modelcontextprotocol/typescript-sdk

下方各示例可直接使用

Stdio 写法

typescript 复制代码
import {
    McpServer,
    ResourceTemplate,
  } from "@modelcontextprotocol/sdk/server/mcp.js";
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  import { z } from "zod";
  
  // Create an MCP server
  const server = new McpServer({
    name: "demo-server",
    version: "1.0.0",
  });
  
  // Add an addition tool
  server.registerTool(
    "add",
    {
      title: "Addition Tool",
      description: "用于计算任意两个数字的加法,包括小数和整数。无论数字大小都可以使用此工具进行精确计算。", // 必写,使用自然语言告诉大模型,这个工具是干什么的
      inputSchema: { a: z.number(), b: z.number() },
    },
    async ({ a, b }) => ({
      content: [{ type: "text", text: String(a + b) + " 计算成功" }],
    })
  );
  
  // Add a dynamic greeting resource
  server.registerResource(
    "greeting",
    new ResourceTemplate("greeting://{name}", { list: undefined }),
    {
      title: "Greeting Resource", // Display name for UI
      description: "Dynamic greeting generator",
    },
    async (uri, { name }) => ({
      contents: [
        {
          uri: uri.href,
          text: `Hello, ${name}!`,
        },
      ],
    })
  );
  
  // 启动服务器
  async function main() {
    // Start receiving messages on stdin and sending messages on stdout
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("MCP Server 已启动,等待连接...");
  }
  
  main().catch((error) => {
    console.error("服务器错误:", error);
    process.exit(1);
  });
bash 复制代码
# 构建
npm run build
# 启动
node dist/server.js
json 复制代码
// cursor 相关配置
{
  "mcpServers": {
    // 可自定义 id
    "stdio-add": {
      "isActive": true,
      "name": "my-mcp",
      "type": "stdio",
      // 本地 node 服务的路径,可通过 which node 查看
      "command": "/Users/tacy/.nvm/versions/node/v18.10.0/bin/node",
      "args": [
      // 编译后的文件地址
        "/Users/tacy/test/mcp-server/dist/server.js"
      ]
    }
  }
}

SSE 写法

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

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

// Add an addition tool
server.registerTool(
  "add-sse",
  {
    title: "Addition Tool",
    description: "用于计算任意两个数字的加法,包括小数和整数。无论数字大小都可以使用此工具进行精确计算。", // 必写,使用自然语言告诉大模型,这个工具是干什么的
    inputSchema: { a: z.number(), b: z.number() },
  },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) + " 计算成功 sse" }],
  })
);

// Add a dynamic greeting resource
server.registerResource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  {
    title: "Greeting Resource", // Display name for UI
    description: "Dynamic greeting generator",
  },
  async (uri, { name }) => ({
    contents: [
      {
        uri: uri.href,
        text: `Hello, ${name}!`,
      },
    ],
  })
);

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

// Store transports for each session type
const transports = {
  streamable: {} as Record<string, StreamableHTTPServerTransport>,
  sse: {} as Record<string, SSEServerTransport>
};

// Legacy SSE endpoint for older clients
app.get('/sse', async (req, res) => {
  // Create SSE transport for legacy clients
  const transport = new SSEServerTransport('/messages', res);
  transports.sse[transport.sessionId] = transport;
  
  res.on("close", () => {
    delete transports.sse[transport.sessionId];
  });
  
  await server.connect(transport);
});

// Legacy message endpoint for older clients
app.post('/messages', async (req, res) => {
  const sessionId = req.query.sessionId as string;
  const transport = transports.sse[sessionId];
  if (transport) {
    await transport.handlePostMessage(req, res, req.body);
  } else {
    res.status(400).send('No transport found for sessionId');
  }
});

app.listen(3000);
bash 复制代码
 # 构建
 npm run build
 # 启动
 node dist/server.js
json 复制代码
// cursor 相关配置
{
  "mcpServers": {
    // 可自定义 id
    "sse-add": {
      "name": "sse-mcp",
      "type": "sse",
      "isActive": true,
      // 在 cherry studio 中参数名为 baseUrl
      "url": "http://localhost:3000/sse"  
    }
  }
}

StreamableHttp 写法

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

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

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

// Handle POST requests for client-to-server communication
app.post('/mcp', async (req, res) => {
  // Check for existing session ID
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  let transport: StreamableHTTPServerTransport;

  if (sessionId && transports[sessionId]) {
    // Reuse existing transport
    transport = transports[sessionId];
  } else if (!sessionId && isInitializeRequest(req.body)) {
    // New initialization request
    transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (sessionId) => {
        // Store the transport by session ID
        transports[sessionId] = transport;
      },
      // DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
      // locally, make sure to set:
      // enableDnsRebindingProtection: true,
      // allowedHosts: ['127.0.0.1'],
    });

    // Clean up transport when closed
    transport.onclose = () => {
      if (transport.sessionId) {
        delete transports[transport.sessionId];
      }
    };
    const server = new McpServer({
      name: "example-server",
      version: "1.0.0"
    });

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

    // Add an addition tool
    server.registerTool(
      "add-http",
      {
        title: "Addition Tool",
        description: "用于计算任意两个数字的加法,包括小数和整数。无论数字大小都可以使用此工具进行精确计算。", // 必写,使用自然语言告诉大模型,这个工具是干什么的
        inputSchema: { a: z.number(), b: z.number() },
      },
      async ({ a, b }) => ({
        content: [{ type: "text", text: String(a + b) + " 计算成功 http" }],
      })
    );

    // Add a dynamic greeting resource
    server.registerResource(
      "greeting",
      new ResourceTemplate("greeting://{name}", { list: undefined }),
      {
        title: "Greeting Resource", // Display name for UI
        description: "Dynamic greeting generator",
      },
      async (uri, { name }) => ({
        contents: [
          {
            uri: uri.href,
            text: `Hello, ${name}!`,
          },
        ],
      })
    );

    // Connect to the MCP server
    await server.connect(transport);
  } else {
    // Invalid request
    res.status(400).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: 'Bad Request: No valid session ID provided',
      },
      id: null,
    });
    return;
  }

  // Handle the request
  await transport.handleRequest(req, res, req.body);
});

// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  if (!sessionId || !transports[sessionId]) {
    res.status(400).send('Invalid or missing session ID');
    return;
  }
  
  const transport = transports[sessionId];
  await transport.handleRequest(req, res);
};

// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', handleSessionRequest);

// Handle DELETE requests for session termination
app.delete('/mcp', handleSessionRequest);

app.listen(3000);
bash 复制代码
 # 构建
 npm run build
 # 启动
 node dist/server.js
json 复制代码
// cursor 相关配置
{
  "mcpServers": {
    // 可自定义 id
    "http-add": {
      "name": "http-mcp",
      "type": "streamableHttp",
      "isActive": true,
      // 在 cherry studio 中参数名为 baseUrl
      "url": "http://localhost:3000/mcp"  
    }
  }
}

Cursor 使用演示

相关推荐
敲敲敲敲暴你脑袋1 天前
写个添加注释的vscode插件
javascript·typescript·visual studio code
神秘的猪头1 天前
🔌 给 AI 装上“三头六臂”!实战大模型接入第三方 MCP 全攻略
langchain·llm·mcp
Wect1 天前
LeetCode 207. 课程表:两种解法(BFS+DFS)详细解析
前端·算法·typescript
昨晚我输给了一辆AE862 天前
为什么现在不推荐使用 React.FC 了?
前端·react.js·typescript
神秘的猪头2 天前
🔌 把 MCP 装进大脑!手把手带你构建能“热插拔”工具的 AI Agent
langchain·llm·mcp
小兵张健2 天前
AI 页面与交互迁移流程参考
前端·ai编程·mcp
小兵张健2 天前
掘金发布 SOP(Codex + Playwright MCP + Edge)
前端·mcp
Qinana2 天前
从代码到智能体:MCP 协议如何重塑 AI Agent 的边界
前端·javascript·mcp
神秘的猪头2 天前
🚀 拒绝“手搓”工具!带你硬核手写 MCP Server,解锁 Agent 的无限潜能
agent·mcp·trae
Wect2 天前
LeetCode 130. 被围绕的区域:两种解法详解(BFS/DFS)
前端·算法·typescript