| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 |
- // 进程内 MCP 工具(Claude 专用);M2 起同样的能力经 MCP Gateway 提供给 Codex
- import path from 'node:path'
- import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'
- import { z } from 'zod'
- import { CONVERT_FORMATS, convertDocument, recalcWorkbook } from '../../services/office'
- export const OFFICE_SERVER = 'office'
- export const CONVERT_TOOL = `mcp__${OFFICE_SERVER}__convert_document`
- export const RECALC_TOOL = `mcp__${OFFICE_SERVER}__recalc_workbook`
- export function officeServer(workspaceDir: string, pythonRuntime: string) {
- return createSdkMcpServer({
- name: OFFICE_SERVER,
- version: '0.1.0',
- tools: [
- tool(
- 'convert_document',
- '用 LibreOffice 转换文档格式(例如 docx→pdf、xlsx→pdf、pptx→pdf、doc→docx、html→pdf)。'
- + '输入和输出都必须在工作区内;输出文件名与输入相同、扩展名换成目标格式。不要在 Bash 里直接调用 soffice,它在沙箱中无法运行。',
- {
- input: z.string().describe('输入文件路径(相对工作区或绝对路径)'),
- format: z.enum(CONVERT_FORMATS).describe('目标格式'),
- out_dir: z.string().optional().describe('输出目录,默认 outputs'),
- },
- async args => {
- try {
- const out = await convertDocument({ workspaceDir, input: args.input, format: args.format, outDir: args.out_dir })
- return { content: [{ type: 'text', text: `已生成:${path.relative(workspaceDir, out)}` }] }
- } catch (err) {
- return { content: [{ type: 'text', text: (err as Error).message }], isError: true }
- }
- },
- ),
- tool(
- 'recalc_workbook',
- '用 LibreOffice 重新计算 Excel 文件中的所有公式并写回原文件,然后报告公式数量和错误单元格(#REF!、#DIV/0! 等)。'
- + '用 openpyxl 写入公式后必须调用它,否则文件里没有计算结果。不要在 Bash 里调用 soffice 或 recalc.py,它们在沙箱中无法运行。',
- { input: z.string().describe('xlsx/xlsm 文件路径(相对工作区或绝对路径)') },
- async args => {
- try {
- const r = await recalcWorkbook({ workspaceDir, input: args.input, pythonRuntime })
- const lines = [`已重算:${path.relative(workspaceDir, r.path)},公式 ${r.formulas} 个,错误 ${r.errorCount} 个`]
- for (const e of r.errors) lines.push(`- ${e.sheet}!${e.cell}: ${e.error}`)
- return { content: [{ type: 'text', text: lines.join('\n') }], isError: r.errorCount > 0 }
- } catch (err) {
- return { content: [{ type: 'text', text: (err as Error).message }], isError: true }
- }
- },
- ),
- ],
- })
- }
|