Skip to main content
Glama
gamzadongza

Danbooru-Turso MCP Server

by gamzadongza
index.ts9.99 kB
import express from 'express'; import cors from 'cors'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js'; import { DanbooruService } from './services/danbooru.js'; import { TursoService } from './services/turso.js'; import { collectAndSave } from './tools/collect.js'; // Environment variables const PORT = process.env.PORT || 3000; const TURSO_DATABASE_URL = process.env.TURSO_DATABASE_URL; const TURSO_AUTH_TOKEN = process.env.TURSO_AUTH_TOKEN; if (!TURSO_DATABASE_URL || !TURSO_AUTH_TOKEN) { console.error('Error: TURSO_DATABASE_URL and TURSO_AUTH_TOKEN required'); process.exit(1); } // Initialize services const danbooru = new DanbooruService(); const turso = new TursoService(TURSO_DATABASE_URL, TURSO_AUTH_TOKEN); // Initialize database await turso.initialize(); // Define tools (only collect_and_save) const tools: Tool[] = [ { name: 'collect_and_save', description: `**[DATA COLLECTION]** Collect character posts from Danbooru API and save to Turso database with automatic pagination and upserts (updates existing posts). **IMPORTANT: Before collecting, check post count first!** 1. Use danbooru-tags-mcp.get_post_count(tag) to see total posts and get collection strategy 2. If get_post_count is unavailable, scrape Danbooru website for post count 3. Plan your collection batches using start_page parameter based on the count Use this when: - You want to BUILD a dataset for later analysis - You need to save posts for offline or repeated queries - You want to collect large amounts of data (100+ posts) efficiently - You need to keep the database updated with new posts For large datasets (5000+ posts): - Use start_page parameter to collect in batches - Each page = 200 posts, so start_page=11 begins at post 2001 - Example: 9000 posts = 5 batches (pages 1, 11, 21, 31, 41) After collection, use Turso SQL tools to query and analyze the saved data. For real-time exploration without saving, use Danbooru Tags tools.`, inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Character tag to collect (e.g., "yatogami_tenka")' }, max_posts: { type: 'number', description: 'Maximum number of posts to collect (optional)' }, start_page: { type: 'number', description: 'Starting page number for pagination (default: 1, each page has 200 posts)' } }, required: ['tag'] } } ]; // Create MCP server const server = new Server( { name: 'danbooru-turso-mcp', version: '1.0.0' }, { capabilities: { tools: {} } } ); // Tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { if (name === 'collect_and_save') { const { tag, max_posts, start_page } = args as { tag: string; max_posts?: number; start_page?: number }; const result = await collectAndSave(danbooru, turso, tag, max_posts, start_page); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } throw new Error(`Unknown tool: ${name}`); } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) }], isError: true }; } }); // HTTP Server for Smithery const app = express(); app.use(cors({ origin: '*' })); app.use(express.json()); // Helper function to send response in JSON or SSE format function sendResponse(res: any, acceptHeader: string, data: any) { const wantsSSE = acceptHeader.includes('text/event-stream'); if (wantsSSE) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.write('event: message\n'); res.write(`data: ${JSON.stringify(data)}\n\n`); res.end(); } else { res.json(data); } } // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy' }); }); // Server info app.get('/mcp', (req, res) => { res.json({ name: 'danbooru-turso-mcp', version: '1.0.0', description: 'Collect Danbooru data and save to Turso database', endpoints: { mcp: 'POST /mcp - JSON-RPC endpoint', sse: 'GET /sse - Server-Sent Events endpoint', health: 'GET /health - Health check' }, tools: tools.map(t => ({ name: t.name, description: t.description })) }); }); // Main MCP endpoint app.post('/mcp', async (req, res) => { const acceptHeader = req.headers['accept'] || ''; const request = req.body; try { // Handle initialize if (request.method === 'initialize') { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'danbooru-turso-mcp', version: '1.0.0' } } }); return; } // Handle tools/list if (request.method === 'tools/list') { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { tools } }); return; } // Handle tools/call if (request.method === 'tools/call') { const { name, arguments: args } = request.params; try { if (name === 'collect_and_save') { const { tag, max_posts, start_page } = args as { tag: string; max_posts?: number; start_page?: number }; const toolResult = await collectAndSave(danbooru, turso, tag, max_posts, start_page); sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(toolResult, null, 2) }] } }); } else { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: `Unknown tool: ${name}` } }); } } catch (toolError) { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: toolError instanceof Error ? toolError.message : String(toolError) }) }], isError: true } }); } return; } // Handle empty capabilities if (request.method === 'resources/list') { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { resources: [] } }); return; } if (request.method === 'prompts/list') { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { prompts: [] } }); return; } if (request.method === 'resources/templates/list') { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: { resourceTemplates: [] } }); return; } // Handle ping/heartbeat if (request.method === 'ping' || request.method === 'heartbeat') { sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, result: {} }); return; } // Handle notifications if (request.method?.startsWith('notifications/')) { console.log(`Notification received: ${request.method}`); if (acceptHeader.includes('text/event-stream')) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.write('\n\n'); res.end(); } else { res.status(200).end(); } return; } // Unknown method sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: 'Method not found' } }); } catch (error) { console.error('Request error:', error); sendResponse(res, acceptHeader, { jsonrpc: '2.0', id: request.id, error: { code: -32603, message: error instanceof Error ? error.message : 'Internal error' } }); } }); // SSE endpoint for MCP app.get('/sse', async (req, res) => { console.error('SSE connection established'); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const transport = new SSEServerTransport('/message', res); await server.connect(transport); req.on('close', () => { console.error('SSE connection closed'); }); }); app.post('/message', async (req, res) => { console.error('Message received:', req.body); res.status(200).json({ received: true }); }); // Start HTTP server const server_instance = app.listen(Number(PORT), '0.0.0.0', () => { console.error(`Danbooru-Turso MCP server running on port ${PORT}`); console.error(`SSE endpoint: http://0.0.0.0:${PORT}/sse`); console.error(`Health check: http://0.0.0.0:${PORT}/health`); console.error(`Database: ${TURSO_DATABASE_URL}`); }); // Graceful shutdown process.on('SIGTERM', () => { console.error('SIGTERM signal received: closing HTTP server'); server_instance.close(() => { console.error('HTTP server closed'); process.exit(0); }); }); process.on('SIGINT', () => { console.error('SIGINT signal received: closing HTTP server'); server_instance.close(() => { console.error('HTTP 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/gamzadongza/danbooru-turso-mcp'

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