Skip to main content
Glama

Notion MCP Server

Official
by makenotion
start-server.ts8.59 kB
import path from 'node:path' import { fileURLToPath } from 'url' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js' import { randomUUID, randomBytes } from 'node:crypto' import express from 'express' import { initProxy, ValidationError } from '../src/init-server' export async function startServer(args: string[] = process.argv) { const filename = fileURLToPath(import.meta.url) const directory = path.dirname(filename) const specPath = path.resolve(directory, '../scripts/notion-openapi.json') const baseUrl = process.env.BASE_URL ?? undefined // Parse command line arguments manually (similar to slack-mcp approach) function parseArgs() { const args = process.argv.slice(2); let transport = 'stdio'; // default let port = 3000; let authToken: string | undefined; for (let i = 0; i < args.length; i++) { if (args[i] === '--transport' && i + 1 < args.length) { transport = args[i + 1]; i++; // skip next argument } else if (args[i] === '--port' && i + 1 < args.length) { port = parseInt(args[i + 1], 10); i++; // skip next argument } else if (args[i] === '--auth-token' && i + 1 < args.length) { authToken = args[i + 1]; i++; // skip next argument } else if (args[i] === '--help' || args[i] === '-h') { console.log(` Usage: notion-mcp-server [options] Options: --transport <type> Transport type: 'stdio' or 'http' (default: stdio) --port <number> Port for HTTP server when using Streamable HTTP transport (default: 3000) --auth-token <token> Bearer token for HTTP transport authentication (optional) --help, -h Show this help message Environment Variables: NOTION_TOKEN Notion integration token (recommended) OPENAPI_MCP_HEADERS JSON string with Notion API headers (alternative) AUTH_TOKEN Bearer token for HTTP transport authentication (alternative to --auth-token) Examples: notion-mcp-server # Use stdio transport (default) notion-mcp-server --transport stdio # Use stdio transport explicitly notion-mcp-server --transport http # Use Streamable HTTP transport on port 3000 notion-mcp-server --transport http --port 8080 # Use Streamable HTTP transport on port 8080 notion-mcp-server --transport http --auth-token mytoken # Use Streamable HTTP transport with custom auth token AUTH_TOKEN=mytoken notion-mcp-server --transport http # Use Streamable HTTP transport with auth token from env var `); process.exit(0); } // Ignore unrecognized arguments (like command name passed by Docker) } return { transport: transport.toLowerCase(), port, authToken }; } const options = parseArgs() const transport = options.transport if (transport === 'stdio') { // Use stdio transport (default) const proxy = await initProxy(specPath, baseUrl) await proxy.connect(new StdioServerTransport()) return proxy.getServer() } else if (transport === 'http') { // Use Streamable HTTP transport const app = express() app.use(express.json()) // Generate or use provided auth token (from CLI arg or env var) const authToken = options.authToken || process.env.AUTH_TOKEN || randomBytes(32).toString('hex') if (!options.authToken && !process.env.AUTH_TOKEN) { console.log(`Generated auth token: ${authToken}`) console.log(`Use this token in the Authorization header: Bearer ${authToken}`) } // Authorization middleware const authenticateToken = (req: express.Request, res: express.Response, next: express.NextFunction): void => { const authHeader = req.headers['authorization'] const token = authHeader && authHeader.split(' ')[1] // Bearer TOKEN if (!token) { res.status(401).json({ jsonrpc: '2.0', error: { code: -32001, message: 'Unauthorized: Missing bearer token', }, id: null, }) return } if (token !== authToken) { res.status(403).json({ jsonrpc: '2.0', error: { code: -32002, message: 'Forbidden: Invalid bearer token', }, id: null, }) return } next() } // Health endpoint (no authentication required) app.get('/health', (req, res) => { res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString(), transport: 'http', port: options.port }) }) // Apply authentication to all /mcp routes app.use('/mcp', authenticateToken) // Map to store transports by session ID const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {} // Handle POST requests for client-to-server communication app.post('/mcp', async (req, res) => { try { // Check for existing session ID const sessionId = req.headers['mcp-session-id'] as string | undefined let transport: StreamableHTTPServerTransport if (sessionId && transports[sessionId]) { // Reuse existing transport transport = transports[sessionId] } else if (!sessionId && isInitializeRequest(req.body)) { // New initialization request transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (sessionId) => { // Store the transport by session ID transports[sessionId] = transport } }) // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { delete transports[transport.sessionId] } } const proxy = await initProxy(specPath, baseUrl) await proxy.connect(transport) } else { // Invalid request res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: null, }) return } // Handle the request await transport.handleRequest(req, res, req.body) } catch (error) { console.error('Error handling MCP request:', error) if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }) } } }) // Handle GET requests for server-to-client notifications via Streamable HTTP app.get('/mcp', async (req, res) => { const sessionId = req.headers['mcp-session-id'] as string | undefined if (!sessionId || !transports[sessionId]) { res.status(400).send('Invalid or missing session ID') return } const transport = transports[sessionId] await transport.handleRequest(req, res) }) // Handle DELETE requests for session termination app.delete('/mcp', async (req, res) => { const sessionId = req.headers['mcp-session-id'] as string | undefined if (!sessionId || !transports[sessionId]) { res.status(400).send('Invalid or missing session ID') return } const transport = transports[sessionId] await transport.handleRequest(req, res) }) const port = options.port app.listen(port, '0.0.0.0', () => { console.log(`MCP Server listening on port ${port}`) console.log(`Endpoint: http://0.0.0.0:${port}/mcp`) console.log(`Health check: http://0.0.0.0:${port}/health`) console.log(`Authentication: Bearer token required`) if (options.authToken) { console.log(`Using provided auth token`) } }) // Return a dummy server for compatibility return { close: () => {} } } else { throw new Error(`Unsupported transport: ${transport}. Use 'stdio' or 'http'.`) } } startServer(process.argv).catch(error => { if (error instanceof ValidationError) { console.error('Invalid OpenAPI 3.1 specification:') error.errors.forEach(err => console.error(err)) } else { console.error('Error:', error) } process.exit(1) })

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/makenotion/notion-mcp-server'

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