Skip to main content
Glama
razvan1326

Academiadepolitie.com MCP Server

by razvan1326
server.js17.7 kB
#!/usr/bin/env node /** * Remote MCP Server pentru Academiadepolitie.com * Suportă HTTP/SSE transport pentru Claude Remote Connectors * Complet separat de implementarea locală MCP */ import express from 'express'; import cors from 'cors'; import { createServer } from 'http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import rateLimit from 'express-rate-limit'; import { spawn } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; import cookieParser from 'cookie-parser'; import { authenticateRequest } from '../auth/oauth.js'; import { handleSSE } from './sse-handler.js'; import { tools } from './tools.js'; import * as oauthManager from './oauth-manager.js'; import * as dcr from './dcr.js'; // Pentru ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Load environment variables dotenv.config(); const app = express(); const httpServer = createServer(app); const PORT = process.env.PORT || 3000; // Security middleware app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['https://claude.ai'], credentials: true })); app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); // Servește fișiere statice din public app.use(express.static(path.join(__dirname, '..', 'public'))); // Rate limiting const limiter = rateLimit({ windowMs: (process.env.RATE_LIMIT_WINDOW || 15) * 60 * 1000, max: process.env.RATE_LIMIT_MAX_REQUESTS || 100, message: 'Too many requests from this IP' }); app.use('/mcp', limiter); // Health check endpoint // Root endpoint app.get("/", (req, res) => { res.json({ service: "academiadepolitie-remote-mcp", version: "1.0.0", status: "ready", endpoints: { health: "/health", oauth_discovery: "/.well-known/oauth-authorization-server", oauth_authorize: "/oauth/authorize", oauth_token: "/oauth/token", mcp: "/mcp" } }); }); app.get('/health', (req, res) => { res.json({ status: 'healthy', version: '1.0.0', service: 'academiadepolitie-remote-mcp', timestamp: new Date().toISOString() }); }); // OAuth 2.1 Discovery endpoints app.get('/.well-known/oauth-authorization-server', (req, res) => { res.json({ issuer: 'https://mcp.academiadepolitie.com:8443', authorization_endpoint: 'https://mcp.academiadepolitie.com:8443/oauth/authorize', token_endpoint: 'https://mcp.academiadepolitie.com:8443/oauth/token', registration_endpoint: 'https://mcp.academiadepolitie.com:8443/oauth/register', token_endpoint_auth_methods_supported: ['client_secret_post', 'none'], response_types_supported: ['code'], grant_types_supported: ['authorization_code'], code_challenge_methods_supported: ['S256'], service_documentation: 'https://www.academiadepolitie.com/api/docs', ui_locales_supported: ['ro', 'en'] }); }); // OpenID Configuration endpoint (pentru compatibilitate) app.get('/.well-known/openid-configuration', (req, res) => { res.json({ issuer: 'https://mcp.academiadepolitie.com:8443', authorization_endpoint: 'https://mcp.academiadepolitie.com:8443/oauth/authorize', token_endpoint: 'https://mcp.academiadepolitie.com:8443/oauth/token', registration_endpoint: 'https://mcp.academiadepolitie.com:8443/oauth/register', jwks_uri: 'https://mcp.academiadepolitie.com:8443/.well-known/jwks.json', response_types_supported: ['code'], grant_types_supported: ['authorization_code'], subject_types_supported: ['public'], id_token_signing_alg_values_supported: ['RS256'], token_endpoint_auth_methods_supported: ['client_secret_post', 'none'], code_challenge_methods_supported: ['S256'] }); }); app.get('/.well-known/oauth-protected-resource', (req, res) => { res.json({ resource: 'https://mcp.academiadepolitie.com:8443', authorization_servers: ['https://mcp.academiadepolitie.com:8443'] }); }); /** * Proxy către oauth-bridge.php pentru OAuth endpoints */ async function proxyToPHP(endpoint, req, res) { return new Promise((resolve, reject) => { // Prepare environment variables pentru PHP const env = { ...process.env }; env.REQUEST_METHOD = req.method; env.REQUEST_URI = endpoint; env.QUERY_STRING = new URLSearchParams(req.query).toString(); // Prepare input data pentru POST requests let inputData = ''; if (req.method === 'POST') { inputData = JSON.stringify(req.body); env.CONTENT_TYPE = 'application/json'; env.CONTENT_LENGTH = inputData.length.toString(); } const php = spawn('php', ['/opt/mcp-server/oauth-bridge.php'], { env, stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; let errorOutput = ''; php.stdout.on('data', (data) => { output += data.toString(); }); php.stderr.on('data', (data) => { errorOutput += data.toString(); }); php.on('close', (code) => { if (code === 0) { // Parse PHP output pentru headers și body const parts = output.split('\n\n'); const headers = parts[0] || ''; const body = parts.slice(1).join('\n\n'); // Verifică pentru Location header (redirect) - IMPORTANT pentru OAuth! const locationMatch = headers.match(/Location:\s*(.+)/i); if (locationMatch) { const redirectUrl = locationMatch[1].trim(); console.log('PHP Redirect detected:', redirectUrl); res.redirect(302, redirectUrl); resolve(); return; } // Set response headers if (headers.includes('Content-Type: application/json')) { res.set('Content-Type', 'application/json'); } // Send response if (!res.headersSent) { res.send(body || output); } resolve(); } else { console.error('PHP Error:', errorOutput); res.status(500).json({ error: 'OAuth proxy error' }); reject(new Error(errorOutput)); } }); // Send POST data to PHP if present if (inputData) { php.stdin.write(inputData); } php.stdin.end(); }); } // OAuth endpoints cu autentificare reală app.get('/oauth/authorize', async (req, res) => { try { const { client_id, redirect_uri, state, code_challenge, resource } = req.query; // MCP Auth Spec 2025-06-18: resource parameter este OBLIGATORIU if (!client_id || !redirect_uri || !resource) { return res.status(400).json({ error: 'invalid_request', description: 'Missing required parameters: client_id, redirect_uri, and resource are mandatory per MCP Auth Spec 2025-06-18' }); } // Validare resource parameter - trebuie să fie URL-ul serverului nostru const expectedResource = 'https://mcp.academiadepolitie.com:8443'; if (resource !== expectedResource) { return res.status(400).json({ error: 'invalid_target', description: `Invalid resource parameter. Expected: ${expectedResource}` }); } // Verifică dacă user-ul are sesiune activă const sessionId = req.cookies.mcp_session; const session = sessionId ? oauthManager.getSession(sessionId) : null; if (session && session.userId) { // User autentificat - generează authorization code const authCode = oauthManager.generateAuthCode( session.userId, client_id, redirect_uri, code_challenge ); // Construiește URL redirect let callbackUrl = redirect_uri + '?code=' + encodeURIComponent(authCode); if (state) { callbackUrl += '&state=' + encodeURIComponent(state); } console.log(`OAuth: User ${session.userId} authorized, redirecting to:`, callbackUrl); // HTTP 302 redirect return res.redirect(302, callbackUrl); } else { // User neautentificat - redirect la login page const loginUrl = `/login.html?${new URLSearchParams({ client_id, redirect_uri, state: state || '', code_challenge: code_challenge || '' }).toString()}`; console.log('OAuth: User not authenticated, redirecting to login:', loginUrl); return res.redirect(302, loginUrl); } } catch (error) { console.error('OAuth authorize error:', error); res.status(500).json({ error: 'server_error', description: error.message }); } }); // OAuth token exchange endpoint app.post('/oauth/token', async (req, res) => { try { // Debug logging pentru Claude console.log('OAuth Token Request from Claude:'); console.log('Body:', JSON.stringify(req.body, null, 2)); console.log('Headers:', req.headers); const { grant_type, code, client_id, client_secret, redirect_uri, code_verifier, resource } = req.body; // Validare grant type if (grant_type !== 'authorization_code') { return res.status(400).json({ error: 'unsupported_grant_type', error_description: 'Only authorization_code grant type is supported' }); } // MCP Auth Spec 2025-06-18: resource parameter obligatoriu și în token request if (!code || !client_id || !resource) { return res.status(400).json({ error: 'invalid_request', error_description: 'Missing required parameters: code, client_id, and resource are mandatory per MCP Auth Spec 2025-06-18' }); } // Validare resource parameter - trebuie să fie URL-ul serverului nostru const expectedResource = 'https://mcp.academiadepolitie.com:8443'; if (resource !== expectedResource) { return res.status(400).json({ error: 'invalid_target', error_description: `Invalid resource parameter. Expected: ${expectedResource}` }); } // Validare client cu DCR const clientValidation = dcr.validateClient(client_id, client_secret); if (!clientValidation.valid) { console.log('OAuth Token: Invalid client:', client_id); // MCP Auth Spec 2025-06-18: WWW-Authenticate header pentru 401 res.set('WWW-Authenticate', `Bearer realm="https://mcp.academiadepolitie.com:8443", error="invalid_client", error_description="${clientValidation.error}"`); return res.status(401).json({ error: 'invalid_client', error_description: clientValidation.error }); } // Validează authorization code const validation = oauthManager.validateAuthCode(code, client_id, redirect_uri, code_verifier); if (!validation.valid) { return res.status(400).json({ error: validation.error, error_description: validation.description }); } // Generează access token cu audience validation (MCP Auth Spec 2025-06-18) const tokenData = oauthManager.generateAccessToken(validation.userId, client_id, resource); console.log(`OAuth: Token generated for user ${validation.userId}`); // Returnează token res.json(tokenData); } catch (error) { console.error('OAuth token error:', error); res.status(500).json({ error: 'server_error', error_description: error.message }); } }); // OAuth login endpoint app.post('/oauth/login', async (req, res) => { try { const { username, password, remember, client_id, redirect_uri, state, code_challenge } = req.body; // Verifică credențialele const authResult = await oauthManager.verifyCredentials(username, password); if (!authResult.valid) { // MCP Auth Spec 2025-06-18: WWW-Authenticate header pentru 401 res.set('WWW-Authenticate', `Bearer realm="https://mcp.academiadepolitie.com:8443", error="invalid_credentials"`); return res.status(401).json({ error: authResult.error || 'Invalid credentials' }); } // Creează sesiune const sessionId = oauthManager.createSession(authResult.user.id, authResult.user); // Setează cookie sesiune res.cookie('mcp_session', sessionId, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: remember ? 30 * 24 * 60 * 60 * 1000 : 60 * 60 * 1000 // 30 zile sau 1 oră }); // Generează authorization code const authCode = oauthManager.generateAuthCode( authResult.user.id, client_id, redirect_uri, code_challenge ); // Construiește URL redirect let callbackUrl = redirect_uri + '?code=' + encodeURIComponent(authCode); if (state) { callbackUrl += '&state=' + encodeURIComponent(state); } console.log(`OAuth: User ${authResult.user.username} logged in successfully`); // Returnează URL pentru redirect res.json({ success: true, redirect_url: callbackUrl }); } catch (error) { console.error('OAuth login error:', error); res.status(500).json({ error: 'Authentication service error' }); } }); // Dynamic Client Registration endpoint app.post('/oauth/register', async (req, res) => { try { console.log('DCR Request:', JSON.stringify(req.body, null, 2)); const result = dcr.registerClient(req.body); if (result.error) { return res.status(400).json(result); } // Return client registration response res.status(201).json(result); } catch (error) { console.error('DCR Error:', error); res.status(500).json({ error: 'server_error', error_description: error.message }); } }); app.all('/oauth/login', async (req, res) => { try { await proxyToPHP('/oauth/login', req, res); } catch (error) { console.error('OAuth login error:', error); res.status(500).json({ error: 'Login failed' }); } }); // TEST endpoint pentru MCP Inspector (fără autentificare) app.post('/mcp-test', async (req, res) => { try { const testUser = { id: 4001, username: 'test', api_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhY2FkZW1pYWRlcG9saXRpZS5jb20iLCJhdWQiOiJhcGktdXNlcnMiLCJpYXQiOjE3NTQ2NTAwMTgsImV4cCI6MTc4NjE4NjAxOCwidXNlcl9pZCI6NDAwMSwiZ3J1cCI6MywicGVybWlzc2lvbnMiOlsicHJvZmlsZSIsInNlYXJjaCIsImludGVyYWN0aXZlIiwicHJvZ3Jlc3MiXSwicmF0ZV9saW1pdCI6NTAwLCJlbmRwb2ludHMiOlsiZ2V0X3N0dWRlbnRfZGF0YSIsInNlYXJjaF9hcnRpY2xlcyIsImdldF9hcnRpY2xlX2NvbnRlbnQiLCJhZGRfbm90ZSIsInNlbmRfY2hhbGxlbmdlIiwidXBkYXRlX3JlYWRpbmdfcHJvZ3Jlc3MiXX0.n5Mwa_KZpfYyp2ym_SJZgpHpoCPJ1MdlLI90wpfOxmY' }; const result = await handleJSONRPC(req.body, testUser); res.json(result); } catch (error) { res.status(400).json({ jsonrpc: '2.0', error: { code: error.code || -32603, message: error.message }, id: req.body.id || null }); } }); // MCP Protocol endpoints (HTTP + SSE) app.post('/mcp', authenticateRequest, async (req, res) => { const acceptHeader = req.headers.accept || ''; // Verifică dacă clientul vrea SSE if (acceptHeader.includes('text/event-stream')) { // Upgrade la SSE pentru streaming res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); handleSSE(req, res); } else { // Regular HTTP JSON-RPC response try { const result = await handleJSONRPC(req.body, req.user); res.json(result); } catch (error) { res.status(400).json({ jsonrpc: '2.0', error: { code: error.code || -32603, message: error.message }, id: req.body.id || null }); } } }); // JSON-RPC handler pentru regular HTTP async function handleJSONRPC(request, user) { const { method, params, id } = request; switch (method) { case 'tools/list': return { jsonrpc: '2.0', result: { tools: tools.getToolDefinitions() }, id }; case 'tools/call': const toolName = params?.name; const args = params?.arguments || {}; if (!toolName) { throw new McpError(ErrorCode.InvalidParams, 'Tool name required'); } // Adaugă user context la args args._user = user; const result = await tools.executeTool(toolName, args); return { jsonrpc: '2.0', result: { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }, id }; default: throw new McpError(ErrorCode.MethodNotFound, `Method ${method} not found`); } } // Error handling middleware app.use((err, req, res, next) => { console.error('Server error:', err); res.status(500).json({ error: 'Internal server error', message: process.env.NODE_ENV === 'development' ? err.message : undefined }); }); // Start server httpServer.listen(PORT, "0.0.0.0", () => { console.log(`🚀 Remote MCP Server running on port ${PORT}`); console.log(`🔒 OAuth endpoints ready at https://mcp.academiadepolitie.com:8443`); console.log(`📡 Accepting connections from: ${process.env.ALLOWED_ORIGINS}`); console.log(`🔐 PHP OAuth Bridge: /opt/mcp-server/oauth-bridge.php`); console.log(`\n✅ Ready for Claude Remote Connectors!`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received, closing server...'); httpServer.close(() => { console.log('Server closed'); process.exit(0); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/razvan1326/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server