filesystem mcp server端部署

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"
      }
    }
  }'
  1. 从其他机器测试
# 从局域网内的其他机器测试
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"
      ]
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值