version: '3.8' services: # CouchDB für Obsidian Livesync couchdb: image: couchdb:3.3 container_name: cortex-couchdb ports: - "5984:5984" environment: - COUCHDB_USER=admin - COUCHDB_PASSWORD=admin123 - PUID=1000 - PGID=1000 volumes: - /opt/obsidian/couchdb:/opt/couchdb/data restart: unless-stopped networks: - cortex-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5984/_up"] interval: 30s timeout: 10s retries: 3 # MCP Server für Hermes Agent obsidian-mcp: image: node:20-alpine container_name: cortex-obsidian-mcp working_dir: /app volumes: - /opt/obsidian/vault:/vault - /opt/obsidian/mcp-data:/data ports: - "3002:3002" environment: - NODE_ENV=production - VAULT_PATH=/vault - MCP_PORT=3002 command: > sh -c " npm init -y && npm install express cors body-parser && cat > server.js << 'EOFJS' const express = require('express'); const fs = require('fs'); const path = require('path'); const cors = require('cors'); const app = express(); app.use(cors()); app.use(express.json()); const VAULT_PATH = process.env.VAULT_PATH || '/vault'; // MCP Tool: Search notes app.post('/api/mcp/search', (req, res) => { const { query } = req.body; try { const results = []; const walk = (dir) => { const files = fs.readdirSync(dir); files.forEach(file => { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory() && !file.startsWith('.')) { walk(filePath); } else if (file.endsWith('.md')) { const content = fs.readFileSync(filePath, 'utf8'); if (content.includes(query)) { results.push({ file: path.relative(VAULT_PATH, filePath), matches: content.split('\n').filter(l => l.includes(query)) }); } } }); }; walk(VAULT_PATH); res.json({ results, count: results.length }); } catch (err) { res.status(500).json({ error: err.message }); } }); // MCP Tool: Create note app.post('/api/mcp/create', (req, res) => { const { title, content, tags } = req.body; try { const filename = title.toLowerCase().replace(/\s+/g, '-') + '.md'; const filepath = path.join(VAULT_PATH, filename); const frontmatter = \`--- title: \${title} tags: [\${tags?.join(', ') || ''}] created: \${new Date().toISOString()} --- \`; fs.writeFileSync(filepath, frontmatter + (content || '')); res.json({ success: true, file: filename }); } catch (err) { res.status(500).json({ error: err.message }); } }); // MCP Tool: Get note app.get('/api/mcp/note/:filename', (req, res) => { try { const filepath = path.join(VAULT_PATH, req.params.filename); const content = fs.readFileSync(filepath, 'utf8'); res.json({ content }); } catch (err) { res.status(404).json({ error: 'Note not found' }); } }); // List all notes app.get('/api/mcp/notes', (req, res) => { try { const notes = []; const walk = (dir) => { const files = fs.readdirSync(dir); files.forEach(file => { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory() && !file.startsWith('.')) { walk(filePath); } else if (file.endsWith('.md')) { notes.push(path.relative(VAULT_PATH, filePath)); } }); }; walk(VAULT_PATH); res.json({ notes }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.listen(3002, () => { console.log('Obsidian MCP Server running on port 3002'); }); EOFJS node server.js " restart: unless-stopped networks: - cortex-network depends_on: - couchdb healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3002/api/mcp/notes"] interval: 30s timeout: 10s retries: 3 networks: cortex-network: driver: bridge