Reuben_OS / pages /api /mcp-handler.js
Reubencf's picture
feat: Update MCP integration to use passkey system instead of sessionId
c3ae75f
// pages/api/mcp-handler.js - Updated for Passkey System
import fs from 'fs/promises'
import fssync from 'fs';
import path from 'path';
// Use /data for Hugging Face Spaces persistent storage
const DATA_DIR = process.env.SPACE_ID ? '/data' : path.join(process.cwd(), 'public', 'data');
const PUBLIC_DIR = path.join(DATA_DIR, 'public');
// Ensure directories exist
async function ensureDirectories() {
if (!fssync.existsSync(DATA_DIR)) {
await fs.mkdir(DATA_DIR, { recursive: true });
}
if (!fssync.existsSync(PUBLIC_DIR)) {
await fs.mkdir(PUBLIC_DIR, { recursive: true });
}
}
// Validate passkey format (alphanumeric and hyphens/underscores only)
function isValidPasskey(passkey) {
return passkey && /^[a-zA-Z0-9_-]+$/.test(passkey) && passkey.length >= 4;
}
// Sanitize filename to prevent path traversal
function sanitizeFileName(fileName) {
return fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
}
// Sanitize passkey (used for directory names)
function sanitizePasskey(passkey) {
return passkey.replace(/[^a-zA-Z0-9_-]/g, '');
}
export default async function handler(req, res) {
// Enable CORS
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
try {
await ensureDirectories();
if (req.method === 'POST') {
const { passkey, action, fileName, content, isPublic = false } = req.body;
// Public files don't need passkey
if (!isPublic && !isValidPasskey(passkey)) {
return res.status(400).json({
success: false,
error: 'Invalid or missing passkey (minimum 4 characters required)'
});
}
// Validate required fields
if (!action || !fileName || content === undefined) {
return res.status(400).json({
success: false,
error: 'Missing required fields: action, fileName, or content'
});
}
const sanitizedFileName = sanitizeFileName(fileName);
try {
let targetDir;
let responseData = {};
if (isPublic) {
targetDir = PUBLIC_DIR;
responseData.isPublic = true;
responseData.location = 'Public Files';
} else {
const sanitizedKey = sanitizePasskey(passkey);
targetDir = path.join(DATA_DIR, sanitizedKey);
if (!fssync.existsSync(targetDir)) {
await fs.mkdir(targetDir, { recursive: true });
}
responseData.passkey = sanitizedKey;
responseData.location = 'Secure Data';
}
// Handle different actions
switch (action) {
case 'save_file':
case 'deploy_quiz':
case 'save': {
const filePath = path.join(targetDir, sanitizedFileName);
await fs.writeFile(filePath, content, 'utf8');
return res.status(200).json({
success: true,
message: `File saved successfully to ${responseData.location}`,
fileName: sanitizedFileName,
action: action,
...responseData,
url: isPublic
? `${process.env.SPACE_ID ? 'https://mcp-1st-birthday-reuben-os.hf.space' : 'http://localhost:3000'}/data/public/${sanitizedFileName}`
: null
});
}
case 'delete_file':
case 'delete': {
const filePath = path.join(targetDir, sanitizedFileName);
try {
await fs.unlink(filePath);
return res.status(200).json({
success: true,
message: `File deleted successfully`,
fileName: sanitizedFileName,
...responseData
});
} catch (err) {
return res.status(404).json({
success: false,
error: 'File not found'
});
}
}
case 'clear_session':
case 'clear': {
if (isPublic) {
return res.status(400).json({
success: false,
error: 'Cannot clear public folder via this endpoint'
});
}
const files = await fs.readdir(targetDir);
let deletedCount = 0;
for (const file of files) {
await fs.unlink(path.join(targetDir, file));
deletedCount++;
}
return res.status(200).json({
success: true,
message: `Cleared ${deletedCount} files`,
deletedCount,
...responseData
});
}
default:
return res.status(400).json({
success: false,
error: `Unknown action: ${action}`
});
}
} catch (error) {
console.error('File operation error:', error);
return res.status(500).json({
success: false,
error: `Failed to ${action}: ${error.message}`
});
}
} else if (req.method === 'GET') {
const { passkey, isPublic } = req.query;
let targetDir;
let responseData = {};
if (isPublic === 'true') {
targetDir = PUBLIC_DIR;
responseData.isPublic = true;
responseData.location = 'Public Files';
} else {
if (!isValidPasskey(passkey)) {
return res.status(400).json({
success: false,
error: 'Invalid or missing passkey'
});
}
const sanitizedKey = sanitizePasskey(passkey);
targetDir = path.join(DATA_DIR, sanitizedKey);
if (!fssync.existsSync(targetDir)) {
return res.status(200).json({
success: true,
passkey: sanitizedKey,
files: [],
count: 0,
message: 'No files found for this passkey'
});
}
responseData.passkey = sanitizedKey;
responseData.location = 'Secure Data';
}
try {
const allFiles = await fs.readdir(targetDir);
const fileList = await Promise.all(
allFiles.map(async (file) => {
const filePath = path.join(targetDir, file);
const stats = await fs.stat(filePath);
// Read file content (with size limit)
let content = null;
if (stats.size < 1024 * 1024) { // 1MB limit
try {
content = await fs.readFile(filePath, 'utf8');
} catch (err) {
console.error(`Error reading file ${file}:`, err);
}
}
return {
name: file,
size: stats.size,
modified: stats.mtime,
isQuiz: file === 'quiz.json',
content: content,
extension: path.extname(file).substring(1)
};
})
);
// Sort by modification time (newest first)
fileList.sort((a, b) => new Date(b.modified) - new Date(a.modified));
return res.status(200).json({
success: true,
files: fileList,
count: fileList.length,
...responseData
});
} catch (error) {
console.error('Error reading files:', error);
return res.status(500).json({
success: false,
error: `Failed to retrieve files: ${error.message}`
});
}
} else {
return res.status(405).json({
success: false,
error: 'Method not allowed'
});
}
} catch (error) {
console.error('Unexpected error:', error);
return res.status(500).json({
success: false,
error: `Server error: ${error.message}`
});
}
}