#!/usr/bin/env node
/**
* QoutaMCP - HTTP/SSE Server for Remote Deployment
* Run with: node server.js
*
* Endpoints:
* - GET /sse - SSE connection for MCP protocol
* - POST /message - Send messages to MCP server
* - GET /health - Health check
*/
import express from 'express';
import cors from 'cors';
import { randomUUID } from 'crypto';
// Import tools
import { inspectProject } from './src/tools/inspectProject.js';
import { detectStack } from './src/tools/detectStack.js';
import { listKeyFiles } from './src/tools/listKeyFiles.js';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
// Store active SSE connections
const clients = new Map();
// Tool definitions
const tools = {
inspect_project: {
name: "inspect_project",
description: "Analyze a project directory and return a complete snapshot including detected languages, frameworks, entry points, structure summary, and dependencies. This is the primary tool for understanding a project.",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Path to the project directory to inspect" },
maxDepth: { type: "number", description: "Maximum depth to scan (default: 3)" },
includeHidden: { type: "boolean", description: "Include hidden files and directories" }
},
required: ["path"]
},
handler: inspectProject
},
detect_stack: {
name: "detect_stack",
description: "Quickly detect the technology stack of a project. Faster and lighter than inspect_project, returns runtime, framework, database hints, and frontend info.",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Path to the project directory" }
},
required: ["path"]
},
handler: detectStack
},
list_key_files: {
name: "list_key_files",
description: "List key files in a project, categorized by purpose: entry points, config files, and documentation.",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Path to the project directory" }
},
required: ["path"]
},
handler: listKeyFiles
}
};
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
server: 'qoutamcp',
version: '1.0.0',
tools: Object.keys(tools)
});
});
// SSE endpoint for MCP protocol
app.get('/sse', (req, res) => {
const clientId = randomUUID();
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no');
// Send endpoint info
res.write(`event: endpoint\ndata: /message?clientId=${clientId}\n\n`);
// Store client connection
clients.set(clientId, res);
console.log(`Client connected: ${clientId}`);
// Handle disconnect
req.on('close', () => {
clients.delete(clientId);
console.log(`Client disconnected: ${clientId}`);
});
});
// Message endpoint for receiving requests
app.post('/message', async (req, res) => {
const clientId = req.query.clientId;
const client = clients.get(clientId);
if (!client) {
return res.status(400).json({ error: 'Invalid or expired client session' });
}
const { jsonrpc, id, method, params } = req.body;
try {
let result;
switch (method) {
case 'initialize':
result = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {}
},
serverInfo: {
name: "qoutamcp",
version: "1.0.0"
}
};
break;
case 'tools/list':
result = {
tools: Object.values(tools).map(t => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema
}))
};
break;
case 'tools/call':
const tool = tools[params.name];
if (!tool) {
throw new Error(`Unknown tool: ${params.name}`);
}
const toolResult = await tool.handler(params.arguments);
result = {
content: [
{
type: "text",
text: JSON.stringify(toolResult, null, 2)
}
]
};
break;
case 'notifications/initialized':
// No response needed for notifications
return res.status(204).send();
default:
throw new Error(`Unknown method: ${method}`);
}
// Send response via SSE
const response = { jsonrpc: "2.0", id, result };
client.write(`event: message\ndata: ${JSON.stringify(response)}\n\n`);
res.status(202).json({ status: 'accepted' });
} catch (error) {
const errorResponse = {
jsonrpc: "2.0",
id,
error: {
code: -32603,
message: error.message
}
};
client.write(`event: message\ndata: ${JSON.stringify(errorResponse)}\n\n`);
res.status(202).json({ status: 'accepted' });
}
});
// Start server
app.listen(PORT, () => {
console.log(`
╔══════════════════════════════════════════════════════════╗
║ QoutaMCP Server ║
╠══════════════════════════════════════════════════════════╣
║ HTTP/SSE Mode - Ready for remote connections ║
║ ║
║ Endpoints: ║
║ • SSE: http://localhost:${PORT}/sse ║
║ • Message: http://localhost:${PORT}/message ║
║ • Health: http://localhost:${PORT}/health ║
║ ║
║ Available Tools: ║
║ • inspect_project ║
║ • detect_stack ║
║ • list_key_files ║
╚══════════════════════════════════════════════════════════╝
`);
});