file-share-mcp 1.0.7 → 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 +26 -61
- 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,9 +34,9 @@ app.get('/api/download', async (req, res) => {
|
|
|
34
34
|
const relativePath = req.query.path || '';
|
|
35
35
|
try {
|
|
36
36
|
const safePath = validateSafePath(relativePath);
|
|
37
|
-
|
|
37
|
+
logger.info(`[download] path="${relativePath}" → safePath="${safePath}"`);
|
|
38
38
|
if (!fs.existsSync(safePath) || !fs.statSync(safePath).isFile()) {
|
|
39
|
-
|
|
39
|
+
logger.info(`[download] Not found or not a file: "${safePath}"`);
|
|
40
40
|
res.status(404).send(`File not found: ${relativePath}`);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
@@ -47,18 +47,18 @@ app.get('/api/download', async (req, res) => {
|
|
|
47
47
|
res.setHeader('Content-Length', size);
|
|
48
48
|
const stream = fs.createReadStream(safePath);
|
|
49
49
|
stream.on('error', (err) => {
|
|
50
|
-
|
|
50
|
+
logger.error(`[download] Stream error for "${safePath}": ${err.message}`);
|
|
51
51
|
if (!res.headersSent) {
|
|
52
52
|
res.status(500).send(`Stream error: ${err.message}`);
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
stream.on('end', () => {
|
|
56
|
-
|
|
56
|
+
logger.info(`[download] Completed: "${safePath}"`);
|
|
57
57
|
});
|
|
58
58
|
stream.pipe(res);
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
|
-
|
|
61
|
+
logger.error(`[download] Validation error for "${relativePath}": ${error.message}`);
|
|
62
62
|
res.status(400).send(`Invalid path: ${error.message}`);
|
|
63
63
|
}
|
|
64
64
|
});
|
|
@@ -203,84 +203,49 @@ app.get(/^(?!\/api\/|\/proxy\/)/, (req, res) => {
|
|
|
203
203
|
});
|
|
204
204
|
// Proxy registration & server startup
|
|
205
205
|
const config = getConfig();
|
|
206
|
-
|
|
207
|
-
if (!config.PROXY_URL || !config.PROXY_APP_PATH)
|
|
208
|
-
return null;
|
|
209
|
-
try {
|
|
210
|
-
await axios.get(`${config.PROXY_URL}/proxy/health`, { timeout: 2000 });
|
|
211
|
-
const response = await axios.post(`${config.PROXY_URL}/proxy/register`, {
|
|
212
|
-
path: config.PROXY_APP_PATH,
|
|
213
|
-
name: config.PROXY_APP_NAME
|
|
214
|
-
}, { timeout: 2000 });
|
|
215
|
-
const assignedPort = response.data.port;
|
|
216
|
-
console.log(`Successfully registered with proxy at ${config.PROXY_URL}${config.PROXY_APP_PATH} (port ${assignedPort})`);
|
|
217
|
-
return assignedPort;
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
console.error(`Failed to register with proxy: ${error.message}`);
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
async function unregisterFromProxy() {
|
|
225
|
-
if (!config.PROXY_URL || !config.PROXY_APP_PATH)
|
|
226
|
-
return;
|
|
227
|
-
try {
|
|
228
|
-
await axios.delete(`${config.PROXY_URL}/proxy/unregister`, {
|
|
229
|
-
data: { path: config.PROXY_APP_PATH },
|
|
230
|
-
timeout: 2000
|
|
231
|
-
});
|
|
232
|
-
console.log(`Unregistered from proxy`);
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
// Ignore error on shutdown
|
|
236
|
-
}
|
|
237
|
-
}
|
|
206
|
+
const proxyClient = config.PROXY_URL ? new ProxyClient(config.PROXY_URL) : null;
|
|
238
207
|
let server;
|
|
239
208
|
async function startServer() {
|
|
240
|
-
let port;
|
|
241
|
-
if (config.
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
else {
|
|
247
|
-
console.error(`Could not register with proxy. Falling back to standalone mode on port ${config.GUI_PORT}.`);
|
|
248
|
-
port = config.GUI_PORT;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
port = config.GUI_PORT;
|
|
253
216
|
}
|
|
254
217
|
server = app.listen(port, () => {
|
|
255
|
-
|
|
218
|
+
logger.info(`GUI API server listening on port ${port}`);
|
|
256
219
|
});
|
|
257
220
|
server.on('error', (err) => {
|
|
258
221
|
if (err.code === 'EADDRINUSE') {
|
|
259
|
-
|
|
222
|
+
logger.error(`PORT CONFLICT: Port ${port} is already in use. Another GUI instance is running.`);
|
|
223
|
+
process.exit(0);
|
|
260
224
|
}
|
|
261
225
|
});
|
|
262
226
|
}
|
|
263
227
|
startServer();
|
|
264
228
|
// IPC and signals
|
|
265
229
|
process.on('message', async (msg) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
230
|
+
const type = typeof msg === 'string' ? msg : msg?.type;
|
|
231
|
+
if (type === 'STOP') {
|
|
232
|
+
logger.info('GUI received STOP signal');
|
|
233
|
+
await proxyClient?.unregister();
|
|
269
234
|
server?.close(() => process.exit(0));
|
|
270
235
|
}
|
|
271
236
|
});
|
|
272
237
|
process.on('error', (err) => {
|
|
273
238
|
if (err.code === 'ERR_IPC_CHANNEL_CLOSED') {
|
|
274
|
-
|
|
239
|
+
logger.error('[IPC] Channel closed (expected on disconnect).');
|
|
275
240
|
return;
|
|
276
241
|
}
|
|
277
|
-
|
|
242
|
+
logger.error(`[Process] Error: ${err.message}`);
|
|
278
243
|
});
|
|
279
244
|
process.on('disconnect', () => {
|
|
280
|
-
|
|
245
|
+
logger.error('Disconnected from parent, exiting.');
|
|
281
246
|
process.exit(0);
|
|
282
247
|
});
|
|
283
248
|
process.on('SIGTERM', async () => {
|
|
284
|
-
await
|
|
249
|
+
await proxyClient?.unregister();
|
|
285
250
|
process.exit(0);
|
|
286
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",
|