Skip to main content
Glama

Obsidian MCP Server

by bazylhorsey
index.ts42.4 kB
#!/usr/bin/env node /** * Obsidian MCP Server * Provides access to Obsidian vaults through the Model Context Protocol */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { LocalConnector } from './connectors/LocalConnector.js'; import { RemoteConnector } from './connectors/RemoteConnector.js'; import { BaseConnector } from './connectors/BaseConnector.js'; import { ObsidianAPI } from './api/ObsidianAPI.js'; import { KnowledgeGraphService } from './services/KnowledgeGraph.js'; import { CanvasService } from './services/CanvasService.js'; import { DataviewService } from './services/DataviewService.js'; import { TemplateService } from './services/TemplateService.js'; import { PeriodicNotesService } from './services/PeriodicNotesService.js'; import { loadConfig } from './utils/config.js'; import type { ServerConfig } from './utils/config.js'; class ObsidianMCPServer { private server: Server; private connectors: Map<string, BaseConnector> = new Map(); private obsidianApi?: ObsidianAPI; private knowledgeGraph: KnowledgeGraphService; private canvasService: CanvasService; private dataviewService: DataviewService; private templateService: TemplateService; private periodicNotesService: PeriodicNotesService; private config!: ServerConfig; constructor() { this.server = new Server( { name: 'obsidian-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, }, } ); this.knowledgeGraph = new KnowledgeGraphService(); this.canvasService = new CanvasService(); this.dataviewService = new DataviewService(); this.templateService = new TemplateService(); this.periodicNotesService = new PeriodicNotesService(undefined, this.templateService); this.setupHandlers(); } private setupHandlers(): void { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_note', description: 'Get a note by its path', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path to the note' }, }, required: ['vault', 'path'], }, }, { name: 'search_notes', description: 'Search for notes in a vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, query: { type: 'string', description: 'Search query' }, tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' }, folder: { type: 'string', description: 'Filter by folder' }, limit: { type: 'number', description: 'Maximum number of results' }, }, required: ['vault', 'query'], }, }, { name: 'create_note', description: 'Create a new note', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path for the new note' }, content: { type: 'string', description: 'Note content' }, frontmatter: { type: 'object', description: 'Frontmatter metadata' }, }, required: ['vault', 'path', 'content'], }, }, { name: 'update_note', description: 'Update an existing note', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path to the note' }, content: { type: 'string', description: 'New content' }, frontmatter: { type: 'object', description: 'Updated frontmatter' }, }, required: ['vault', 'path', 'content'], }, }, { name: 'delete_note', description: 'Delete a note', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path to the note' }, }, required: ['vault', 'path'], }, }, { name: 'get_vault_stats', description: 'Get statistics about a vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, { name: 'list_tags', description: 'List all tags in a vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, { name: 'list_folders', description: 'List all folders in a vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, { name: 'get_knowledge_graph', description: 'Get the complete knowledge graph for a vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, { name: 'get_related_notes', description: 'Get notes related to a specific note', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path to the note' }, maxDepth: { type: 'number', description: 'Maximum link depth (default: 2)' }, }, required: ['vault', 'path'], }, }, { name: 'analyze_graph', description: 'Analyze the knowledge graph structure', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, { name: 'suggest_links', description: 'Suggest related notes for linking', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path to the note' }, limit: { type: 'number', description: 'Maximum suggestions (default: 5)' }, }, required: ['vault', 'path'], }, }, // Canvas tools { name: 'get_canvas', description: 'Get canvas file contents', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path to canvas file' }, }, required: ['vault', 'path'], }, }, { name: 'create_canvas', description: 'Create a new canvas file', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Path for new canvas' }, }, required: ['vault', 'path'], }, }, { name: 'add_canvas_node', description: 'Add a node to canvas (file, text, link, or group)', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, canvasPath: { type: 'string', description: 'Path to canvas file' }, nodeType: { type: 'string', enum: ['file', 'text', 'link', 'group'], description: 'Type of node' }, x: { type: 'number', description: 'X coordinate' }, y: { type: 'number', description: 'Y coordinate' }, width: { type: 'number', description: 'Node width' }, height: { type: 'number', description: 'Node height' }, file: { type: 'string', description: 'File path (for file nodes)' }, text: { type: 'string', description: 'Text content (for text nodes)' }, url: { type: 'string', description: 'URL (for link nodes)' }, label: { type: 'string', description: 'Label (for group nodes)' }, color: { type: 'string', description: 'Node color (1-6)' }, }, required: ['vault', 'canvasPath', 'nodeType', 'x', 'y', 'width', 'height'], }, }, { name: 'add_canvas_edge', description: 'Add an edge between nodes in canvas', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, canvasPath: { type: 'string', description: 'Path to canvas file' }, fromNode: { type: 'string', description: 'Source node ID' }, toNode: { type: 'string', description: 'Target node ID' }, label: { type: 'string', description: 'Edge label' }, color: { type: 'string', description: 'Edge color (1-6)' }, }, required: ['vault', 'canvasPath', 'fromNode', 'toNode'], }, }, { name: 'list_canvas_files', description: 'List all canvas files in vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, // Dataview tools { name: 'dataview_query', description: 'Execute a Dataview-style query on notes', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, from: { type: 'string', description: 'Source filter (folder path or #tag)' }, where: { type: 'array', items: { type: 'object', properties: { field: { type: 'string' }, operator: { type: 'string', enum: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'contains', 'startsWith', 'endsWith', 'exists'] }, value: { type: 'string' } } }, description: 'Filter conditions' }, sort: { type: 'array', items: { type: 'object', properties: { field: { type: 'string' }, direction: { type: 'string', enum: ['asc', 'desc'] } } }, description: 'Sort order' }, groupBy: { type: 'string', description: 'Field to group by' }, select: { type: 'array', items: { type: 'string' }, description: 'Fields to select' }, limit: { type: 'number', description: 'Maximum results' } }, required: ['vault'], }, }, { name: 'get_note_metadata', description: 'Get metadata for a specific note', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, path: { type: 'string', description: 'Note path' }, }, required: ['vault', 'path'], }, }, { name: 'get_unique_values', description: 'Get all unique values for a metadata field', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, field: { type: 'string', description: 'Metadata field name' }, }, required: ['vault', 'field'], }, }, // Template tools { name: 'list_templates', description: 'List all available templates', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, }, required: ['vault'], }, }, { name: 'render_template', description: 'Render a template with variables', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, templatePath: { type: 'string', description: 'Path to template file' }, variables: { type: 'object', description: 'Template variables' }, frontmatter: { type: 'object', description: 'Additional frontmatter' }, targetPath: { type: 'string', description: 'Target note path (for context)' }, }, required: ['vault', 'templatePath'], }, }, { name: 'create_from_template', description: 'Create a new note from a template', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, templatePath: { type: 'string', description: 'Path to template file' }, targetPath: { type: 'string', description: 'Path for new note' }, variables: { type: 'object', description: 'Template variables' }, frontmatter: { type: 'object', description: 'Additional frontmatter' }, }, required: ['vault', 'templatePath', 'targetPath'], }, }, // Periodic notes tools { name: 'create_daily_note', description: 'Create a daily note for a specific date', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, date: { type: 'string', description: 'Date (YYYY-MM-DD), defaults to today' }, variables: { type: 'object', description: 'Additional template variables' }, }, required: ['vault'], }, }, { name: 'create_weekly_note', description: 'Create a weekly note for a specific date', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, date: { type: 'string', description: 'Date in the week (YYYY-MM-DD), defaults to this week' }, variables: { type: 'object', description: 'Additional template variables' }, }, required: ['vault'], }, }, { name: 'create_monthly_note', description: 'Create a monthly note for a specific date', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, date: { type: 'string', description: 'Date in the month (YYYY-MM-DD), defaults to this month' }, variables: { type: 'object', description: 'Additional template variables' }, }, required: ['vault'], }, }, { name: 'create_yearly_note', description: 'Create a yearly note for a specific year', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, date: { type: 'string', description: 'Date in the year (YYYY-MM-DD), defaults to this year' }, variables: { type: 'object', description: 'Additional template variables' }, }, required: ['vault'], }, }, { name: 'get_periodic_note_info', description: 'Get info about a periodic note', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, type: { type: 'string', enum: ['daily', 'weekly', 'monthly', 'yearly'], description: 'Note type' }, date: { type: 'string', description: 'Date (YYYY-MM-DD), defaults to today' }, }, required: ['vault', 'type'], }, }, { name: 'list_periodic_notes', description: 'List periodic notes of a specific type', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, type: { type: 'string', enum: ['daily', 'weekly', 'monthly', 'yearly'], description: 'Note type' }, startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' }, endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }, }, required: ['vault', 'type'], }, }, ], })); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_note': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.getNote(args?.path as string); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'search_notes': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.searchNotes({ query: args?.query as string, tags: args?.tags as string[] | undefined, folder: args?.folder as string | undefined, limit: args?.limit as number | undefined, includeContent: true, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'create_note': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.createNote(args?.path as string, args?.content as string, args?.frontmatter as Record<string, any> | undefined); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'update_note': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.updateNote(args?.path as string, args?.content as string, args?.frontmatter as Record<string, any> | undefined); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'delete_note': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.deleteNote(args?.path as string); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'get_vault_stats': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.getStats(); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'list_tags': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.listTags(); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'list_folders': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.listFolders(); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'get_knowledge_graph': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (notesResult.success && notesResult.data) { this.knowledgeGraph.updateNotes(notesResult.data); const graph = this.knowledgeGraph.buildGraph(); return { content: [{ type: 'text', text: JSON.stringify(graph, null, 2) }], }; } throw new Error('Failed to build knowledge graph'); } case 'get_related_notes': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (notesResult.success && notesResult.data) { this.knowledgeGraph.updateNotes(notesResult.data); const related = this.knowledgeGraph.getRelatedNotes(args?.path as string, (args?.maxDepth as number) || 2); return { content: [{ type: 'text', text: JSON.stringify(related, null, 2) }], }; } throw new Error('Failed to get related notes'); } case 'analyze_graph': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (notesResult.success && notesResult.data) { this.knowledgeGraph.updateNotes(notesResult.data); const analysis = this.knowledgeGraph.analyzeGraph(); return { content: [{ type: 'text', text: JSON.stringify(analysis, null, 2) }], }; } throw new Error('Failed to analyze graph'); } case 'suggest_links': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (notesResult.success && notesResult.data) { this.knowledgeGraph.updateNotes(notesResult.data); const suggestions = this.knowledgeGraph.suggestRelatedNotes(args?.path as string, (args?.limit as number) || 5); return { content: [{ type: 'text', text: JSON.stringify(suggestions, null, 2) }], }; } throw new Error('Failed to suggest links'); } // Canvas tools case 'get_canvas': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.canvasService.readCanvas(connector.vaultPath, args?.path as string); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'create_canvas': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.canvasService.createCanvas(connector.vaultPath, args?.path as string); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'add_canvas_node': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const nodeType = args?.nodeType as string; const canvasPath = args?.canvasPath as string; const commonOpts = { x: args?.x as number, y: args?.y as number, width: args?.width as number, height: args?.height as number, color: args?.color as any, }; let result; switch (nodeType) { case 'file': result = await this.canvasService.addFileNode(connector.vaultPath, canvasPath, { ...commonOpts, file: args?.file as string, subpath: args?.subpath as string | undefined, }); break; case 'text': result = await this.canvasService.addTextNode(connector.vaultPath, canvasPath, { ...commonOpts, text: args?.text as string, }); break; case 'link': result = await this.canvasService.addLinkNode(connector.vaultPath, canvasPath, { ...commonOpts, url: args?.url as string, }); break; case 'group': result = await this.canvasService.addGroupNode(connector.vaultPath, canvasPath, { ...commonOpts, label: args?.label as string | undefined, }); break; default: throw new Error(`Unknown node type: ${nodeType}`); } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'add_canvas_edge': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.canvasService.addEdge(connector.vaultPath, args?.canvasPath as string, { fromNode: args?.fromNode as string, toNode: args?.toNode as string, label: args?.label as string | undefined, color: args?.color as any, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'list_canvas_files': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.canvasService.listCanvasFiles(connector.vaultPath); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } // Dataview tools case 'dataview_query': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (!notesResult.success || !notesResult.data) { throw new Error('Failed to get notes'); } this.dataviewService.updateNotes(notesResult.data); const query: any = {}; if (args?.from) query.from = args.from; if (args?.where) query.where = args.where; if (args?.sort) query.sort = args.sort; if (args?.groupBy) query.groupBy = args.groupBy; if (args?.select) query.select = args.select; if (args?.limit) query.limit = args.limit; const result = await this.dataviewService.executeQuery(query); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'get_note_metadata': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (!notesResult.success || !notesResult.data) { throw new Error('Failed to get notes'); } this.dataviewService.updateNotes(notesResult.data); const metadata = this.dataviewService.getMetadata(args?.path as string); if (!metadata) { throw new Error(`Note not found: ${args?.path}`); } return { content: [{ type: 'text', text: JSON.stringify(metadata, null, 2) }], }; } case 'get_unique_values': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const notesResult = await connector.getAllNotes(); if (!notesResult.success || !notesResult.data) { throw new Error('Failed to get notes'); } this.dataviewService.updateNotes(notesResult.data); const values = this.dataviewService.getUniqueValues(args?.field as string); return { content: [{ type: 'text', text: JSON.stringify(values, null, 2) }], }; } // Template tools case 'list_templates': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.templateService.listTemplates(connector.vaultPath); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'render_template': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.templateService.renderTemplate( connector.vaultPath, args?.templatePath as string, { variables: args?.variables as Record<string, any> | undefined, frontmatter: args?.frontmatter as Record<string, any> | undefined, targetPath: args?.targetPath as string | undefined, } ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'create_from_template': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const result = await this.templateService.createFromTemplate( connector.vaultPath, args?.templatePath as string, args?.targetPath as string, { variables: args?.variables as Record<string, any> | undefined, frontmatter: args?.frontmatter as Record<string, any> | undefined, } ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } // Periodic notes tools case 'create_daily_note': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const date = args?.date ? new Date(args.date as string) : undefined; const result = await this.periodicNotesService.createPeriodicNote( connector.vaultPath, 'daily', date, args?.variables as Record<string, any> | undefined ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'create_weekly_note': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const date = args?.date ? new Date(args.date as string) : undefined; const result = await this.periodicNotesService.createPeriodicNote( connector.vaultPath, 'weekly', date, args?.variables as Record<string, any> | undefined ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'create_monthly_note': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const date = args?.date ? new Date(args.date as string) : undefined; const result = await this.periodicNotesService.createPeriodicNote( connector.vaultPath, 'monthly', date, args?.variables as Record<string, any> | undefined ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'create_yearly_note': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const date = args?.date ? new Date(args.date as string) : undefined; const result = await this.periodicNotesService.createPeriodicNote( connector.vaultPath, 'yearly', date, args?.variables as Record<string, any> | undefined ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'get_periodic_note_info': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const type = args?.type as 'daily' | 'weekly' | 'monthly' | 'yearly'; const date = args?.date ? new Date(args.date as string) : undefined; const result = await this.periodicNotesService.getPeriodicNoteInfo( connector.vaultPath, type, date ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'list_periodic_notes': { const connector = this.connectors.get(args?.vault as string); if (!connector || !connector.vaultPath) { throw new Error(`Vault "${args?.vault}" not found or not a local vault`); } const type = args?.type as 'daily' | 'weekly' | 'monthly' | 'yearly'; const startDate = args?.startDate ? new Date(args.startDate as string) : undefined; const endDate = args?.endDate ? new Date(args.endDate as string) : undefined; const result = await this.periodicNotesService.listPeriodicNotes( connector.vaultPath, type, startDate, endDate ); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } default: 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, }; } }); // List resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => { const resources = []; for (const [vaultName, connector] of this.connectors.entries()) { const notesResult = await connector.getAllNotes(); if (notesResult.success && notesResult.data) { for (const note of notesResult.data) { resources.push({ uri: `obsidian://${vaultName}/${note.path}`, name: note.title, mimeType: 'text/markdown', description: `Note from ${vaultName} vault`, }); } } } return { resources }; }); // Read resource this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; const match = uri.match(/^obsidian:\/\/([^\/]+)\/(.+)$/); if (!match) { throw new Error('Invalid resource URI'); } const [, vaultName, notePath] = match; const connector = this.connectors.get(vaultName); if (!connector) { throw new Error(`Vault "${vaultName}" not found`); } const result = await connector.getNote(notePath); if (!result.success || !result.data) { throw new Error(result.error || 'Failed to read note'); } return { contents: [ { uri, mimeType: 'text/markdown', text: result.data.content, }, ], }; }); } async initialize(): Promise<void> { // Load configuration this.config = await loadConfig(); // Initialize connectors for each vault for (const vaultConfig of this.config.vaults) { let connector: BaseConnector; if (vaultConfig.type === 'local') { connector = new LocalConnector(vaultConfig); } else if (vaultConfig.type === 'remote') { connector = new RemoteConnector(vaultConfig); } else { console.error(`Unknown vault type: ${vaultConfig.type}`); continue; } const result = await connector.initialize(); if (result.success) { this.connectors.set(vaultConfig.name, connector); console.error(`Initialized ${vaultConfig.type} vault: ${vaultConfig.name}`); } else { console.error(`Failed to initialize vault ${vaultConfig.name}: ${result.error}`); } } // Initialize Obsidian API if configured if (this.config.obsidianApi) { this.obsidianApi = new ObsidianAPI(this.config.obsidianApi); const available = await this.obsidianApi.isAvailable(); if (available) { console.error('Obsidian API connected'); } else { console.error('Obsidian API not available'); } } console.error(`Server initialized with ${this.connectors.size} vault(s)`); } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Obsidian MCP server running on stdio'); } async shutdown(): Promise<void> { for (const connector of this.connectors.values()) { await connector.close(); } if (this.obsidianApi) { this.obsidianApi.close(); } } } // Main execution const server = new ObsidianMCPServer(); async function main() { try { await server.initialize(); await server.run(); } catch (error) { console.error('Server error:', error); process.exit(1); } } // Handle shutdown process.on('SIGINT', async () => { console.error('Shutting down...'); await server.shutdown(); process.exit(0); }); process.on('SIGTERM', async () => { console.error('Shutting down...'); await server.shutdown(); process.exit(0); }); main();

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/bazylhorsey/obsidian-mcp-server'

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