前言
在上一篇文章中,Trae帮助我们深入理解了MCP(Model Context Protocol)是什么,以及MCP标准输入输出的格式。MCP是一种轻量级的通信协议,通过标准输入输出(stdin/stdout)进行数据交换,使用JSON格式来传递消息。
这种协议非常适合构建微服务,因为它简单、高效,且易于实现。
在本文中,我们将进一步深入,使用官方SDK来创建一个支持文件夹增删改查功能的MCP服务。我们将通过一个具体的实例,展示如何从零开始构建这样一个服务,并确保它能够与MCP协议无缝对接。
首先先让Trae使用官方的sdk帮我创建一个可以crud文件夹的mcp服务,使用nodejs来编写这个Mcp服务

可以看到所谓的官方的sdk是这个@modelcontextprotocol/sdk
,如果对这个不了解的话,可以自己去npm上面看,这里可以简单的理解为,有了这个官方的sdk,我们的mcp就可以做很多事情

2.1 初始化项目
首先,我们需要创建一个新的Node.js项目并初始化。
Trae在你的工作目录中运行以下命令:
bash
mkdir folder-mcp--server
cd folder-mcp--server
npm init -y
这将创建一个名为folder-mcp--server
的文件夹,并在其中初始化一个新的Node.js项目。
2.2 安装官方SDK
接下来,我们需要安装@modelcontextprotocol/sdk
。
bash
npm install @modelcontextprotocol/sdk
安装完成后,你可以在项目的node_modules
文件夹中找到@modelcontextprotocol/sdk
。
2.3 编写服务代码
Trae在项目src目录中创建一个名为index.js
的文件,并添加以下代码:
javascript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { promises as fs, constants as fsConstants } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
创建一个mcp服务
css
const server = new Server(
{
name: 'folder-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
辅助函数:检查路径是否存在
typescript
async function pathExists(targetPath: string): Promise<boolean> {
try {
await fs.access(targetPath);
return true;
} catch {
return false;
}
}
辅助函数:计算文件夹大小
typescript
async function getFolderSize(folderPath: string): Promise<number> {
let totalSize = 0;
try {
const stats = await fs.stat(folderPath);
if (!stats.isDirectory()) {
return stats.size;
}
const items = await fs.readdir(folderPath, { withFileTypes: true });
for (const item of items) {
const itemPath = path.join(folderPath, item.name);
if (item.isDirectory()) {
totalSize += await getFolderSize(itemPath);
} else {
const itemStats = await fs.stat(itemPath);
totalSize += itemStats.size;
}
}
} catch (error) {
console.error(`Error calculating folder size: ${error}`);
}
return totalSize;
}
辅助函数:格式化文件大小
ini
function formatBytes(bytes: number, decimals = 2): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
辅助函数:递归复制文件夹
csharp
async function copyFolder(source: string, destination: string): Promise<void> {
await fs.mkdir(destination, { recursive: true });
const items = await fs.readdir(source, { withFileTypes: true });
for (const item of items) {
const sourcePath = path.join(source, item.name);
const destinationPath = path.join(destination, item.name);
if (item.isDirectory()) {
await copyFolder(sourcePath, destinationPath);
} else {
await fs.copyFile(sourcePath, destinationPath);
}
}
}
注册工具
php
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'create_folder',
description: '创建新文件夹',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '要创建的文件夹路径',
},
recursive: {
type: 'boolean',
description: '是否递归创建父目录',
default: true,
},
},
required: ['path'],
},
},
{
name: 'delete_folder',
description: '删除文件夹',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '要删除的文件夹路径',
},
force: {
type: 'boolean',
description: '是否强制删除非空文件夹',
default: false,
},
},
required: ['path'],
},
},
{
name: 'rename_folder',
description: '重命名或移动文件夹',
inputSchema: {
type: 'object',
properties: {
oldPath: {
type: 'string',
description: '原文件夹路径',
},
newPath: {
type: 'string',
description: '新文件夹路径',
},
},
required: ['oldPath', 'newPath'],
},
},
{
name: 'list_folder',
description: '列出文件夹内容',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '要列出的文件夹路径',
},
recursive: {
type: 'boolean',
description: '是否递归列出子文件夹内容',
default: false,
},
},
required: ['path'],
},
},
}
});
启动服务器的方法
javascript
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('文件夹管理MCP服务器已启动');
}
main().catch((error) => {
console.error('服务器启动失败:', error);
process.exit(1);
});
然后让Trae帮我们创建一个可以测试的js脚本

执行node simple-test.js
,可以看到控制台,输出打印和文件夹创建test-demo文件夹

执行一下删除文件夹,把刚刚新建的也删除了
