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 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 axios from 'axios';
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
- console.log(`[download] path="${relativePath}" → safePath="${safePath}"`);
38
- if (fs.existsSync(safePath) && fs.statSync(safePath).isFile()) {
39
- res.download(safePath, path.basename(safePath), (err) => {
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
- console.error(`[download] Validation error for "${relativePath}":`, error);
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
- async function registerWithProxy() {
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.PROXY_URL) {
238
- const assignedPort = await registerWithProxy();
239
- if (assignedPort) {
240
- port = assignedPort;
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
- console.log(`GUI API server listening on port ${port}`);
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
- console.error(`PORT CONFLICT: Port ${port} is already in use.`);
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
- if (msg === 'STOP') {
263
- console.log('GUI received STOP signal');
264
- await unregisterFromProxy();
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
- console.error('[IPC] Channel closed (expected on disconnect).');
239
+ logger.error('[IPC] Channel closed (expected on disconnect).');
271
240
  return;
272
241
  }
273
- console.error(`[Process] Error: ${err.message}`);
242
+ logger.error(`[Process] Error: ${err.message}`);
274
243
  });
275
244
  process.on('disconnect', () => {
276
- console.error('Disconnected from parent, exiting.');
245
+ logger.error('Disconnected from parent, exiting.');
277
246
  process.exit(0);
278
247
  });
279
248
  process.on('SIGTERM', async () => {
280
- await unregisterFromProxy();
249
+ await proxyClient?.unregister();
281
250
  process.exit(0);
282
251
  });
@@ -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 './utils/logger.js';
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
- let guiProcess = null;
26
- /**
27
- * Envoi IPC robuste — vérifie que le canal est connecté avant d'envoyer
28
- */
29
- function safeSend(proc, message) {
30
- if (proc && typeof proc.send === 'function' && proc.connected) {
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
- catch (err) {
35
- console.error(`[IPC] Failed to send message: ${err.message}`);
36
- return false;
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
- console.log('File Share MCP Server started');
353
- startGui();
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.6';
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.6",
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",