Skip to main content
Glama

Excel MCP Server

index.ts23.8 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; import ExcelJS from "exceljs"; import fs from "fs/promises"; import path from "path"; // MCP適切なスキーマ定義(Zodスキーマ) const CreateWorkbookSchema = z.object({ filePath: z.string().describe("作成するExcelファイルの絶対パス。例: C:/Users/Username/Documents/report.xlsx。ファイル拡張子は.xlsxである必要があります。注意: 作成されるワークブックは空でシートを含みません"), }); const GetWorkbookInfoSchema = z.object({ filePath: z.string().describe("情報を取得するExcelファイルの絶対パス"), }); const AddWorksheetSchema = z.object({ filePath: z.string().describe("対象のExcelファイルの絶対パス。既存のファイルである必要があります"), sheetName: z.string().describe("作成するワークシート名。英数字、日本語、アンダースコア、ハイフンが使用可能です"), }); const SetCellValueSchema = z.object({ filePath: z.string().describe("対象のExcelファイルの絶対パス"), sheetName: z.string().describe("対象のワークシート名。既存のワークシート名を指定してください"), cell: z.string().describe("セル位置。A1形式で指定(例: A1, B2, AA10, Z99)。範囲指定(A1:B2)は不可"), value: z.union([z.string(), z.number(), z.boolean()]).describe("セルに設定する値。文字列、数値、真偽値のいずれか"), }); const GetCellValueSchema = z.object({ filePath: z.string().describe("対象のExcelファイルの絶対パス"), sheetName: z.string().describe("対象のワークシート名"), cell: z.string().describe("セル位置。A1形式で指定(例: A1, B2, AA10)"), }); const SetRangeValuesSchema = z.object({ filePath: z.string().describe("対象のExcelファイルの絶対パス"), sheetName: z.string().describe("対象のワークシート名"), startCell: z.string().describe("データ入力を開始するセル位置(例: A1)。ここから右下方向にデータが入力されます"), values: z.array(z.array(z.union([z.string(), z.number(), z.boolean()]))).describe("2次元配列のデータ。外側の配列が行、内側の配列が列を表します。例: [[\"商品名\", \"価格\"], [\"商品A\", 1000]]"), }); const GetRangeValuesSchema = z.object({ filePath: z.string().describe("対象のExcelファイルの絶対パス"), sheetName: z.string().describe("対象のワークシート名"), range: z.string().describe("取得する範囲。A1:C3形式で指定(例: A1:C10, B2:D5)"), }); const FormatCellSchema = z.object({ filePath: z.string().describe("Excelファイルのパス"), sheetName: z.string().describe("ワークシート名"), cell: z.string().describe("セル位置(例: A1)"), format: z.object({ font: z.object({ bold: z.boolean().optional().describe("太字設定"), italic: z.boolean().optional().describe("斜体設定"), size: z.number().optional().describe("フォントサイズ"), color: z.string().optional().describe("フォント色(ARGB形式)"), }).optional().describe("フォント設定"), fill: z.object({ type: z.literal("pattern").describe("塗りつぶしタイプ"), pattern: z.string().describe("パターン(solid等)"), fgColor: z.string().describe("前景色(ARGB形式)"), }).optional().describe("塗りつぶし設定"), border: z.object({ top: z.object({ style: z.string(), color: z.string() }).optional(), left: z.object({ style: z.string(), color: z.string() }).optional(), bottom: z.object({ style: z.string(), color: z.string() }).optional(), right: z.object({ style: z.string(), color: z.string() }).optional(), }).optional().describe("罫線設定"), }).describe("セルの書式設定"), }); const FindDataSchema = z.object({ filePath: z.string().describe("Excelファイルのパス"), sheetName: z.string().describe("ワークシート名"), searchValue: z.union([z.string(), z.number()]).describe("検索する値"), }); const AddFormulaSchema = z.object({ filePath: z.string().describe("Excelファイルのパス"), sheetName: z.string().describe("ワークシート名"), cell: z.string().describe("セル位置(例: A1)"), formula: z.string().describe("数式(=SUM(A1:A10)など、=で始まる)"), }); const ExportToCSVSchema = z.object({ filePath: z.string().describe("Excelファイルのパス(既存ファイル)"), sheetName: z.string().describe("ワークシート名(既存シート)"), csvPath: z.string().describe("CSVファイルの出力パス"), }); // 引数検証ヘルパー関数 function validateFilePath(filePath: string): void { if (!filePath) { throw new Error("ファイルパスが指定されていません"); } if (!filePath.endsWith('.xlsx') && !filePath.endsWith('.xls')) { throw new Error("ファイル拡張子は .xlsx または .xls である必要があります"); } if (!path.isAbsolute(filePath)) { throw new Error("絶対パスを指定してください(例: C:/Users/Username/Documents/file.xlsx)"); } } function validateCellAddress(cell: string): void { const cellPattern = /^[A-Z]+[1-9]\d*$/; if (!cellPattern.test(cell)) { throw new Error(`無効なセル位置: '${cell}'。正しい形式: A1, B2, AA10など`); } } function validateRangeAddress(range: string): void { const rangePattern = /^[A-Z]+[1-9]\d*:[A-Z]+[1-9]\d*$/; if (!rangePattern.test(range)) { throw new Error(`無効な範囲指定: '${range}'。正しい形式: A1:C3, B2:D10など`); } } function getSheetNames(workbook: ExcelJS.Workbook): string { const sheetNames: string[] = []; workbook.eachSheet((worksheet) => { sheetNames.push(worksheet.name); }); return sheetNames.join(', '); } const server = new Server( { name: "excel-mcp-server", version: "0.1.0", }, { capabilities: { tools: {}, }, } ); // ワークブック作成ツール async function createWorkbook(filePath: string): Promise<string> { try { validateFilePath(filePath); // 空のワークブックを作成(シートは含まれない) const workbook = new ExcelJS.Workbook(); await workbook.xlsx.writeFile(filePath); return `Excelワークブック '${filePath}' を作成しました。注意: このワークブックはシートを含んでいません。データを操作する前に、add_worksheetツールを使用してワークシートを追加してください。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `ワークブック作成エラー: ${error}`); } } // ワークブック情報取得 async function getWorkbookInfo(filePath: string): Promise<string> { try { validateFilePath(filePath); // ファイル存在確認 try { await fs.access(filePath); } catch { throw new Error(`ファイルが見つかりません: ${filePath}`); } const workbook = new ExcelJS.Workbook(); await workbook.xlsx.readFile(filePath); const info = { ファイルパス: filePath, ワークシート数: workbook.worksheets.length, ワークシート名一覧: workbook.worksheets.map(sheet => sheet.name), 作成者: workbook.creator || "不明", 最終更新者: workbook.lastModifiedBy || "不明", 作成日時: workbook.created ? workbook.created.toISOString() : "不明", 最終更新日時: workbook.modified ? workbook.modified.toISOString() : "不明" }; return `ワークブック情報:\n${JSON.stringify(info, null, 2)}`; } catch (error) { throw new McpError(ErrorCode.InternalError, `ワークブック情報取得エラー: ${error}`); } } // ワークブック読み込み async function loadWorkbook(filePath: string): Promise<ExcelJS.Workbook> { try { validateFilePath(filePath); const workbook = new ExcelJS.Workbook(); await workbook.xlsx.readFile(filePath); return workbook; } catch (error) { throw new McpError(ErrorCode.InternalError, `ワークブック読み込みエラー: ${error}`); } } // ワークシート追加 async function addWorksheet(filePath: string, sheetName: string): Promise<string> { try { if (!sheetName || sheetName.trim() === '') { throw new Error("ワークシート名が空です"); } const workbook = await loadWorkbook(filePath); // 既存のシート名と重複チェック const existingSheet = workbook.getWorksheet(sheetName); if (existingSheet) { throw new Error(`ワークシート '${sheetName}' は既に存在します`); } workbook.addWorksheet(sheetName); await workbook.xlsx.writeFile(filePath); return `ワークシート '${sheetName}' を追加しました。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `ワークシート追加エラー: ${error}`); } } // セル値設定 async function setCellValue(filePath: string, sheetName: string, cell: string, value: any): Promise<string> { try { validateCellAddress(cell); const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。利用可能なシート: ${getSheetNames(workbook)}`); } worksheet.getCell(cell).value = value; await workbook.xlsx.writeFile(filePath); return `セル ${cell} に値 '${value}' を設定しました。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `セル値設定エラー: ${error}`); } } // セル値取得 async function getCellValue(filePath: string, sheetName: string, cell: string): Promise<string> { try { validateCellAddress(cell); const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。利用可能なシート: ${getSheetNames(workbook)}`); } const cellValue = worksheet.getCell(cell).value; return `セル ${cell} の値: ${cellValue}`; } catch (error) { throw new McpError(ErrorCode.InternalError, `セル値取得エラー: ${error}`); } } // 範囲値設定 async function setRangeValues(filePath: string, sheetName: string, startCell: string, values: any[][]): Promise<string> { try { validateCellAddress(startCell); if (!Array.isArray(values) || values.length === 0) { throw new Error("valuesは空でない2次元配列である必要があります"); } // 2次元配列の検証 for (let i = 0; i < values.length; i++) { if (!Array.isArray(values[i])) { throw new Error(`${i+1}行目が配列ではありません。2次元配列を指定してください`); } } const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。利用可能なシート: ${getSheetNames(workbook)}`); } const startCellObj = worksheet.getCell(startCell); const startRow = startCellObj.row; const startCol = startCellObj.col; for (let i = 0; i < values.length; i++) { for (let j = 0; j < values[i].length; j++) { worksheet.getCell(startRow + i, startCol + j).value = values[i][j]; } } await workbook.xlsx.writeFile(filePath); return `範囲 ${startCell} から ${values.length}行 x ${values[0].length}列 のデータを設定しました。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `範囲値設定エラー: ${error}`); } } // 範囲値取得 async function getRangeValues(filePath: string, sheetName: string, range: string): Promise<string> { try { validateRangeAddress(range); const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。利用可能なシート: ${getSheetNames(workbook)}`); } const rangeCells = worksheet.getCell(range); const values: any[][] = []; // 指定範囲のデータを取得する正しい実装 const [startCell, endCell] = range.split(':'); const startCellObj = worksheet.getCell(startCell); const endCellObj = worksheet.getCell(endCell); const startRow = Number(startCellObj.row); const startCol = Number(startCellObj.col); const endRow = Number(endCellObj.row); const endCol = Number(endCellObj.col); for (let row = startRow; row <= endRow; row++) { const rowValues: any[] = []; for (let col = startCol; col <= endCol; col++) { rowValues.push(worksheet.getCell(row, col).value); } values.push(rowValues); } return `範囲 ${range} の値:\n${JSON.stringify(values, null, 2)}`; } catch (error) { throw new McpError(ErrorCode.InternalError, `範囲値取得エラー: ${error}`); } } // セル書式設定 async function formatCell(filePath: string, sheetName: string, cell: string, format: any): Promise<string> { try { const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。`); } const targetCell = worksheet.getCell(cell); // フォント設定 if (format.font) { const fontFormat: any = {}; if (format.font.bold !== undefined) fontFormat.bold = format.font.bold; if (format.font.size) fontFormat.size = format.font.size; if (format.font.color) { fontFormat.color = { argb: format.font.color }; } targetCell.font = fontFormat; } // 背景色設定 if (format.fill) { if (format.fill.type === 'pattern') { const fillFormat: any = { type: 'pattern', pattern: format.fill.pattern || 'solid' }; if (format.fill.fgColor) { fillFormat.fgColor = { argb: format.fill.fgColor }; } if (format.fill.bgColor) { fillFormat.bgColor = { argb: format.fill.bgColor }; } targetCell.fill = fillFormat; } else { // 簡単な背景色設定 targetCell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: format.fill.fgColor || format.fill } }; } } // 罫線設定 if (format.border) { targetCell.border = format.border; } await workbook.xlsx.writeFile(filePath); return `セル ${cell} の書式を設定しました。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `セル書式設定エラー: ${error}`); } } // 数式追加 async function addFormula(filePath: string, sheetName: string, cell: string, formula: string): Promise<string> { try { const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。`); } worksheet.getCell(cell).value = { formula: formula }; await workbook.xlsx.writeFile(filePath); return `セル ${cell} に数式 '${formula}' を設定しました。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `数式追加エラー: ${error}`); } } // データ検索 async function findData(filePath: string, sheetName: string, searchValue: any): Promise<string> { try { const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。`); } const results: string[] = []; worksheet.eachRow((row, rowNumber) => { row.eachCell((cell, colNumber) => { if (cell.value === searchValue) { const cellAddress = worksheet.getCell(rowNumber, colNumber).address; results.push(cellAddress); } }); }); return `値 '${searchValue}' が見つかったセル: ${results.join(', ')}`; } catch (error) { throw new McpError(ErrorCode.InternalError, `データ検索エラー: ${error}`); } } // CSV出力 async function exportToCSV(filePath: string, sheetName: string, csvPath: string): Promise<string> { try { const workbook = await loadWorkbook(filePath); const worksheet = workbook.getWorksheet(sheetName); if (!worksheet) { throw new Error(`ワークシート '${sheetName}' が見つかりません。`); } await workbook.csv.writeFile(csvPath, { sheetName }); return `ワークシート '${sheetName}' をCSVファイル '${csvPath}' にエクスポートしました。`; } catch (error) { throw new McpError(ErrorCode.InternalError, `CSV出力エラー: ${error}`); } } // ツールリストハンドラー(直接定義) server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "create_workbook", description: "新しいExcelワークブックを作成します。注意: 作成されるワークブックは空でシートを含みません。データを操作する前にadd_worksheetツールでワークシートを追加する必要があります。", inputSchema: zodToJsonSchema(CreateWorkbookSchema) }, { name: "get_workbook_info", description: "Excelワークブックの詳細情報を取得します(シート一覧、メタデータ等)", inputSchema: zodToJsonSchema(GetWorkbookInfoSchema) }, { name: "add_worksheet", description: "既存のワークブックにワークシートを追加します", inputSchema: zodToJsonSchema(AddWorksheetSchema) }, { name: "set_cell_value", description: "指定されたセルに値を設定します", inputSchema: zodToJsonSchema(SetCellValueSchema) }, { name: "get_cell_value", description: "指定されたセルの値を取得します", inputSchema: zodToJsonSchema(GetCellValueSchema) }, { name: "set_range_values", description: "指定された範囲に2次元配列のデータを設定します", inputSchema: zodToJsonSchema(SetRangeValuesSchema) }, { name: "get_range_values", description: "指定された範囲のデータを取得します", inputSchema: zodToJsonSchema(GetRangeValuesSchema) }, { name: "format_cell", description: "セルの書式(フォント、塗りつぶし、罫線)を設定します", inputSchema: zodToJsonSchema(FormatCellSchema) }, { name: "add_formula", description: "セルに数式を追加します", inputSchema: zodToJsonSchema(AddFormulaSchema) }, { name: "find_data", description: "ワークシート内で指定された値を検索します", inputSchema: zodToJsonSchema(FindDataSchema) }, { name: "export_to_csv", description: "ワークシートをCSVファイルにエクスポートします", inputSchema: zodToJsonSchema(ExportToCSVSchema) } ] }; }); // ツール実装関数のマップ const toolImplementations: { [key: string]: (...args: any[]) => Promise<string> } = { create_workbook: async (args: any) => { const { filePath } = CreateWorkbookSchema.parse(args); return await createWorkbook(filePath); }, get_workbook_info: async (args: any) => { const { filePath } = GetWorkbookInfoSchema.parse(args); return await getWorkbookInfo(filePath); }, add_worksheet: async (args: any) => { const { filePath, sheetName } = AddWorksheetSchema.parse(args); return await addWorksheet(filePath, sheetName); }, set_cell_value: async (args: any) => { const { filePath, sheetName, cell, value } = SetCellValueSchema.parse(args); return await setCellValue(filePath, sheetName, cell, value); }, get_cell_value: async (args: any) => { const { filePath, sheetName, cell } = GetCellValueSchema.parse(args); return await getCellValue(filePath, sheetName, cell); }, set_range_values: async (args: any) => { const { filePath, sheetName, startCell, values } = SetRangeValuesSchema.parse(args); return await setRangeValues(filePath, sheetName, startCell, values); }, get_range_values: async (args: any) => { const { filePath, sheetName, range } = GetRangeValuesSchema.parse(args); return await getRangeValues(filePath, sheetName, range); }, format_cell: async (args: any) => { const { filePath, sheetName, cell, format } = FormatCellSchema.parse(args); return await formatCell(filePath, sheetName, cell, format); }, add_formula: async (args: any) => { const { filePath, sheetName, cell, formula } = AddFormulaSchema.parse(args); return await addFormula(filePath, sheetName, cell, formula); }, find_data: async (args: any) => { const { filePath, sheetName, searchValue } = FindDataSchema.parse(args); return await findData(filePath, sheetName, searchValue); }, export_to_csv: async (args: any) => { const { filePath, sheetName, csvPath } = ExportToCSVSchema.parse(args); return await exportToCSV(filePath, sheetName, csvPath); } }; // ツール呼び出しハンドラー(自動化) server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const implementation = toolImplementations[name]; if (!implementation) { throw new McpError(ErrorCode.MethodNotFound, `未知のツール: ${name}`); } const result = await implementation(args); return { content: [{ type: "text", text: result }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessages = error.errors.map(e => { const field = e.path.join('.'); const message = e.message; const expectedValues = e.code === 'invalid_enum_value' ? `期待値: ${(e as any).options?.join(', ')}` : ''; return `${field}: ${message} ${expectedValues}`.trim(); }); throw new McpError( ErrorCode.InvalidParams, `引数エラー:\n${errorMessages.join('\n')}\n\n使用例を参考にしてください。` ); } if (error instanceof McpError) { throw error; } // その他のエラー throw new McpError( ErrorCode.InternalError, `予期しないエラー: ${error instanceof Error ? error.message : String(error)}` ); } }); // サーバー開始 async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); // 正常な起動メッセージは標準出力に出力 console.log("Excel MCP Server が開始されました"); } runServer().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SuperPyonchiX/excel_mcp_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server