
从 0 到 1 搭建一个 Excel 转配置的 MCP Server前言这篇文章记录一次从实际前端工作流出发搭建一个 MCP Server 的完整过程。我们要解决的问题很具体运营或策划经常把活动配置、奖池配置、规则配置整理在 Excel 里前端最终需要的是固定结构的 JS 配置文件。过去这类工作通常有几种做法手动复制 Excel 内容再一点点整理成 JS 对象。写一个一次性脚本遇到新 Excel 格式再改脚本。让 AI 帮忙转但每次都要把规则重新讲一遍结果也缺少稳定性。MCP 的价值在这里就很明显把“读取 Excel、识别 sheet、生成 JS 配置文件”封装成一个稳定工具让 Codex、Claude Desktop 或其他 MCP Client 可以直接调用。AI 不再只是“猜怎么生成”而是调用我们提供的确定性工具完成生成。本文以excel-to-config-mcp-server为例完整讲一遍为什么要做 MCP Server。项目如何初始化。MCP 工具如何定义。Excel 如何解析成配置。如何构建成dist。如何本地验证。如何配置到 Codex 使用。后续如果发布 npm需要注意什么。一、先理解 MCP Server 在这里承担什么角色MCP全称 Model Context Protocol可以简单理解为“AI 应用调用外部工具的一套标准协议”。在这个项目里角色关系是这样的用户 | v Codex / 其他 MCP Client | | 通过 MCP 调用工具 excel_to_config v excel-to-config-mcp-server | | 读取 xlsx生成 JS 配置 v detail001.js / detail002.js / ...也就是说MCP Server 本身不负责聊天、不负责推理它负责提供一个稳定的工具能力。AI Client 根据用户意图决定什么时候调用这个工具工具本身负责把输入变成确定的输出。这个边界很重要。如果让 AI 直接读 Excel 再生成代码它会受到上下文长度、表格复杂度、提示词准确度影响。而 MCP Server 把这部分变成普通程序逻辑读取文件、解析单元格、生成 JS 文件。AI 只需要负责调度。二、项目目标这个 MCP Server 的目标是读取一个.xlsx文件从指定 sheet 开始为每个 sheet 生成一个前端 JS 配置文件。例如输入demo/demo.xlsx输出detail001.js detail002.js detail003.js detail004.js每个文件导出一个multipleXXX对象exportconstmultiple001{table1:{bigTitle:配置标题,topTitle:[],table1:[],table2:[],bottomContent:[],},};这个结构里有两层table外层table1/table2/...表示一个 sheet 内的多个配置块。内层table1/table2/...表示单个配置块里的多个表格。这样做的好处是Excel 的排版可以比较灵活但最终输出给前端的结构是统一的。三、项目初始化项目是一个 TypeScript Node.js 的 MCP Server。核心依赖有三个{dependencies:{modelcontextprotocol/sdk:^1.29.0,xlsx:^0.18.5,zod:^4.4.3}}它们分别负责modelcontextprotocol/sdk创建 MCP Server、注册工具、连接 stdio transport。xlsx读取 Excel 文件。zod声明 MCP 工具入参 schema。开发依赖{devDependencies:{types/node:^26.1.1,tsx:^4.22.4,typescript:^6.0.3}}脚本配置如下{scripts:{build:tsc,start:node dist/index.js,dev:node --import tsx src/index.ts}}这里没有使用 webpack、rollup 或 esbuild 打包也没有做压缩。原因很简单MCP Server 是 Node 进程不是浏览器前端资源。主流做法就是 TypeScript 编译到dist然后由 Node 直接运行。官方一些 TypeScript MCP Server 也是类似方式源码在src构建产物在distbin或启动命令指向dist/index.js。四、目录设计当前项目目录比较克制src/index.ts MCP 服务入口同时提供 --generate CLI 调试入口 src/xlsx.ts Excel 读取层 src/generator.ts 配置生成层 demo/ 示例 Excel 与生成结果 dist/ TypeScript 构建产物这里特意把逻辑拆成三层index.ts 负责 MCP 工具注册、CLI 入口、服务启动 xlsx.ts 负责把 Excel 转成原始二维行数据 generator.ts 负责识别配置块、表格、说明文本并生成 JS 配置内容这样拆分后MCP 只是入口之一。我们既可以通过 Codex 调用也可以直接用 CLI 调试生成。这对开发非常重要因为调试 Excel 解析逻辑时不需要每次都走 MCP Client。五、实现 MCP 服务入口MCP 服务入口在src/index.ts。文件开头保留了 shebang#!/usr/bin/env node这样后续如果发布成 npm CLI可以通过命令直接执行。创建 MCP ServerconstservernewMcpServer({name:excel-to-config-mcp-server,version:1.0.0,});然后注册工具server.registerTool(excel_to_config,{title:Excel 转 JS 配置,description:读取 xlsx 文件中的每个 sheet并生成 multipleXXX 格式的 JS 配置文件。,inputSchema:{excelPath:z.string().describe(xlsx 文件的绝对路径或相对路径。),outputDir:z.string().optional().describe(生成的 JS 配置文件输出目录默认与 excelPath 所在目录相同。,),startSheet:z.union([z.string(),z.number().int()]).optional().describe(从哪个 sheet 开始生成可传 sheet 名或从 1 开始的序号默认第一个。,),startCode:z.number().int().default(1).describe(生成文件编号起始值默认 1对应 detail001.js。),writeFiles:z.boolean().default(true).describe(是否写入文件设为 false 时只预览生成结果。),},},async({excelPath,outputDir,startSheet,startCode,writeFiles}){constfilesawaitgenerateConfigFiles({excelPath,outputDir,startSheet,startCode,writeFiles,});constresult{files:files.map(({sheetName,code,filePath})({sheetName,code,filePath,})),};return{content:[{type:text,text:JSON.stringify(result,null,2),},],structuredContent:result,};},);这里有几个设计点第一工具名使用excel_to_config名称清晰适合 AI Client 根据语义选择。第二入参 schema 写得尽量明确。MCP 工具不是只给人看的也是给模型看的。describe写清楚后模型更容易传对参数。第三返回值同时提供content和structuredContent。content适合直接展示给用户structuredContent适合客户端继续结构化处理。最后启动 stdio transportconsttransportnewStdioServerTransport();awaitserver.connect(transport);stdio 是本地 MCP Server 最常见的连接方式客户端启动一个本地进程通过标准输入输出和它通信。六、保留 CLI 调试入口入口里还有一个小设计当传入--generate时不启动 MCP 服务而是进入 CLI 生成模式。asyncfunctionmain(){if(process.argv[2]--generate){awaitrunCli(process.argv.slice(3));return;}consttransportnewStdioServerTransport();awaitserver.connect(transport);}这样同一个入口支持两种用法nodedist/index.js启动 MCP Server。nodedist/index.js--generatedemo/demo.xlsx直接把 Excel 转成配置文件。这个设计非常实用。因为 MCP 调试链路会多一层客户端排查问题时不如 CLI 直接。先用 CLI 验证核心逻辑再接入 MCP会顺很多。七、Excel 读取层只做一件事Excel 读取逻辑在src/xlsx.ts。这一层只负责把工作簿读成按 Excel 列名索引的行数据exporttypeSheetRows{name:string;rows:ArrayRecordstring,CellValue;};比如一个 sheet 会被转成{name:Sheet1,rows:[{A:标题},{A:列1,B:列2},{A:值1,B:值2}]}读取工作簿exportfunctionreadWorkbook(filePath:string):SheetRows[]{constworkbookXLSX.readFile(filePath,{cellDates:true,cellFormula:false,cellNF:true,});returnworkbook.SheetNames.map((name)({name,rows:readSheetRows(workbook.Sheets[name]),}));}这里有几个关键选项cellDates: true日期尽量保留为 Date。cellFormula: false公式只读取缓存结果不把公式字符串写进配置。cellNF: true保留数字格式方便识别百分比。读取 sheet 时不使用简单的 JSON 转换而是按单元格范围逐格扫描for(letrowIndexrange.s.r;rowIndexrange.e.r;rowIndex1){constrow:Recordstring,CellValue{};for(letcolIndexrange.s.c;colIndexrange.e.c;colIndex1){constaddressXLSX.utils.encode_cell({r:rowIndex,c:colIndex});constvaluereadCellValue(sheet[address]);if(hasValue(value)){row[XLSX.utils.encode_col(colIndex)]value;}}rows[rowIndex]row;}为什么要这么做因为 Excel 里的空行、空列、相对位置本身就是结构信息。比如一个空行可能表示一个表格结束短文本行后面跟数据行可能表示新配置块开始。如果直接转成紧凑数组这些位置信息就丢了。百分比也做了特殊处理if((isPercentCell(cell)||isPercentText(cell.w))typeofcell.wstringcell.w.trim()!){returncell.w.trim();}Excel 底层可能把28%存成0.28。但前端配置通常需要展示值所以这里保留 Excel 里看到的28%避免生成结果和业务预期不一致。八、配置生成层把 Excel 排版转成统一对象生成逻辑在src/generator.ts。入口函数是exportasyncfunctiongenerateConfigFiles(options:GenerateOptions,):PromiseGeneratedFile[]{conststartCodeoptions.startCode??1;constoutputDiroptions.outputDir??dirname(options.excelPath);constshouldWriteoptions.writeFiles??true;constworkbookSheetsreadWorkbook(options.excelPath);constsheetsworkbookSheets.slice(resolveStartSheetIndex(workbookSheets,options.startSheet),);constgeneratedsheets.map((sheet,index){constcodeString(startCodeindex).padStart(3,0);constcontentgenerateSheetContent(sheet,code);return{sheetName:sheet.name,code,filePath:join(outputDir,detail${code}.js),content,};});if(shouldWrite){awaitmkdir(outputDir,{recursive:true});awaitPromise.all(generated.map((file)writeFile(file.filePath,file.content,utf8)),);}returngenerated;}它完成几件事读取 Excel。根据startSheet决定从哪个 sheet 开始生成。根据startCode生成001/002/003编号。为每个 sheet 生成一个detailXXX.js。根据writeFiles决定是否真正写入磁盘。writeFiles是一个很重要的调试参数。设置为false时可以只预览生成结果不污染目标目录。1. 起始 sheet 的处理startSheet支持数字和字符串startSheet?:string|number;数字按 Excel 页签序号理解从 1 开始functionnormalizeStartSheetNumber(startSheet:number,sheetCount:number,):number{if(!Number.isInteger(startSheet)||startSheet1||startSheetsheetCount){thrownewError(起始 sheet 序号必须在 1 到${sheetCount}之间);}returnstartSheet-1;}字符串则按 sheet 名精确匹配。这样使用时更灵活{startSheet:3}或者{startSheet:活动配置}2. 生成 JS 模块单个 sheet 最终生成exportfunctiongenerateSheetContent(sheet:SheetRows,code:string):string{returnexport const multiple${code}${formatValue(parseSheetAsMultipleConfig(sheet),0)}\n;}也就是exportconstmultiple001{table1:{table1:[// ...],},};3. 识别配置块一个 sheet 里可能有多个配置块所以会先拆 blocksheet block 1 bigTitle topTitle table 1 table 2 bottomContent block 2 bigTitle table 1解析过程大致是constblocksparseBlocks(sheet.rows);parseBlocks会先找第一个表头再根据短标题行、顶部说明行判断配置块边界。这样做是为了适配真实 Excel 的写法有些配置块有明确大标题有些配置块没有大标题只有几行说明后直接跟表格。4. 识别表格在单个配置块内继续查找所有表头consttableHeaderIndexesfindTableHeaderIndexes(rows,firstHeaderIndex,end);每个表头往后读取连续数据行直到遇到空行。下一个表头。当前配置块结束。这种规则不绑定具体业务字段。也就是说生成器不需要知道“奖池”“概率”“道具名”这些业务含义只需要识别出表格结构。这点很关键通用 MCP Server 不应该把某个活动或某个项目的中文表头写死在核心逻辑里。否则下次换一份 Excel 就又要改代码。九、构建为什么是 dist而不是压缩包这个项目的 TypeScript 配置如下{compilerOptions:{target:ES2022,module:NodeNext,moduleResolution:NodeNext,outDir:./dist,rootDir:./src,strict:true,esModuleInterop:true,resolveJsonModule:true,types:[node]},include:[src/**/*],type:module}执行pnpmbuild会把src/index.ts src/generator.ts src/xlsx.ts编译成dist/index.js dist/generator.js dist/xlsx.js对于本地 MCP Server 来说这就是主流发布形态。不压缩的原因MCP Server 是 Node 进程不是浏览器资源不需要为了首屏加载压缩。可读的dist更容易排查线上或本地问题。npm 包发布时本身会打成.tgz传输层已经压缩。真正占体积的通常是依赖压缩业务入口文件收益很小。如果后续发布 npm更应该关注的是发布边界而不是压缩{bin:{excel-to-config-mcp-server:dist/index.js},files:[dist,README.md]}并且构建后确保入口文件有可执行权限chmodx dist/index.js跨平台项目可以用shx chmod x dist/*.js。十、本地验证验证建议分三层构建验证、CLI 验证、模块导入验证。1. 构建验证pnpmbuild这一步验证 TypeScript 类型和编译结果。2. CLI 生成验证nodedist/index.js--generatedemo/demo.xlsx /tmp/excel-config-output1参数含义node dist/index.js --generate excelPath [outputDir] [startCode] [startSheet]例如excelPath: demo/demo.xlsx outputDir: /tmp/excel-config-output startCode: 1 startSheet: 默认第一个 sheet生成后可以检查目录ls/tmp/excel-config-output预期看到detail001.js detail002.js detail003.js detail004.js3. 检查生成文件能否导入node--input-typemodule-eawait import(/tmp/excel-config-output/detail001.js); console.log(ok)如果输出ok说明生成的 JS 文件至少语法正确可以被 ESM 正常导入。这一层验证很有必要。因为 Excel 里的特殊字符、换行、引号都可能导致生成 JS 时出现转义问题。导入验证可以及时发现这类问题。十一、配置到 Codex构建完成后就可以把这个 MCP Server 配置给 Codex。在需要使用该工具的业务项目中创建.codex/config.toml写入[mcp_servers.excel_to_config] command node args [/Users/chenyupeng/Desktop/YPProject/frontend-mcp/excel-to-config-mcp-server/dist/index.js] startup_timeout_sec 10 tool_timeout_sec 60 enabled true注意这里的args要写dist/index.js的绝对路径。配置完成后建议重启 Codex 或新开会话。然后在 Codex 中执行/mcp查看 MCP 是否加载成功。加载成功后就可以直接对 Codex 说用 excel_to_config 把 /path/to/demo.xlsx 转成 JS 配置输出到 /path/to/src/config工具实际收到的参数类似{excelPath:/path/to/demo.xlsx,outputDir:/path/to/src/config,startSheet:1,startCode:1,writeFiles:true}Codex 会调用 MCP 工具工具负责生成文件并返回生成结果{files:[{sheetName:Sheet1,code:001,filePath:/path/to/src/config/detail001.js}]}十二、一次完整使用流程把上面的步骤串起来完整流程是pnpminstallpnpmbuildnodedist/index.js--generatedemo/demo.xlsx /tmp/excel-config-output1node--input-typemodule-eawait import(/tmp/excel-config-output/detail001.js); console.log(ok)然后在业务项目配置[mcp_servers.excel_to_config] command node args [/Users/frontend-mcp/excel-to-config-mcp-server/dist/index.js] startup_timeout_sec 10 tool_timeout_sec 60 enabled true最后在 Codex 里通过自然语言调用把这个 Excel 转成前端配置文件输出到 src/config/activity这就是一个从开发、构建、验证到接入 AI 工作流的完整闭环。十三、设计上的几个经验1. MCP 工具要小而稳定不要把所有事情都塞进一个工具里。这个项目只做“Excel 到 JS 配置”这一件事。工具边界越清晰模型越容易正确调用后续维护也越简单。2. 入参描述要写给模型看inputSchema不是摆设。例如startSheet如果只写类型不写“可传 sheet 名或从 1 开始的序号”模型就可能传 0或者传错页签名。描述越准确调用成功率越高。3. 核心逻辑不要依赖具体业务文案Excel 里可能有中文、韩文、英文也可能每个项目表头不一样。通用工具应该识别结构而不是猜业务含义。当前生成器主要根据空行。短标题行。表头行。数据行。编号说明行。来判断结构而不是写死某个字段名。4. CLI 调试入口很值得保留MCP Server 最终是给 AI Client 调用的但开发时最方便的还是 CLI。CLI 可以快速验证Excel 是否能读。sheet 是否识别正确。输出文件是否符合预期。生成 JS 是否能导入。等核心逻辑稳定后再接 MCP排查成本会低很多。5. 先保证确定性再交给 AI 调度AI 擅长理解意图和调度工具但配置生成这种事情应该尽量确定。这个项目的理想分工是AI 负责理解用户要把哪个 Excel 转到哪里 程序负责稳定地解析 Excel 并生成配置不要反过来让 AI 每次猜 Excel 怎么转。十四、后续可以继续增强的方向当前版本已经可以完成从 Excel 到 JS 配置的基本闭环但后续还可以继续扩展支持配置化表头别名。支持指定只生成某几个 sheet。支持 dry-run 返回完整内容预览。支持生成 TypeScript 配置。支持输出格式化选项比如单引号、尾逗号、缩进。增加测试用例覆盖多 sheet、多表格、百分比、空表头、说明文本等场景。发布成 npm 包后通过npx excel-to-config-mcp-server直接配置 MCP。其中最推荐优先做的是 npm 发布。发布后 Codex 配置可以从[mcp_servers.excel_to_config] command node args [/absolute/path/to/dist/index.js]演进为[mcp_servers.excel_to_config] command npx args [-y, excel-to-config-mcp-server]这样其他同事接入时就不用关心本地源码路径只要复制 MCP 配置即可。总结这个 MCP Server 的核心价值不是“写了一个 Excel 脚本”而是把一个高频、容易出错、依赖上下文的前端配置生成流程沉淀成了 AI 可以稳定调用的工具。整个过程可以概括成四步定义工具边界 - 用 TypeScript 实现确定性生成逻辑 - 构建到 dist 并本地验证 - 配置到 Codex 让 AI 调用当工具能力被 MCP 化之后AI 工作流会发生一个很实在的变化过去我们反复给 AI 解释规则现在我们把规则写进工具里让 AI 负责调用。这也是 MCP 在工程实践里的价值。它不是让 AI 替代所有程序逻辑而是让我们把稳定逻辑工具化再把这些工具交给 AI 编排。