项目背景在写论文的时候感觉Introduction写起来很有难度要合理的将段落和文献结合起来相对比较繁琐。虽然之前也有阅读大量相关文献但是总感觉写起来很奇怪很别扭。因此想着能否有一款AI应用能够帮助我自动生成Introduction并且能够自动生成参考文献列表。同时我还希望它能够参考其他文献的行文风格来创作因为每一类期刊的文章都有自己相对应的风格。同时考虑到我使用的是Vscode Latex这样一个编辑环境因此我希望直接可以使用mcp工具来帮助我进行辅助创作因此我使用了之前文章提到的Vscode Roo Code MCP的方案来进行实现。项目目标实现一个基于MCP协议的Latex论文写作助手。能够参考其他文献的行文风格来创作。能够将生成的论文内容保存到本地。在生成相应章节内容时懂得自动引用相关文献并且能够将其和行文合理串联起来。具体实现1. 创建MCP服务器和传统服务一样都需要创建服务器进行交互。// 创建 MCP 服务器 const server new Server( { name: mdpi-server-mcp-server, version: 1.0.0, }, { capabilities: { tools: {}, }, } );2.注册工具列表处理器MCP中的ListToolsRequestSchema主要做两件事描述工具清单name工具的唯一标识description用途说明给大模型看帮助它判断何时调用inputSchema参数定义JSON Schema 格式客户端据此校验输入按协议响应MCP 定义了一个标准请求码 ListToolsRequestSchema客户端连接后会发送这个请求。服务器的处理器必须返回符合规范的 JSON。// 注册工具列表处理器 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: [ { name: search_arxiv, description: 搜索 arXiv 论文, inputSchema: { type: object, properties: { query: { type: string, description: 搜索英文关键词 }, maxResults: { type: number, description: 最大结果数量, default: 5 } }, required: [query] } }, { name: revise_section, description: 对已有章节内容进行学术化润色、扩写或调整保持 MDPI LaTeX 格式。, inputSchema: { type: object, properties: { content: { type: string, description: 需要修改的原始文本LaTeX 片段 }, instruction: { type: string, description: 修改要求如 扩写至 500 字、加强逻辑衔接、转为被动语态 }, autoRefs: { type: boolean, description: 是否自动搜索并引用相关 arXiv 论文默认 false, default: false }, refQuery: { type: string, description: 自定义文献搜索关键词若不提供则从章节主题中自动生成 }, refCount: { type: number, description: 期望引用的参考文献数量默认 5, default: 5 } }, required: [content, instruction] } }, { name: search_and_format_references, description: 根据关键词搜索 arXiv 论文返回格式化的 BibTeX 条目可直接用于 LaTeX 文档。, inputSchema: { type: object, properties: { query: { type: string, description: 搜索关键词如 sEMG joint moment prediction }, maxResults: { type: number, description: 返回结果数量默认 5, default: 5 } }, required: [query] } }, { name: generate_paper_section, description: 根据用户指定的章节类型如 introduction、related_work、experiments、conclusion和自定义要点生成 LaTeX 格式的章节草稿。可指定参考文献目录以模仿其写作风格。, inputSchema: { type: object, properties: { section: { type: string, enum: [abstract, introduction, related_work, method, experiments, results, discussion, conclusion], description: 要生成的章节类型 }, additionalInstructions: { type: string, description: 额外要求如强调创新点、包含对比方法等 }, previousContent: { type: string, description: 已有的前文内容用于上下文连贯 }, referenceDir: { type: string, description: 包含参考文献文件的本地目录路径用于模仿其写作风格可选 }, autoRefs: { type: boolean, description: 是否自动搜索并引用相关 arXiv 论文默认 false, default: false }, refQuery: { type: string, description: 自定义文献搜索关键词若不提供则从章节主题中自动生成 }, refCount: { type: number, description: 期望引用的参考文献数量默认 5, default: 5 } }, required: [section] } } ] }; });3.注册工具调用处理器CallToolRequestSchema负责处理客户端请求。具体职责有解析请求提取工具名和参数。分发执行根据工具名跳转到对应的业务逻辑通常是一个 switch分支。返回结果将执行结果封装成 MCP 规定的响应格式{ content: [...] }返回给客户端。server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; try { switch (name) { case revise_section: { const { content, instruction, autoRefs, refQuery, refCount 5 } args as any; let refContext ; let bibPaths: string[] []; // 自动搜索文献 if (autoRefs) { const query refQuery || buildRefQuery(, instruction, content); try { const { refPrompt, bibContent } await buildRefContext(query, refCount); if (refPrompt.trim()) { refContext refPrompt; const bibFileName refs_${Date.now()}.bib; fs.writeFileSync(path.join(WORK_DIR, bibFileName), bibContent, utf-8); bibPaths.push(bibFileName); } } catch (err) { console.warn(自动参考文献搜索失败:, err); } } // 构建提示 const systemPrompt You are a meticulous academic editor. Revise the provided LaTeX text according to the users instruction. Maintain the LaTeX formatting and existing citation commands. ${autoRefs ? If you add new statements, cite the provided references where appropriate using \\cite{id}. : }; let userPrompt Original text:\n${content}\n\nRevision instruction: ${instruction}; if (refContext) { userPrompt \n\n${refContext}\n\nAdd or keep citations as appropriate.; } const revised await callOpenRouterAPI(userPrompt, systemPrompt); const outPath path.join(WORK_DIR, revised_${Date.now()}.tex); fs.writeFileSync(outPath, revised, utf-8); let resultText revised; if (bibPaths.length 0) { resultText \n\n 参考文献文件已保存: ${bibPaths.join(, )}; } return { content: [{ type: text, text: resultText, file: path.basename(outPath), ...(bibPaths.length 0 { bibFiles: bibPaths }) }] }; } case search_and_format_references: { const { query, maxResults 5 } args as any; const bib await searchAndFormatReferences(query, maxResults); const outPath path.join(WORK_DIR, refs_${Date.now()}.bib); fs.writeFileSync(outPath, bib, utf-8); return { content: [{ type: text, text: bib, file: path.basename(outPath) }] }; } case generate_paper_section: { const { section, additionalInstructions, previousContent, autoRefs, refQuery, refCount 5 } args as any; let refContext ; let bibPaths: string[] []; // 自动搜索文献 if (autoRefs) { const query refQuery || buildRefQuery(section, additionalInstructions, previousContent); try { const { refPrompt, bibContent } await buildRefContext(query, refCount); if (refPrompt.trim()) { refContext refPrompt; // 保存 BibTeX 文件 const bibFileName refs_${Date.now()}.bib; fs.writeFileSync(path.join(WORK_DIR, bibFileName), bibContent, utf-8); bibPaths.push(bibFileName); } } catch (err) { console.warn(自动参考文献搜索失败:, err); } } // 构建系统提示 const systemPrompt You are an academic writer specializing in lower-limb joint moment estimation using deep learning. Write a ${section} section in LaTeX format suitable for MDPI template. Use professional, coherent English. ${autoRefs ? You MUST cite the provided references at appropriate places using \\cite{id}. : }; let userPrompt Generate the ${section} section for a paper on deep learning-based joint moment estimation.; if (previousContent) { userPrompt \n\nContext from previous sections:\n${previousContent.slice(0, 1000)}; } if (additionalInstructions) { userPrompt \n\nAdditional instructions: ${additionalInstructions}; } if (refContext) { userPrompt \n\n${refContext}\n\nPlease incorporate citations naturally in the generated text.; } userPrompt \n\nOutput only the LaTeX content for this section.; const sectionText await callOpenRouterAPI(userPrompt, systemPrompt); const outPath path.join(WORK_DIR, ${section}_${Date.now()}.tex); fs.writeFileSync(outPath, sectionText, utf-8); let resultText sectionText; if (bibPaths.length 0) { resultText \n\n 参考文献文件已保存: ${bibPaths.join(, )}; } return { content: [{ type: text, text: resultText, file: path.basename(outPath), ...(bibPaths.length 0 { bibFiles: bibPaths }) }] }; } case search_arxiv: { const { query, maxResults 5 } args as { query: string; maxResults?: number }; const results await searchArxivPapers(query, maxResults); return { content: [{ type: text, text: 找到 ${results.papers.length} 篇相关论文总计 ${results.totalResults} 篇\n\n${results.papers.map((paper, index) ${index 1}. **${paper.title}**\n ID: ${paper.id}\n 发布日期: ${paper.published}\n 作者: ${paper.authors.map((author: any) author.name || author).join(, )}\n 摘要: ${paper.summary.substring(0, 300)}...\n URL: ${paper.url}\n ).join(\n)} }] }; } default: throw new Error(Unknown tool: ${name}); } } catch (error) { return { content: [{ type: text, text: 工具执行失败: ${error instanceof Error ? error.message : String(error)} }], isError: true }; } });4.一些工具函数和辅助函数// 搜索文献并且格式化为 BibTeX 条目 async function searchAndFormatReferences(query: string, maxResults: number): Promisestring { const results await searchArxivPapers(query, maxResults); const bibEntries: string[] []; for (const paper of results.papers) { const entry article{${paper.id}, title {${paper.title}}, author {${paper.authors.map((a: any) a.name).join( and )}}, journal {arXiv preprint}, year {${paper.published.slice(0, 4)}}, eprint {${paper.id}}, archivePrefix {arXiv}, primaryClass {cs.LG}, url {${paper.url}} }; bibEntries.push(entry); } return bibEntries.join(\n\n); } // 调用 OpenRouter API async function callOpenRouterAPI(prompt: string, systemPrompt?: string): Promisestring { try { const messages: Array{ role: string, content: string } []; if (systemPrompt) { messages.push({ role: system, content: systemPrompt }); } messages.push({ role: user, content: prompt }); const response await axios.post(OPENROUTER_API_URL, { model: OPENROUTER_MODEL, messages: messages, stream: false, max_tokens: 8192, temperature: 0.7, top_p: 0.7, }, { headers: { Authorization: Bearer ${OPENROUTER_API_KEY}, Content-Type: application/json, HTTP-Referer: http://localhost, // 根据 OpenRouter 要求添加 X-Title: Mdpi Writer MCP Server } }); return response.data.choices[0].message.content; } catch (error) { console.error(调用 OpenRouter API 时出错:, error); throw new Error(AI 调用失败: ${error instanceof Error ? error.message : String(error)}); } } // 构建文献查询 function buildRefQuery( section: string, additionalInstructions?: string, textContent?: string ): string { const baseQuery lower limb joint moment estimation deep learning; const sectionKeywords: Recordstring, string { abstract: summary abstract, introduction: introduction background, related_work: related work literature review, method: method model architecture, experiments: experiment dataset evaluation, results: results analysis, discussion: discussion implications, conclusion: conclusion future work }; let query baseQuery; if (sectionKeywords[section]) query sectionKeywords[section]; if (additionalInstructions) query additionalInstructions; if (textContent) { // 取前 200 个字符作为补充 const snippet textContent.slice(0, 200).replace(/[^a-zA-Z0-9\s]/g, ); query snippet; } return query.slice(0, 300); // 限制长度 } // 构建引用bib格式 async function buildRefContext(query: string, maxResults: number): Promise{ refPrompt: string; bibContent: string; } { const results await searchArxivPapers(query, maxResults); const bibEntries: string[] []; const summaries: string[] []; for (const paper of results.papers) { bibEntries.push(article{${paper.id}, title {${paper.title}}, author {${paper.authors.map((a: any) a.name).join( and )}}, journal {arXiv preprint}, year {${paper.published.slice(0,4)}}, eprint {${paper.id}}, archivePrefix {arXiv}, primaryClass {cs.LG}, url {${paper.url}} }); summaries.push(- \\cite{${paper.id}} ${paper.title}. ${paper.summary.substring(0, 200)}...); } const refPrompt **Relevant literature for citation (use \\cite{id} in the text):** ${summaries.join(\n)} ; const bibContent bibEntries.join(\n\n); return { refPrompt, bibContent }; } // 支持的文献文件扩展名 const REFERENCE_EXTS [.txt, .md, .tex, .bib, .pdf]; // 读取文献内容这里截取了字符长度可以按需修改 async function readReferenceContents(dirPath: string): Promisestring { if (!fs.existsSync(dirPath)) { throw new Error(目录不存在: ${dirPath}); } const files fs.readdirSync(dirPath); let combinedText ; for (const file of files) { const ext path.extname(file).toLowerCase(); if (!REFERENCE_EXTS.includes(ext)) continue; const filePath path.join(dirPath, file); const content fs.readFileSync(filePath, utf-8); // 截取前 5000 字符以免 token 过多 combinedText \n\n--- 文件: ${file} ---\n${content.slice(0, 5000)}; } if (!combinedText) { throw new Error(目录下没有可读取的文献文件支持 .txt, .md, .tex, .bib, .pdf文本内容); } return combinedText; } // 分析写作风格 async function analyzeWritingStyle(referenceText: string): Promisestring { const systemPrompt You are an expert in academic writing analysis.; const userPrompt Analyze the following academic writing samples and summarize the key characteristics of the writing style. Include aspects such as: - Sentence structure (e.g., long and complex vs. short and direct) - Use of technical terminology and jargon - Tone (e.g., formal, objective, cautious) - Paragraph organization and flow - Typical expressions or phrase patterns Provide a concise paragraph describing the style, which can be used as guidance for generating new text in the same style. Samples: ${referenceText}; return await callOpenRouterAPI(userPrompt, systemPrompt); } // 工具函数搜索 arXiv 论文 async function searchArxivPapers(query: string, maxResults: number 5): Promise{ totalResults: number, papers: any[] } { try { const results await arxivClient.search({ start: 0, searchQuery: { include: [ { field: all, value: query } ] }, maxResults: maxResults }); const papers results.entries.map(entry { const urlParts entry.url.split(/); const arxivId urlParts[urlParts.length - 1]; return { id: arxivId, url: entry.url, title: entry.title.replace(/\s/g, ).trim(), summary: entry.summary.replace(/\s/g, ).trim(), published: entry.published, authors: entry.authors || [] }; }); return { totalResults: results.totalResults, papers: papers }; } catch (error) { console.error(搜索 arXiv 论文时出错:, error); throw new Error(搜索失败: ${error instanceof Error ? error.message : String(error)}); } }导入MCP服务详细可见我之前的一篇文章详细介绍了在Vscode中如何导入MCP服务。链接在Vscode中导入MCP服务效果样例生成摘要文献引用bib文献列表生成结语这一套流程下来还是挺花费token的并且基本上只能生成一个大概的introduction可能不如直接使用cli或者网页版交互更快。不过大部分文献还是可以用的文献和引用的对应大多还是比较精准的。如果对项目感兴趣可以访问项目链接