file-share-mcp 1.0.6 → 1.0.8
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/gui.js +42 -73
- package/dist/mcp-server.js +15 -65
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/gui.js
CHANGED
|
@@ -5,13 +5,13 @@ import path from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import os from 'os';
|
|
8
|
-
import
|
|
9
|
-
import { setupLogging } from './utils/logger.js';
|
|
8
|
+
import { setupLogging, createLogger, ProxyClient } from '@imenam/mcp-gui-interface';
|
|
10
9
|
import { getConfig, saveConfig } from './utils/config-manager.js';
|
|
11
10
|
import { getSharedFiles, validateSafePath, deleteFile, createDirectory, moveFile, deleteDirectory } from './utils/file-manager.js';
|
|
12
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
12
|
const __dirname = path.dirname(__filename);
|
|
14
|
-
setupLogging('GUI');
|
|
13
|
+
setupLogging({ processLabel: 'GUI', logDir: path.join(os.homedir(), '.file-share-mcp', 'logs') });
|
|
14
|
+
const logger = createLogger('GUI');
|
|
15
15
|
const app = express();
|
|
16
16
|
app.use(cors());
|
|
17
17
|
app.use(express.json());
|
|
@@ -34,27 +34,31 @@ app.get('/api/download', async (req, res) => {
|
|
|
34
34
|
const relativePath = req.query.path || '';
|
|
35
35
|
try {
|
|
36
36
|
const safePath = validateSafePath(relativePath);
|
|
37
|
-
|
|
38
|
-
if (fs.existsSync(safePath)
|
|
39
|
-
|
|
40
|
-
if (err) {
|
|
41
|
-
console.error(`[download] Stream error for "${safePath}":`, err);
|
|
42
|
-
if (!res.headersSent) {
|
|
43
|
-
res.status(500).send(`Stream error: ${err.message}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
console.log(`[download] Completed: "${safePath}"`);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
console.warn(`[download] Not found or not a file: "${safePath}"`);
|
|
37
|
+
logger.info(`[download] path="${relativePath}" → safePath="${safePath}"`);
|
|
38
|
+
if (!fs.existsSync(safePath) || !fs.statSync(safePath).isFile()) {
|
|
39
|
+
logger.info(`[download] Not found or not a file: "${safePath}"`);
|
|
53
40
|
res.status(404).send(`File not found: ${relativePath}`);
|
|
41
|
+
return;
|
|
54
42
|
}
|
|
43
|
+
const filename = path.basename(safePath);
|
|
44
|
+
const { size } = fs.statSync(safePath);
|
|
45
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
|
|
46
|
+
res.setHeader('Content-Type', 'application/octet-stream');
|
|
47
|
+
res.setHeader('Content-Length', size);
|
|
48
|
+
const stream = fs.createReadStream(safePath);
|
|
49
|
+
stream.on('error', (err) => {
|
|
50
|
+
logger.error(`[download] Stream error for "${safePath}": ${err.message}`);
|
|
51
|
+
if (!res.headersSent) {
|
|
52
|
+
res.status(500).send(`Stream error: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
stream.on('end', () => {
|
|
56
|
+
logger.info(`[download] Completed: "${safePath}"`);
|
|
57
|
+
});
|
|
58
|
+
stream.pipe(res);
|
|
55
59
|
}
|
|
56
60
|
catch (error) {
|
|
57
|
-
|
|
61
|
+
logger.error(`[download] Validation error for "${relativePath}": ${error.message}`);
|
|
58
62
|
res.status(400).send(`Invalid path: ${error.message}`);
|
|
59
63
|
}
|
|
60
64
|
});
|
|
@@ -199,84 +203,49 @@ app.get(/^(?!\/api\/|\/proxy\/)/, (req, res) => {
|
|
|
199
203
|
});
|
|
200
204
|
// Proxy registration & server startup
|
|
201
205
|
const config = getConfig();
|
|
202
|
-
|
|
203
|
-
if (!config.PROXY_URL || !config.PROXY_APP_PATH)
|
|
204
|
-
return null;
|
|
205
|
-
try {
|
|
206
|
-
await axios.get(`${config.PROXY_URL}/proxy/health`, { timeout: 2000 });
|
|
207
|
-
const response = await axios.post(`${config.PROXY_URL}/proxy/register`, {
|
|
208
|
-
path: config.PROXY_APP_PATH,
|
|
209
|
-
name: config.PROXY_APP_NAME
|
|
210
|
-
}, { timeout: 2000 });
|
|
211
|
-
const assignedPort = response.data.port;
|
|
212
|
-
console.log(`Successfully registered with proxy at ${config.PROXY_URL}${config.PROXY_APP_PATH} (port ${assignedPort})`);
|
|
213
|
-
return assignedPort;
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
console.error(`Failed to register with proxy: ${error.message}`);
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async function unregisterFromProxy() {
|
|
221
|
-
if (!config.PROXY_URL || !config.PROXY_APP_PATH)
|
|
222
|
-
return;
|
|
223
|
-
try {
|
|
224
|
-
await axios.delete(`${config.PROXY_URL}/proxy/unregister`, {
|
|
225
|
-
data: { path: config.PROXY_APP_PATH },
|
|
226
|
-
timeout: 2000
|
|
227
|
-
});
|
|
228
|
-
console.log(`Unregistered from proxy`);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
// Ignore error on shutdown
|
|
232
|
-
}
|
|
233
|
-
}
|
|
206
|
+
const proxyClient = config.PROXY_URL ? new ProxyClient(config.PROXY_URL) : null;
|
|
234
207
|
let server;
|
|
235
208
|
async function startServer() {
|
|
236
|
-
let port;
|
|
237
|
-
if (config.
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
else {
|
|
243
|
-
console.error(`Could not register with proxy. Falling back to standalone mode on port ${config.GUI_PORT}.`);
|
|
244
|
-
port = config.GUI_PORT;
|
|
209
|
+
let port = config.GUI_PORT;
|
|
210
|
+
if (proxyClient && config.PROXY_APP_PATH) {
|
|
211
|
+
const result = await proxyClient.register({ path: config.PROXY_APP_PATH, name: config.PROXY_APP_NAME }, config.GUI_PORT);
|
|
212
|
+
port = result.port;
|
|
213
|
+
if (!result.success) {
|
|
214
|
+
logger.warn(`Could not register with proxy. Falling back to standalone mode on port ${port}.`);
|
|
245
215
|
}
|
|
246
216
|
}
|
|
247
|
-
else {
|
|
248
|
-
port = config.GUI_PORT;
|
|
249
|
-
}
|
|
250
217
|
server = app.listen(port, () => {
|
|
251
|
-
|
|
218
|
+
logger.info(`GUI API server listening on port ${port}`);
|
|
252
219
|
});
|
|
253
220
|
server.on('error', (err) => {
|
|
254
221
|
if (err.code === 'EADDRINUSE') {
|
|
255
|
-
|
|
222
|
+
logger.error(`PORT CONFLICT: Port ${port} is already in use. Another GUI instance is running.`);
|
|
223
|
+
process.exit(0);
|
|
256
224
|
}
|
|
257
225
|
});
|
|
258
226
|
}
|
|
259
227
|
startServer();
|
|
260
228
|
// IPC and signals
|
|
261
229
|
process.on('message', async (msg) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
230
|
+
const type = typeof msg === 'string' ? msg : msg?.type;
|
|
231
|
+
if (type === 'STOP') {
|
|
232
|
+
logger.info('GUI received STOP signal');
|
|
233
|
+
await proxyClient?.unregister();
|
|
265
234
|
server?.close(() => process.exit(0));
|
|
266
235
|
}
|
|
267
236
|
});
|
|
268
237
|
process.on('error', (err) => {
|
|
269
238
|
if (err.code === 'ERR_IPC_CHANNEL_CLOSED') {
|
|
270
|
-
|
|
239
|
+
logger.error('[IPC] Channel closed (expected on disconnect).');
|
|
271
240
|
return;
|
|
272
241
|
}
|
|
273
|
-
|
|
242
|
+
logger.error(`[Process] Error: ${err.message}`);
|
|
274
243
|
});
|
|
275
244
|
process.on('disconnect', () => {
|
|
276
|
-
|
|
245
|
+
logger.error('Disconnected from parent, exiting.');
|
|
277
246
|
process.exit(0);
|
|
278
247
|
});
|
|
279
248
|
process.on('SIGTERM', async () => {
|
|
280
|
-
await
|
|
249
|
+
await proxyClient?.unregister();
|
|
281
250
|
process.exit(0);
|
|
282
251
|
});
|
package/dist/mcp-server.js
CHANGED
|
@@ -3,17 +3,17 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
import { fork } from 'child_process';
|
|
7
6
|
import path from 'path';
|
|
8
7
|
import { fileURLToPath } from 'url';
|
|
9
8
|
import fs from 'fs';
|
|
10
|
-
import { setupLogging } from '
|
|
9
|
+
import { GuiLauncher, setupLogging, createLogger } from '@imenam/mcp-gui-interface';
|
|
11
10
|
import { getConfig } from './utils/config-manager.js';
|
|
12
11
|
import { getSharedFiles, validateSafePath, writeSharedFile, copyToSharedFolder, createDirectory, deleteDirectory, moveFile, } from './utils/file-manager.js';
|
|
13
12
|
import { VERSION } from './version.js';
|
|
14
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
14
|
const __dirname = path.dirname(__filename);
|
|
16
|
-
setupLogging('MASTER');
|
|
15
|
+
setupLogging({ processLabel: 'MASTER' });
|
|
16
|
+
const logger = createLogger('MASTER');
|
|
17
17
|
const server = new Server({
|
|
18
18
|
name: 'file-share-mcp',
|
|
19
19
|
version: VERSION,
|
|
@@ -22,68 +22,18 @@ const server = new Server({
|
|
|
22
22
|
tools: {},
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
return proc.send(message);
|
|
25
|
+
const gui = new GuiLauncher({
|
|
26
|
+
guiPath: path.join(__dirname, 'gui.js'),
|
|
27
|
+
onMessage: (msg) => {
|
|
28
|
+
if (msg.type === 'RESTART') {
|
|
29
|
+
logger.info('GUI requested restart...');
|
|
30
|
+
gui.start();
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
else if (msg.type === 'STOP') {
|
|
33
|
+
logger.info('GUI requested stop...');
|
|
34
|
+
gui.cleanup().then(() => process.exit(0));
|
|
37
35
|
}
|
|
38
|
-
}
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
function startGui() {
|
|
42
|
-
if (guiProcess) {
|
|
43
|
-
guiProcess.kill();
|
|
44
|
-
}
|
|
45
|
-
const guiPath = path.join(__dirname, 'gui.js');
|
|
46
|
-
guiProcess = fork(guiPath, [], {
|
|
47
|
-
stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
|
|
48
|
-
env: { ...process.env }
|
|
49
|
-
});
|
|
50
|
-
// Redirect GUI stdout to stderr to protect JSON-RPC
|
|
51
|
-
guiProcess.stdout?.on('data', (data) => {
|
|
52
|
-
process.stderr.write(data);
|
|
53
|
-
});
|
|
54
|
-
guiProcess.on('message', (message) => {
|
|
55
|
-
if (message === 'RESTART') {
|
|
56
|
-
console.log('GUI requested restart...');
|
|
57
|
-
startGui();
|
|
58
|
-
}
|
|
59
|
-
else if (message === 'STOP') {
|
|
60
|
-
console.log('GUI requested stop...');
|
|
61
|
-
cleanup().then(() => process.exit(0));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
guiProcess.on('exit', (code) => {
|
|
65
|
-
if (code !== 0 && code !== null) {
|
|
66
|
-
console.error(`GUI process exited with code ${code}. Restarting...`);
|
|
67
|
-
setTimeout(startGui, 2000);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async function cleanup() {
|
|
72
|
-
console.log('Cleaning up...');
|
|
73
|
-
if (guiProcess) {
|
|
74
|
-
safeSend(guiProcess, 'STOP');
|
|
75
|
-
// Give it a moment to stop gracefully
|
|
76
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
77
|
-
guiProcess.kill();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
process.on('SIGINT', async () => {
|
|
81
|
-
await cleanup();
|
|
82
|
-
process.exit(0);
|
|
83
|
-
});
|
|
84
|
-
process.on('SIGTERM', async () => {
|
|
85
|
-
await cleanup();
|
|
86
|
-
process.exit(0);
|
|
36
|
+
},
|
|
87
37
|
});
|
|
88
38
|
// Tool Definitions
|
|
89
39
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -349,8 +299,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
349
299
|
async function main() {
|
|
350
300
|
const transport = new StdioServerTransport();
|
|
351
301
|
await server.connect(transport);
|
|
352
|
-
|
|
353
|
-
|
|
302
|
+
logger.info('File Share MCP Server started');
|
|
303
|
+
gui.start();
|
|
354
304
|
}
|
|
355
305
|
main().catch((error) => {
|
|
356
306
|
console.error('Fatal error in main():', error);
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.0.
|
|
1
|
+
export const VERSION = '1.0.8';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file-share-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "MCP server for bidirectional file sharing between AI agents and users, with a built-in web GUI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "ISC",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@imenam/mcp-gui-interface": "file:../mcp-gui-interface",
|
|
40
41
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
41
42
|
"axios": "^1.13.5",
|
|
42
43
|
"cors": "^2.8.6",
|