Skip to main content
Glama

Obsidian MCP Server

Apache 2.0
338
222
  • Apple
  • Linux
obsidianStatUtils.ts7.48 kB
/** * @fileoverview Utilities for formatting Obsidian stat objects, * including timestamps and calculating estimated token counts. * @module src/utils/obsidian/obsidianStatUtils */ import { format } from "date-fns"; import { BaseErrorCode, McpError } from "../../types-global/errors.js"; import { logger, RequestContext } from "../internal/index.js"; import { countTokens } from "../metrics/index.js"; /** * Default format string for timestamps, providing a human-readable date and time. * Example output: "08:40:00 PM | 05-02-2025" */ const DEFAULT_TIMESTAMP_FORMAT = "hh:mm:ss a | MM-dd-yyyy"; /** * Formats a Unix timestamp (in milliseconds since the epoch) into a human-readable string. * * @param {number | undefined | null} timestampMs - The Unix timestamp in milliseconds. * @param {RequestContext} context - The request context for logging and error reporting. * @param {string} [formatString=DEFAULT_TIMESTAMP_FORMAT] - Optional format string adhering to `date-fns` tokens. * Defaults to 'hh:mm:ss a | MM-dd-yyyy'. * @returns {string} The formatted timestamp string. * @throws {McpError} If the provided `timestampMs` is invalid (e.g., undefined, null, not a finite number, or results in an invalid Date object). */ export function formatTimestamp( timestampMs: number | undefined | null, context: RequestContext, formatString: string = DEFAULT_TIMESTAMP_FORMAT, ): string { const operation = "formatTimestamp"; if ( timestampMs === undefined || timestampMs === null || !Number.isFinite(timestampMs) ) { const errorMessage = `Invalid timestamp provided for formatting: ${timestampMs}`; logger.warning(errorMessage, { ...context, operation }); throw new McpError(BaseErrorCode.VALIDATION_ERROR, errorMessage, { ...context, operation, }); } try { const date = new Date(timestampMs); if (isNaN(date.getTime())) { const errorMessage = `Timestamp resulted in an invalid date: ${timestampMs}`; logger.warning(errorMessage, { ...context, operation }); throw new McpError(BaseErrorCode.VALIDATION_ERROR, errorMessage, { ...context, operation, }); } return format(date, formatString); } catch (error) { const errorMessage = `Failed to format timestamp ${timestampMs}: ${error instanceof Error ? error.message : String(error)}`; logger.error(errorMessage, error instanceof Error ? error : undefined, { ...context, operation, }); throw new McpError(BaseErrorCode.INTERNAL_ERROR, errorMessage, { ...context, operation, originalError: error instanceof Error ? error.message : String(error), }); } } /** * Represents the structure of an Obsidian API Stat object. */ export interface ObsidianStat { /** Creation time as a Unix timestamp (milliseconds). */ ctime: number; /** Modification time as a Unix timestamp (milliseconds). */ mtime: number; /** File size in bytes. */ size: number; } /** * Represents formatted timestamp information derived from an Obsidian Stat object. */ export interface FormattedTimestamps { /** Human-readable creation time string. */ createdTime: string; /** Human-readable modification time string. */ modifiedTime: string; } /** * Formats the `ctime` (creation time) and `mtime` (modification time) from an * Obsidian API Stat object into human-readable strings. * * @param {ObsidianStat | undefined | null} stat - The Stat object from the Obsidian API. * If undefined or null, placeholder strings ('N/A') are returned. * @param {RequestContext} context - The request context for logging and error reporting. * @returns {FormattedTimestamps} An object containing `createdTime` and `modifiedTime` strings. */ export function formatStatTimestamps( stat: ObsidianStat | undefined | null, context: RequestContext, ): FormattedTimestamps { const operation = "formatStatTimestamps"; if (!stat) { logger.debug( "Stat object is undefined or null, returning N/A for timestamps.", { ...context, operation }, ); return { createdTime: "N/A", modifiedTime: "N/A", }; } try { return { createdTime: formatTimestamp(stat.ctime, context), modifiedTime: formatTimestamp(stat.mtime, context), }; } catch (error) { // Log the error from formatTimestamp if it occurs during this higher-level operation logger.error( `Error formatting timestamps within formatStatTimestamps for ctime: ${stat.ctime}, mtime: ${stat.mtime}`, error instanceof Error ? error : undefined, { ...context, operation }, ); // Return N/A as a fallback if formatting fails at this stage return { createdTime: "N/A", modifiedTime: "N/A", }; } } /** * Represents a fully formatted stat object, including human-readable timestamps * and an estimated token count for the file content. */ export interface FormattedStatWithTokenCount extends FormattedTimestamps { /** Estimated number of tokens in the file content. -1 if counting failed or content was empty. */ tokenCountEstimate: number; } /** * Creates a formatted stat object that includes human-readable timestamps * (creation and modification times) and an estimated token count for the provided file content. * * @param {ObsidianStat | null | undefined} stat - The original Stat object from the Obsidian API. * If null or undefined, the function will return the input value (null or undefined). * @param {string} content - The file content string from which to calculate the token count. * @param {RequestContext} context - The request context for logging and error reporting. * @returns {Promise<FormattedStatWithTokenCount | null | undefined>} A promise resolving to an object * containing `createdTime`, `modifiedTime`, and `tokenCountEstimate`. Returns `null` or `undefined` * if the input `stat` object was `null` or `undefined`, respectively. */ export async function createFormattedStatWithTokenCount( stat: ObsidianStat | null | undefined, content: string, context: RequestContext, ): Promise<FormattedStatWithTokenCount | null | undefined> { const operation = "createFormattedStatWithTokenCount"; if (stat === null || stat === undefined) { logger.debug("Input stat is null or undefined, returning as is.", { ...context, operation, }); return stat; // Return original null/undefined } const formattedTimestamps = formatStatTimestamps(stat, context); let tokenCountEstimate = -1; // Default: indicates error or empty content if (content && content.trim().length > 0) { try { tokenCountEstimate = await countTokens(content, context); } catch (tokenError) { logger.warning( `Failed to count tokens for stat object. Error: ${tokenError instanceof Error ? tokenError.message : String(tokenError)}`, { ...context, operation, originalError: tokenError instanceof Error ? tokenError.message : String(tokenError), }, ); // tokenCountEstimate remains -1 } } else { logger.debug( "Content is empty or whitespace-only, setting tokenCountEstimate to 0.", { ...context, operation }, ); tokenCountEstimate = 0; } return { createdTime: formattedTimestamps.createdTime, modifiedTime: formattedTimestamps.modifiedTime, tokenCountEstimate: tokenCountEstimate, }; }

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