@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.
- package/dist/index.js +100 -5
- 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'
|
|
191
|
+
if (!args || typeof args !== 'object') {
|
|
155
192
|
return {
|
|
156
193
|
isError: true,
|
|
157
|
-
content: [{ type: "text", text: "
|
|
194
|
+
content: [{ type: "text", text: "Invalid arguments provided." }]
|
|
158
195
|
};
|
|
159
196
|
}
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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);
|