【MCP开发】Nodejs+Typescript+pnpm+Studio搭建Mcp服务

发布于:2025-08-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

MCP服务支持两种协议,Studio和SSE/HTTP,目前官方提供的SDK有各种语言。
在这里插入图片描述
开发方式有以下几种:

编程语言 MCP命令 协议 发布方式
Python uvx STUDIO pypi
Python 远程调用 SSE 服务器部署
Nodejs pnpm STUDIO pnpm
Nodejs 远程调用 SSE 服务器部署

一、初始化项目结构和配置文件

1、创建package.json文件来初始化项目配置

{
  "name": "wjb-mcp-server-studio",
  "version": "1.0.0",
  "description": "A local MCP server based on Studio protocol using Node.js and TypeScript",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js",
    "clean": "rimraf dist",
    "type-check": "tsc --noEmit"
  },
  "keywords": [
    "mcp",
    "studio",
    "server",
    "typescript",
    "nodejs"
  ],
  "author": "Your Name",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^20.0.0",
    "rimraf": "^5.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.5.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

2、创建TypeScript配置文件

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "node",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "removeComments": false,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noUncheckedIndexedAccess": true,
    "allowUnusedLabels": false,
    "allowUnreachableCode": false
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

3、创建src目录结构并实现MCP服务器的主入口文件

#!/usr/bin/env node

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

// 服务器信息
const SERVER_NAME = 'wjb-mcp-server-studio';
const SERVER_VERSION = '1.0.0';

// 创建服务器实例
const server = new Server(
  {
    name: SERVER_NAME,
    version: SERVER_VERSION,
  },
  {
    capabilities: {
      resources: {},
      tools: {},
    },
  }
);

// 工具列表
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'echo',
        description: 'Echo back the input text',
        inputSchema: {
          type: 'object',
          properties: {
            text: {
              type: 'string',
              description: 'Text to echo back',
            },
          },
          required: ['text'],
        },
      },
      {
        name: 'get_system_info',
        description: 'Get basic system information',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'calculate',
        description: 'Perform basic mathematical calculations',
        inputSchema: {
          type: 'object',
          properties: {
            expression: {
              type: 'string',
              description: 'Mathematical expression to evaluate (e.g., "2 + 3 * 4")',
            },
          },
          required: ['expression'],
        },
      },
    ],
  };
});

// 工具调用处理
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'echo': {
      const text = args?.['text'] as string;
      if (!text) {
        throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: text');
      }
      return {
        content: [
          {
            type: 'text',
            text: `Echo: ${text}`,
          },
        ],
      };
    }

    case 'get_system_info': {
      const systemInfo = {
        platform: process.platform,
        arch: process.arch,
        nodeVersion: process.version,
        uptime: process.uptime(),
        memoryUsage: process.memoryUsage(),
        timestamp: new Date().toISOString(),
      };

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(systemInfo, null, 2),
          },
        ],
      };
    }

    case 'calculate': {
      const expression = args?.['expression'] as string;
      if (!expression) {
        throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: expression');
      }

      try {
        // 简单的数学表达式计算(仅支持基本运算符)
        const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, '');
        if (sanitizedExpression !== expression) {
          throw new Error('Invalid characters in expression');
        }

        // 使用 Function 构造器安全地计算表达式
        const result = Function(`"use strict"; return (${sanitizedExpression})`)();

        return {
          content: [
            {
              type: 'text',
              text: `${expression} = ${result}`,
            },
          ],
        };
      } catch (error) {
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to calculate expression: ${error instanceof Error ? error.message : 'Unknown error'}`
        );
      }
    }

    default:
      throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
  }
});

// 资源列表
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: 'studio://server-info',
        mimeType: 'application/json',
        name: 'Server Information',
        description: 'Information about this MCP server',
      },
      {
        uri: 'studio://capabilities',
        mimeType: 'application/json',
        name: 'Server Capabilities',
        description: 'List of server capabilities and features',
      },
    ],
  };
});

// 资源读取处理
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  switch (uri) {
    case 'studio://server-info': {
      const serverInfo = {
        name: SERVER_NAME,
        version: SERVER_VERSION,
        description: 'A local MCP server based on Studio protocol',
        author: 'Your Name',
        capabilities: ['tools', 'resources'],
        uptime: process.uptime(),
        timestamp: new Date().toISOString(),
      };

      return {
        contents: [
          {
            uri,
            mimeType: 'application/json',
            text: JSON.stringify(serverInfo, null, 2),
          },
        ],
      };
    }

    case 'studio://capabilities': {
      const capabilities = {
        tools: {
          count: 3,
          available: ['echo', 'get_system_info', 'calculate'],
        },
        resources: {
          count: 2,
          available: ['studio://server-info', 'studio://capabilities'],
        },
        features: {
          stdio_transport: true,
          json_rpc: true,
          error_handling: true,
        },
      };

      return {
        contents: [
          {
            uri,
            mimeType: 'application/json',
            text: JSON.stringify(capabilities, null, 2),
          },
        ],
      };
    }

    default:
      throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`);
  }
});

// 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);

  // 优雅关闭处理
  process.on('SIGINT', async () => {
    await server.close();
    process.exit(0);
  });

  process.on('SIGTERM', async () => {
    await server.close();
    process.exit(0);
  });
}

// 错误处理
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1);
});

// 启动服务器
main().catch((error) => {
  console.error('Failed to start server:', error);
  process.exit(1);
});

二、安装必要的依赖包

执行安装命令:

# 切换到淘宝镜像
# pnpm config set registry https://registry.npmjs.org/ 

# 安装
pnpm i

三、构建并启动服务

1、构建

pnpm build

构建完成后,在根目录生成dist目录
在这里插入图片描述

2、启动

# echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}}}' | pnpm dev 

# 或直接运行 
pnpm dev 

四、测试MCP服务器功能

1、测试代码

根目录:/test-client.cjs

#!/usr/bin/env node

// 简单的MCP客户端测试脚本
const { spawn } = require('child_process');
const readline = require('readline');

// 启动MCP服务器
const server = spawn('node', ['dist/index.js'], {
  stdio: ['pipe', 'pipe', 'inherit']
});

// 创建readline接口来处理服务器响应
const rl = readline.createInterface({
  input: server.stdout,
  crlfDelay: Infinity
});

// 监听服务器响应
rl.on('line', (line) => {
  console.log('服务器响应:', line);
});

// 发送测试请求的函数
function sendRequest(request) {
  console.log('发送请求:', JSON.stringify(request));
  server.stdin.write(JSON.stringify(request) + '\n');
}

// 等待一下然后发送测试请求
setTimeout(() => {
  // 1. 初始化请求
  sendRequest({
    jsonrpc: '2.0',
    id: 1,
    method: 'initialize',
    params: {
      protocolVersion: '2024-11-05',
      capabilities: { tools: {} },
      clientInfo: {
        name: 'test-client',
        version: '1.0.0'
      }
    }
  });

  // 2. 列出工具
  setTimeout(() => {
    sendRequest({
      jsonrpc: '2.0',
      id: 2,
      method: 'tools/list'
    });
  }, 1000);

  // 3. 调用echo工具
  setTimeout(() => {
    sendRequest({
      jsonrpc: '2.0',
      id: 3,
      method: 'tools/call',
      params: {
        name: 'echo',
        arguments: {
          text: 'Hello, MCP Server!'
        }
      }
    });
  }, 2000);

  // 4. 调用计算工具
  setTimeout(() => {
    sendRequest({
      jsonrpc: '2.0',
      id: 4,
      method: 'tools/call',
      params: {
        name: 'calculate',
        arguments: {
          expression: '2 + 3 * 4'
        }
      }
    });
  }, 3000);

  // 5. 列出资源
  setTimeout(() => {
    sendRequest({
      jsonrpc: '2.0',
      id: 5,
      method: 'resources/list'
    });
  }, 4000);

  // 6. 读取资源
  setTimeout(() => {
    sendRequest({
      jsonrpc: '2.0',
      id: 6,
      method: 'resources/read',
      params: {
        uri: 'studio://server-info'
      }
    });
  }, 5000);

  // 7. 关闭服务器
  setTimeout(() => {
    console.log('\n测试完成,关闭服务器...');
    server.kill();
    process.exit(0);
  }, 6000);

}, 500);

// 错误处理
server.on('error', (error) => {
  console.error('服务器错误:', error);
});

server.on('close', (code) => {
  console.log(`服务器进程退出,退出码: ${code}`);
});

2、运行测试文件

node test-client.cjs 

运行成功,打印如下:

发送请求: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"clientInfo":{"name":"test-client","version":"1.0.0"}}}
服务器响应: {"result":{"protocolVersion":"2024-11-05","capabilities":{"resources":{},"tools":{}},"serverInfo":{"name":"wjb-mcp-server-studio","version":"1.0.0"}},"jsonrpc":"2.0","id":1}
发送请求: {"jsonrpc":"2.0","id":2,"method":"tools/list"}
服务器响应: {"result":{"tools":[{"name":"echo","description":"Echo back the input text","inputSchema":{"type":"object","properties":{"text":{"type":"string","description":"Text to echo back"}},"required":["text"]}},{"name":"get_system_info","description":"Get basic system information","inputSchema":{"type":"object","properties":{}}},{"name":"calculate","description":"Perform basic mathematical calculations","inputSchema":{"type":"object","properties":{"expression":{"type":"string","description":"Mathematical expression to evaluate (e.g., \"2 + 3 * 4\")"}},"required":["expression"]}}]},"jsonrpc":"2.0","id":2}
发送请求: {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"text":"Hello, MCP Server!"}}}
服务器响应: {"result":{"content":[{"type":"text","text":"Echo: Hello, MCP Server!"}]},"jsonrpc":"2.0","id":3}
发送请求: {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"calculate","arguments":{"expression":"2 + 3 * 4"}}}
服务器响应: {"result":{"content":[{"type":"text","text":"2 + 3 * 4 = 14"}]},"jsonrpc":"2.0","id":4}
发送请求: {"jsonrpc":"2.0","id":5,"method":"resources/list"}
服务器响应: {"result":{"resources":[{"uri":"studio://server-info","mimeType":"application/json","name":"Server Information","description":"Information about this MCP server"},{"uri":"studio://capabilities","mimeType":"application/json","name":"Server Capabilities","description":"List of server capabilities and features"}]},"jsonrpc":"2.0","id":5}
发送请求: {"jsonrpc":"2.0","id":6,"method":"resources/read","params":{"uri":"studio://server-info"}}
服务器响应: {"result":{"contents":[{"uri":"studio://server-info","mimeType":"application/json","text":"{\n  \"name\": \"wjb-mcp-server-studio\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A local MCP server based on Studio protocol\",\n  \"author\": \"Your Name\",\n  \"capabilities\": [\n    \"tools\",\n    \"resources\"\n  ],\n  \"uptime\": 5.5274685,\n  \"timestamp\": \"2025-08-13T14:21:45.028Z\"\n}"}]},"jsonrpc":"2.0","id":6}

测试完成,关闭服务器...

已经成功基于Studio协议搭建了一个本地MCP服务,使用Node.js + TypeScript + pnpm技术栈。

总结

工具 (Tools):

  • echo - 回显文本
  • get_system_info - 获取系统信息
  • calculate - 数学计算

资源 (Resources):

  • studio://server-info - 服务器信息
  • studio://capabilities - 服务器能力列表

使用方法

# 开发模式
pnpm dev

# 构建项目
pnpm build

# 运行服务器
pnpm start

# 测试验证服务调用
node test-client.cjs 

源码:
https://gitee.com/6feel/mcp_nodejs_studio


网站公告

今日签到

点亮在社区的每一天
去签到