【初学】使用 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 使用演示

相关推荐
wuhanwhite9 小时前
在 Trae 国际版中添加 Chrome Dev MCP Server(Windows 实战指南)
mcp
晨启AI11 小时前
Claude Code 实战指南(三):AI辅助开发工作流 Spec Workflow MCP教程
ai·实战·mcp·claude code
濮水大叔15 小时前
VonaJS提供的读写分离,直观,优雅🌼
typescript·nodejs·nestjs
RoyLin16 小时前
命名实体识别
前端·后端·typescript
SelectDB技术团队16 小时前
Apache Doris 4.0 AI 能力揭秘(二):为企业级应用而生的 AI 函数设计与实践
数据库·人工智能·apache·olap·mcp
Ares-Wang1 天前
Vue3 》》vite》》TS》》封装 axios ,Promise<T>
vue.js·typescript
Ares-Wang1 天前
Vue3》》 ref 获取子组件实例 原理
javascript·vue.js·typescript
TZOF1 天前
TypeScript的新类型(二):unknown
前端·后端·typescript