当前位置: 首页 > news >正文

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

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

编程语言MCP命令协议发布方式
PythonuvxSTUDIOpypi
Python远程调用SSE服务器部署
NodejspnpmSTUDIOpnpm
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

http://www.xdnf.cn/news/1294201.html

相关文章:

  • 从零到一:TCP 回声服务器与客户端的完整实现与原理详解
  • 基于UniApp的智能在线客服系统前端设计与实现
  • 人工智能入门①:AI基础知识(上)
  • 18.10 SQuAD数据集实战:5步高效获取与预处理,BERT微调避坑指南
  • InnoDB如何解决脏读、不可重复读和幻读的?
  • 公司项目用户密码加密方案推荐(兼顾安全、可靠与通用性)
  • HiSmartPerf使用WIFI方式连接Android机显示当前设备0.0.0.0无法ping通!设备和电脑连接同一网络,将设备保持亮屏重新尝试
  • antdv Modal的简单使用
  • Hive 创建事务表的方法
  • 18. parseInt 的参数有几个
  • 开源工具can-utils的使用
  • Docker 在 Linux 中的额外资源占用分析
  • 云计算-实战 OpenStack 私有云运维:服务部署、安全加固、性能优化、从服务部署到性能调优(含数据库、内核、组件优化)全流程
  • OpenCV常见问题汇总
  • Java JDK8环境配置
  • 基于Java的Markdown转Word工具(标题、段落、表格、Echarts图等)
  • 药房智能盘库系统:基于CV与时间序列预测的库存革命
  • 《算法导论》第 22 章 - 基本的图算法
  • Kubernetes-03:Service
  • Notepad++插件开发实战
  • Mac安装ant
  • [系统架构设计师]系统架构基础知识(一)
  • Flutter权限管理三步曲:检查、申请、处理全攻略
  • 三、非线性规划
  • 第十二节:粒子系统:海量点渲染
  • nm命令和nm -D命令参数
  • Docker部署 Neo4j 及集成 APOC 插件:安装与配置完整指南(docker-compose)
  • python的游戏评级论坛系统
  • [AI React Web] 包与依赖管理 | `axios`库 | `framer-motion`库
  • 《探索C++ set与multiset容器:深入有序唯一性集合的实现与应用》