structurize-mcp
Version:
Anthropic MCP Server for generating structured CSV files from natural language descriptions
241 lines (214 loc) • 6.49 kB
text/typescript
// 使用最新的 MCP SDK 导入方式
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { generateCSV } from './csvGenerator';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import express from 'express';
import fileUpload from 'express-fileupload';
import fs from 'fs-extra';
import path from 'path';
import net from 'net';
// 检查端口是否可用
function isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = net.createServer()
.once('error', () => {
resolve(false);
})
.once('listening', () => {
server.close();
resolve(true);
})
.listen(port);
});
}
// 查找可用端口
async function findAvailablePort(startPort: number): Promise<number> {
let port = startPort;
let attempts = 0;
const maxAttempts = 50; // 最多尝试50个端口
while (!(await isPortAvailable(port))) {
port++;
attempts++;
if (attempts >= maxAttempts) {
console.warn(`警告: 尝试了${maxAttempts}个端口后仍未找到可用端口,将使用随机端口`);
return 0; // 使用随机端口
}
}
return port;
}
// 解析命令行参数
const argv = yargs(hideBin(process.argv))
.option('gemini-api-key', {
alias: 'k',
type: 'string',
description: 'Gemini API Key',
demandOption: false
})
.option('csv-dir', {
alias: 'd',
type: 'string',
description: 'CSV 文件保存目录',
demandOption: false
})
.option('port', {
alias: 'p',
type: 'number',
description: 'HTTP 服务器端口',
default: 3000
})
.option('auto-port', {
alias: 'a',
type: 'boolean',
description: '自动查找可用端口',
default: true
})
.help()
.alias('help', 'h')
.parseSync();
// 提取命令行参数
const apiKey = argv['gemini-api-key'];
const csvDir = argv['csv-dir'];
let port = argv['port'];
const autoPort = argv['auto-port'];
// 记录启动参数
console.log('启动参数:');
console.log(`- CSV 保存目录: ${csvDir || '默认目录'}`);
console.log(`- Gemini API Key: ${apiKey ? '已提供' : '未提供'}`);
console.log(`- HTTP 服务器端口: ${port}`);
console.log(`- 自动查找端口: ${autoPort ? '启用' : '禁用'}`);
// 启动 HTTP 服务器
const app = express();
app.use(express.json());
app.use(fileUpload({
limits: { fileSize: 5 * 1024 * 1024 }, // 限制文件大小为 5MB
}));
// 处理 CSV 生成请求
app.post('/generate-csv', async (req, res) => {
try {
const { description } = req.body;
if (!description) {
return res.status(400).json({ error: '缺少描述文本' });
}
if (!apiKey) {
return res.status(400).json({ error: '未提供 Gemini API Key' });
}
// 在这里调用 CSV 生成逻辑
// 省略具体实现,因为这里只是添加路由
return res.json({ message: 'CSV 生成功能尚未实现' });
} catch (error) {
console.error('CSV 生成错误:', error);
return res.status(500).json({
error: error instanceof Error ? error.message : 'CSV 生成失败'
});
}
});
// 添加健康检查端点
app.get('/health', (req, res) => {
res.json({ status: 'ok', version: '1.1.0' });
});
// 启动 HTTP 服务器
async function startHttpServer() {
try {
// 如果启用自动端口,则查找可用端口
if (autoPort) {
const availablePort = await findAvailablePort(port);
if (availablePort !== port) {
console.log(`端口 ${port} 已被占用,将使用端口 ${availablePort}`);
port = availablePort;
}
}
return new Promise<void>((resolve, reject) => {
// 创建服务器
const server = app.listen(port, () => {
console.log(`HTTP 服务器已启动,监听端口 ${port}`);
resolve();
});
// 处理启动错误
server.on('error', (err: any) => {
if (err.code === 'EADDRINUSE' && !autoPort) {
console.error(`错误: 端口 ${port} 已被占用`);
console.error(`提示: 使用以下命令启用自动端口查找功能:`);
console.error(` --auto-port 或 -a`);
console.error(`或指定其他端口:`);
console.error(` --port <端口号> 或 -p <端口号>`);
reject(err);
} else {
console.error('HTTP 服务器启动错误:', err);
reject(err);
}
});
// 优雅关闭
process.on('SIGINT', () => {
console.log('正在关闭 HTTP 服务器...');
server.close(() => {
console.log('HTTP 服务器已关闭');
process.exit(0);
});
});
});
} catch (error) {
console.error('启动 HTTP 服务器失败:', error);
throw error;
}
}
// 创建 MCP Server 实例
const mcpServer = new McpServer({
name: 'structurize-mcp',
version: '1.1.0',
});
// 参数定义
const csvToolParams = {
title: z.string(),
structure: z.string(),
data: z.string(),
delimiter: z.string().optional(),
};
// 注册生成 CSV 文件的工具
mcpServer.tool(
'generate-csv',
csvToolParams,
async (params) => {
try {
const { title, structure, data, delimiter = ',' } = params;
// 调用 CSV 生成函数,传递命令行参数
const result = await generateCSV(title, structure, data, delimiter, csvDir, apiKey);
return {
content: [
{
type: 'text',
text: `CSV 文件已成功生成!\n文件路径: ${result.filePath}\n行数: ${result.rowCount}\n列数: ${result.columnCount}`,
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `生成 CSV 文件时出错: ${errorMessage}`,
},
],
};
}
}
);
// 主函数
async function main() {
try {
// 启动 HTTP 服务器
await startHttpServer();
// 启动 MCP Server
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
console.log('MCP 服务器已启动,等待请求...');
} catch (error) {
console.error('服务器启动失败:', error);
process.exit(1);
}
}
// 执行主函数
main();