@makeshkumar/mcp-xl-reader 1.0.0 → 1.0.2

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 (2) hide show
  1. package/dist/index.js +100 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -42,6 +42,33 @@ async function validateFilePath(filePath) {
42
42
  }
43
43
  return absolutePath;
44
44
  }
45
+ // Helper for security and directory existence check
46
+ async function validateDirectoryPath(directoryPath) {
47
+ const absolutePath = path.resolve(directoryPath);
48
+ try {
49
+ const stats = await fs.stat(absolutePath);
50
+ if (!stats.isDirectory()) {
51
+ throw new Error(`Path exists but is not a directory: ${absolutePath}`);
52
+ }
53
+ }
54
+ catch (error) {
55
+ if (error.code === 'ENOENT') {
56
+ throw new Error(`Directory Not Found: ${absolutePath}`);
57
+ }
58
+ else if (error.code === 'EACCES') {
59
+ throw new Error(`Permission Denied: ${absolutePath}`);
60
+ }
61
+ throw error;
62
+ }
63
+ const allowedDirectories = process.env.ALLOWED_DIRECTORIES ? process.env.ALLOWED_DIRECTORIES.split(',') : [];
64
+ if (allowedDirectories.length > 0) {
65
+ const isAllowed = allowedDirectories.some(dir => absolutePath.startsWith(path.resolve(dir)));
66
+ if (!isAllowed) {
67
+ throw new Error(`Security Violation: Access to ${absolutePath} is not within allowed directories.`);
68
+ }
69
+ }
70
+ return absolutePath;
71
+ }
45
72
  // Helper to format cell values properly
46
73
  function formatCellValue(cell) {
47
74
  if (cell.value === null || cell.value === undefined) {
@@ -104,6 +131,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
104
131
  required: ["filePath"]
105
132
  }
106
133
  },
134
+ {
135
+ name: "find_excel_files",
136
+ description: "Finds all .xlsx files in a given directory. If directoryPath is omitted, it automatically searches the allowed directories or current working directory.",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ directoryPath: { type: "string", description: "Absolute path to the directory to search (optional)." }
141
+ }
142
+ }
143
+ },
107
144
  {
108
145
  name: "read_spreadsheet",
109
146
  description: "Returns JSON representation of rows. Streams using worksheet.eachRow for memory efficiency.",
@@ -151,16 +188,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
151
188
  });
152
189
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
153
190
  const { name, arguments: args } = request.params;
154
- if (!args || typeof args !== 'object' || !args.filePath) {
191
+ if (!args || typeof args !== 'object') {
155
192
  return {
156
193
  isError: true,
157
- content: [{ type: "text", text: "Missing required filePath parameter." }]
194
+ content: [{ type: "text", text: "Invalid arguments provided." }]
158
195
  };
159
196
  }
160
- const { filePath } = args;
161
- let absolutePath;
197
+ if (name !== "find_excel_files") {
198
+ if (!args.filePath) {
199
+ return {
200
+ isError: true,
201
+ content: [{ type: "text", text: "Missing required filePath parameter." }]
202
+ };
203
+ }
204
+ }
205
+ let absolutePath = "";
162
206
  try {
163
- absolutePath = await validateFilePath(filePath);
207
+ if (name !== "find_excel_files") {
208
+ absolutePath = await validateFilePath(args.filePath);
209
+ }
164
210
  }
165
211
  catch (error) {
166
212
  return {
@@ -171,6 +217,55 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
171
217
  try {
172
218
  const workbook = new ExcelJS.Workbook();
173
219
  switch (name) {
220
+ case "find_excel_files": {
221
+ let directoriesToSearch = [];
222
+ if (args && args.directoryPath) {
223
+ directoriesToSearch.push(await validateDirectoryPath(args.directoryPath));
224
+ }
225
+ else {
226
+ const allowedDirectories = process.env.ALLOWED_DIRECTORIES ? process.env.ALLOWED_DIRECTORIES.split(',') : [];
227
+ if (allowedDirectories.length > 0) {
228
+ for (const dir of allowedDirectories) {
229
+ try {
230
+ directoriesToSearch.push(await validateDirectoryPath(dir.trim()));
231
+ }
232
+ catch (e) {
233
+ // ignore invalid ones
234
+ }
235
+ }
236
+ }
237
+ else {
238
+ // fallback to current working directory
239
+ directoriesToSearch.push(process.cwd());
240
+ }
241
+ }
242
+ if (directoriesToSearch.length === 0) {
243
+ return {
244
+ isError: true,
245
+ content: [{ type: "text", text: "No valid directories to search." }]
246
+ };
247
+ }
248
+ const allExcelFiles = [];
249
+ for (const dir of directoriesToSearch) {
250
+ try {
251
+ const files = await fs.readdir(dir, { recursive: true, withFileTypes: true });
252
+ const excelFiles = files
253
+ .filter(dirent => dirent.isFile() && dirent.name.toLowerCase().endsWith('.xlsx'))
254
+ .map(dirent => {
255
+ const d = dirent;
256
+ const parentPath = d.parentPath || d.path || dir;
257
+ return path.resolve(parentPath, dirent.name);
258
+ });
259
+ allExcelFiles.push(...excelFiles);
260
+ }
261
+ catch (e) {
262
+ // Skip if one directory fails
263
+ }
264
+ }
265
+ return {
266
+ content: [{ type: "text", text: JSON.stringify(allExcelFiles, null, 2) }]
267
+ };
268
+ }
174
269
  case "list_sheets": {
175
270
  await workbook.xlsx.readFile(absolutePath);
176
271
  const sheets = workbook.worksheets.map(ws => ws.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makeshkumar/mcp-xl-reader",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "scripts": {