@makeshkumar/mcp-xl-reader 1.0.2 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -55
  2. package/dist/index.js +171 -2
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -1,65 +1,23 @@
1
- # XL Reader MCP Server
2
-
3
- A Model Context Protocol (MCP) server that provides AI assistants with the ability to read, search, and update local `.xlsx` Excel files.
4
-
5
- ## Features
6
-
7
- - **Read Spreadsheets**: Stream memory-efficient JSON rows from large Excel workbooks.
8
- - **Search within Sheets**: Perform case-insensitive fast searches on specific columns or headers.
9
- - **Update Cells**: Dynamically write string or numeric values back to the local Excel document.
10
- - **List Sheets**: Easily pull the names of all worksheets in a workbook.
11
-
12
- ## Installation
13
-
14
- ### Method 1: NPX (Recommended for quick use)
15
- You can directly run this server using `npx` in your MCP client configuration without globally installing it.
16
-
17
- ```json
18
- {
19
- "mcpServers": {
20
- "xl-reader": {
21
- "command": "npx",
22
- "args": ["-y", "@makeshkumar/mcp-xl-reader"]
23
- }
24
- }
25
- }
26
- ```
27
- *(Replace `@makeshkumar/mcp-xl-reader` with your actual published npm package name once published)*
28
-
29
- ### Method 2: Global Install
30
- ```bash
31
- npm install -g @makeshkumar/mcp-xl-reader
32
- mcp-xl-reader
33
- ```
34
-
35
- ### Method 3: Local Build
36
- 1. Clone the repository.
37
- 2. Run `npm install` to install dependencies.
38
- 3. Run `npm run build` to compile the TypeScript to JavaScript.
39
- 4. Start the server using `npm start` or point your MCP client to the `dist/index.js` file.
40
-
41
- ## Configuration Details
42
-
43
- ### Security
44
- By default, the server can access any `.xlsx` file on your system using the absolute path provided by the language model.
45
-
46
- To restrict access, set the `ALLOWED_DIRECTORIES` environment variable:
47
- ```bash
48
- export ALLOWED_DIRECTORIES="/Users/yourname/Documents/Spreadsheets,/Users/yourname/Downloads"
49
- ```
50
-
51
- If defined, the server will strictly reject paths outside these directory trees.
52
-
53
1
  ## Tools Exposed
54
2
 
55
3
  | Tool Name | Description |
56
4
  |-----------|-------------|
57
5
  | `list_sheets` | Returns an array of worksheet names available in the workbook. |
6
+ | `find_excel_files` | Recursively finds all .xlsx files in directories. |
58
7
  | `read_spreadsheet` | Returns JSON representation of rows. Streams using `worksheet.eachRow` for memory efficiency. |
59
8
  | `search_spreadsheet` | Performs a filtered search based on a `columnName` and `queryValue` and returns matching rows. |
60
9
  | `update_cell` | Updates the local file and saves changes by setting a new cell value at a specific alphanumeric address (e.g. 'B2'). |
10
+ | `add_worksheet_with_data` | Creates a new worksheet in an existing workbook and populates it with rows. |
11
+ | `create_new_workbook` | Creates a brand new Excel workbook with initial sheet and data. **NEW: Supports optional filePath with reference file directory.** |
12
+
13
+ ### create_new_workbook - New Feature
61
14
 
62
- ## Publishing to NPM
15
+ You can now create a new workbook without specifying the full path. Instead, provide a reference file path and the new file will be created in the same directory:
63
16
 
64
- 1. Login to your npm account using `npm login`.
65
- 2. Run `npm publish`.
17
+ **Option 1: Direct Path (original way)**
18
+ ```json
19
+ {
20
+ "filePath": "/Users/user/new_file.xlsx",
21
+ "columns": [{"header": "Name", "key": "name"}],
22
+ "rows": [{"name": "John"}]
23
+ }
package/dist/index.js CHANGED
@@ -42,6 +42,18 @@ async function validateFilePath(filePath) {
42
42
  }
43
43
  return absolutePath;
44
44
  }
45
+ // Helper for security check when creating new files (bypasses existence check)
46
+ async function validateNewFilePath(filePath) {
47
+ const absolutePath = path.resolve(filePath);
48
+ const allowedDirectories = process.env.ALLOWED_DIRECTORIES ? process.env.ALLOWED_DIRECTORIES.split(',') : [];
49
+ if (allowedDirectories.length > 0) {
50
+ const isAllowed = allowedDirectories.some(dir => absolutePath.startsWith(path.resolve(dir)));
51
+ if (!isAllowed) {
52
+ throw new Error(`Security Violation: Access to ${absolutePath} is not within allowed directories.`);
53
+ }
54
+ }
55
+ return absolutePath;
56
+ }
45
57
  // Helper for security and directory existence check
46
58
  async function validateDirectoryPath(directoryPath) {
47
59
  const absolutePath = path.resolve(directoryPath);
@@ -182,6 +194,64 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
182
194
  },
183
195
  required: ["filePath", "cellAddress", "newValue"]
184
196
  }
197
+ },
198
+ {
199
+ name: "add_worksheet_with_data",
200
+ description: "Creates a new worksheet in an existing workbook and populates it with an array of JSON rows. Great for generating summary reports.",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {
204
+ filePath: { type: "string" },
205
+ sheetName: { type: "string", description: "Name of the new sheet to create." },
206
+ columns: {
207
+ type: "array",
208
+ items: {
209
+ type: "object",
210
+ properties: {
211
+ header: { type: "string" },
212
+ key: { type: "string" }
213
+ }
214
+ },
215
+ description: "Array of column definitions, e.g., [{header: 'Name', key: 'name'}]"
216
+ },
217
+ rows: {
218
+ type: "array",
219
+ items: { type: "object" },
220
+ description: "Array of JSON objects representing the rows to insert."
221
+ }
222
+ },
223
+ required: ["filePath", "sheetName", "columns", "rows"]
224
+ }
225
+ },
226
+ {
227
+ name: "create_new_workbook",
228
+ description: "Creates a brand new Excel workbook file. If filePath is omitted, it creates the file in the same directory as the reference file.",
229
+ inputSchema: {
230
+ type: "object",
231
+ properties: {
232
+ filePath: { type: "string", description: "Absolute path where the new .xlsx file should be created (optional if referenceFilePath provided)." },
233
+ referenceFilePath: { type: "string", description: "Path to an existing file to use as directory reference (optional)." },
234
+ fileName: { type: "string", description: "Name of the new file (e.g., 'report.xlsx'). Required if referenceFilePath is used." },
235
+ sheetName: { type: "string", description: "Name of the first sheet.", default: "Sheet1" },
236
+ columns: {
237
+ type: "array",
238
+ items: {
239
+ type: "object",
240
+ properties: {
241
+ header: { type: "string" },
242
+ key: { type: "string" }
243
+ }
244
+ },
245
+ description: "Array of column definitions, e.g., [{header: 'Name', key: 'name'}]"
246
+ },
247
+ rows: {
248
+ type: "array",
249
+ items: { type: "object" },
250
+ description: "Array of JSON objects representing the rows to insert."
251
+ }
252
+ },
253
+ required: ["columns", "rows"]
254
+ }
185
255
  }
186
256
  ]
187
257
  };
@@ -194,7 +264,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
194
264
  content: [{ type: "text", text: "Invalid arguments provided." }]
195
265
  };
196
266
  }
197
- if (name !== "find_excel_files") {
267
+ if (name !== "find_excel_files" && name !== "create_new_workbook") {
198
268
  if (!args.filePath) {
199
269
  return {
200
270
  isError: true,
@@ -202,9 +272,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
202
272
  };
203
273
  }
204
274
  }
275
+ else if (name === "create_new_workbook") {
276
+ // Path validation handled within the case statement
277
+ }
205
278
  let absolutePath = "";
206
279
  try {
207
- if (name !== "find_excel_files") {
280
+ if (name === "create_new_workbook") {
281
+ // Path validation handled within the case statement
282
+ if (args.filePath) {
283
+ absolutePath = await validateNewFilePath(args.filePath);
284
+ }
285
+ }
286
+ else if (name !== "find_excel_files") {
208
287
  absolutePath = await validateFilePath(args.filePath);
209
288
  }
210
289
  }
@@ -398,6 +477,96 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
398
477
  content: [{ type: "text", text: `Successfully updated cell ${cellAddress} to ${newValue}` }]
399
478
  };
400
479
  }
480
+ case "add_worksheet_with_data": {
481
+ const { sheetName, columns, rows } = args;
482
+ await workbook.xlsx.readFile(absolutePath);
483
+ // Check if sheet exists to avoid crashing
484
+ if (workbook.getWorksheet(sheetName)) {
485
+ throw new Error(`A worksheet named '${sheetName}' already exists in this file.`);
486
+ }
487
+ const ws = workbook.addWorksheet(sheetName);
488
+ // Set columns
489
+ if (Array.isArray(columns)) {
490
+ ws.columns = columns.map(col => ({
491
+ header: col.header,
492
+ key: col.key,
493
+ width: 20 // Default reasonable width
494
+ }));
495
+ }
496
+ // Add data rows
497
+ if (Array.isArray(rows)) {
498
+ rows.forEach(rowData => {
499
+ ws.addRow(rowData);
500
+ });
501
+ }
502
+ // Make the header bold to look professional
503
+ const headerRow = ws.getRow(1);
504
+ headerRow.font = { bold: true };
505
+ headerRow.commit();
506
+ await workbook.xlsx.writeFile(absolutePath);
507
+ return {
508
+ content: [{ type: "text", text: `Successfully generated new reporting sheet '${sheetName}' with ${rows.length} rows of data and saved it to the file.` }]
509
+ };
510
+ }
511
+ case "create_new_workbook": {
512
+ let finalFilePath = absolutePath;
513
+ const { sheetName = "Sheet1", columns, rows, referenceFilePath, fileName } = args;
514
+ // Handle path resolution
515
+ if (!args.filePath) {
516
+ if (!referenceFilePath || !fileName) {
517
+ return {
518
+ isError: true,
519
+ content: [{ type: "text", text: "Either provide 'filePath' OR both 'referenceFilePath' and 'fileName'." }]
520
+ };
521
+ }
522
+ try {
523
+ const refPath = await validateFilePath(args.referenceFilePath);
524
+ const refDir = path.dirname(refPath);
525
+ finalFilePath = path.join(refDir, fileName);
526
+ }
527
+ catch (error) {
528
+ return {
529
+ isError: true,
530
+ content: [{ type: "text", text: error.message }]
531
+ };
532
+ }
533
+ }
534
+ try {
535
+ const stats = await fs.stat(finalFilePath);
536
+ if (stats) {
537
+ throw new Error(`File already exists at '${finalFilePath}'. Use add_worksheet_with_data or update_cell to modify it, or use a different file path.`);
538
+ }
539
+ }
540
+ catch (e) {
541
+ if (e.code !== 'ENOENT') {
542
+ throw e;
543
+ }
544
+ }
545
+ const newWorkbook = new ExcelJS.Workbook();
546
+ const ws = newWorkbook.addWorksheet(sheetName);
547
+ // Set columns
548
+ if (Array.isArray(columns)) {
549
+ ws.columns = columns.map(col => ({
550
+ header: col.header,
551
+ key: col.key,
552
+ width: 20
553
+ }));
554
+ }
555
+ // Add data rows
556
+ if (Array.isArray(rows)) {
557
+ rows.forEach(rowData => {
558
+ ws.addRow(rowData);
559
+ });
560
+ }
561
+ // Make the header bold
562
+ const headerRow = ws.getRow(1);
563
+ headerRow.font = { bold: true };
564
+ headerRow.commit();
565
+ await newWorkbook.xlsx.writeFile(finalFilePath);
566
+ return {
567
+ content: [{ type: "text", text: `Successfully created new workbook '${finalFilePath}' with sheet '${sheetName}' containing ${rows?.length || 0} rows of data.` }]
568
+ };
569
+ }
401
570
  default:
402
571
  throw new Error(`Unknown tool: ${name}`);
403
572
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makeshkumar/mcp-xl-reader",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,11 +21,12 @@
21
21
  "xlsx",
22
22
  "ai",
23
23
  "claude",
24
- "tools"
24
+ "tools",
25
+ "workbook"
25
26
  ],
26
27
  "author": "Makesh Kumar",
27
28
  "license": "MIT",
28
- "description": "An MCP server to read, search, and update local Excel (.xlsx) files.",
29
+ "description": "An MCP server to read, search, and update local Excel (.xlsx) files. Create new workbooks with reference path support.",
29
30
  "dependencies": {
30
31
  "@modelcontextprotocol/sdk": "^1.27.1",
31
32
  "exceljs": "^4.4.0"