Skip to main content
Glama

Obsidian MCP Server

Apache 2.0
338
222
  • Apple
  • Linux
server.ts12.7 kB
/** * @fileoverview Main entry point for the MCP (Model Context Protocol) server. * This file orchestrates the server's lifecycle: * 1. Initializes the core `McpServer` instance (from `@modelcontextprotocol/sdk`) with its identity and capabilities. * 2. Registers available resources and tools, making them discoverable and usable by clients. * 3. Selects and starts the appropriate communication transport (stdio or Streamable HTTP) * based on configuration. * 4. Handles top-level error management during startup. * * MCP Specification References: * - Lifecycle: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/lifecycle.mdx * - Overview (Capabilities): https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/index.mdx * - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx * @module src/mcp-server/server */ import { ServerType } from "@hono/node-server"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // Import validated configuration and environment details. import { config, environment } from "../config/index.js"; // Import core utilities: ErrorHandler, logger, requestContextService. import { ErrorHandler, logger, requestContextService } from "../utils/index.js"; // Import the Obsidian service import { ObsidianRestApiService } from "../services/obsidianRestAPI/index.js"; // Import the Vault Cache service import { VaultCacheService } from "../services/obsidianRestAPI/vaultCache/index.js"; // Import registration functions for specific resources and tools. import { registerObsidianDeleteNoteTool } from "./tools/obsidianDeleteNoteTool/index.js"; import { registerObsidianGlobalSearchTool } from "./tools/obsidianGlobalSearchTool/index.js"; import { registerObsidianListNotesTool } from "./tools/obsidianListNotesTool/index.js"; import { registerObsidianReadNoteTool } from "./tools/obsidianReadNoteTool/index.js"; import { registerObsidianSearchReplaceTool } from "./tools/obsidianSearchReplaceTool/index.js"; import { registerObsidianUpdateNoteTool } from "./tools/obsidianUpdateNoteTool/index.js"; import { registerObsidianManageFrontmatterTool } from "./tools/obsidianManageFrontmatterTool/index.js"; import { registerObsidianManageTagsTool } from "./tools/obsidianManageTagsTool/index.js"; // Import transport setup functions. import { startHttpTransport } from "./transports/httpTransport.js"; import { connectStdioTransport } from "./transports/stdioTransport.js"; /** * Creates and configures a new instance of the `McpServer`. * * This function is central to defining the server's identity and functionality * as presented to connecting clients during the MCP initialization phase. * It uses pre-instantiated shared services like Obsidian API and Vault Cache. * * MCP Spec Relevance: * - Server Identity (`serverInfo`): The `name` and `version` provided here are part * of the `ServerInformation` object returned in the `InitializeResult` message. * - Capabilities Declaration: Declares supported features (logging, dynamic resources/tools). * - Resource/Tool Registration: Calls registration functions, passing necessary service instances. * * Design Note: This factory is called once for 'stdio' transport and per session for 'http' transport. * * @param {ObsidianRestApiService} obsidianService - The shared Obsidian REST API service instance. * @param {VaultCacheService | undefined} vaultCacheService - The shared Vault Cache service instance, which may be undefined if disabled. * @returns {Promise<McpServer>} A promise resolving with the configured `McpServer` instance. * @throws {Error} If any resource or tool registration fails. * @private */ async function createMcpServerInstance( obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined, ): Promise<McpServer> { const context = requestContextService.createRequestContext({ operation: "createMcpServerInstance", }); logger.info("Initializing MCP server instance with shared services", context); requestContextService.configure({ appName: config.mcpServerName, appVersion: config.mcpServerVersion, environment, }); logger.debug("Instantiating McpServer with capabilities", { ...context, serverInfo: { name: config.mcpServerName, version: config.mcpServerVersion, }, capabilities: { logging: {}, resources: { listChanged: true }, tools: { listChanged: true }, }, }); const server = new McpServer( { name: config.mcpServerName, version: config.mcpServerVersion }, { capabilities: { logging: {}, // Server can receive logging/setLevel and send notifications/message resources: { listChanged: true }, // Server supports dynamic resource lists tools: { listChanged: true }, // Server supports dynamic tool lists }, }, ); try { logger.debug( "Registering resources and tools using shared services...", context, ); // Register all tools, passing the vaultCacheService which may be undefined await registerObsidianListNotesTool(server, obsidianService); await registerObsidianReadNoteTool(server, obsidianService); await registerObsidianDeleteNoteTool( server, obsidianService, vaultCacheService, ); if (vaultCacheService) { await registerObsidianGlobalSearchTool( server, obsidianService, vaultCacheService, ); } else { logger.warning( "Skipping registration of 'obsidian_global_search' because the Vault Cache Service is disabled.", context, ); } await registerObsidianSearchReplaceTool( server, obsidianService, vaultCacheService, ); await registerObsidianUpdateNoteTool( server, obsidianService, vaultCacheService, ); await registerObsidianManageFrontmatterTool( server, obsidianService, vaultCacheService, ); await registerObsidianManageTagsTool( server, obsidianService, vaultCacheService, ); logger.info("Resources and tools registered successfully", context); if (vaultCacheService) { logger.info( "Triggering background vault cache build (if not already built/building)...", context, ); // Intentionally not awaiting this promise to allow server startup to proceed. // Errors are logged within the catch block. vaultCacheService.buildVaultCache().catch((cacheBuildError) => { logger.error("Error occurred during background vault cache build", { ...context, // Use the initial context for correlation subOperation: "BackgroundVaultCacheBuild", // Add sub-operation for clarity error: cacheBuildError instanceof Error ? cacheBuildError.message : String(cacheBuildError), stack: cacheBuildError instanceof Error ? cacheBuildError.stack : undefined, }); }); } } catch (err) { logger.error("Failed to register resources/tools", { ...context, error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined, }); throw err; // Re-throw to be caught by the caller (e.g., startTransport) } return server; } /** * Selects, sets up, and starts the appropriate MCP transport layer based on configuration. * This function acts as the bridge between the core server logic and the communication channel. * It now accepts shared service instances to pass them down the chain. * * MCP Spec Relevance: * - Transport Selection: Uses `config.mcpTransportType` ('stdio' or 'http'). * - Transport Connection: Calls dedicated functions for chosen transport. * - Server Instance Lifecycle: Single instance for 'stdio', per-session for 'http'. * * @param {ObsidianRestApiService} obsidianService - The shared Obsidian REST API service instance. * @param {VaultCacheService | undefined} vaultCacheService - The shared Vault Cache service instance. * @returns {Promise<McpServer | void>} Resolves with the `McpServer` instance for 'stdio', or `void` for 'http'. * @throws {Error} If the configured transport type is unsupported or if transport setup fails. * @private */ async function startTransport( obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined, ): Promise<McpServer | ServerType | void> { const transportType = config.mcpTransportType; const context = requestContextService.createRequestContext({ operation: "startTransport", transport: transportType, }); logger.info(`Starting transport: ${transportType}`, context); if (transportType === "http") { logger.debug( "Delegating to startHttpTransport with a factory for McpServer instances...", context, ); // For HTTP, startHttpTransport manages its own lifecycle and server instances per session. // It needs a factory function to create new McpServer instances, passing along the shared services. const mcpServerFactory = async () => createMcpServerInstance(obsidianService, vaultCacheService); const httpServerInstance = await startHttpTransport( mcpServerFactory, context, ); return httpServerInstance; // Return the http.Server instance. } if (transportType === "stdio") { logger.debug( "Creating single McpServer instance for stdio transport using shared services...", context, ); const server = await createMcpServerInstance( obsidianService, vaultCacheService, ); logger.debug("Delegating to connectStdioTransport...", context); await connectStdioTransport(server, context); return server; // Return the single server instance for stdio. } // Should not be reached if config validation is effective. logger.fatal( `Unsupported transport type configured: ${transportType}`, context, ); throw new Error( `Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`, ); } /** * Main application entry point. Initializes services and starts the MCP server. * Orchestrates server startup, transport selection, and top-level error handling. * * MCP Spec Relevance: * - Manages server startup, leading to a server ready for MCP messages. * - Handles critical startup failures, ensuring appropriate process exit. * * @param {ObsidianRestApiService} obsidianService - The shared Obsidian REST API service instance, instantiated by the caller (e.g., index.ts). * @param {VaultCacheService | undefined} vaultCacheService - The shared Vault Cache service instance, instantiated by the caller (e.g., index.ts). * @returns {Promise<void | McpServer>} For 'stdio', resolves with `McpServer`. For 'http', runs indefinitely. * Rejects on critical failure, leading to process exit. */ export async function initializeAndStartServer( obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined, ): Promise<void | McpServer | ServerType> { const context = requestContextService.createRequestContext({ operation: "initializeAndStartServer", }); logger.info( "MCP Server initialization sequence started (services provided).", context, ); try { // Services are now provided by the caller (e.g., index.ts) logger.debug( "Using provided shared services (ObsidianRestApiService, VaultCacheService).", context, ); // Initiate the transport setup based on configuration, passing shared services. const result = await startTransport(obsidianService, vaultCacheService); logger.info( "MCP Server initialization sequence completed successfully.", context, ); return result; } catch (err) { logger.fatal("Critical error during MCP server initialization.", { ...context, error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined, }); // Ensure the error is handled by our centralized handler, which might log more details or perform cleanup. ErrorHandler.handleError(err, { operation: "initializeAndStartServer", // More specific operation context: context, // Pass the existing context critical: true, // This is a critical failure }); logger.info( "Exiting process due to critical initialization error.", context, ); process.exit(1); // Exit with a non-zero code to indicate failure. } }

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

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