filesystem-mcp 1.0.0

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/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # filesystem-mcp
2
+
3
+ MCP server for filesystem operations - read, create, and edit files.
4
+
5
+ ## Use Cases
6
+
7
+ **Save email attachments**: "Download all PDFs from my accountant's emails this month" → searches Gmail for matching emails, extracts attachments, and saves them to a local folder.
8
+
9
+ **Update config files**: "Change the API endpoint from staging to production in my config" → finds the config file, replaces the URL, and shows you the diff.
10
+
11
+ **Generate reports**: "Create a summary of today's calendar events" → reads your calendar, formats a markdown report, and saves it to your notes folder.
12
+
13
+ **Code refactoring**: "Rename the function `getData` to `fetchUserData` in src/api.ts" → reads the file, makes the precise replacement, and confirms the change.
14
+
15
+ **Backup before changes**: "Save a copy of my .zshrc before I modify it" → reads the current file and writes a timestamped backup.
16
+
17
+ ## Setup
18
+
19
+ ```bash
20
+ claude mcp add filesystem-mcp -- npx -y filesystem-mcp
21
+ ```
22
+
23
+ Or with HTTP transport:
24
+
25
+ ```bash
26
+ # Start the server
27
+ MCP_TRANSPORT=http PORT=3000 npx -y filesystem-mcp
28
+
29
+ # Add to Claude
30
+ claude mcp add --transport http filesystem-mcp http://localhost:3000/mcp
31
+ ```
32
+
33
+ ## Tools
34
+
35
+ | Tool | Description |
36
+ |------|-------------|
37
+ | `view` | Read file contents or list directory (with line numbers) |
38
+ | `create` | Create or overwrite a file |
39
+ | `str_replace` | Replace an exact string in a file |
40
+ | `insert` | Insert text at a specific line |
41
+
42
+ ## Contributing
43
+
44
+ Pull requests are welcomed on GitHub! To get started:
45
+
46
+ 1. Install Git and Node.js
47
+ 2. Clone the repository
48
+ 3. Install dependencies with `npm install`
49
+ 4. Run `npm run test` to run tests
50
+ 5. Build with `npm run build`
51
+
52
+ ## Releases
53
+
54
+ Versions follow the [semantic versioning spec](https://semver.org/).
55
+
56
+ To release:
57
+
58
+ 1. Use `npm version <major | minor | patch>` to bump the version
59
+ 2. Run `git push --follow-tags` to push with tags
60
+ 3. Wait for GitHub Actions to publish to the NPM registry.
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createServer(): McpServer;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createServer = createServer;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const index_js_1 = require("./tools/index.js");
6
+ function createServer() {
7
+ const server = new mcp_js_1.McpServer({
8
+ name: 'filesystem-mcp',
9
+ version: '1.0.0',
10
+ });
11
+ (0, index_js_1.registerAll)(server);
12
+ return server;
13
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/main.js ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
8
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
9
+ const express_1 = __importDefault(require("express"));
10
+ const index_js_1 = require("./index.js");
11
+ function setupSignalHandlers(cleanup) {
12
+ process.on('SIGINT', async () => {
13
+ await cleanup();
14
+ process.exit(0);
15
+ });
16
+ process.on('SIGTERM', async () => {
17
+ await cleanup();
18
+ process.exit(0);
19
+ });
20
+ }
21
+ const transport = process.env.MCP_TRANSPORT || 'stdio';
22
+ (async () => {
23
+ if (transport === 'stdio') {
24
+ const server = (0, index_js_1.createServer)();
25
+ setupSignalHandlers(async () => server.close());
26
+ const stdioTransport = new stdio_js_1.StdioServerTransport();
27
+ await server.connect(stdioTransport);
28
+ console.error('filesystem-mcp running on stdio');
29
+ }
30
+ else if (transport === 'http') {
31
+ const app = (0, express_1.default)();
32
+ app.use(express_1.default.json());
33
+ const port = parseInt(process.env.PORT || '3000', 10);
34
+ const baseUrl = process.env.MCP_BASE_URL || `http://localhost:${port}`;
35
+ app.post('/mcp', async (req, res) => {
36
+ const server = (0, index_js_1.createServer)();
37
+ try {
38
+ const httpTransport = new streamableHttp_js_1.StreamableHTTPServerTransport({
39
+ sessionIdGenerator: undefined,
40
+ enableJsonResponse: true,
41
+ });
42
+ await server.connect(httpTransport);
43
+ await httpTransport.handleRequest(req, res, req.body);
44
+ res.on('close', () => {
45
+ void httpTransport.close();
46
+ void server.close();
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.error('Error handling MCP request:', error);
51
+ if (!res.headersSent) {
52
+ res.status(500).json({
53
+ jsonrpc: '2.0',
54
+ error: { code: -32603, message: 'Internal server error' },
55
+ id: null,
56
+ });
57
+ }
58
+ }
59
+ });
60
+ const httpServer = app.listen(port, () => {
61
+ console.error(`filesystem-mcp running on ${baseUrl}/mcp`);
62
+ });
63
+ httpServer.on('error', (err) => {
64
+ console.error('FATAL: Server error', err.message);
65
+ process.exit(1);
66
+ });
67
+ setupSignalHandlers(async () => {
68
+ httpServer.close();
69
+ });
70
+ }
71
+ else {
72
+ console.error(`Unknown transport: ${transport}. Use MCP_TRANSPORT=stdio or MCP_TRANSPORT=http`);
73
+ process.exit(1);
74
+ }
75
+ })();
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCreate(server: McpServer): void;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerCreate = registerCreate;
37
+ const zod_1 = require("zod");
38
+ const fs = __importStar(require("node:fs/promises"));
39
+ const path = __importStar(require("node:path"));
40
+ const response_js_1 = require("../utils/response.js");
41
+ const description = `Create or overwrite a file with the specified content.
42
+
43
+ - Creates parent directories if they don't exist
44
+ - Overwrites existing files without warning
45
+ - Use absolute paths`;
46
+ function registerCreate(server) {
47
+ server.registerTool('create', {
48
+ title: 'Create',
49
+ description,
50
+ inputSchema: {
51
+ path: zod_1.z.string().describe('Absolute path where file will be created'),
52
+ file_text: zod_1.z.string().describe('Content to write to the file'),
53
+ },
54
+ annotations: {
55
+ readOnlyHint: false,
56
+ destructiveHint: true,
57
+ },
58
+ }, async (args) => {
59
+ const targetPath = args.path;
60
+ const content = args.file_text;
61
+ // Create parent directories if needed
62
+ const dir = path.dirname(targetPath);
63
+ await fs.mkdir(dir, { recursive: true });
64
+ // Write the file
65
+ await fs.writeFile(targetPath, content, 'utf-8');
66
+ return (0, response_js_1.jsonResult)({
67
+ success: true,
68
+ path: targetPath,
69
+ bytesWritten: Buffer.byteLength(content, 'utf-8'),
70
+ });
71
+ });
72
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAll(server: McpServer): void;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAll = registerAll;
4
+ const view_js_1 = require("./view.js");
5
+ const create_js_1 = require("./create.js");
6
+ const str_replace_js_1 = require("./str-replace.js");
7
+ const insert_js_1 = require("./insert.js");
8
+ function registerAll(server) {
9
+ (0, view_js_1.registerView)(server);
10
+ (0, create_js_1.registerCreate)(server);
11
+ (0, str_replace_js_1.registerStrReplace)(server);
12
+ (0, insert_js_1.registerInsert)(server);
13
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerInsert(server: McpServer): void;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerInsert = registerInsert;
37
+ const zod_1 = require("zod");
38
+ const fs = __importStar(require("node:fs/promises"));
39
+ const response_js_1 = require("../utils/response.js");
40
+ const description = `Insert text at a specific line in a file.
41
+
42
+ - insert_line = 0: Insert at the beginning
43
+ - insert_line = N: Insert after line N
44
+ - insert_text should typically end with a newline
45
+ - Use absolute paths`;
46
+ function registerInsert(server) {
47
+ server.registerTool('insert', {
48
+ title: 'Insert',
49
+ description,
50
+ inputSchema: {
51
+ path: zod_1.z.string().describe('Absolute path to file'),
52
+ insert_line: zod_1.z.number().int().min(0).describe('Line number to insert after (0 = beginning)'),
53
+ insert_text: zod_1.z.string().describe('Text to insert (should end with newline)'),
54
+ },
55
+ annotations: {
56
+ readOnlyHint: false,
57
+ destructiveHint: true,
58
+ },
59
+ }, async (args) => {
60
+ const targetPath = args.path;
61
+ const insertLine = args.insert_line;
62
+ const insertText = args.insert_text;
63
+ const content = await fs.readFile(targetPath, 'utf-8');
64
+ const lines = content.split('\n');
65
+ // Validate line number
66
+ if (insertLine > lines.length) {
67
+ throw new Error(`insert_line ${insertLine} is beyond file length (${lines.length} lines)`);
68
+ }
69
+ // Warn if insert_text doesn't end with newline
70
+ let warning;
71
+ if (!insertText.endsWith('\n')) {
72
+ warning = 'insert_text does not end with newline - text will run together with next line';
73
+ }
74
+ // Insert the text
75
+ // Split insert_text into lines (removing trailing newline for array ops)
76
+ const insertLines = insertText.endsWith('\n')
77
+ ? insertText.slice(0, -1).split('\n')
78
+ : insertText.split('\n');
79
+ lines.splice(insertLine, 0, ...insertLines);
80
+ const newContent = lines.join('\n');
81
+ await fs.writeFile(targetPath, newContent, 'utf-8');
82
+ return (0, response_js_1.jsonResult)({
83
+ success: true,
84
+ path: targetPath,
85
+ insertedAt: insertLine,
86
+ linesInserted: insertLines.length,
87
+ ...(warning ? { warning } : {}),
88
+ });
89
+ });
90
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerStrReplace(server: McpServer): void;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerStrReplace = registerStrReplace;
37
+ const zod_1 = require("zod");
38
+ const fs = __importStar(require("node:fs/promises"));
39
+ const response_js_1 = require("../utils/response.js");
40
+ const description = `Replace an exact string in a file.
41
+
42
+ - old_str must match exactly and be unique in the file
43
+ - If old_str appears 0 times or more than once, the operation fails
44
+ - If new_str is omitted, old_str is deleted
45
+ - Use absolute paths`;
46
+ function registerStrReplace(server) {
47
+ server.registerTool('str_replace', {
48
+ title: 'String Replace',
49
+ description,
50
+ inputSchema: {
51
+ path: zod_1.z.string().describe('Absolute path to file'),
52
+ old_str: zod_1.z.string().describe('Exact string to find (must be unique)'),
53
+ new_str: zod_1.z.string().optional().describe('Replacement string (omit to delete)'),
54
+ },
55
+ annotations: {
56
+ readOnlyHint: false,
57
+ destructiveHint: true,
58
+ },
59
+ }, async (args) => {
60
+ const targetPath = args.path;
61
+ const oldStr = args.old_str;
62
+ const newStr = args.new_str ?? '';
63
+ const content = await fs.readFile(targetPath, 'utf-8');
64
+ // Check for empty file
65
+ if (content.length === 0) {
66
+ throw new Error('Cannot use str_replace on an empty file');
67
+ }
68
+ // Count occurrences
69
+ const occurrences = countOccurrences(content, oldStr);
70
+ if (occurrences === 0) {
71
+ const preview = oldStr.length > 100 ? `${oldStr.slice(0, 100)}...` : oldStr;
72
+ throw new Error(`old_str not found in file: "${preview}"`);
73
+ }
74
+ if (occurrences > 1) {
75
+ // Find line numbers of all occurrences
76
+ const lines = content.split('\n');
77
+ const matchingLines = [];
78
+ for (let i = 0; i < lines.length; i++) {
79
+ const line = lines[i];
80
+ if (line?.includes(oldStr)) {
81
+ matchingLines.push(i + 1);
82
+ }
83
+ }
84
+ throw new Error(`old_str appears ${occurrences} times (must be unique). Found on lines: ${matchingLines.join(', ')}`);
85
+ }
86
+ // Replace
87
+ const newContent = content.replace(oldStr, newStr);
88
+ await fs.writeFile(targetPath, newContent, 'utf-8');
89
+ return (0, response_js_1.jsonResult)({
90
+ success: true,
91
+ path: targetPath,
92
+ replacements: 1,
93
+ });
94
+ });
95
+ }
96
+ function countOccurrences(str, substr) {
97
+ if (substr.length === 0) {
98
+ return 0;
99
+ }
100
+ let count = 0;
101
+ let pos = 0;
102
+ while ((pos = str.indexOf(substr, pos)) !== -1) {
103
+ count += 1;
104
+ pos += substr.length;
105
+ }
106
+ return count;
107
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerView(server: McpServer): void;
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerView = registerView;
37
+ const zod_1 = require("zod");
38
+ const fs = __importStar(require("node:fs/promises"));
39
+ const path = __importStar(require("node:path"));
40
+ const response_js_1 = require("../utils/response.js");
41
+ const description = `View file contents or list directory.
42
+
43
+ For files:
44
+ - Returns content with line numbers (format: " N\\t<content>")
45
+ - Use view_range to read specific lines [start, end] (1-indexed, inclusive)
46
+ - Large files are truncated at 16000 characters
47
+
48
+ For directories:
49
+ - Lists contents with type indicator (/ for directories)
50
+ - Shows 2 levels deep by default
51
+ - Ignores hidden files and node_modules`;
52
+ function registerView(server) {
53
+ server.registerTool('view', {
54
+ title: 'View',
55
+ description,
56
+ inputSchema: {
57
+ path: zod_1.z.string().describe('Absolute path to file or directory'),
58
+ view_range: zod_1.z.tuple([zod_1.z.number(), zod_1.z.number()]).optional().describe('Line range [start, end] for text files (1-indexed, inclusive)'),
59
+ },
60
+ annotations: {
61
+ readOnlyHint: true,
62
+ },
63
+ }, async (args) => {
64
+ const targetPath = args.path;
65
+ const stat = await fs.stat(targetPath);
66
+ if (stat.isDirectory()) {
67
+ const entries = await listDirectory(targetPath, 2);
68
+ return (0, response_js_1.jsonResult)({
69
+ type: 'directory',
70
+ path: targetPath,
71
+ entries,
72
+ });
73
+ }
74
+ // File
75
+ const content = await fs.readFile(targetPath, 'utf-8');
76
+ const lines = content.split('\n');
77
+ let startLine = 1;
78
+ let endLine = lines.length;
79
+ if (args.view_range) {
80
+ [startLine, endLine] = args.view_range;
81
+ startLine = Math.max(1, startLine);
82
+ endLine = Math.min(lines.length, endLine);
83
+ }
84
+ // Format with line numbers (6-char padding + tab)
85
+ const numberedLines = lines
86
+ .slice(startLine - 1, endLine)
87
+ .map((line, i) => {
88
+ const lineNum = (startLine + i).toString().padStart(6, ' ');
89
+ return `${lineNum}\t${line}`;
90
+ });
91
+ let result = numberedLines.join('\n');
92
+ // Truncate if too large
93
+ const MAX_CHARS = 16000;
94
+ if (result.length > MAX_CHARS) {
95
+ result = `${result.slice(0, MAX_CHARS)}\n... (truncated)`;
96
+ }
97
+ return (0, response_js_1.jsonResult)({
98
+ type: 'file',
99
+ path: targetPath,
100
+ totalLines: lines.length,
101
+ viewedRange: [startLine, endLine],
102
+ content: result,
103
+ });
104
+ });
105
+ }
106
+ async function listDirectory(dirPath, depth) {
107
+ if (depth <= 0) {
108
+ return [];
109
+ }
110
+ const entries = [];
111
+ const items = await fs.readdir(dirPath, { withFileTypes: true });
112
+ for (const item of items) {
113
+ // Skip hidden files and node_modules
114
+ if (item.name.startsWith('.') || item.name === 'node_modules') {
115
+ continue;
116
+ }
117
+ if (item.isDirectory()) {
118
+ entries.push(`${item.name}/`);
119
+ if (depth > 1) {
120
+ // eslint-disable-next-line no-await-in-loop -- sequential traversal for predictable ordering
121
+ const subEntries = await listDirectory(path.join(dirPath, item.name), depth - 1);
122
+ for (const sub of subEntries) {
123
+ entries.push(` ${item.name}/${sub}`);
124
+ }
125
+ }
126
+ }
127
+ else {
128
+ entries.push(item.name);
129
+ }
130
+ }
131
+ return entries;
132
+ }
@@ -0,0 +1,2 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare function jsonResult<T extends Record<string, unknown>>(data: T): CallToolResult;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsonResult = jsonResult;
4
+ function jsonResult(data) {
5
+ return {
6
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
7
+ structuredContent: data,
8
+ };
9
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "filesystem-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for filesystem operations (view, create, edit files)",
5
+ "license": "MIT",
6
+ "author": "Adam Jones (domdomegg)",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/domdomegg/filesystem-mcp.git"
10
+ },
11
+ "main": "dist/main.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "mcpName": "io.github.domdomegg/filesystem-mcp",
17
+ "scripts": {
18
+ "start": "npm run build && node ./dist/main.js",
19
+ "start:http": "npm run build && MCP_TRANSPORT=http node ./dist/main.js",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest --watch",
22
+ "lint": "eslint",
23
+ "clean": "rm -rf dist",
24
+ "build": "tsc --project tsconfig.build.json",
25
+ "prepublishOnly": "npm run clean && npm run build"
26
+ },
27
+ "devDependencies": {
28
+ "@tsconfig/node-lts": "^24.0.0",
29
+ "@types/express": "^5.0.0",
30
+ "@types/node": "^22.0.0",
31
+ "eslint": "^9.0.0",
32
+ "eslint-config-domdomegg": "^2.0.0",
33
+ "tsconfig-domdomegg": "^1.0.0",
34
+ "typescript": "^5.8.0",
35
+ "vitest": "^3.0.0"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.24.0",
39
+ "express": "^5.0.0",
40
+ "zod": "^3.24.0"
41
+ }
42
+ }