#
tokens: 11872/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .dockerignore
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── .npmignore
├── dist
│   ├── handlers
│   │   ├── excelHandlers.js
│   │   └── logHandlers.js
│   ├── index.js
│   ├── tools
│   │   ├── cacheTools.js
│   │   ├── readTools.js
│   │   ├── structureTools.js
│   │   └── writeTools.js
│   ├── types
│   │   └── index.js
│   └── utils
│       ├── config.js
│       ├── excelUtils.js
│       ├── logCleaner.js
│       ├── utils.js
│       └── workbookCache.js
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README_CN.md
├── README.md
├── smithery.yaml
├── src
│   ├── handlers
│   │   ├── excelHandlers.ts
│   │   └── logHandlers.ts
│   ├── index.ts
│   ├── tools
│   │   ├── cacheTools.ts
│   │   ├── readTools.ts
│   │   ├── structureTools.ts
│   │   └── writeTools.ts
│   ├── types
│   │   └── index.ts
│   └── utils
│       ├── config.ts
│       ├── excelUtils.ts
│       ├── logCleaner.ts
│       ├── utils.ts
│       └── workbookCache.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Excel MCP Server
[![npm](https://img.shields.io/npm/v/@zhiweixu/excel-mcp-server)](https://www.npmjs.com/package/@zhiweixu/excel-mcp-server)
[![smithery badge](https://smithery.ai/badge/@zhiwei5576/excel-mcp-server)](https://smithery.ai/server/@zhiwei5576/excel-mcp-server)
[简体中文](./README_CN.md) | English

Excel file processing server based on Model Context Protocol (MCP), providing functionalities for reading, writing, and analyzing Excel files.

## Features

- 📖 Read Excel Files

  - Get worksheet list
  - Read specific worksheet data
  - Read all worksheets data

- ✍️ Write Excel Files

  - Create new Excel files
  - Write to specific worksheet
  - Support multiple worksheets

- 🔍 Analyze Excel Structure

  - Analyze worksheet structure
  - Export structure to new file

- 💾 Cache Management

  - Automatic file content caching
  - Scheduled cache cleanup
  - Manual cache clearing

- 📝 Log Management
  - Automatic operation logging
  - Periodic log cleanup

## Installation

### Installing via Smithery

To install excel-mcp-server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@zhiwei5576/excel-mcp-server):

```bash
npx -y @smithery/cli install @zhiwei5576/excel-mcp-server --client claude
```

### Installing Manually
Installing via NPM
excel-mcp-server can be automatically installed by adding the following configuration to the MCP servers configuration.

Windows Platform:

```bash
{
  "mcpServers": {
    "excel": {
        "command": "cmd",
        "args": ["/c", "npx", "--yes", "@zhiweixu/excel-mcp-server"],
        "env": {
            "LOG_PATH": "[set an accessible absolute path]",
            "CACHE_MAX_AGE": "1",
            "CACHE_CLEANUP_INTERVAL": "4",
            "LOG_RETENTION_DAYS": "7",
            "LOG_CLEANUP_INTERVAL": "24"
        }
    }
}
```

Other Platforms:

```bash
{
  "mcpServers": {
    "excel": {
        "command": "npx",
        "args": ["--yes", "@zhiweixu/excel-mcp-server"],
        "env": {
            "LOG_PATH": "[set an accessible absolute path]",
            "CACHE_MAX_AGE": "1",
            "CACHE_CLEANUP_INTERVAL": "4",
            "LOG_RETENTION_DAYS": "7",
            "LOG_CLEANUP_INTERVAL": "24"
        }
    }
}
```
Note: LOG_PATH is optional. If not set, logs will be stored in the 'logs' folder under the application root directory.other arguments are optional.

## API Tools

### Structure Tools

1. analyzeExcelStructure
   - Function: Get Excel file structure including sheet list and column headers in JSON format
   - Parameters:
     - fileAbsolutePath: Absolute path of the Excel file
     - headerRows: Number of header rows (default: 1)

2. exportExcelStructure
   - Function: Export Excel file structure (sheets and headers) to a new Excel template file
   - Parameters:
     - sourceFilePath: Source Excel file path
     - targetFilePath: Target Excel file path
     - headerRows: Number of header rows (default: 1)

### Read Tools

1. readSheetNames
   - Function: Get all sheet names from the Excel file
   - Parameters:
     - fileAbsolutePath: Absolute path of the Excel file

2. readDataBySheetName
   - Function: Get data from a specific sheet in the Excel file
   - Parameters:
     - fileAbsolutePath: Absolute path of the Excel file
     - sheetName: Name of the sheet to read
     - headerRow: Header row number (default: 1)
     - dataStartRow: Data start row number (default: 2)

3. readSheetData
   - Function: Get data from all sheets in the Excel file
   - Parameters:
     - fileAbsolutePath: Absolute path of the Excel file
     - headerRow: Header row number (default: 1)
     - dataStartRow: Data start row number (default: 2)

### Write Tools

1. writeDataBySheetName
   - Function: Write data to a specific sheet in the Excel file (overwrites if sheet exists)
   - Parameters:
     - fileAbsolutePath: Absolute path of the Excel file
     - sheetName: Name of the sheet to write
     - data: Array of data to write

2. writeSheetData
   - Function: Create a new Excel file with provided data
   - Parameters:
     - fileAbsolutePath: Absolute path for the new Excel file
     - data: Object containing multiple sheet data

### Cache Tools

1. clearFileCache
   - Function: Clear cached data for the specified Excel file
   - Parameters:
     - fileAbsolutePath: Absolute path of the Excel file to clear from cache

## Configuration

### Environment Variables

- `LOG_PATH`: Log files storage path
  - Optional
  - Default: 'logs' folder under application root directory

- `CACHE_MAX_AGE`: Cache expiration time (hours)
  - Optional
  - Default: 1

- `CACHE_CLEANUP_INTERVAL`: Cache cleanup interval (hours)
  - Optional
  - Default: 4

- `LOG_RETENTION_DAYS`: Log retention days
  - Optional
  - Default: 7

- `LOG_CLEANUP_INTERVAL`: Log cleanup interval (hours)
  - Optional
  - Default: 24

### Default Configuration

- Cache Configuration
  - Cache expiration time: 1 hour
  - Cache cleanup interval: 4 hours

- Log Configuration
  - Log retention days: 7 days
  - Cleanup interval: 24 hours

## Dependencies

- @modelcontextprotocol/sdk: ^1.7.0
- xlsx: ^0.18.5
- typescript: ^5.8.2

## Development Dependencies

- @types/node: ^22.13.10
- nodemon: ^3.1.9
- ts-node: ^10.9.2

## License

This project is licensed under the MIT License. This means you are free to:

- Use the software for commercial or non-commercial purposes
- Modify the source code
- Distribute original or modified code
  Requirements:

- Retain the original copyright notice
- No liability can be claimed against the authors for software use
  For detailed license information,please see the [LICENSE](./LICENSE) file.

```

--------------------------------------------------------------------------------
/src/handlers/logHandlers.ts:
--------------------------------------------------------------------------------

```typescript
import { LogCleaner } from "../utils/logCleaner.js";


export function initializeLogger() {
    const logCleaner = new LogCleaner();
    logCleaner.start();
    
    return logCleaner;
}
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# 构建阶段
FROM node:18 AS builder

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

RUN npm run build

# 生产阶段
FROM node:18-slim

WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY package*.json ./

RUN npm install --production

EXPOSE 3000

CMD ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required: []
    properties:
      logPath:
        type: string
        description: Absolute path for log storage. Optional. If not provided, logs will
          be stored in the './logs' folder.
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.js'],
      env: config.logPath ? { LOG_PATH: config.logPath } : {}
    })
  exampleConfig:
    logPath: /var/log/excel-mcp-server

```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
name: Publish Package

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          registry-url: 'https://registry.npmjs.org'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build
        run: npm run build
        
      - name: Publish to NPM
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          generate_release_notes: true
          draft: false
          prerelease: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import packageJson from '../package.json' with { type: "json" };
import { structureTools } from "./tools/structureTools.js";
import { writeTools } from "./tools/writeTools.js";
import { readTools } from "./tools/readTools.js";
import { cacheTools } from "./tools/cacheTools.js";
import { initializeLogger } from "./handlers/logHandlers.js";

// Create an MCP server
const server = new McpServer({
  name: "excel-mcp-server",
  version: packageJson.version
});

// 初始化日志清理器
initializeLogger();

// 注册工具
structureTools(server);
readTools(server);
writeTools(server);
cacheTools(server);   


// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@zhiweixu/excel-mcp-server",
  "version": "0.0.5",
  "type": "module",
  "main": "index.js",
  "bin": {
    "excel-mcp-server": "./dist/index.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/zhiwei5576/excel-mcp-server.git"
  },
  "files": [
    "dist",
    "README_CN.md"
  ],
  "scripts": {
    "start": "node dist/index.js",
    "dev": "nodemon src/index.ts",
    "build": "tsc",
    "watch": "tsc -w"
  },
  "keywords": [],
  "author": "zhiweixu",
  "license": "MIT",
  "description": "Excel file processing server based on Model Context Protocol (MCP)",
  "devDependencies": {
    "@types/node": "^22.13.10",
    "nodemon": "^3.1.9",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "xlsx": "^0.18.5"
  },
  "publishConfig": {
    "access": "public"
  },
  "volta": {
    "node": "18.20.6",
    "npm": "10.8.2"
  }
}

```

--------------------------------------------------------------------------------
/src/utils/logCleaner.ts:
--------------------------------------------------------------------------------

```typescript
import fs from 'fs';
import path from 'path';
import { getLogPath, getConfig } from './config.js';

export class LogCleaner {
    private logDir: string;
    private retentionDays: number;
    private interval: NodeJS.Timeout | null = null;

    constructor() {
        const config = getConfig();
        this.logDir = getLogPath();
        this.retentionDays = config.log.retentionDays;
    }

    start() {
        const config = getConfig();
        // 立即执行一次清理
        this.cleanOldLogs();
        // 设置定时任务
        this.interval = setInterval(() => {
            this.cleanOldLogs();
        }, config.log.cleanupInterval * 60 * 60 * 1000);
    }

    stop() {
        if (this.interval) {
            clearInterval(this.interval);
        }
    }

    private cleanOldLogs() {
        try {
            const files = fs.readdirSync(this.logDir);
            const now = new Date();

            files.forEach(file => {
                if (file.endsWith('.log')) {
                    const filePath = path.join(this.logDir, file);
                    const stats = fs.statSync(filePath);
                    const fileAge = (now.getTime() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);

                    if (fileAge > this.retentionDays) {
                        fs.unlinkSync(filePath);
                    }
                }
            });
        } catch (error) {
            console.error('Error cleaning log files:', error);
        }
    }
}
```

--------------------------------------------------------------------------------
/src/utils/config.ts:
--------------------------------------------------------------------------------

```typescript
import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';
import type { Config } from '../types/index.js';


const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export function getConfig(): Config {
    return {
        logPath: process.env.LOG_PATH || '',
        cache: {
            maxAge: Number(process.env.CACHE_MAX_AGE) || 1,    // 默认1小时
            cleanupInterval: Number(process.env.CACHE_CLEANUP_INTERVAL) || 4, // 默认4小时
        },
        log: {
            retentionDays: Number(process.env.LOG_RETENTION_DAYS) || 7,    // 默认7天
            cleanupInterval: Number(process.env.LOG_CLEANUP_INTERVAL) || 24 // 默认24小时
        }
    };
}

export function getLogPath(): string {
    const config = getConfig();
    let logPath: string;

    if (config.logPath) {
        try {
            //在指定目录下创建子目录excel-mcp-server-logs
            const subDir = 'excel-mcp-server-logs';
            const subDirPath = path.join(config.logPath, subDir);
            if (!fs.existsSync(subDirPath)) {
                fs.mkdirSync(subDirPath, { recursive: true });
            }
            fs.accessSync(subDirPath);
            logPath = subDirPath;
        } catch (error) {
            console.log(`LOG_PATH environment variable specifies an invalid or inaccessible path: ${config.logPath}, using default path instead.`);
            logPath = path.join(__dirname, '../logs');
            // 检查默认目录是否存在,不存在则创建
            if (!fs.existsSync(logPath)) {
                fs.mkdirSync(logPath, { recursive: true });
            }
        }
    } else {
        logPath = path.join(__dirname, '../logs');
        // 检查默认目录是否存在,不存在则创建
        if (!fs.existsSync(logPath)) {
            fs.mkdirSync(logPath, { recursive: true });
        }
    }

    return logPath;
}
```

--------------------------------------------------------------------------------
/src/utils/workbookCache.ts:
--------------------------------------------------------------------------------

```typescript
import type { WorkBook } from 'xlsx'
import type { EnsureWorkbookResult } from '../types/index.js'

interface CacheItem {
  workbook: WorkBook;
  timestamp: number;
}

import { getConfig } from './config.js';

class WorkbookCache {
  private cache = new Map<string, CacheItem>();
  private readonly maxAge: number;
  private readonly cleanupInterval: number;

  constructor() {
    const config = getConfig();
    // 将小时转换为毫秒
    this.maxAge = config.cache.maxAge * 60 * 60 * 1000;
    this.cleanupInterval = config.cache.cleanupInterval * 60 * 60 * 1000;

    // 创建定时器,定期清理过期缓存
    setInterval(() => {
      const now = Date.now();
      for (const [key, item] of this.cache.entries()) {
        if (now - item.timestamp > this.maxAge) {
          this.delete(key);
        }
      }
    }, this.cleanupInterval);
  }

  set(filePathWithName: string, workbook: WorkBook): void {
    this.cache.set(filePathWithName, {
      workbook,
      timestamp: Date.now()
    });
  }

  get(filePathWithName: string): WorkBook | undefined {
    const item = this.cache.get(filePathWithName);
    if (!item) return undefined;
    
    // 检查是否过期
    if (Date.now() - item.timestamp > this.maxAge) {
      this.delete(filePathWithName);
      return undefined;
    }
    
    return item.workbook;
  }

  ensureWorkbook(filePathWithName: string): EnsureWorkbookResult {
    const workbook = this.get(filePathWithName);
    if (!workbook) {
      return {
        success: false
      };
    }
    return {
      success: true,
      data: workbook
    };
  }
  // 删除工作簿
  delete(filePathWithName: string): boolean {
    if (!this.cache.has(filePathWithName)) return false
    return this.cache.delete(filePathWithName)
  }

  // 清空缓存
  clear(): void {
    this.cache.clear()
  }

  // 检查是否存在
  has(filePathWithName: string): boolean {
    return this.cache.has(filePathWithName)
  }
}

export const workbookCache = new WorkbookCache();
```

--------------------------------------------------------------------------------
/src/tools/cacheTools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { fileExists, normalizePath } from "../utils/utils.js";
import { workbookCache } from "../utils/workbookCache.js";
export const cacheTools = (server: any) => {
    server.tool("clearFileCache", 'Clear cached data for the specified Excel file',
        {
            fileAbsolutePath: z.string().describe("The absolute path of the Excel file to clear from cache")
        },
        async (params: { fileAbsolutePath: string }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid file path: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                const deleted = workbookCache.delete(normalizedPath);

                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            success: true,
                            message: deleted
                                ? `Cache cleared successfully for file: ${normalizedPath}`
                                : `No cache found for file: ${normalizedPath}`
                        })
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to clear cache: ${error}`,
                            suggestion: "Please verify the file path"
                        })
                    }]
                };
            }
        }
    );

}


```

--------------------------------------------------------------------------------
/src/utils/utils.ts:
--------------------------------------------------------------------------------

```typescript
import * as fs from 'fs/promises';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { getLogPath } from './config.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

function formatDate(date: Date): string {
    return date.toLocaleString('zh-CN', {
        timeZone: 'Asia/Shanghai',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false
    });
}

export async function logToFile(message: string) {
    try {
        const logDir = getLogPath();
        const date = new Date();
        const fileName = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}.log`;
        const logPath = path.join(logDir, fileName);

        await fs.appendFile(logPath, `${formatDate(date)} - ${message}\n`);
    } catch (error) {
        console.error('log to file failure:', error);
    }
}
export async function normalizePath(filePathWithName: string): Promise<string> {
    try {
        // 对路径进行编码处理
        const encodedPath = encodeURIComponent(filePathWithName)
            .replace(/%2F/g, '/') // 保留路径分隔符
            .replace(/%20/g, ' '); // 保留空格

        // 解码路径,确保中文字符正确显示
        const decodedPath = decodeURIComponent(encodedPath);

        return decodedPath.trim();
        // return normalizedPath;
    } catch (error) {
        await logToFile(`normalize path failure: ${error}`);
        return 'error';
    }
}

//confirm file is exist
export async function fileExists(filePathWithName: string): Promise<boolean> {
    try {
        const fs = await import('fs');
        if (!filePathWithName) {
            await logToFile("path is null");
            return false;
        }
        // 尝试规范化路径
        const fileExists = fs.existsSync(filePathWithName);
        if (!fileExists) {
            await logToFile(`file is not exist: ${fileExists}`);
            return false;
        }
        return true;
    } catch (error) {
        await logToFile(`file is not exist: ${error}`);
        return false;
    }
}
```

--------------------------------------------------------------------------------
/src/utils/excelUtils.ts:
--------------------------------------------------------------------------------

```typescript

import * as fs from 'fs'
import * as XLSX from 'xlsx'
import { workbookCache } from './workbookCache.js'
import { logToFile } from './utils.js';
import type { ReadSheetNamesResult } from '../types/index.js';

const READ_TIMEOUT = 300000;
export async function readAndCacheFile(filePathWithName: string): Promise<ReadSheetNamesResult> {
    try {
        const timeout = new Promise((_, reject) => {
            setTimeout(() => reject(new Error('File reading timeout')), READ_TIMEOUT);
        });

        const readOperation = new Promise<ReadSheetNamesResult>(async (resolve, reject) => {
            try {
                // Read file in chunks
                const fileStream = fs.createReadStream(filePathWithName, {
                    highWaterMark: 1024 * 1024 // 1MB chunks
                });

                const chunks: Buffer[] = [];

                fileStream.on('data', (chunk: string | Buffer) => {
                    if (Buffer.isBuffer(chunk)) {
                        chunks.push(chunk);
                    } else {
                        chunks.push(Buffer.from(chunk));
                    }
                });

                fileStream.on('end', () => {
                    const buffer = Buffer.concat(chunks);
                    const workbook = XLSX.read(buffer, {
                        type: 'buffer',
                        cellDates: true,
                        cellNF: false,
                        cellText: false,
                    });
                    workbookCache.set(filePathWithName, workbook);
                    resolve({
                        success: true,
                        data: {
                            SheetNames: workbook.SheetNames,
                            errors: ''
                        }
                    });
                });

                fileStream.on('error', (error) => {
                    reject(error);
                });
            } catch (error) {
                reject(error);
            }
        });

        const result = await Promise.race([readOperation, timeout]);
        return result as ReadSheetNamesResult;

    } catch (bufferError) {
        await logToFile(`[read-and-cache-file] Buffer read failure: ${bufferError}`);
        return {
            success: false,
            data: {
                SheetNames: [],
                errors: JSON.stringify(bufferError)
            }
        };
    }
}
```

--------------------------------------------------------------------------------
/src/tools/structureTools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { fileExists, normalizePath } from "../utils/utils.js";
import { analyzeExcelStructure, exportExcelStructure } from '../handlers/excelHandlers.js'

export const structureTools = (server: any) => {
    server.tool("analyzeExcelStructure", 'Get Excel file structure including sheet list and column headers in JSON format',
        {
            fileAbsolutePath: z.string().describe("The absolute path of the Excel file"),
            headerRows: z.number().default(1).describe("Number of header rows to read (default: 1)")
        },
        async (params: {
            fileAbsolutePath: string;
            headerRows: number;
        }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid file path: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                if (!(await fileExists(normalizedPath))) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `File not found: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                const result = await analyzeExcelStructure(normalizedPath, params.headerRows);

                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify(result)
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to get Excel structure: ${error}`,
                            suggestion: "Please verify all parameters"
                        })
                    }]
                };
            }
        }
    );
    server.tool("exportExcelStructure", 'Export Excel file structure (sheets and headers) to a new Excel template file',
        {
            sourceFilePath: z.string().describe("The source Excel file path to analyze"),
            targetFilePath: z.string().describe("The target Excel file path to save structure"),
            headerRows: z.number().default(1).describe("Number of header rows to analyze (default: 1)")
        },
        async (params: {
            sourceFilePath: string;
            targetFilePath: string;
            headerRows: number;
        }) => {
            try {
                // 验证源文件路径
                const normalizedSourcePath = await normalizePath(params.sourceFilePath);
                if (normalizedSourcePath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid source file path: ${params.sourceFilePath}`,
                                suggestion: "Please verify the source file path"
                            })
                        }]
                    };
                }

                // 验证源文件是否存在
                if (!(await fileExists(normalizedSourcePath))) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Source file not found: ${params.sourceFilePath}`,
                                suggestion: "Please verify the source file exists"
                            })
                        }]
                    };
                }

                // 验证目标文件路径
                const normalizedTargetPath = await normalizePath(params.targetFilePath);
                if (normalizedTargetPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid target file path: ${params.targetFilePath}`,
                                suggestion: "Please verify the target file path"
                            })
                        }]
                    };
                }

                // 验证目标文件是否已存在
                if (await fileExists(normalizedTargetPath)) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Target file already exists: ${params.targetFilePath}`,
                                suggestion: "Please specify a different target file path"
                            })
                        }]
                    };
                }

                // 导出结构
                await exportExcelStructure(normalizedSourcePath, normalizedTargetPath, params.headerRows);

                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            success: true,
                            message: `Excel structure exported successfully to: ${normalizedTargetPath}`
                        })
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to export Excel structure: ${error}`,
                            suggestion: "Please verify all parameters and try again"
                        })
                    }]
                };
            }
        }
    );
}


```

--------------------------------------------------------------------------------
/src/tools/writeTools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { fileExists, normalizePath } from "../utils/utils.js";
import { writeDataBySheetName, writeSheetData } from '../handlers/excelHandlers.js'

export const writeTools = (server: any) => {
    server.tool("writeDataBySheetName", 'Write data to a specific sheet in the Excel file (overwrites if sheet exists)',
        {
            fileAbsolutePath: z.string().describe("The absolute path of the Excel file"),
            sheetName: z.string().describe("The name of the sheet to write"),
            data: z.array(
                z.record(
                    z.string(),
                    z.any()
                )
            ).describe("Array of objects to write to the sheet")
        },
        async (params: {
            fileAbsolutePath: string;
            sheetName: string;
            data: Record<string, any>[];
        }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid file path: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                // 校验数据结构
                if (!Array.isArray(params.data) || params.data.length === 0) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: "Empty data array provided",
                                suggestion: "Please provide non-empty array of data"
                            })
                        }]
                    };
                }

                // 校验工作表名称
                if (!params.sheetName) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: "Invalid sheet name",
                                suggestion: "Please provide a valid sheet name"
                            })
                        }]
                    };
                }

                await writeDataBySheetName(normalizedPath, params.sheetName, params.data);
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            success: true,
                            message: `Data written successfully to sheet '${params.sheetName}' in file: ${normalizedPath}`
                        })
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to write sheet data: ${error}`,
                            suggestion: "Please verify all parameters and try again"
                        })
                    }]
                };
            }
        }
    );
    server.tool("writeSheetData", 'Create a new Excel file with provided data',
        {
            fileAbsolutePath: z.string().describe("The absolute path for the new Excel file"),
            data: z.record(
                z.string(), // 表名(动态)
                z.array(    // 表数据数组
                    z.record( // 每行数据对象
                        z.string(), // 字段名(动态)
                        z.any()     // 字段值(任意类型)
                    )
                )
            ).describe("Data object with dynamic sheet names and column names")
        },
        async (params: {
            fileAbsolutePath: string;
            data: Record<string, Record<string, any>[]>;
        }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid file path: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                // 校验数据结构
                if (Object.keys(params.data).length === 0) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: "Empty data object provided",
                                suggestion: "Please provide at least one sheet with data"
                            })
                        }]
                    };
                }

                // 校验每个表的数据
                for (const [sheetName, sheetData] of Object.entries(params.data)) {
                    if (!Array.isArray(sheetData) || sheetData.length === 0) {
                        return {
                            content: [{
                                type: "text",
                                text: JSON.stringify({
                                    error: `Invalid data format in sheet "${sheetName}": Data must be a non-empty array`,
                                    suggestion: "Please check the data format of each sheet"
                                })
                            }]
                        };
                    }
                }

                if (await fileExists(normalizedPath)) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `File already exists: ${params.fileAbsolutePath}`,
                                suggestion: "Please specify a different file path"
                            })
                        }]
                    };
                }

                await writeSheetData(normalizedPath, params.data);
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            success: true,
                            message: `Excel file created successfully: ${normalizedPath}`
                        })
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to write Excel data: ${error}`,
                            suggestion: "Please verify the data format and file path"
                        })
                    }]
                };
            }
        }
    );
}


```

--------------------------------------------------------------------------------
/src/tools/readTools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { fileExists, normalizePath } from "../utils/utils.js";
import { readAllSheetData, readDataBySheetName, readSheetNames } from '../handlers/excelHandlers.js'

export const readTools = (server: any) => {
    server.tool("readSheetNames", 'Get all sheet names from the Excel file',
        {
            fileAbsolutePath: z.string().describe("The absolute path of the Excel file")
        },
        async (params: { fileAbsolutePath: string }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `path is not valid: ${params.fileAbsolutePath}`,
                                suggestion: "please check the path and filename"
                            })
                        }]
                    };
                }
                if (!(await fileExists(normalizedPath))) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `file is not exist: ${params.fileAbsolutePath}`,
                                suggestion: "please check the path and filename"
                            })
                        }]
                    };

                }
                const result = await readSheetNames(normalizedPath);
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify(result)
                    }]
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `read sheet names failure: ${error}`,
                            suggestion: "please check the path and filename"
                        })
                    }]
                };
            }

        }
    );

    server.tool("readDataBySheetName", 'Get data from a specific sheet in the Excel file',
        {
            fileAbsolutePath: z.string().describe("The absolute path of the Excel file"),
            sheetName: z.string().describe("tThe name of the sheet to read"),
            headerRow: z.number().default(1).describe("tThe row number to use as field names (default: 1)"),
            dataStartRow: z.number().default(2).describe("The row number to start reading data from (default: 2)")
        },
        async (params: {
            fileAbsolutePath: string,
            sheetName: string,
            headerRow: number,
            dataStartRow: number
        }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid file path: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }
                if (!(await fileExists(normalizedPath))) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `File not exist: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };

                }
                if (!params.sheetName) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid sheet name: ${params.sheetName}`,
                                suggestion: "Please verify the sheet name"
                            })
                        }]
                    };

                }
                const result = await readDataBySheetName(normalizedPath, params.sheetName, params.headerRow, params.dataStartRow);
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify(result)
                    }]
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to read data from sheet: ${params.sheetName} failure: ${error}`,
                            suggestion: "Please verify all parameters"
                        })
                    }]
                };
            }

        }
    );
    server.tool("readSheetData", 'Get data from all sheets in the Excel file',
        {
            fileAbsolutePath: z.string().describe("The absolute path of the Excel file"),
            headerRow: z.number().default(1).describe("The row number to use as field names (default: 1)"),
            dataStartRow: z.number().default(2).describe("The row number to start reading data from (default: 2)")
        },
        async (params: { fileAbsolutePath: string, headerRow: number, dataStartRow: number }) => {
            try {
                const normalizedPath = await normalizePath(params.fileAbsolutePath);
                if (normalizedPath === 'error') {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `Invalid file path: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                if (!(await fileExists(normalizedPath))) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: `File not found: ${params.fileAbsolutePath}`,
                                suggestion: "Please verify the file path and name"
                            })
                        }]
                    };
                }

                const result = await readAllSheetData(
                    normalizedPath,
                    params.headerRow,
                    params.dataStartRow
                );

                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify(result)
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify({
                            error: `Failed to read Excel data: ${error}`,
                            suggestion: "Please verify all parameters"
                        })
                    }]
                };
            }
        }
    );
}


```

--------------------------------------------------------------------------------
/src/handlers/excelHandlers.ts:
--------------------------------------------------------------------------------

```typescript
import { workbookCache } from "../utils/workbookCache.js";
import * as XLSX from 'xlsx'
import { logToFile,fileExists } from '../utils/utils.js';
import { readAndCacheFile } from "../utils/excelUtils.js";
import type { EnsureWorkbookResult, ReadSheetNamesResult, SheetData ,ExcelStructure} from '../types/index.js';
export async function readSheetNames(filePathWithName: string): Promise<string[]> {


    try {
        //从缓存中获取workbook
        const workbookResult: EnsureWorkbookResult = workbookCache.ensureWorkbook(filePathWithName);

        if (!workbookResult.success) {
            // 缓存中没有workbook,尝试读取并缓存文件
            const readResult: ReadSheetNamesResult = await readAndCacheFile(filePathWithName);
            if (!readResult.success) {
                // 读取文件失败,返回错误信息
                throw new Error(`read file failure: ${readResult.data.errors}`);
            } else {
                return readResult.data.SheetNames;
            }

        } else {
            const workbook = workbookResult.data as XLSX.WorkBook;
            return workbook.SheetNames
        }

    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`read file failure: ${errorMessage}`);
    }
}

export async function readDataBySheetName(
    filePathWithName: string,
    sheetName: string,
    headerRow: number = 1,  // 默认第一行为表头
    dataStartRow: number = 2  // 默认第二行开始为数据
): Promise<any> {
    try {
        const workbookResult: EnsureWorkbookResult = workbookCache.ensureWorkbook(filePathWithName);
        let workbook: XLSX.WorkBook;

        if (!workbookResult.success) {
            const readResult: ReadSheetNamesResult = await readAndCacheFile(filePathWithName);
            if (!readResult.success) {
                throw new Error(`read file failure: ${readResult.data.errors}`);
            }
            workbook = workbookCache.get(filePathWithName)!;
        } else {
            workbook = workbookResult.data as XLSX.WorkBook;
        }

        const worksheet = workbook.Sheets[sheetName];
        if (!worksheet) {
            throw new Error(`sheet ${sheetName} not found`);
        }

        // 将 Excel 行号转换为数组索引(减1)
        const headerIndex = headerRow - 1;
        const dataStartIndex = dataStartRow - 1;

        // 获取原始数据
        const rawData = XLSX.utils.sheet_to_json(worksheet, {
            raw: true,
            defval: '',
            header: 1
        });

        if (rawData.length <= headerIndex) {
            throw new Error(`sheet is empty or header row (${headerRow}) exceeds sheet length`);
        }

        // 获取表头
        const headers = rawData[headerIndex] as string[];

        // 验证数据起始行
        if (rawData.length <= dataStartIndex) {
            throw new Error(`data start row (${dataStartRow}) exceeds sheet length`);
        }

        // 处理数据行
        const dataRows = rawData.slice(dataStartIndex);
        const result = dataRows.map((row: any) => {
            const item: { [key: string]: any } = {};
            headers.forEach((header, index) => {
                if (header) {
                    item[header] = row[index] || '';
                }
            });
            return item;
        });

        return result;

    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`read sheet data failure: ${errorMessage}`);
    }
}

export async function readAllSheetData(
    filePathWithName: string,
    headerRow: number = 1,
    dataStartRow: number = 2
): Promise<{ [sheetName: string]: any[] }> {
    try {
        const workbookResult: EnsureWorkbookResult = workbookCache.ensureWorkbook(filePathWithName);
        let workbook: XLSX.WorkBook;

        if (!workbookResult.success) {
            const readResult: ReadSheetNamesResult = await readAndCacheFile(filePathWithName);
            if (!readResult.success) {
                throw new Error(`Failed to read file: ${readResult.data.errors}`);
            }
            workbook = workbookCache.get(filePathWithName)!;
        } else {
            workbook = workbookResult.data as XLSX.WorkBook;
        }

        const result: { [sheetName: string]: any[] } = {};
        const headerIndex = headerRow - 1;
        const dataStartIndex = dataStartRow - 1;

        // 遍历所有工作表
        for (const sheetName of workbook.SheetNames) {
            const worksheet = workbook.Sheets[sheetName];

            // 获取原始数据
            const rawData = XLSX.utils.sheet_to_json(worksheet, {
                raw: true,
                defval: '',
                header: 1
            });

            if (rawData.length <= headerIndex) {
                await logToFile(`Sheet ${sheetName} is empty or header row (${headerRow}) exceeds sheet length`);
                continue;
            }

            // 获取表头
            const headers = rawData[headerIndex] as string[];

            // 验证数据起始行
            if (rawData.length <= dataStartIndex) {
                await logToFile(`Sheet ${sheetName} data start row (${dataStartRow}) exceeds sheet length`);
                continue;
            }

            // 处理数据行
            const dataRows = rawData.slice(dataStartIndex);
            result[sheetName] = dataRows.map((row: any) => {
                const item: { [key: string]: any } = {};
                headers.forEach((header, index) => {
                    if (header) {
                        item[header] = row[index] || '';
                    }
                });
                return item;
            });
        }

        return result;

    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`Failed to read Excel data: ${errorMessage}`);
    }
}


export async function writeSheetData(
    filePathWithName: string,
    data: SheetData
): Promise<boolean> {
    try {
        // 创建新的工作簿
        const workbook = XLSX.utils.book_new();

        // 遍历数据对象的每个工作表
        for (const [sheetName, sheetData] of Object.entries(data)) {
            // 将数据转换为工作表
            const worksheet = XLSX.utils.json_to_sheet(sheetData);

            // 将工作表添加到工作簿
            XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
        }

        // 写入文件
        XLSX.writeFile(workbook, filePathWithName);

        return true;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`Failed to write Excel data: ${errorMessage}`);
    }
}

export async function writeDataBySheetName(
    filePathWithName: string,
    sheetName: string,
    data: any[]
): Promise<boolean> {
    try {
        let workbook: XLSX.WorkBook;


        // 检查文件是否存在,注:filePathWithName ,已经经过了normalizePath
        if (await fileExists(filePathWithName)) {
            // 如果文件存在,读取现有工作簿
            const workbookResult = workbookCache.ensureWorkbook(filePathWithName);
            if (!workbookResult.success) {
                const readResult = await readAndCacheFile(filePathWithName);
                if (!readResult.success) {
                    throw new Error(`Failed to read existing file: ${readResult.data.errors}`);
                }
                workbook = workbookCache.get(filePathWithName)!;
            } else {
                workbook = workbookResult.data as XLSX.WorkBook;
            }
        } else {
            // 如果文件不存在,创建新的工作簿
            workbook = XLSX.utils.book_new();
        }

        // 将数据转换为工作表
        const worksheet = XLSX.utils.json_to_sheet(data);

        // 检查工作表是否已存在
        if (workbook.SheetNames.includes(sheetName)) {
            // 如果存在,删除旧的工作表
            const index = workbook.SheetNames.indexOf(sheetName);
            workbook.SheetNames.splice(index, 1);
            delete workbook.Sheets[sheetName];
        }

        // 添加新的工作表
        XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);

        // 写入文件
        XLSX.writeFile(workbook, filePathWithName);

        // 更新缓存
        workbookCache.set(filePathWithName, workbook);

        return true;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`Failed to write sheet data: ${errorMessage}`);
    }
}

export async function analyzeExcelStructure(
    filePathWithName: string,
    headerRows: number = 1
): Promise<ExcelStructure> {
    try {
        const workbookResult: EnsureWorkbookResult = workbookCache.ensureWorkbook(filePathWithName);
        let workbook: XLSX.WorkBook;

        if (!workbookResult.success) {
            const readResult = await readAndCacheFile(filePathWithName);
            if (!readResult.success) {
                throw new Error(`Failed to read file: ${readResult.data.errors}`);
            }
            workbook = workbookCache.get(filePathWithName)!;
        } else {
            workbook = workbookResult.data as XLSX.WorkBook;
        }

        const result: ExcelStructure = {
            sheetList: [],
            sheetField: []
        };

        result.sheetList = workbook.SheetNames.map((sheetName, index) => ({
            SheetNo: index + 1,  // 添加从1开始的序号
            SheetName: sheetName
        }));
        // 遍历所有工作表
        for (const sheetName of workbook.SheetNames) {
            const worksheet = workbook.Sheets[sheetName];
            
            // 获取原始数据
            const rawData = XLSX.utils.sheet_to_json(worksheet, {
                raw: true,
                defval: '',
                header: 1
            });

            if (rawData.length === 0) {
                continue;
            }
            // 获取每列的数据
            const columnCount = (rawData[0] as any[]).length;
            for (let colIndex = 0; colIndex < columnCount; colIndex++) {
                const fieldInfo: any = {
                    SheetName: sheetName
                };

                // 根据 headerRows 获取指定数量的表头行
                for (let i = 1; i <= headerRows; i++) {
                    const headerIndex = i - 1;
                    if (rawData.length > headerIndex) {
                        const rowData = rawData[headerIndex] as any[];
                        fieldInfo[`Field${i}`] = rowData[colIndex] || '';
                    } else {
                        fieldInfo[`Field${i}`] = '';
                    }
                }

                result.sheetField = result.sheetField || [];
                result.sheetField.push(fieldInfo);
            }
        }

        return  result
        // {
        //     // 修改 sheetList 的映射,添加 SheetNo
        //     sheetList: workbook.SheetNames.map((sheetName, index) => ({ 
        //         SheetNo: index + 1,  // 添加从1开始的序号
        //         SheetName: sheetName 
        //     })),
        //     sheetField: result.sheetField || []
        // };

    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`Failed to get Excel structure: ${errorMessage}`);
    }
}

export async function exportExcelStructure(
    sourceFilePath: string,
    targetFilePath: string,
    headerRows: number = 1
): Promise<boolean> {
    try {
        // 1. 获取源文件的结构
        const structure = await analyzeExcelStructure(sourceFilePath, headerRows);

        // 校验结构数据
        if (!structure.sheetList || !Array.isArray(structure.sheetList) || structure.sheetList.length === 0) {
            throw new Error('Invalid Excel structure: sheetList is empty or invalid');
        }

        if (!structure.sheetField || !Array.isArray(structure.sheetField) || structure.sheetField.length === 0) {
            throw new Error('Invalid Excel structure: sheetField is empty or invalid');
        }

        // 2. 转换为 SheetData 格式
        const data: SheetData = {
            'sheetList': structure.sheetList,
            'sheetField': structure.sheetField
        };

        // 3. 使用 writeSheetData 写入文件
        return await writeSheetData(targetFilePath, data);

    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`Failed to export Excel structure: ${errorMessage}`);
    }
}


```