obsidian-mcp-server 1.5.8 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +248 -104
  2. package/dist/config/index.d.ts +41 -0
  3. package/dist/config/index.js +191 -0
  4. package/dist/index.d.ts +1 -5
  5. package/dist/index.js +296 -18
  6. package/dist/mcp-server/server.d.ts +33 -0
  7. package/dist/mcp-server/server.js +211 -0
  8. package/dist/mcp-server/tools/obsidianDeleteFileTool/index.d.ts +12 -0
  9. package/dist/mcp-server/tools/obsidianDeleteFileTool/index.js +12 -0
  10. package/dist/mcp-server/tools/obsidianDeleteFileTool/logic.d.ts +51 -0
  11. package/dist/mcp-server/tools/obsidianDeleteFileTool/logic.js +168 -0
  12. package/dist/mcp-server/tools/obsidianDeleteFileTool/registration.d.ts +19 -0
  13. package/dist/mcp-server/tools/obsidianDeleteFileTool/registration.js +91 -0
  14. package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +12 -0
  15. package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +12 -0
  16. package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +77 -0
  17. package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +341 -0
  18. package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +18 -0
  19. package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +69 -0
  20. package/dist/mcp-server/tools/obsidianListFilesTool/index.d.ts +12 -0
  21. package/dist/mcp-server/tools/obsidianListFilesTool/index.js +12 -0
  22. package/dist/mcp-server/tools/obsidianListFilesTool/logic.d.ts +64 -0
  23. package/dist/mcp-server/tools/obsidianListFilesTool/logic.js +179 -0
  24. package/dist/mcp-server/tools/obsidianListFilesTool/registration.d.ts +19 -0
  25. package/dist/mcp-server/tools/obsidianListFilesTool/registration.js +96 -0
  26. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +3 -0
  27. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +2 -0
  28. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +42 -0
  29. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +152 -0
  30. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +3 -0
  31. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +52 -0
  32. package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +3 -0
  33. package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +2 -0
  34. package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +28 -0
  35. package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +161 -0
  36. package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +3 -0
  37. package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +52 -0
  38. package/dist/mcp-server/tools/obsidianReadFileTool/index.d.ts +12 -0
  39. package/dist/mcp-server/tools/obsidianReadFileTool/index.js +12 -0
  40. package/dist/mcp-server/tools/obsidianReadFileTool/logic.d.ts +87 -0
  41. package/dist/mcp-server/tools/obsidianReadFileTool/logic.js +216 -0
  42. package/dist/mcp-server/tools/obsidianReadFileTool/registration.d.ts +20 -0
  43. package/dist/mcp-server/tools/obsidianReadFileTool/registration.js +101 -0
  44. package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +12 -0
  45. package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +12 -0
  46. package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +255 -0
  47. package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +583 -0
  48. package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +22 -0
  49. package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +111 -0
  50. package/dist/mcp-server/tools/obsidianUpdateFileTool/index.d.ts +12 -0
  51. package/dist/mcp-server/tools/obsidianUpdateFileTool/index.js +12 -0
  52. package/dist/mcp-server/tools/obsidianUpdateFileTool/logic.d.ts +183 -0
  53. package/dist/mcp-server/tools/obsidianUpdateFileTool/logic.js +490 -0
  54. package/dist/mcp-server/tools/obsidianUpdateFileTool/registration.d.ts +21 -0
  55. package/dist/mcp-server/tools/obsidianUpdateFileTool/registration.js +108 -0
  56. package/dist/mcp-server/transports/authentication/authContext.d.ts +33 -0
  57. package/dist/mcp-server/transports/authentication/authContext.js +24 -0
  58. package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +30 -0
  59. package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
  60. package/dist/mcp-server/transports/authentication/authUtils.d.ts +18 -0
  61. package/dist/mcp-server/transports/authentication/authUtils.js +45 -0
  62. package/dist/mcp-server/transports/authentication/oauthMiddleware.d.ts +24 -0
  63. package/dist/mcp-server/transports/authentication/oauthMiddleware.js +109 -0
  64. package/dist/mcp-server/transports/authentication/types.d.ts +17 -0
  65. package/dist/mcp-server/transports/authentication/types.js +5 -0
  66. package/dist/mcp-server/transports/httpTransport.d.ts +24 -0
  67. package/dist/mcp-server/transports/httpTransport.js +496 -0
  68. package/dist/mcp-server/transports/stdioTransport.d.ts +42 -0
  69. package/dist/mcp-server/transports/stdioTransport.js +63 -0
  70. package/dist/services/obsidianRestAPI/index.d.ts +15 -0
  71. package/dist/services/obsidianRestAPI/index.js +17 -0
  72. package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +38 -0
  73. package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +62 -0
  74. package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +22 -0
  75. package/dist/services/obsidianRestAPI/methods/commandMethods.js +31 -0
  76. package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +16 -0
  77. package/dist/services/obsidianRestAPI/methods/openMethods.js +21 -0
  78. package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +37 -0
  79. package/dist/services/obsidianRestAPI/methods/patchMethods.js +94 -0
  80. package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +42 -0
  81. package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +66 -0
  82. package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +25 -0
  83. package/dist/services/obsidianRestAPI/methods/searchMethods.js +36 -0
  84. package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +58 -0
  85. package/dist/services/obsidianRestAPI/methods/vaultMethods.js +144 -0
  86. package/dist/services/obsidianRestAPI/service.d.ts +195 -0
  87. package/dist/services/obsidianRestAPI/service.js +379 -0
  88. package/dist/services/obsidianRestAPI/types.d.ts +127 -0
  89. package/dist/services/obsidianRestAPI/types.js +7 -0
  90. package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +4 -0
  91. package/dist/services/obsidianRestAPI/vaultCache/index.js +4 -0
  92. package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +88 -0
  93. package/dist/services/obsidianRestAPI/vaultCache/service.js +299 -0
  94. package/dist/types-global/errors.d.ts +73 -0
  95. package/dist/types-global/errors.js +71 -0
  96. package/dist/utils/index.d.ts +5 -8
  97. package/dist/utils/index.js +13 -9
  98. package/dist/utils/internal/asyncUtils.d.ts +54 -0
  99. package/dist/utils/internal/asyncUtils.js +101 -0
  100. package/dist/utils/internal/errorHandler.d.ts +176 -0
  101. package/dist/utils/internal/errorHandler.js +351 -0
  102. package/dist/utils/internal/index.d.ts +4 -0
  103. package/dist/utils/internal/index.js +4 -0
  104. package/dist/utils/internal/logger.d.ts +141 -0
  105. package/dist/utils/internal/logger.js +406 -0
  106. package/dist/utils/internal/requestContext.d.ts +83 -0
  107. package/dist/utils/internal/requestContext.js +72 -0
  108. package/dist/utils/metrics/index.d.ts +1 -0
  109. package/dist/utils/metrics/index.js +1 -0
  110. package/dist/utils/metrics/tokenCounter.d.ts +27 -0
  111. package/dist/utils/metrics/tokenCounter.js +128 -0
  112. package/dist/utils/obsidian/index.d.ts +5 -0
  113. package/dist/utils/obsidian/index.js +5 -0
  114. package/dist/utils/obsidian/obsidianApiUtils.d.ts +14 -0
  115. package/dist/utils/obsidian/obsidianApiUtils.js +29 -0
  116. package/dist/utils/obsidian/obsidianStatUtils.d.ts +68 -0
  117. package/dist/utils/obsidian/obsidianStatUtils.js +143 -0
  118. package/dist/utils/parsing/dateParser.d.ts +56 -0
  119. package/dist/utils/parsing/dateParser.js +104 -0
  120. package/dist/utils/parsing/index.d.ts +2 -0
  121. package/dist/utils/parsing/index.js +3 -0
  122. package/dist/utils/parsing/jsonParser.d.ts +80 -0
  123. package/dist/utils/parsing/jsonParser.js +133 -0
  124. package/dist/utils/security/idGenerator.d.ts +140 -0
  125. package/dist/utils/security/idGenerator.js +194 -0
  126. package/dist/utils/security/index.d.ts +3 -0
  127. package/dist/utils/security/index.js +3 -0
  128. package/dist/utils/security/rateLimiter.d.ts +156 -0
  129. package/dist/utils/security/rateLimiter.js +235 -0
  130. package/dist/utils/security/sanitization.d.ts +244 -0
  131. package/dist/utils/security/sanitization.js +599 -0
  132. package/package.json +58 -37
  133. package/dist/index.js.map +0 -1
  134. package/dist/mcp/handlers.d.ts +0 -29
  135. package/dist/mcp/handlers.js +0 -305
  136. package/dist/mcp/handlers.js.map +0 -1
  137. package/dist/mcp/index.d.ts +0 -6
  138. package/dist/mcp/index.js +0 -7
  139. package/dist/mcp/index.js.map +0 -1
  140. package/dist/mcp/server.d.ts +0 -18
  141. package/dist/mcp/server.js +0 -240
  142. package/dist/mcp/server.js.map +0 -1
  143. package/dist/mcp/types.d.ts +0 -70
  144. package/dist/mcp/types.js +0 -49
  145. package/dist/mcp/types.js.map +0 -1
  146. package/dist/obsidian/client.d.ts +0 -109
  147. package/dist/obsidian/client.js +0 -403
  148. package/dist/obsidian/client.js.map +0 -1
  149. package/dist/obsidian/errors.d.ts +0 -28
  150. package/dist/obsidian/errors.js +0 -75
  151. package/dist/obsidian/errors.js.map +0 -1
  152. package/dist/obsidian/index.d.ts +0 -6
  153. package/dist/obsidian/index.js +0 -7
  154. package/dist/obsidian/index.js.map +0 -1
  155. package/dist/obsidian/types.d.ts +0 -107
  156. package/dist/obsidian/types.js +0 -12
  157. package/dist/obsidian/types.js.map +0 -1
  158. package/dist/resources/index.d.ts +0 -13
  159. package/dist/resources/index.js +0 -15
  160. package/dist/resources/index.js.map +0 -1
  161. package/dist/resources/tags.d.ts +0 -39
  162. package/dist/resources/tags.js +0 -257
  163. package/dist/resources/tags.js.map +0 -1
  164. package/dist/resources/types.d.ts +0 -27
  165. package/dist/resources/types.js +0 -5
  166. package/dist/resources/types.js.map +0 -1
  167. package/dist/tools/base.d.ts +0 -46
  168. package/dist/tools/base.js +0 -88
  169. package/dist/tools/base.js.map +0 -1
  170. package/dist/tools/files/content.d.ts +0 -58
  171. package/dist/tools/files/content.js +0 -171
  172. package/dist/tools/files/content.js.map +0 -1
  173. package/dist/tools/files/index.d.ts +0 -14
  174. package/dist/tools/files/index.js +0 -22
  175. package/dist/tools/files/index.js.map +0 -1
  176. package/dist/tools/files/list.d.ts +0 -35
  177. package/dist/tools/files/list.js +0 -133
  178. package/dist/tools/files/list.js.map +0 -1
  179. package/dist/tools/index.d.ts +0 -21
  180. package/dist/tools/index.js +0 -31
  181. package/dist/tools/index.js.map +0 -1
  182. package/dist/tools/properties/index.d.ts +0 -14
  183. package/dist/tools/properties/index.js +0 -19
  184. package/dist/tools/properties/index.js.map +0 -1
  185. package/dist/tools/properties/manager.d.ts +0 -62
  186. package/dist/tools/properties/manager.js +0 -302
  187. package/dist/tools/properties/manager.js.map +0 -1
  188. package/dist/tools/properties/tools.d.ts +0 -47
  189. package/dist/tools/properties/tools.js +0 -239
  190. package/dist/tools/properties/tools.js.map +0 -1
  191. package/dist/tools/properties/types.d.ts +0 -141
  192. package/dist/tools/properties/types.js +0 -70
  193. package/dist/tools/properties/types.js.map +0 -1
  194. package/dist/tools/search/complex.d.ts +0 -38
  195. package/dist/tools/search/complex.js +0 -270
  196. package/dist/tools/search/complex.js.map +0 -1
  197. package/dist/tools/search/index.d.ts +0 -14
  198. package/dist/tools/search/index.js +0 -20
  199. package/dist/tools/search/index.js.map +0 -1
  200. package/dist/tools/search/simple.d.ts +0 -25
  201. package/dist/tools/search/simple.js +0 -127
  202. package/dist/tools/search/simple.js.map +0 -1
  203. package/dist/utils/errors.d.ts +0 -24
  204. package/dist/utils/errors.js +0 -59
  205. package/dist/utils/errors.js.map +0 -1
  206. package/dist/utils/idGenerator.d.ts +0 -15
  207. package/dist/utils/idGenerator.js +0 -21
  208. package/dist/utils/idGenerator.js.map +0 -1
  209. package/dist/utils/index.js.map +0 -1
  210. package/dist/utils/logging.d.ts +0 -245
  211. package/dist/utils/logging.js +0 -417
  212. package/dist/utils/logging.js.map +0 -1
  213. package/dist/utils/rate-limiting.d.ts +0 -50
  214. package/dist/utils/rate-limiting.js +0 -94
  215. package/dist/utils/rate-limiting.js.map +0 -1
  216. package/dist/utils/tokenization.d.ts +0 -28
  217. package/dist/utils/tokenization.js +0 -75
  218. package/dist/utils/tokenization.js.map +0 -1
  219. package/dist/utils/validation.d.ts +0 -22
  220. package/dist/utils/validation.js +0 -92
  221. package/dist/utils/validation.js.map +0 -1
@@ -0,0 +1,128 @@
1
+ import { encoding_for_model } from "tiktoken";
2
+ import { BaseErrorCode } from "../../types-global/errors.js";
3
+ // Import utils from the main barrel file (ErrorHandler, logger, RequestContext from ../internal/*)
4
+ import { ErrorHandler, logger } from "../index.js";
5
+ // Define the model used specifically for token counting
6
+ const TOKENIZATION_MODEL = "gpt-4o"; // Note this is strictly for token counting, not the model used for inference
7
+ /**
8
+ * Calculates the number of tokens for a given text using the 'gpt-4o' tokenizer.
9
+ * Uses ErrorHandler for consistent error management.
10
+ *
11
+ * @param text - The input text to tokenize.
12
+ * @param context - Optional request context for logging and error handling.
13
+ * @returns The number of tokens.
14
+ * @throws {McpError} Throws an McpError if tokenization fails.
15
+ */
16
+ export async function countTokens(text, context) {
17
+ // Wrap the synchronous operation in tryCatch which handles both sync/async
18
+ return ErrorHandler.tryCatch(() => {
19
+ let encoding = null;
20
+ try {
21
+ // Always use the defined TOKENIZATION_MODEL
22
+ encoding = encoding_for_model(TOKENIZATION_MODEL);
23
+ const tokens = encoding.encode(text);
24
+ return tokens.length;
25
+ }
26
+ finally {
27
+ encoding?.free(); // Ensure the encoder is freed if it was successfully created
28
+ }
29
+ }, {
30
+ operation: "countTokens",
31
+ context: context,
32
+ input: { textSample: text.substring(0, 50) + "..." }, // Log sanitized input
33
+ errorCode: BaseErrorCode.INTERNAL_ERROR, // Use INTERNAL_ERROR for external lib issues
34
+ // rethrow is implicitly true for tryCatch
35
+ // Removed onErrorReturn as we now rethrow
36
+ });
37
+ }
38
+ /**
39
+ * Calculates the number of tokens for chat messages using the ChatCompletionMessageParam structure
40
+ * and the 'gpt-4o' tokenizer, considering special tokens and message overhead.
41
+ * This implementation is based on OpenAI's guidelines for gpt-4/gpt-3.5-turbo models.
42
+ * Uses ErrorHandler for consistent error management.
43
+ *
44
+ * See: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
45
+ *
46
+ * @param messages - An array of chat messages in the `ChatCompletionMessageParam` format.
47
+ * @param context - Optional request context for logging and error handling.
48
+ * @returns The estimated number of tokens.
49
+ * @throws {McpError} Throws an McpError if tokenization fails.
50
+ */
51
+ export async function countChatTokens(messages, // Use the complex type
52
+ context) {
53
+ // Wrap the synchronous operation in tryCatch
54
+ return ErrorHandler.tryCatch(() => {
55
+ let encoding = null;
56
+ let num_tokens = 0;
57
+ try {
58
+ // Always use the defined TOKENIZATION_MODEL
59
+ encoding = encoding_for_model(TOKENIZATION_MODEL);
60
+ // Define tokens per message/name based on gpt-4o (same as gpt-4/gpt-3.5-turbo)
61
+ const tokens_per_message = 3;
62
+ const tokens_per_name = 1;
63
+ for (const message of messages) {
64
+ num_tokens += tokens_per_message;
65
+ // Encode role
66
+ num_tokens += encoding.encode(message.role).length;
67
+ // Encode content - handle potential null or array content (vision)
68
+ if (typeof message.content === "string") {
69
+ num_tokens += encoding.encode(message.content).length;
70
+ }
71
+ else if (Array.isArray(message.content)) {
72
+ // Handle multi-part content (e.g., text + image) - simplified: encode text parts only
73
+ for (const part of message.content) {
74
+ if (part.type === "text") {
75
+ num_tokens += encoding.encode(part.text).length;
76
+ }
77
+ else {
78
+ // Add placeholder token count for non-text parts (e.g., images) if needed
79
+ // This requires specific model knowledge (e.g., OpenAI vision model token costs)
80
+ logger.warning(`Non-text content part found (type: ${part.type}), token count contribution ignored.`, context);
81
+ // num_tokens += IMAGE_TOKEN_COST; // Placeholder
82
+ }
83
+ }
84
+ } // else: content is null, add 0 tokens
85
+ // Encode name if present (often associated with 'tool' or 'function' roles in newer models)
86
+ if ("name" in message && message.name) {
87
+ num_tokens += tokens_per_name;
88
+ num_tokens += encoding.encode(message.name).length;
89
+ }
90
+ // --- Handle tool calls (specific to newer models) ---
91
+ // Assistant message requesting tool calls
92
+ if (message.role === "assistant" &&
93
+ "tool_calls" in message &&
94
+ message.tool_calls) {
95
+ for (const tool_call of message.tool_calls) {
96
+ // Add tokens for the function name and arguments
97
+ if (tool_call.function.name) {
98
+ num_tokens += encoding.encode(tool_call.function.name).length;
99
+ }
100
+ if (tool_call.function.arguments) {
101
+ // Arguments are often JSON strings
102
+ num_tokens += encoding.encode(tool_call.function.arguments).length;
103
+ }
104
+ }
105
+ }
106
+ // Tool message providing results
107
+ if (message.role === "tool" &&
108
+ "tool_call_id" in message &&
109
+ message.tool_call_id) {
110
+ num_tokens += encoding.encode(message.tool_call_id).length;
111
+ // Content of the tool message (the result) is already handled by the string content check above
112
+ }
113
+ }
114
+ num_tokens += 3; // every reply is primed with <|start|>assistant<|message|>
115
+ return num_tokens;
116
+ }
117
+ finally {
118
+ encoding?.free();
119
+ }
120
+ }, {
121
+ operation: "countChatTokens",
122
+ context: context,
123
+ input: { messageCount: messages.length }, // Log sanitized input
124
+ errorCode: BaseErrorCode.INTERNAL_ERROR, // Use INTERNAL_ERROR
125
+ // rethrow is implicitly true for tryCatch
126
+ // Removed onErrorReturn
127
+ });
128
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel file for Obsidian-specific utilities.
3
+ */
4
+ export * from "./obsidianStatUtils.js";
5
+ export * from "./obsidianApiUtils.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel file for Obsidian-specific utilities.
3
+ */
4
+ export * from "./obsidianStatUtils.js";
5
+ export * from "./obsidianApiUtils.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module ObsidianApiUtils
3
+ * @description
4
+ * Internal utilities for the Obsidian REST API service.
5
+ */
6
+ /**
7
+ * Encodes a vault-relative file path correctly for API URLs.
8
+ * Ensures path separators '/' are not encoded, but individual components are.
9
+ * Handles leading slashes correctly.
10
+ *
11
+ * @param filePath - The raw vault-relative file path (e.g., "/Notes/My File.md" or "Notes/My File.md").
12
+ * @returns The URL-encoded path suitable for appending to `/vault`.
13
+ */
14
+ export declare function encodeVaultPath(filePath: string): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module ObsidianApiUtils
3
+ * @description
4
+ * Internal utilities for the Obsidian REST API service.
5
+ */
6
+ /**
7
+ * Encodes a vault-relative file path correctly for API URLs.
8
+ * Ensures path separators '/' are not encoded, but individual components are.
9
+ * Handles leading slashes correctly.
10
+ *
11
+ * @param filePath - The raw vault-relative file path (e.g., "/Notes/My File.md" or "Notes/My File.md").
12
+ * @returns The URL-encoded path suitable for appending to `/vault`.
13
+ */
14
+ export function encodeVaultPath(filePath) {
15
+ // 1. Trim whitespace and remove any leading/trailing slashes for consistent processing.
16
+ const trimmedPath = filePath.trim().replace(/^\/+|\/+$/g, "");
17
+ // 2. If the original path was just '/' or empty, return an empty string (represents root for files).
18
+ if (trimmedPath === "") {
19
+ // For file operations, the API expects /vault/filename.md at the root,
20
+ // so an empty encoded path segment is correct here.
21
+ // For listFiles, we handle the root case separately.
22
+ return "";
23
+ }
24
+ // 3. Split into components, encode each component, then rejoin with literal '/'.
25
+ const encodedComponents = trimmedPath.split("/").map(encodeURIComponent);
26
+ const encodedPath = encodedComponents.join("/");
27
+ // 4. Prepend the leading slash.
28
+ return `/${encodedPath}`;
29
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @fileoverview Utilities for formatting Obsidian stat objects,
3
+ * including timestamps and calculating estimated token counts.
4
+ * @module src/utils/obsidian/obsidianStatUtils
5
+ */
6
+ import { RequestContext } from "../internal/index.js";
7
+ /**
8
+ * Formats a Unix timestamp (in milliseconds since the epoch) into a human-readable string.
9
+ *
10
+ * @param {number | undefined | null} timestampMs - The Unix timestamp in milliseconds.
11
+ * @param {RequestContext} context - The request context for logging and error reporting.
12
+ * @param {string} [formatString=DEFAULT_TIMESTAMP_FORMAT] - Optional format string adhering to `date-fns` tokens.
13
+ * Defaults to 'hh:mm:ss a | MM-dd-yyyy'.
14
+ * @returns {string} The formatted timestamp string.
15
+ * @throws {McpError} If the provided `timestampMs` is invalid (e.g., undefined, null, not a finite number, or results in an invalid Date object).
16
+ */
17
+ export declare function formatTimestamp(timestampMs: number | undefined | null, context: RequestContext, formatString?: string): string;
18
+ /**
19
+ * Represents the structure of an Obsidian API Stat object.
20
+ */
21
+ export interface ObsidianStat {
22
+ /** Creation time as a Unix timestamp (milliseconds). */
23
+ ctime: number;
24
+ /** Modification time as a Unix timestamp (milliseconds). */
25
+ mtime: number;
26
+ /** File size in bytes. */
27
+ size: number;
28
+ }
29
+ /**
30
+ * Represents formatted timestamp information derived from an Obsidian Stat object.
31
+ */
32
+ export interface FormattedTimestamps {
33
+ /** Human-readable creation time string. */
34
+ createdTime: string;
35
+ /** Human-readable modification time string. */
36
+ modifiedTime: string;
37
+ }
38
+ /**
39
+ * Formats the `ctime` (creation time) and `mtime` (modification time) from an
40
+ * Obsidian API Stat object into human-readable strings.
41
+ *
42
+ * @param {ObsidianStat | undefined | null} stat - The Stat object from the Obsidian API.
43
+ * If undefined or null, placeholder strings ('N/A') are returned.
44
+ * @param {RequestContext} context - The request context for logging and error reporting.
45
+ * @returns {FormattedTimestamps} An object containing `createdTime` and `modifiedTime` strings.
46
+ */
47
+ export declare function formatStatTimestamps(stat: ObsidianStat | undefined | null, context: RequestContext): FormattedTimestamps;
48
+ /**
49
+ * Represents a fully formatted stat object, including human-readable timestamps
50
+ * and an estimated token count for the file content.
51
+ */
52
+ export interface FormattedStatWithTokenCount extends FormattedTimestamps {
53
+ /** Estimated number of tokens in the file content. -1 if counting failed or content was empty. */
54
+ tokenCountEstimate: number;
55
+ }
56
+ /**
57
+ * Creates a formatted stat object that includes human-readable timestamps
58
+ * (creation and modification times) and an estimated token count for the provided file content.
59
+ *
60
+ * @param {ObsidianStat | null | undefined} stat - The original Stat object from the Obsidian API.
61
+ * If null or undefined, the function will return the input value (null or undefined).
62
+ * @param {string} content - The file content string from which to calculate the token count.
63
+ * @param {RequestContext} context - The request context for logging and error reporting.
64
+ * @returns {Promise<FormattedStatWithTokenCount | null | undefined>} A promise resolving to an object
65
+ * containing `createdTime`, `modifiedTime`, and `tokenCountEstimate`. Returns `null` or `undefined`
66
+ * if the input `stat` object was `null` or `undefined`, respectively.
67
+ */
68
+ export declare function createFormattedStatWithTokenCount(stat: ObsidianStat | null | undefined, content: string, context: RequestContext): Promise<FormattedStatWithTokenCount | null | undefined>;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @fileoverview Utilities for formatting Obsidian stat objects,
3
+ * including timestamps and calculating estimated token counts.
4
+ * @module src/utils/obsidian/obsidianStatUtils
5
+ */
6
+ import { format } from "date-fns";
7
+ import { BaseErrorCode, McpError } from "../../types-global/errors.js";
8
+ import { logger } from "../internal/index.js";
9
+ import { countTokens } from "../metrics/index.js";
10
+ /**
11
+ * Default format string for timestamps, providing a human-readable date and time.
12
+ * Example output: "08:40:00 PM | 05-02-2025"
13
+ */
14
+ const DEFAULT_TIMESTAMP_FORMAT = "hh:mm:ss a | MM-dd-yyyy";
15
+ /**
16
+ * Formats a Unix timestamp (in milliseconds since the epoch) into a human-readable string.
17
+ *
18
+ * @param {number | undefined | null} timestampMs - The Unix timestamp in milliseconds.
19
+ * @param {RequestContext} context - The request context for logging and error reporting.
20
+ * @param {string} [formatString=DEFAULT_TIMESTAMP_FORMAT] - Optional format string adhering to `date-fns` tokens.
21
+ * Defaults to 'hh:mm:ss a | MM-dd-yyyy'.
22
+ * @returns {string} The formatted timestamp string.
23
+ * @throws {McpError} If the provided `timestampMs` is invalid (e.g., undefined, null, not a finite number, or results in an invalid Date object).
24
+ */
25
+ export function formatTimestamp(timestampMs, context, formatString = DEFAULT_TIMESTAMP_FORMAT) {
26
+ const operation = "formatTimestamp";
27
+ if (timestampMs === undefined ||
28
+ timestampMs === null ||
29
+ !Number.isFinite(timestampMs)) {
30
+ const errorMessage = `Invalid timestamp provided for formatting: ${timestampMs}`;
31
+ logger.warning(errorMessage, { ...context, operation });
32
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, errorMessage, {
33
+ ...context,
34
+ operation,
35
+ });
36
+ }
37
+ try {
38
+ const date = new Date(timestampMs);
39
+ if (isNaN(date.getTime())) {
40
+ const errorMessage = `Timestamp resulted in an invalid date: ${timestampMs}`;
41
+ logger.warning(errorMessage, { ...context, operation });
42
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, errorMessage, {
43
+ ...context,
44
+ operation,
45
+ });
46
+ }
47
+ return format(date, formatString);
48
+ }
49
+ catch (error) {
50
+ const errorMessage = `Failed to format timestamp ${timestampMs}: ${error instanceof Error ? error.message : String(error)}`;
51
+ logger.error(errorMessage, error instanceof Error ? error : undefined, {
52
+ ...context,
53
+ operation,
54
+ });
55
+ throw new McpError(BaseErrorCode.INTERNAL_ERROR, errorMessage, {
56
+ ...context,
57
+ operation,
58
+ originalError: error instanceof Error ? error.message : String(error),
59
+ });
60
+ }
61
+ }
62
+ /**
63
+ * Formats the `ctime` (creation time) and `mtime` (modification time) from an
64
+ * Obsidian API Stat object into human-readable strings.
65
+ *
66
+ * @param {ObsidianStat | undefined | null} stat - The Stat object from the Obsidian API.
67
+ * If undefined or null, placeholder strings ('N/A') are returned.
68
+ * @param {RequestContext} context - The request context for logging and error reporting.
69
+ * @returns {FormattedTimestamps} An object containing `createdTime` and `modifiedTime` strings.
70
+ */
71
+ export function formatStatTimestamps(stat, context) {
72
+ const operation = "formatStatTimestamps";
73
+ if (!stat) {
74
+ logger.debug("Stat object is undefined or null, returning N/A for timestamps.", { ...context, operation });
75
+ return {
76
+ createdTime: "N/A",
77
+ modifiedTime: "N/A",
78
+ };
79
+ }
80
+ try {
81
+ return {
82
+ createdTime: formatTimestamp(stat.ctime, context),
83
+ modifiedTime: formatTimestamp(stat.mtime, context),
84
+ };
85
+ }
86
+ catch (error) {
87
+ // Log the error from formatTimestamp if it occurs during this higher-level operation
88
+ logger.error(`Error formatting timestamps within formatStatTimestamps for ctime: ${stat.ctime}, mtime: ${stat.mtime}`, error instanceof Error ? error : undefined, { ...context, operation });
89
+ // Return N/A as a fallback if formatting fails at this stage
90
+ return {
91
+ createdTime: "N/A",
92
+ modifiedTime: "N/A",
93
+ };
94
+ }
95
+ }
96
+ /**
97
+ * Creates a formatted stat object that includes human-readable timestamps
98
+ * (creation and modification times) and an estimated token count for the provided file content.
99
+ *
100
+ * @param {ObsidianStat | null | undefined} stat - The original Stat object from the Obsidian API.
101
+ * If null or undefined, the function will return the input value (null or undefined).
102
+ * @param {string} content - The file content string from which to calculate the token count.
103
+ * @param {RequestContext} context - The request context for logging and error reporting.
104
+ * @returns {Promise<FormattedStatWithTokenCount | null | undefined>} A promise resolving to an object
105
+ * containing `createdTime`, `modifiedTime`, and `tokenCountEstimate`. Returns `null` or `undefined`
106
+ * if the input `stat` object was `null` or `undefined`, respectively.
107
+ */
108
+ export async function createFormattedStatWithTokenCount(stat, content, context) {
109
+ const operation = "createFormattedStatWithTokenCount";
110
+ if (stat === null || stat === undefined) {
111
+ logger.debug("Input stat is null or undefined, returning as is.", {
112
+ ...context,
113
+ operation,
114
+ });
115
+ return stat; // Return original null/undefined
116
+ }
117
+ const formattedTimestamps = formatStatTimestamps(stat, context);
118
+ let tokenCountEstimate = -1; // Default: indicates error or empty content
119
+ if (content && content.trim().length > 0) {
120
+ try {
121
+ tokenCountEstimate = await countTokens(content, context);
122
+ }
123
+ catch (tokenError) {
124
+ logger.warning(`Failed to count tokens for stat object. Error: ${tokenError instanceof Error ? tokenError.message : String(tokenError)}`, {
125
+ ...context,
126
+ operation,
127
+ originalError: tokenError instanceof Error
128
+ ? tokenError.message
129
+ : String(tokenError),
130
+ });
131
+ // tokenCountEstimate remains -1
132
+ }
133
+ }
134
+ else {
135
+ logger.debug("Content is empty or whitespace-only, setting tokenCountEstimate to 0.", { ...context, operation });
136
+ tokenCountEstimate = 0;
137
+ }
138
+ return {
139
+ createdTime: formattedTimestamps.createdTime,
140
+ modifiedTime: formattedTimestamps.modifiedTime,
141
+ tokenCountEstimate: tokenCountEstimate,
142
+ };
143
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview Provides utilities for parsing natural language date strings
3
+ * into Date objects or detailed parsing results using the 'chrono-node' library.
4
+ * @module src/utils/parsing/dateParser
5
+ */
6
+ import * as chrono from "chrono-node";
7
+ import { RequestContext } from "../internal/index.js";
8
+ /**
9
+ * Parses a natural language date string (e.g., "tomorrow", "in 5 days", "2024-01-15")
10
+ * into a JavaScript `Date` object.
11
+ *
12
+ * @async
13
+ * @param {string} text - The natural language date string to parse.
14
+ * @param {RequestContext} context - The request context for logging and error tracking.
15
+ * @param {Date} [refDate] - Optional reference date for parsing relative date expressions.
16
+ * Defaults to the current date and time if not provided.
17
+ * @returns {Promise<Date | null>} A promise that resolves to a `Date` object representing
18
+ * the parsed date, or `null` if `chrono-node` could not parse the input string into a date.
19
+ * @throws {McpError} If an unexpected error occurs during the parsing process,
20
+ * an `McpError` with `BaseErrorCode.PARSING_ERROR` is thrown.
21
+ */
22
+ declare function parseDateString(text: string, context: RequestContext, refDate?: Date): Promise<Date | null>;
23
+ /**
24
+ * Parses a natural language date string and returns detailed parsing results,
25
+ * including all components and their confidence levels, as provided by `chrono-node`.
26
+ *
27
+ * @async
28
+ * @param {string} text - The natural language date string to parse.
29
+ * @param {RequestContext} context - The request context for logging and error tracking.
30
+ * @param {Date} [refDate] - Optional reference date for parsing relative date expressions.
31
+ * Defaults to the current date and time if not provided.
32
+ * @returns {Promise<chrono.ParsedResult[]>} A promise that resolves to an array of
33
+ * `chrono.ParsedResult` objects. The array will be empty if no date components
34
+ * could be parsed from the input string.
35
+ * @throws {McpError} If an unexpected error occurs during the parsing process,
36
+ * an `McpError` with `BaseErrorCode.PARSING_ERROR` is thrown.
37
+ */
38
+ declare function parseDateStringDetailed(text: string, context: RequestContext, refDate?: Date): Promise<chrono.ParsedResult[]>;
39
+ /**
40
+ * Provides methods for parsing natural language date strings.
41
+ * - `parseToDate`: Parses a string to a single `Date` object or `null`.
42
+ * - `getDetailedResults`: Provides comprehensive parsing results from `chrono-node`.
43
+ */
44
+ export declare const dateParser: {
45
+ /**
46
+ * Parses a natural language date string into a `Date` object.
47
+ * @see {@link parseDateString}
48
+ */
49
+ parseToDate: typeof parseDateString;
50
+ /**
51
+ * Parses a natural language date string and returns detailed `chrono.ParsedResult` objects.
52
+ * @see {@link parseDateStringDetailed}
53
+ */
54
+ getDetailedResults: typeof parseDateStringDetailed;
55
+ };
56
+ export {};
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @fileoverview Provides utilities for parsing natural language date strings
3
+ * into Date objects or detailed parsing results using the 'chrono-node' library.
4
+ * @module src/utils/parsing/dateParser
5
+ */
6
+ import * as chrono from "chrono-node";
7
+ import { BaseErrorCode } from "../../types-global/errors.js";
8
+ import { ErrorHandler, logger } from "../internal/index.js";
9
+ /**
10
+ * Parses a natural language date string (e.g., "tomorrow", "in 5 days", "2024-01-15")
11
+ * into a JavaScript `Date` object.
12
+ *
13
+ * @async
14
+ * @param {string} text - The natural language date string to parse.
15
+ * @param {RequestContext} context - The request context for logging and error tracking.
16
+ * @param {Date} [refDate] - Optional reference date for parsing relative date expressions.
17
+ * Defaults to the current date and time if not provided.
18
+ * @returns {Promise<Date | null>} A promise that resolves to a `Date` object representing
19
+ * the parsed date, or `null` if `chrono-node` could not parse the input string into a date.
20
+ * @throws {McpError} If an unexpected error occurs during the parsing process,
21
+ * an `McpError` with `BaseErrorCode.PARSING_ERROR` is thrown.
22
+ */
23
+ async function parseDateString(text, context, refDate) {
24
+ const operation = "parseDateString";
25
+ // Ensure context for logging includes all relevant details
26
+ const logContext = {
27
+ ...context,
28
+ operation,
29
+ inputText: text,
30
+ refDate: refDate?.toISOString(),
31
+ };
32
+ logger.debug(`Attempting to parse date string: "${text}"`, logContext);
33
+ return await ErrorHandler.tryCatch(async () => {
34
+ // chrono.parseDate returns a Date object or null if no date is found.
35
+ const parsedDate = chrono.parseDate(text, refDate, { forwardDate: true });
36
+ if (parsedDate) {
37
+ logger.debug(`Successfully parsed "${text}" to ${parsedDate.toISOString()}`, logContext);
38
+ return parsedDate;
39
+ }
40
+ else {
41
+ // This is not an error, but chrono-node couldn't find a date.
42
+ logger.info(`Could not parse a date from string: "${text}"`, logContext);
43
+ return null;
44
+ }
45
+ }, {
46
+ operation,
47
+ context: logContext, // Pass the enriched logContext
48
+ input: { text, refDate: refDate?.toISOString() }, // Log refDate as ISO string for consistency
49
+ errorCode: BaseErrorCode.PARSING_ERROR, // Default error code for unexpected parsing failures
50
+ });
51
+ }
52
+ /**
53
+ * Parses a natural language date string and returns detailed parsing results,
54
+ * including all components and their confidence levels, as provided by `chrono-node`.
55
+ *
56
+ * @async
57
+ * @param {string} text - The natural language date string to parse.
58
+ * @param {RequestContext} context - The request context for logging and error tracking.
59
+ * @param {Date} [refDate] - Optional reference date for parsing relative date expressions.
60
+ * Defaults to the current date and time if not provided.
61
+ * @returns {Promise<chrono.ParsedResult[]>} A promise that resolves to an array of
62
+ * `chrono.ParsedResult` objects. The array will be empty if no date components
63
+ * could be parsed from the input string.
64
+ * @throws {McpError} If an unexpected error occurs during the parsing process,
65
+ * an `McpError` with `BaseErrorCode.PARSING_ERROR` is thrown.
66
+ */
67
+ async function parseDateStringDetailed(text, context, refDate) {
68
+ const operation = "parseDateStringDetailed";
69
+ const logContext = {
70
+ ...context,
71
+ operation,
72
+ inputText: text,
73
+ refDate: refDate?.toISOString(),
74
+ };
75
+ logger.debug(`Attempting detailed parse of date string: "${text}"`, logContext);
76
+ return await ErrorHandler.tryCatch(async () => {
77
+ // chrono.parse returns an array of results.
78
+ const results = chrono.parse(text, refDate, { forwardDate: true });
79
+ logger.debug(`Detailed parse of "${text}" resulted in ${results.length} result(s).`, logContext);
80
+ return results;
81
+ }, {
82
+ operation,
83
+ context: logContext,
84
+ input: { text, refDate: refDate?.toISOString() },
85
+ errorCode: BaseErrorCode.PARSING_ERROR,
86
+ });
87
+ }
88
+ /**
89
+ * Provides methods for parsing natural language date strings.
90
+ * - `parseToDate`: Parses a string to a single `Date` object or `null`.
91
+ * - `getDetailedResults`: Provides comprehensive parsing results from `chrono-node`.
92
+ */
93
+ export const dateParser = {
94
+ /**
95
+ * Parses a natural language date string into a `Date` object.
96
+ * @see {@link parseDateString}
97
+ */
98
+ parseToDate: parseDateString,
99
+ /**
100
+ * Parses a natural language date string and returns detailed `chrono.ParsedResult` objects.
101
+ * @see {@link parseDateStringDetailed}
102
+ */
103
+ getDetailedResults: parseDateStringDetailed,
104
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./jsonParser.js";
2
+ export * from "./dateParser.js";
@@ -0,0 +1,3 @@
1
+ export * from "./jsonParser.js";
2
+ export * from "./dateParser.js";
3
+ // Removed export for dateUtils.js as it was moved
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @fileoverview Provides a utility class for parsing potentially partial JSON strings,
3
+ * with support for handling and logging optional LLM <think> blocks.
4
+ * It wraps the 'partial-json' library.
5
+ * @module src/utils/parsing/jsonParser
6
+ */
7
+ import { RequestContext } from "../internal/index.js";
8
+ /**
9
+ * Enum mirroring `partial-json`'s `Allow` constants. These constants specify
10
+ * what types of partial JSON structures are permissible during parsing.
11
+ * They can be combined using bitwise OR (e.g., `Allow.STR | Allow.OBJ`).
12
+ *
13
+ * - `Allow.OBJ`: Allows partial objects (e.g., `{"key": "value",`)
14
+ * - `Allow.ARR`: Allows partial arrays (e.g., `[1, 2,`)
15
+ * - `Allow.STR`: Allows partial strings (e.g., `"abc`)
16
+ * - `Allow.NUM`: Allows partial numbers (e.g., `1.2e+`)
17
+ * - `Allow.BOOL`: Allows partial booleans (e.g., `tru`)
18
+ * - `Allow.NULL`: Allows partial nulls (e.g., `nul`)
19
+ * - `Allow.ALL`: Allows all types of partial JSON structures (default).
20
+ */
21
+ export declare const Allow: {
22
+ STR: number;
23
+ NUM: number;
24
+ ARR: number;
25
+ OBJ: number;
26
+ NULL: number;
27
+ BOOL: number;
28
+ NAN: number;
29
+ INFINITY: number;
30
+ _INFINITY: number;
31
+ INF: number;
32
+ SPECIAL: number;
33
+ ATOM: number;
34
+ COLLECTION: number;
35
+ ALL: number;
36
+ };
37
+ /**
38
+ * Utility class for parsing JSON strings that may be partial or incomplete.
39
+ * It wraps the 'partial-json' library to provide a consistent parsing interface
40
+ * and includes logic to handle and log optional `<think>...</think>` blocks
41
+ * that might precede the JSON content (often found in LLM outputs).
42
+ */
43
+ declare class JsonParser {
44
+ /**
45
+ * Parses a JSON string, which may be partial or prefixed with an LLM `<think>` block.
46
+ *
47
+ * @template T The expected type of the parsed JavaScript value. Defaults to `any`.
48
+ * @param {string} jsonString - The JSON string to parse.
49
+ * @param {number} [allowPartial=Allow.ALL] - A bitwise OR combination of `Allow` constants
50
+ * specifying which types of partial JSON structures are permissible (e.g., `Allow.OBJ | Allow.ARR`).
51
+ * Defaults to `Allow.ALL`, permitting any form of partial JSON.
52
+ * @param {RequestContext} [providedContext] - Optional `RequestContext` for logging,
53
+ * especially for capturing `<think>` block content or parsing errors.
54
+ * @returns {T} The parsed JavaScript value.
55
+ * @throws {McpError} Throws an `McpError` with `BaseErrorCode.VALIDATION_ERROR` if:
56
+ * - The string is empty after removing a `<think>` block.
57
+ * - The remaining content does not appear to be a valid JSON structure (object, array, or permitted primitive).
58
+ * - The `partial-json` library encounters a parsing error.
59
+ */
60
+ parse<T = any>(jsonString: string, allowPartial?: number, providedContext?: RequestContext): T;
61
+ }
62
+ /**
63
+ * Singleton instance of the `JsonParser`.
64
+ * Use this instance for all partial JSON parsing needs.
65
+ *
66
+ * Example:
67
+ * ```typescript
68
+ * import { jsonParser, Allow, RequestContext } from './jsonParser';
69
+ * import { requestContextService } from '../internal'; // Assuming requestContextService is exported from internal utils
70
+ * const context: RequestContext = requestContextService.createRequestContext({ operation: 'MyOperation' });
71
+ * try {
72
+ * const data = jsonParser.parse('<think>Thinking...</think>{"key": "value", "arr": [1,', Allow.ALL, context);
73
+ * console.log(data); // Output: { key: "value", arr: [ 1 ] }
74
+ * } catch (e) {
75
+ * console.error("Parsing failed:", e);
76
+ * }
77
+ * ```
78
+ */
79
+ export declare const jsonParser: JsonParser;
80
+ export {};