Skip to main content
Glama

Obsidian MCP Server

Apache 2.0
338
222
  • Apple
  • Linux
index.ts7.06 kB
import dotenv from "dotenv"; import { existsSync, mkdirSync, readFileSync, statSync } from "fs"; import path, { dirname, join } from "path"; import { fileURLToPath } from "url"; import { z } from "zod"; dotenv.config(); // --- Determine Project Root --- /** * Finds the project root directory by searching upwards for package.json. * @param startDir The directory to start searching from. * @returns The absolute path to the project root, or throws an error if not found. */ const findProjectRoot = (startDir: string): string => { let currentDir = startDir; while (true) { const packageJsonPath = join(currentDir, "package.json"); if (existsSync(packageJsonPath)) { return currentDir; } const parentDir = dirname(currentDir); if (parentDir === currentDir) { // Reached the root of the filesystem without finding package.json throw new Error( `Could not find project root (package.json) starting from ${startDir}`, ); } currentDir = parentDir; } }; let projectRoot: string; try { // For ESM, __dirname is not available directly. const currentModuleDir = dirname(fileURLToPath(import.meta.url)); projectRoot = findProjectRoot(currentModuleDir); } catch (error: any) { console.error(`FATAL: Error determining project root: ${error.message}`); projectRoot = process.cwd(); console.warn( `Warning: Using process.cwd() (${projectRoot}) as fallback project root.`, ); } // --- End Determine Project Root --- const pkgPath = join(projectRoot, "package.json"); let pkg = { name: "obsidian-mcp-server", version: "0.0.0" }; try { pkg = JSON.parse(readFileSync(pkgPath, "utf-8")); } catch (error) { if (process.stderr.isTTY) { console.error( "Warning: Could not read package.json for default config values. Using hardcoded defaults.", error, ); } } /** * Zod schema for validating environment variables. * @private */ const EnvSchema = z.object({ MCP_SERVER_NAME: z.string().optional(), MCP_SERVER_VERSION: z.string().optional(), MCP_LOG_LEVEL: z.string().default("info"), LOGS_DIR: z.string().default(path.join(projectRoot, "logs")), NODE_ENV: z.string().default("development"), MCP_TRANSPORT_TYPE: z.enum(["stdio", "http"]).default("stdio"), MCP_HTTP_PORT: z.coerce.number().int().positive().default(3010), MCP_HTTP_HOST: z.string().default("127.0.0.1"), MCP_ALLOWED_ORIGINS: z.string().optional(), MCP_AUTH_MODE: z.enum(["jwt", "oauth"]).optional(), MCP_AUTH_SECRET_KEY: z .string() .min( 32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long for security", ) .optional(), OAUTH_ISSUER_URL: z.string().url().optional(), OAUTH_AUDIENCE: z.string().optional(), OAUTH_JWKS_URI: z.string().url().optional(), // --- Obsidian Specific Config --- OBSIDIAN_API_KEY: z.string().min(1, "OBSIDIAN_API_KEY cannot be empty"), OBSIDIAN_BASE_URL: z.string().url().default("http://127.0.0.1:27123"), OBSIDIAN_VERIFY_SSL: z .string() .transform((val) => val.toLowerCase() === "true") .default("false"), OBSIDIAN_CACHE_REFRESH_INTERVAL_MIN: z.coerce .number() .int() .positive() .default(10), OBSIDIAN_ENABLE_CACHE: z .string() .transform((val) => val.toLowerCase() === "true") .default("true"), OBSIDIAN_API_SEARCH_TIMEOUT_MS: z.coerce .number() .int() .positive() .default(30000), }); const parsedEnv = EnvSchema.safeParse(process.env); if (!parsedEnv.success) { const errorDetails = parsedEnv.error.flatten().fieldErrors; if (process.stderr.isTTY) { console.error("❌ Invalid environment variables:", errorDetails); } throw new Error( `Invalid environment configuration. Please check your .env file or environment variables. Details: ${JSON.stringify(errorDetails)}`, ); } const env = parsedEnv.data; // --- Directory Ensurance Function --- const ensureDirectory = ( dirPath: string, rootDir: string, dirName: string, ): string | null => { const resolvedDirPath = path.isAbsolute(dirPath) ? dirPath : path.resolve(rootDir, dirPath); if ( !resolvedDirPath.startsWith(rootDir + path.sep) && resolvedDirPath !== rootDir ) { if (process.stderr.isTTY) { console.error( `Error: ${dirName} path "${dirPath}" resolves to "${resolvedDirPath}", which is outside the project boundary "${rootDir}".`, ); } return null; } if (!existsSync(resolvedDirPath)) { try { mkdirSync(resolvedDirPath, { recursive: true }); } catch (err: unknown) { if (process.stderr.isTTY) { console.error( `Error creating ${dirName} directory at ${resolvedDirPath}: ${err instanceof Error ? err.message : String(err)}`, ); } return null; } } else { try { if (!statSync(resolvedDirPath).isDirectory()) { if (process.stderr.isTTY) { console.error( `Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`, ); } return null; } } catch (statError: any) { if (process.stderr.isTTY) { console.error( `Error accessing ${dirName} path ${resolvedDirPath}: ${statError.message}`, ); } return null; } } return resolvedDirPath; }; // --- End Directory Ensurance Function --- const validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs"); if (!validatedLogsPath) { if (process.stderr.isTTY) { console.error( "FATAL: Logs directory configuration is invalid or could not be created. Please check permissions and path. Exiting.", ); } process.exit(1); } /** * Main application configuration object. */ export const config = { pkg, mcpServerName: env.MCP_SERVER_NAME || pkg.name, mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version, logLevel: env.MCP_LOG_LEVEL, logsPath: validatedLogsPath, environment: env.NODE_ENV, mcpTransportType: env.MCP_TRANSPORT_TYPE, mcpHttpPort: env.MCP_HTTP_PORT, mcpHttpHost: env.MCP_HTTP_HOST, mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",") .map((origin) => origin.trim()) .filter(Boolean), mcpAuthMode: env.MCP_AUTH_MODE, mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY, oauthIssuerUrl: env.OAUTH_ISSUER_URL, oauthAudience: env.OAUTH_AUDIENCE, oauthJwksUri: env.OAUTH_JWKS_URI, obsidianApiKey: env.OBSIDIAN_API_KEY, obsidianBaseUrl: env.OBSIDIAN_BASE_URL, obsidianVerifySsl: env.OBSIDIAN_VERIFY_SSL, obsidianCacheRefreshIntervalMin: env.OBSIDIAN_CACHE_REFRESH_INTERVAL_MIN, obsidianEnableCache: env.OBSIDIAN_ENABLE_CACHE, obsidianApiSearchTimeoutMs: env.OBSIDIAN_API_SEARCH_TIMEOUT_MS, }; /** * The configured logging level for the application. * Exported separately for convenience (e.g., logger initialization). * @type {string} */ export const logLevel = config.logLevel; /** * The configured runtime environment for the application. * Exported separately for convenience. * @type {string} */ export const environment = config.environment;

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