当前位置: 首页 > 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.dtcms.com/a/328728.html

相关文章:

  • 【数据可视化-88】航空公司航班数据分析与可视化:Python + pyecharts洞察航空旅行趋势
  • 通用安全指南
  • 关于在img标签的src里面直接使用“~/assets/images/xxx“可以,但是若将这个路径写成变量的形式就会报错
  • Java Stream API 中常用方法复习及项目实战示例
  • BGP综合实验_Te. BGP笔记
  • 七大排序算法全解析:从入门到精通
  • 开源模型应用落地-用LLaMA-Factory点亮Qwen3-4B的“读心术”(十九)
  • Java开发环境搭建(WIN+IDEA+Maven)
  • davici configurator 报错:License file of SIP has no valid checksum.
  • 高可用实战之Nginx + Apache篇
  • 【IntelliJ IDEA】如何在pom.xml中去除maven中未使用的依赖
  • EI学术会议 | 低碳经济、可持续发展
  • 人机虚拟样机仿真
  • Linux的进程信号
  • 开发WPF项目时遇到的问题总结
  • 《吃透 C++ 类和对象(中):构造函数与析构函数的核心逻辑》
  • WPF 开发的瑞士军刀:Prism 框架从入门到精通指南
  • k8s兼容沐曦c500
  • 【AI实践】本地部署ASR模型OpenAI Whisper
  • Kafka工作机制深度解析:Broker、Partition 与消费者组协作原理
  • 自由学习记录(83)
  • Linux 软件编程:文件IO、目录IO、时间函数
  • GitHub分支保护介绍(Branch Protection)(git分支保护)(通过设置规则和权限来限制对特定分支的操作的功能)
  • 11.用反射为静态类的属性赋值 C#例子 WPF例子
  • K8S中,kubectl cordon、uncordon、drain、taint的区别
  • 计算机网络---用户数据报协议User Datagram Protocol(UDP)
  • 【Part 4 未来趋势与技术展望】第一节|技术上的抉择:三维实时渲染与VR全景视频的共生
  • vue--video使用动态src时,视频不更新
  • Java零基础笔记16(Java编程核心:存储读写数据方案—File文件操作、IO流、IO框架)
  • 利用生成式AI与大语言模型(LLM)革新自动化软件测试 —— 测试工程师必读深度解析