文章目录
filesystem mcp server端部署
服务器为rocky linux
安装nodejs
# 方法1:使用 NodeSource 仓库(推荐)
curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -
# 安装 Node.js
dnf install -y nodejs
# 验证安装
node --version
npm --version
npx --version
# 方法2:如果上述方法失败,使用官方二进制包
# cd /opt
# wget https://nodejs.org/dist/v18.18.0/node-v18.18.0-linux-x64.tar.xz
# tar -xf node-v18.18.0-linux-x64.tar.xz
# ln -s /opt/node-v18.18.0-linux-x64/bin/node /usr/local/bin/node
# ln -s /opt/node-v18.18.0-linux-x64/bin/npm /usr/local/bin/npm
# ln -s /opt/node-v18.18.0-linux-x64/bin/npx /usr/local/bin/npx
创建项目目录:
mkdir -p /data/mcp/mcp-http-filesystem/{logs,data,shared}
cd /data/mcp/mcp-http-filesystem
# 初始化 Node.js 项目
npm init -y
# 安装生产依赖
npm install express
# 安装 MCP 文件系统服务器
npm install @modelcontextprotocol/server-filesystem
# 如果需要开发工具
npm install --save-dev nodemon
# 创建允许访问的目录
mkdir -p /data/mcp/mcp-http-filesystem/shared/documents
mkdir -p /data/mcp/mcp-http-filesystem/shared/projects
mkdir -p /data/mcp/mcp-http-filesystem/data
# 创建测试文件
echo "Hello from Rocky Linux MCP Server" > /data/mcp/mcp-http-filesystem/shared/welcome.txt
echo "Test document" > /data/mcp/mcp-http-filesystem/shared/documents/test.txt
创建 HTTP MCP 服务器主文件 http-mcp-server.js:
const express = require('express');
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const app = express();
// 配置默认值
const config = {
PORT: process.env.PORT || 8008,
HOST: process.env.HOST || '0.0.0.0',
NODE_ENV: process.env.NODE_ENV || 'production',
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
SHARED_DIR: process.env.SHARED_DIR || '/data/mcp/mcp-http-filesystem/shared',
DATA_DIR: process.env.DATA_DIR || '/data/mcp/mcp-http-filesystem/data',
MAX_RESTARTS: parseInt(process.env.MAX_RESTARTS) || 5,
REQUEST_TIMEOUT: parseInt(process.env.REQUEST_TIMEOUT) || 30000
};
// 尝试从 .env 文件加载配置
try {
if (fs.existsSync('.env')) {
const envContent = fs.readFileSync('.env', 'utf8');
const envLines = envContent.split('\n').filter(line => line.trim() && !line.startsWith('#'));
envLines.forEach(line => {
const [key, value] = line.split('=').map(part => part.trim());
if (key && value) {
// 转换数字类型的配置
if (['PORT', 'MAX_RESTARTS', 'REQUEST_TIMEOUT'].includes(key)) {
config[key] = parseInt(value);
} else {
config[key] = value;
}
}
});
}
} catch (error) {
console.warn('Warning: Could not load .env file, using defaults');
}
// 日志配置
const LOG_DIR = path.join(__dirname, 'logs');
const LOG_FILE = path.join(LOG_DIR, 'mcp-server.log');
// 确保日志目录存在
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR, { recursive: true });
}
// 日志函数
function log(level, message, data = null) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${level}] ${message}` + (data ? ` ${JSON.stringify(data)}` : '');
console.log(logMessage);
fs.appendFileSync(LOG_FILE, logMessage + '\n');
}
// 根据配置的日志级别过滤日志
function shouldLog(level) {
const levels = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
return levels[level] <= levels[config.LOG_LEVEL];
}
app.use(express.json({ limit: '50mb' }));
log('INFO', `Starting MCP HTTP Server with configuration...`);
log('INFO', 'Configuration loaded', {
port: config.PORT,
host: config.HOST,
environment: config.NODE_ENV,
logLevel: config.LOG_LEVEL,
sharedDir: config.SHARED_DIR,
dataDir: config.DATA_DIR,
maxRestarts: config.MAX_RESTARTS,
requestTimeout: config.REQUEST_TIMEOUT
});
class HttpMcpServer {
constructor() {
this.mcpProcess = null;
this.pendingRequests = new Map();
this.requestId = 0;
this.isInitialized = false;
this.restartCount = 0;
this.maxRestarts = config.MAX_RESTARTS;
log('INFO', 'Initializing HTTP MCP Server');
this.startMcpProcess();
}
startMcpProcess() {
if (this.restartCount >= this.maxRestarts) {
log('ERROR', 'Max restart attempts reached, stopping MCP process');
return;
}
this.restartCount++;
log('INFO', `Starting MCP filesystem process (attempt ${this.restartCount}/${this.maxRestarts})`);
const sharedDir = config.SHARED_DIR;
const dataDir = config.DATA_DIR;
log('INFO', 'Allowed directories', { sharedDir, dataDir });
// 确保目录存在
[sharedDir, dataDir].forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
log('INFO', 'Created directory', { directory: dir });
}
});
try {
// 使用正确的 MCP 服务器路径
const mcpServerPath = path.join(__dirname, 'node_modules', '@modelcontextprotocol', 'server-filesystem', 'dist', 'index.js');
log('INFO', 'Using MCP server path', { path: mcpServerPath });
this.mcpProcess = spawn('node', [
mcpServerPath,
sharedDir,
dataDir
], {
stdio: ['pipe', 'pipe', 'pipe'],
cwd: process.cwd(),
env: {
...process.env,
NODE_NO_WARNINGS: '1'
}
});
log('INFO', 'MCP process spawned', { pid: this.mcpProcess.pid });
// 处理 MCP 进程标准输出
let buffer = '';
this.mcpProcess.stdout.on('data', (data) => {
const output = data.toString();
buffer += output;
if (shouldLog('debug')) {
log('DEBUG', 'MCP stdout', { output: output.trim() });
}
const lines = buffer.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line) {
try {
const parsed = JSON.parse(line);
if (shouldLog('debug')) {
log('DEBUG', 'MCP JSON response', {
id: parsed.id,
method: parsed.method
});
}
this.handleMcpResponse(parsed);
} catch (error) {
// 不是 JSON,可能是启动信息
if (line.includes('MCP') || line.includes('Server')) {
log('INFO', 'MCP startup message', { message: line });
}
}
}
}
buffer = lines[lines.length - 1];
});
// 处理错误输出
this.mcpProcess.stderr.on('data', (data) => {
const errorOutput = data.toString().trim();
log('ERROR', 'MCP process stderr', { stderr: errorOutput });
});
// 处理进程退出
this.mcpProcess.on('close', (code, signal) => {
log('WARN', 'MCP process exited', { code, signal, restartCount: this.restartCount });
this.pendingRequests.forEach((request, id) => {
request.reject(new Error(`MCP process terminated (code: ${code}, signal: ${signal})`));
});
this.pendingRequests.clear();
this.isInitialized = false;
this.mcpProcess = null;
if (this.restartCount < this.maxRestarts) {
log('INFO', `Restarting MCP process in 10 seconds... (${this.restartCount}/${this.maxRestarts})`);
setTimeout(() => this.startMcpProcess(), 10000);
} else {
log('ERROR', 'Max restart attempts reached, MCP process will not be restarted');
}
});
this.mcpProcess.on('error', (error) => {
log('ERROR', 'Failed to start MCP process', { error: error.message });
});
// 发送初始化请求
setTimeout(() => {
if (this.mcpProcess && !this.mcpProcess.killed) {
this.sendInitializeRequest();
}
}, 1000);
} catch (error) {
log('ERROR', 'Exception starting MCP process', { error: error.message });
}
}
sendInitializeRequest() {
const initRequest = {
jsonrpc: "2.0",
id: this.requestId++,
method: "initialize",
params: {
protocolVersion: "2024-11-05",
capabilities: {},
clientInfo: {
name: "HTTP-MCP-Server",
version: "1.0.0"
}
}
};
log('INFO', 'Sending initialization request to MCP process');
if (this.mcpProcess && !this.mcpProcess.killed) {
this.mcpProcess.stdin.write(JSON.stringify(initRequest) + '\n');
}
}
handleMcpResponse(response) {
if (shouldLog('debug')) {
log('DEBUG', 'Processing MCP response', {
id: response.id,
method: response.method,
hasError: !!response.error
});
}
if (response.jsonrpc === '2.0' && response.id !== undefined) {
const pending = this.pendingRequests.get(response.id);
if (pending) {
const { resolve, reject, timeout } = pending;
this.pendingRequests.delete(response.id);
clearTimeout(timeout);
if (response.error) {
log('ERROR', 'MCP request failed', {
id: response.id,
error: response.error
});
reject(new Error(response.error.message || 'MCP error'));
} else {
log('INFO', 'MCP request completed', { id: response.id });
resolve(response);
}
}
}
if (!this.isInitialized && response.result && response.result.protocolVersion) {
this.isInitialized = true;
this.restartCount = 0;
log('INFO', 'MCP process initialized successfully');
}
}
async sendRequest(request) {
return new Promise((resolve, reject) => {
if (!this.mcpProcess || this.mcpProcess.killed) {
const error = 'MCP process not available';
log('ERROR', error);
reject(new Error(error));
return;
}
const requestId = this.requestId++;
const requestWithId = {
...request,
jsonrpc: '2.0',
id: requestId
};
log('INFO', 'Sending MCP request', {
id: requestId,
method: request.method
});
const timeout = setTimeout(() => {
if (this.pendingRequests.has(requestId)) {
this.pendingRequests.delete(requestId);
log('ERROR', 'Request timeout', { id: requestId });
reject(new Error(`Request ${requestId} timeout after ${config.REQUEST_TIMEOUT}ms`));
}
}, config.REQUEST_TIMEOUT);
this.pendingRequests.set(requestId, { resolve, reject, timeout });
try {
this.mcpProcess.stdin.write(JSON.stringify(requestWithId) + '\n');
if (shouldLog('debug')) {
log('DEBUG', 'Request sent to MCP process', { id: requestId });
}
} catch (error) {
this.pendingRequests.delete(requestId);
clearTimeout(timeout);
log('ERROR', 'Failed to send request', {
id: requestId,
error: error.message
});
reject(new Error(`Failed to send request: ${error.message}`));
}
});
}
}
// 创建 HTTP MCP 服务器实例
const mcpServer = new HttpMcpServer();
// 配置查看端点
app.get('/config', (req, res) => {
// 返回安全的配置信息(不包含敏感信息)
const safeConfig = {
port: config.PORT,
host: config.HOST,
environment: config.NODE_ENV,
logLevel: config.LOG_LEVEL,
sharedDir: config.SHARED_DIR,
dataDir: config.DATA_DIR,
maxRestarts: config.MAX_RESTARTS,
requestTimeout: config.REQUEST_TIMEOUT
};
res.json(safeConfig);
});
// 健康检查端点
app.get('/health', (req, res) => {
const status = {
status: 'ok',
timestamp: new Date().toISOString(),
mcpProcess: mcpServer.mcpProcess && !mcpServer.mcpProcess.killed ? 'running' : 'stopped',
mcpInitialized: mcpServer.isInitialized,
pendingRequests: mcpServer.pendingRequests.size,
restartCount: mcpServer.restartCount
};
if (shouldLog('debug')) {
log('DEBUG', 'Health check requested', { client: req.ip });
}
res.json(status);
});
// MCP 请求端点
app.post('/mcp', async (req, res) => {
try {
log('INFO', 'MCP request received', {
method: req.body.method,
client: req.ip
});
if (!req.body.jsonrpc || req.body.jsonrpc !== '2.0') {
throw new Error('Invalid JSON-RPC 2.0 request');
}
const response = await mcpServer.sendRequest(req.body);
log('INFO', 'MCP request completed successfully', { method: req.body.method });
res.json(response);
} catch (error) {
log('ERROR', 'MCP request failed', {
method: req.body?.method,
error: error.message
});
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: error.message
}
});
}
});
// 获取服务器信息
app.get('/info', (req, res) => {
const info = {
name: 'HTTP MCP Filesystem Server - Configurable Version',
version: '1.0.0',
platform: process.platform,
nodeVersion: process.version,
serverTime: new Date().toISOString(),
logFile: LOG_FILE,
endpoints: {
config: '/config',
health: '/health',
mcp: '/mcp',
info: '/info',
logs: '/logs'
},
status: {
mcpProcess: mcpServer.mcpProcess && !mcpServer.mcpProcess.killed ? 'running' : 'stopped',
initialized: mcpServer.isInitialized,
restartCount: mcpServer.restartCount
}
};
if (shouldLog('debug')) {
log('DEBUG', 'Info endpoint requested', { client: req.ip });
}
res.json(info);
});
// 日志查看端点
app.get('/logs', (req, res) => {
const lines = parseInt(req.query.lines) || 50;
try {
if (!fs.existsSync(LOG_FILE)) {
return res.status(404).json({ error: 'Log file not found' });
}
const logContent = fs.readFileSync(LOG_FILE, 'utf8');
const logLines = logContent.split('\n').filter(line => line.trim());
const lastLines = logLines.slice(-lines);
res.json({
file: LOG_FILE,
lines: lastLines,
totalLines: logLines.length
});
} catch (error) {
log('ERROR', 'Failed to read log file', { error: error.message });
res.status(500).json({ error: 'Failed to read logs' });
}
});
// 根路径
app.get('/', (req, res) => {
res.json({
message: 'MCP HTTP Filesystem Server (Configurable Version)',
endpoints: ['/', '/config', '/health', '/info', '/mcp', '/logs'],
documentation: 'Use POST /mcp for MCP requests'
});
});
// 启动服务器
app.listen(config.PORT, config.HOST, () => {
log('INFO', 'Server started successfully', {
host: config.HOST,
port: config.PORT,
nodeVersion: process.version,
platform: process.platform,
logFile: LOG_FILE
});
console.log(`HTTP MCP Filesystem Server running on http://${config.HOST}:${config.PORT}`);
console.log(`Log file: ${LOG_FILE}`);
console.log(`Health check: curl http://localhost:${config.PORT}/health`);
console.log(`Config check: curl http://localhost:${config.PORT}/config`);
});
// 优雅关闭处理
function gracefulShutdown(signal) {
log('INFO', 'Received signal, shutting down', { signal });
if (mcpServer.mcpProcess) {
log('INFO', 'Stopping MCP process');
mcpServer.mcpProcess.kill();
}
setTimeout(() => {
log('INFO', 'Server shutdown complete');
process.exit(0);
}, 1000);
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('uncaughtException', (error) => {
log('ERROR', 'Uncaught Exception', {
error: error.message,
stack: error.stack
});
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
log('ERROR', 'Unhandled Rejection', {
reason: reason instanceof Error ? reason.message : reason
});
});
创建启动脚本 start-server.sh:
#!/bin/bash
# MCP HTTP Server Startup Script for Rocky Linux
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "=========================================="
echo " MCP HTTP Filesystem Server Startup"
echo "=========================================="
echo "Time: $(date)"
echo "User: $(whoami)"
echo "Directory: $(pwd)"
echo "Node.js: $(node --version)"
echo "NPM: $(npm --version)"
echo "=========================================="
# 检查 Node.js 是否安装
if ! command -v node &> /dev/null; then
echo "Error: Node.js is not installed"
exit 1
fi
# 检查依赖是否安装
if [ ! -d "node_modules" ]; then
echo "Installing dependencies..."
npm install
fi
# 设置环境变量
export NODE_ENV=production
export PORT=3000
export HOST=0.0.0.0
echo "Starting server..."
echo "Server will be available at: http://$(hostname -I | awk '{print $1}'):8008"
# 启动服务器
exec node http-mcp-server.js
给脚本执行权限:
chmod +x start-server.sh
创建 systemd 服务:
sudo vi /etc/systemd/system/mcp-http.service
内容如下:
[Unit]
Description=HTTP MCP Filesystem Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=houlj
Group=houlj
WorkingDirectory=/data/mcp/mcp-http-filesystem
Environment=NODE_ENV=production
Environment=PORT=8008
Environment=HOST=0.0.0.0
ExecStart=/usr/bin/node /data/mcp/mcp-http-filesystem/http-mcp-server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# 安全设置
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/data/mcp/mcp-http-filesystem
[Install]
WantedBy=multi-user.target
启用并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable mcp-http.service
sudo systemctl start mcp-http.service
检查服务状态:
sudo systemctl status mcp-http.service
journalctl -u mcp-http.service -f # 查看实时日志
sudo journalctl -u mcp-http.service -n 100 --no-pager # 后100行
测试服务器运行
# 测试健康检查
curl http://localhost:8008/health
# 测试服务器信息
curl http://localhost:8008/info
# 测试 MCP 端点(列出可用工具)
curl -X POST http://localhost:8008/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'
测试文件系统操作
# 测试列出允许的目录
curl -X POST http://localhost:8008/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "callTool",
"params": {
"name": "list_allowed_directories",
"arguments": {}
}
}'
# 测试列出共享目录内容
curl -X POST http://localhost:8008/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "callTool",
"params": {
"name": "list_directory",
"arguments": {
"path": "/home/mcpserver/mcp-http-filesystem/shared"
}
}
}'
- 从其他机器测试
# 从局域网内的其他机器测试
curl http://your-server-ip:8008/health
mcp客户端配置
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"mcp-over-http-client@latest",
"http://your-server-ip:8008/mcp"
]
}
}
}
2万+

被折叠的 条评论
为什么被折叠?



