Skip to main content
Glama

Obsidian MCP Server

Apache 2.0
338
222
  • Apple
  • Linux
registration.ts9.1 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { ObsidianRestApiService, VaultCacheService, } from "../../../services/obsidianRestAPI/index.js"; import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; import { ErrorHandler, logger, RequestContext, requestContextService, } from "../../../utils/index.js"; // Import necessary types and schemas from the logic file import type { ObsidianSearchReplaceRegistrationInput, ObsidianSearchReplaceResponse, } from "./logic.js"; import { ObsidianSearchReplaceInputSchema, ObsidianSearchReplaceInputSchemaShape, processObsidianSearchReplace, } from "./logic.js"; /** * Registers the 'obsidian_search_replace' tool with the MCP server. * * This tool performs one or more search-and-replace operations within a specified * Obsidian note (identified by file path, the active file, or a periodic note). * It reads the note content, applies the replacements sequentially based on the * provided options (regex, case sensitivity, etc.), writes the modified content * back to the vault, and returns the operation results. * * The response includes success status, a summary message, the total number of * replacements made, formatted file statistics (timestamp, token count), and * optionally the final content of the note. * * @param {McpServer} server - The MCP server instance to register the tool with. * @param {ObsidianRestApiService} obsidianService - An instance of the Obsidian REST API service * used to interact with the user's Obsidian vault. * @returns {Promise<void>} A promise that resolves when the tool registration is complete or rejects on error. * @throws {McpError} Throws an McpError if registration fails critically. */ export const registerObsidianSearchReplaceTool = async ( server: McpServer, obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined, ): Promise<void> => { const toolName = "obsidian_search_replace"; const toolDescription = "Performs one or more search-and-replace operations within a target Obsidian note (file path, active, or periodic). Reads the file, applies replacements sequentially in memory, and writes the modified content back, overwriting the original. Supports string/regex search, case sensitivity toggle, replacing first/all occurrences, flexible whitespace matching (non-regex), and whole word matching. Returns success status, message, replacement count, a formatted timestamp string, file stats (stats), and optionally the final file content."; // Create a context specifically for the registration process. const registrationContext: RequestContext = requestContextService.createRequestContext({ operation: "RegisterObsidianSearchReplaceTool", toolName: toolName, module: "ObsidianSearchReplaceRegistration", // Identify the module }); logger.info(`Attempting to register tool: ${toolName}`, registrationContext); // Wrap the registration logic in a tryCatch block for robust error handling during server setup. await ErrorHandler.tryCatch( async () => { // Use the high-level SDK method `server.tool` for registration. // It handles schema generation from the shape, basic validation, and routing. server.tool( toolName, toolDescription, ObsidianSearchReplaceInputSchemaShape, // Provide the base Zod schema shape for input definition. /** * The handler function executed when the 'obsidian_search_replace' tool is called by the client. * * @param {ObsidianSearchReplaceRegistrationInput} params - The raw input parameters received from the client, * matching the structure defined by ObsidianSearchReplaceInputSchemaShape. * @returns {Promise<CallToolResult>} A promise resolving to the structured result for the MCP client, * containing either the successful response data (serialized JSON) or an error indication. */ async (params: ObsidianSearchReplaceRegistrationInput) => { // Create a specific context for this handler invocation, linked to the registration context. const handlerContext: RequestContext = requestContextService.createRequestContext({ parentContext: registrationContext, operation: "HandleObsidianSearchReplaceRequest", toolName: toolName, params: { // Log key parameters for debugging (excluding potentially large replacements array) targetType: params.targetType, targetIdentifier: params.targetIdentifier, replacementCount: params.replacements?.length ?? 0, // Log count instead of full array useRegex: params.useRegex, replaceAll: params.replaceAll, caseSensitive: params.caseSensitive, flexibleWhitespace: params.flexibleWhitespace, wholeWord: params.wholeWord, returnContent: params.returnContent, }, }); logger.debug(`Handling '${toolName}' request`, handlerContext); // Wrap the core logic execution in a tryCatch block for handling errors during processing. return await ErrorHandler.tryCatch( async () => { // **Crucial Step:** Explicitly parse and validate the raw input parameters using the // *refined* Zod schema (`ObsidianSearchReplaceInputSchema`). This applies stricter rules // and cross-field validations defined in logic.ts. const validatedParams = ObsidianSearchReplaceInputSchema.parse(params); logger.debug( `Input parameters successfully validated against refined schema.`, handlerContext, ); // Delegate the actual search/replace logic to the dedicated processing function. // Pass the *validated* parameters, the handler context, and the Obsidian service instance. const response: ObsidianSearchReplaceResponse = await processObsidianSearchReplace( validatedParams, handlerContext, obsidianService, vaultCacheService, ); logger.debug( `'${toolName}' processed successfully`, handlerContext, ); // Format the successful response object from the logic function into the required MCP CallToolResult structure. // The entire response object (containing success, message, count, stat, etc.) is serialized to JSON. return { content: [ { type: "text", // Standard content type for structured JSON data text: JSON.stringify(response, null, 2), // Pretty-print JSON for readability }, ], isError: false, // Indicate successful execution to the client }; }, { // Configuration for the inner error handler (processing logic). operation: `processing ${toolName} handler`, context: handlerContext, input: params, // Log the full raw input parameters if an error occurs during processing. // Custom error mapping to ensure consistent McpError format is returned to the client. errorMapper: (error: unknown) => new McpError( // Use the specific code from McpError if available, otherwise default to INTERNAL_ERROR. error instanceof McpError ? error.code : BaseErrorCode.INTERNAL_ERROR, `Error processing ${toolName} tool: ${error instanceof Error ? error.message : "Unknown error"}`, { ...handlerContext }, // Include context in the error details ), }, ); // End of inner ErrorHandler.tryCatch }, ); // End of server.tool call logger.info( `Tool registered successfully: ${toolName}`, registrationContext, ); }, { // Configuration for the outer error handler (registration process). operation: `registering tool ${toolName}`, context: registrationContext, errorCode: BaseErrorCode.INTERNAL_ERROR, // Default error code for registration failure. // Custom error mapping for registration failures. errorMapper: (error: unknown) => new McpError( error instanceof McpError ? error.code : BaseErrorCode.INTERNAL_ERROR, `Failed to register tool '${toolName}': ${error instanceof Error ? error.message : "Unknown error"}`, { ...registrationContext }, // Include context ), critical: true, // Treat registration failure as critical, potentially halting server startup. }, ); // End of outer ErrorHandler.tryCatch };

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