【MCP开发】Nodejs+Typescript+pnpm+Studio搭建Mcp服务
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 nodeimport { 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