Skip to content

Instantly share code, notes, and snippets.

@gunzip
Created December 24, 2025 09:30
Show Gist options
  • Select an option

  • Save gunzip/adbab8d0968147fcda55024c29a02aff to your computer and use it in GitHub Desktop.

Select an option

Save gunzip/adbab8d0968147fcda55024c29a02aff to your computer and use it in GitHub Desktop.
import express from 'express';
import jwt from 'jsonwebtoken';
import { createClient } from 'redis';
import { URLSearchParams } from 'url';
import { createMcpServer, McpServerConfig } from '@modelcontextprotocol/sdk/server';
import { Request, Response } from 'express';
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
// Redis client
const redis = createClient({ url: process.env.REDIS_URL });
redis.on('error', err => console.error('Redis Error:', err));
await redis.connect();
const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-key';
// Configurazione OAuth providers
const oauthProviders = {
github: {
authorization_endpoint: 'https://github.com/login/oauth/authorize',
token_endpoint: 'https://github.com/login/oauth/access_token',
userinfo_endpoint: 'https://api.github.com/user',
client_id: process.env.GITHUB_CLIENT_ID!,
client_secret: process.env.GITHUB_CLIENT_SECRET!,
scopes: ['user:email'],
redirect_uri: 'https://your-server.com/mcp/callback/github', // cambia con il tuo dominio
},
notion: {
authorization_endpoint: 'https://www.notion.com/oauth/authorize',
token_endpoint: 'https://api.notion.com/v1/oauth/token',
client_id: process.env.NOTION_CLIENT_ID!,
client_secret: process.env.NOTION_CLIENT_SECRET!,
scopes: ['pages:read', 'pages:write'],
redirect_uri: 'https://your-server.com/mcp/callback/notion',
},
// Aggiungi qui altri provider se necessario
};
// Middleware per autenticazione con token interno JWT
function authenticate(req: Request, res: Response, next: Function) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, JWT_SECRET) as { githubLogin: string };
(req as any).githubLogin = payload.githubLogin;
(req as any).userId = payload.githubLogin;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
// 1. Inizia login con GitHub
app.get('/mcp/authorize', (req: Request, res: Response) => {
const state = Math.random().toString(36).substring(2);
const authUrl = `${oauthProviders.github.authorization_endpoint}?` +
`client_id=${oauthProviders.github.client_id}&` +
`redirect_uri=${encodeURIComponent(oauthProviders.github.redirect_uri)}&` +
`scope=${oauthProviders.github.scopes.join(' ')}&` +
`state=${state}`;
res.redirect(authUrl);
});
// 2. Callback GitHub → crea token interno
app.get('/mcp/callback/github', async (req: Request, res: Response) => {
const { code } = req.query;
if (!code) return res.status(400).send('No code received');
try {
// Scambia code per access_token GitHub
const tokenResponse = await fetch(oauthProviders.github.token_endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: oauthProviders.github.client_id,
client_secret: oauthProviders.github.client_secret,
code: code as string,
redirect_uri: oauthProviders.github.redirect_uri,
}),
});
if (!tokenResponse.ok) throw new Error('Failed to exchange code for token');
const tokenData = await tokenResponse.json();
const access_token = tokenData.access_token;
// Ottieni info utente GitHub
const userResponse = await fetch(oauthProviders.github.userinfo_endpoint, {
headers: { Authorization: `Bearer ${access_token}` },
});
if (!userResponse.ok) throw new Error('Failed to fetch user info');
const userData = await userResponse.json();
const githubLogin = userData.login;
// Crea token interno JWT
const internalToken = jwt.sign(
{ githubLogin, sub: githubLogin },
JWT_SECRET,
{ expiresIn: '24h' }
);
// Salva sessione
await redis.set(`session:${githubLogin}`, internalToken, { EX: 86400 });
// Risposta per l'utente
res.send(`
<html>
<body>
<h1>Benvenuto, @${githubLogin}!</h1>
<p>Autorizzazione completata.</p>
<p>Chiudi questa finestra e torna all'app.</p>
<script>
if (window.location.href.includes('claude://')) {
window.location = "claude://authorized?token=${internalToken}";
}
</script>
</body>
</html>
`);
} catch (err) {
console.error(err);
res.status(500).send('Errore durante l\'autorizzazione');
}
});
// 3. Autorizza un provider specifico (es. Notion)
app.get('/mcp/authorize-provider', authenticate, (req: Request, res: Response) => {
const { provider } = req.query as { provider: string };
if (!provider || !oauthProviders[provider]) {
return res.status(400).json({ error: 'Provider non supportato' });
}
const state = Math.random().toString(36).substring(2);
const authUrl = `${oauthProviders[provider].authorization_endpoint}?` +
`client_id=${oauthProviders[provider].client_id}&` +
`redirect_uri=${encodeURIComponent(oauthProviders[provider].redirect_uri)}&` +
`response_type=code&` +
`scope=${oauthProviders[provider].scopes.join(' ')}&` +
`state=${state}`;
res.redirect(authUrl);
});
// 4. Callback per provider esterno
app.get('/mcp/callback/:provider', authenticate, async (req: Request, res: Response) => {
const { provider } = req.params;
const { code } = req.query;
if (!code || !oauthProviders[provider]) return res.status(400).send('Callback non valido');
try {
const userId = (req as any).githubLogin;
const tokenResponse = await fetch(oauthProviders[provider].token_endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
code: code as string,
client_id: oauthProviders[provider].client_id,
client_secret: oauthProviders[provider].client_secret,
redirect_uri: oauthProviders[provider].redirect_uri,
grant_type: 'authorization_code',
}),
});
if (!tokenResponse.ok) throw new Error('Failed to exchange code');
const tokenData = await tokenResponse.json();
const access_token = tokenData.access_token;
const refresh_token = tokenData.refresh_token;
const expires_in = tokenData.expires_in || 3600;
// Memorizza token
await redis.set(`token:${userId}:${provider}`, access_token, { EX: expires_in });
if (refresh_token) {
await redis.set(`refresh:${userId}:${provider}`, refresh_token);
}
res.send(`
<html>
<body>
<h1>${provider} autorizzato!</h1>
<p>Torna all'applicazione.</p>
</body>
</html>
`);
} catch (err) {
console.error(err);
res.status(500).send('Errore');
}
});
// 5. Esempio tool: Crea pagina Notion
const notionTool = {
name: 'create_notion_page',
description: 'Crea una nuova pagina in Notion',
parameters: {
title: { type: 'string', required: true },
content: { type: 'string', required: true },
},
execute: async (params: { title: string; content: string }, context: any) => {
const userId = context.user?.githubLogin || (context.request as any).githubLogin;
if (!userId) throw new Error('Utente non autenticato');
let accessToken = await redis.get(`token:${userId}:notion`);
if (!accessToken) {
throw {
status: 401,
headers: { 'WWW-Authenticate': 'Bearer realm="notion"' },
body: { error: 'login_required', provider: 'notion' },
};
}
// Chiama Notion API
const response = await fetch('https://api.notion.com/v1/pages', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
},
body: JSON.stringify({
parent: { page_id: 'YOUR_PARENT_PAGE_ID' }, // cambia con il tuo
properties: { title: [{ text: { content: params.title } }] },
children: [{ paragraph: { rich_text: [{ text: { content: params.content } }] } }],
}),
});
if (!response.ok) {
if (response.status === 401) {
// Potresti implementare qui il refresh token
throw { status: 401, body: { error: 'token_expired' } };
}
throw new Error('Errore Notion API');
}
const data = await response.json();
return { page_id: data.id, url: data.url };
},
};
// Configurazione MCP Server
const mcpConfig: McpServerConfig = {
tools: [notionTool],
protectedResourceMetadata: true,
};
const mcpServer = createMcpServer(mcpConfig);
// Proteggi tutte le route MCP con autenticazione
app.use('/mcp', authenticate, mcpServer.handler);
app.listen(port, () => {
console.log(`Server MCP avviato su http://localhost:${port}/mcp`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment