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,133 @@
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 { parse as parsePartialJson, Allow as PartialJsonAllow, } from "partial-json";
8
+ import { BaseErrorCode, McpError } from "../../types-global/errors.js";
9
+ import { logger, requestContextService, } from "../internal/index.js"; // Corrected import path for internal utils
10
+ /**
11
+ * Enum mirroring `partial-json`'s `Allow` constants. These constants specify
12
+ * what types of partial JSON structures are permissible during parsing.
13
+ * They can be combined using bitwise OR (e.g., `Allow.STR | Allow.OBJ`).
14
+ *
15
+ * - `Allow.OBJ`: Allows partial objects (e.g., `{"key": "value",`)
16
+ * - `Allow.ARR`: Allows partial arrays (e.g., `[1, 2,`)
17
+ * - `Allow.STR`: Allows partial strings (e.g., `"abc`)
18
+ * - `Allow.NUM`: Allows partial numbers (e.g., `1.2e+`)
19
+ * - `Allow.BOOL`: Allows partial booleans (e.g., `tru`)
20
+ * - `Allow.NULL`: Allows partial nulls (e.g., `nul`)
21
+ * - `Allow.ALL`: Allows all types of partial JSON structures (default).
22
+ */
23
+ export const Allow = PartialJsonAllow;
24
+ // Regex to find a <think> block at the start of a string,
25
+ // capturing its content and the rest of the string.
26
+ const thinkBlockRegex = /^<think>([\s\S]*?)<\/think>\s*([\s\S]*)$/;
27
+ /**
28
+ * Utility class for parsing JSON strings that may be partial or incomplete.
29
+ * It wraps the 'partial-json' library to provide a consistent parsing interface
30
+ * and includes logic to handle and log optional `<think>...</think>` blocks
31
+ * that might precede the JSON content (often found in LLM outputs).
32
+ */
33
+ class JsonParser {
34
+ /**
35
+ * Parses a JSON string, which may be partial or prefixed with an LLM `<think>` block.
36
+ *
37
+ * @template T The expected type of the parsed JavaScript value. Defaults to `any`.
38
+ * @param {string} jsonString - The JSON string to parse.
39
+ * @param {number} [allowPartial=Allow.ALL] - A bitwise OR combination of `Allow` constants
40
+ * specifying which types of partial JSON structures are permissible (e.g., `Allow.OBJ | Allow.ARR`).
41
+ * Defaults to `Allow.ALL`, permitting any form of partial JSON.
42
+ * @param {RequestContext} [providedContext] - Optional `RequestContext` for logging,
43
+ * especially for capturing `<think>` block content or parsing errors.
44
+ * @returns {T} The parsed JavaScript value.
45
+ * @throws {McpError} Throws an `McpError` with `BaseErrorCode.VALIDATION_ERROR` if:
46
+ * - The string is empty after removing a `<think>` block.
47
+ * - The remaining content does not appear to be a valid JSON structure (object, array, or permitted primitive).
48
+ * - The `partial-json` library encounters a parsing error.
49
+ */
50
+ parse(jsonString, allowPartial = Allow.ALL, providedContext) {
51
+ const operation = "JsonParser.parse";
52
+ // Ensure opContext is always a valid RequestContext for internal logging
53
+ const opContext = providedContext ||
54
+ requestContextService.createRequestContext({ operation });
55
+ let stringToParse = jsonString;
56
+ let thinkContentExtracted;
57
+ const match = jsonString.match(thinkBlockRegex);
58
+ if (match) {
59
+ thinkContentExtracted = match[1].trim();
60
+ const restOfString = match[2];
61
+ if (thinkContentExtracted) {
62
+ logger.debug("LLM <think> block content extracted.", {
63
+ ...opContext,
64
+ operation,
65
+ thinkContent: thinkContentExtracted,
66
+ });
67
+ }
68
+ else {
69
+ logger.debug("Empty LLM <think> block detected and removed.", {
70
+ ...opContext,
71
+ operation,
72
+ });
73
+ }
74
+ stringToParse = restOfString; // Continue parsing with the remainder of the string
75
+ }
76
+ stringToParse = stringToParse.trim(); // Trim whitespace from the string that will be parsed
77
+ if (!stringToParse) {
78
+ const errorMsg = "JSON string is empty after potential <think> block removal and trimming.";
79
+ logger.warning(errorMsg, {
80
+ ...opContext,
81
+ operation,
82
+ originalInput: jsonString,
83
+ });
84
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, errorMsg, {
85
+ ...opContext,
86
+ operation,
87
+ });
88
+ }
89
+ try {
90
+ // The pre-check for firstChar and specific primitive types has been removed.
91
+ // We now directly rely on parsePartialJson to validate the structure according
92
+ // to the 'allowPartial' flags. If parsePartialJson fails, it will throw an
93
+ // error which is caught below and wrapped in an McpError.
94
+ return parsePartialJson(stringToParse, allowPartial);
95
+ }
96
+ catch (error) {
97
+ const errorMessage = `Failed to parse JSON content: ${error.message}`;
98
+ logger.error(errorMessage, error, {
99
+ ...opContext, // Use the guaranteed valid opContext
100
+ operation,
101
+ contentAttempted: stringToParse,
102
+ thinkContentFound: thinkContentExtracted,
103
+ });
104
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, errorMessage, {
105
+ ...opContext, // Use the guaranteed valid opContext
106
+ operation,
107
+ originalContent: stringToParse,
108
+ thinkContentProcessed: !!thinkContentExtracted,
109
+ rawError: error instanceof Error
110
+ ? { message: error.message, stack: error.stack }
111
+ : String(error),
112
+ });
113
+ }
114
+ }
115
+ }
116
+ /**
117
+ * Singleton instance of the `JsonParser`.
118
+ * Use this instance for all partial JSON parsing needs.
119
+ *
120
+ * Example:
121
+ * ```typescript
122
+ * import { jsonParser, Allow, RequestContext } from './jsonParser';
123
+ * import { requestContextService } from '../internal'; // Assuming requestContextService is exported from internal utils
124
+ * const context: RequestContext = requestContextService.createRequestContext({ operation: 'MyOperation' });
125
+ * try {
126
+ * const data = jsonParser.parse('<think>Thinking...</think>{"key": "value", "arr": [1,', Allow.ALL, context);
127
+ * console.log(data); // Output: { key: "value", arr: [ 1 ] }
128
+ * } catch (e) {
129
+ * console.error("Parsing failed:", e);
130
+ * }
131
+ * ```
132
+ */
133
+ export const jsonParser = new JsonParser();
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @fileoverview Provides a generic ID generator class for creating unique,
3
+ * prefixed identifiers and standard UUIDs. It supports custom character sets,
4
+ * lengths, and separators for generated IDs.
5
+ * @module src/utils/security/idGenerator
6
+ */
7
+ /**
8
+ * Defines the structure for configuring entity prefixes, mapping entity types (strings)
9
+ * to their corresponding ID prefixes (strings).
10
+ */
11
+ export interface EntityPrefixConfig {
12
+ [key: string]: string;
13
+ }
14
+ /**
15
+ * Options for customizing ID generation.
16
+ */
17
+ export interface IdGenerationOptions {
18
+ /** The length of the random part of the ID. Defaults to `IdGenerator.DEFAULT_LENGTH`. */
19
+ length?: number;
20
+ /** The separator string used between a prefix and the random part. Defaults to `IdGenerator.DEFAULT_SEPARATOR`. */
21
+ separator?: string;
22
+ /** The character set from which the random part of the ID is generated. Defaults to `IdGenerator.DEFAULT_CHARSET`. */
23
+ charset?: string;
24
+ }
25
+ /**
26
+ * A generic ID Generator class for creating and managing unique identifiers.
27
+ * It can generate IDs with entity-specific prefixes or standard UUIDs.
28
+ */
29
+ export declare class IdGenerator {
30
+ /** Default character set for the random part of generated IDs (uppercase alphanumeric). */
31
+ private static DEFAULT_CHARSET;
32
+ /** Default separator used between a prefix and the random part of an ID. */
33
+ private static DEFAULT_SEPARATOR;
34
+ /** Default length for the random part of generated IDs. */
35
+ private static DEFAULT_LENGTH;
36
+ private entityPrefixes;
37
+ private prefixToEntityType;
38
+ /**
39
+ * Constructs an `IdGenerator` instance.
40
+ * @param {EntityPrefixConfig} [entityPrefixes={}] - An optional map of entity types
41
+ * to their desired ID prefixes (e.g., `{ project: 'PROJ', task: 'TASK' }`).
42
+ */
43
+ constructor(entityPrefixes?: EntityPrefixConfig);
44
+ /**
45
+ * Sets or updates the entity prefix configuration and rebuilds the internal
46
+ * reverse lookup table (prefix to entity type).
47
+ * @param {EntityPrefixConfig} entityPrefixes - A map of entity types to their prefixes.
48
+ */
49
+ setEntityPrefixes(entityPrefixes: EntityPrefixConfig): void;
50
+ /**
51
+ * Retrieves a copy of the current entity prefix configuration.
52
+ * @returns {EntityPrefixConfig} The current entity prefix configuration.
53
+ */
54
+ getEntityPrefixes(): EntityPrefixConfig;
55
+ /**
56
+ * Generates a cryptographically secure random string of a specified length
57
+ * from a given character set.
58
+ * @param {number} [length=IdGenerator.DEFAULT_LENGTH] - The desired length of the random string.
59
+ * @param {string} [charset=IdGenerator.DEFAULT_CHARSET] - The character set to use for generation.
60
+ * @returns {string} A random string.
61
+ */
62
+ generateRandomString(length?: number, charset?: string): string;
63
+ /**
64
+ * Generates a unique ID, optionally with a specified prefix.
65
+ * @param {string} [prefix] - An optional prefix for the ID.
66
+ * @param {IdGenerationOptions} [options={}] - Optional parameters for customizing
67
+ * the length, separator, and charset of the random part of the ID.
68
+ * @returns {string} A unique identifier string.
69
+ */
70
+ generate(prefix?: string, options?: IdGenerationOptions): string;
71
+ /**
72
+ * Generates a unique ID for a specified entity type, using its configured prefix.
73
+ * The format is typically `PREFIX_RANDOMPART`.
74
+ * @param {string} entityType - The type of entity for which to generate an ID (must be registered
75
+ * via `setEntityPrefixes` or constructor).
76
+ * @param {IdGenerationOptions} [options={}] - Optional parameters for customizing the ID generation.
77
+ * @returns {string} A unique identifier string for the entity (e.g., "PROJ_A6B3J0").
78
+ * @throws {McpError} If the `entityType` is not registered (i.e., no prefix is configured for it).
79
+ */
80
+ generateForEntity(entityType: string, options?: IdGenerationOptions): string;
81
+ /**
82
+ * Validates if a given ID string matches the expected format for a specified entity type,
83
+ * including its prefix, separator, and random part characteristics.
84
+ * @param {string} id - The ID string to validate.
85
+ * @param {string} entityType - The expected entity type of the ID.
86
+ * @param {IdGenerationOptions} [options={}] - Optional parameters to specify the expected
87
+ * length and separator if they differ from defaults for this validation.
88
+ * @returns {boolean} `true` if the ID is valid for the entity type, `false` otherwise.
89
+ */
90
+ isValid(id: string, entityType: string, options?: IdGenerationOptions): boolean;
91
+ /**
92
+ * Strips the prefix from a prefixed ID string.
93
+ * @param {string} id - The ID string (e.g., "PROJ_A6B3J0").
94
+ * @param {string} [separator=IdGenerator.DEFAULT_SEPARATOR] - The separator used in the ID.
95
+ * @returns {string} The part of the ID after the first separator, or the original ID if no separator is found.
96
+ */
97
+ stripPrefix(id: string, separator?: string): string;
98
+ /**
99
+ * Determines the entity type from a prefixed ID string.
100
+ * @param {string} id - The ID string (e.g., "PROJ_A6B3J0").
101
+ * @param {string} [separator=IdGenerator.DEFAULT_SEPARATOR] - The separator used in the ID.
102
+ * @returns {string} The determined entity type.
103
+ * @throws {McpError} If the ID format is invalid or the prefix does not map to a known entity type.
104
+ */
105
+ getEntityType(id: string, separator?: string): string;
106
+ /**
107
+ * Normalizes an entity ID to ensure the prefix matches the configured case
108
+ * (if specific casing is important for the system) and the random part is uppercase.
109
+ * This implementation assumes prefixes are stored/used consistently and focuses on random part casing.
110
+ * @param {string} id - The ID to normalize.
111
+ * @param {string} [separator=IdGenerator.DEFAULT_SEPARATOR] - The separator used in the ID.
112
+ * @returns {string} The normalized ID string.
113
+ * @throws {McpError} If the entity type cannot be determined from the ID.
114
+ */
115
+ normalize(id: string, separator?: string): string;
116
+ }
117
+ /**
118
+ * A default, shared instance of the `IdGenerator`.
119
+ * This instance can be configured with entity prefixes at application startup
120
+ * or used directly for generating unprefixed random IDs or UUIDs.
121
+ *
122
+ * Example:
123
+ * ```typescript
124
+ * import { idGenerator, generateUUID } from './idGenerator';
125
+ *
126
+ * // Configure prefixes (optional, typically at app start)
127
+ * idGenerator.setEntityPrefixes({ user: 'USR', order: 'ORD' });
128
+ *
129
+ * const userId = idGenerator.generateForEntity('user'); // e.g., USR_X7V2L9
130
+ * const simpleId = idGenerator.generate(); // e.g., K3P8A1
131
+ * const standardUuid = generateUUID(); // e.g., '123e4567-e89b-12d3-a456-426614174000'
132
+ * ```
133
+ */
134
+ export declare const idGenerator: IdGenerator;
135
+ /**
136
+ * Generates a standard Version 4 UUID (Universally Unique Identifier).
137
+ * Uses the `crypto.randomUUID()` method for cryptographically strong randomness.
138
+ * @returns {string} A UUID string (e.g., "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx").
139
+ */
140
+ export declare const generateUUID: () => string;
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @fileoverview Provides a generic ID generator class for creating unique,
3
+ * prefixed identifiers and standard UUIDs. It supports custom character sets,
4
+ * lengths, and separators for generated IDs.
5
+ * @module src/utils/security/idGenerator
6
+ */
7
+ import { randomBytes, randomUUID as cryptoRandomUUID } from "crypto";
8
+ import { BaseErrorCode, McpError } from "../../types-global/errors.js";
9
+ /**
10
+ * A generic ID Generator class for creating and managing unique identifiers.
11
+ * It can generate IDs with entity-specific prefixes or standard UUIDs.
12
+ */
13
+ export class IdGenerator {
14
+ /**
15
+ * Constructs an `IdGenerator` instance.
16
+ * @param {EntityPrefixConfig} [entityPrefixes={}] - An optional map of entity types
17
+ * to their desired ID prefixes (e.g., `{ project: 'PROJ', task: 'TASK' }`).
18
+ */
19
+ constructor(entityPrefixes = {}) {
20
+ this.entityPrefixes = {};
21
+ this.prefixToEntityType = {};
22
+ this.setEntityPrefixes(entityPrefixes);
23
+ }
24
+ /**
25
+ * Sets or updates the entity prefix configuration and rebuilds the internal
26
+ * reverse lookup table (prefix to entity type).
27
+ * @param {EntityPrefixConfig} entityPrefixes - A map of entity types to their prefixes.
28
+ */
29
+ setEntityPrefixes(entityPrefixes) {
30
+ this.entityPrefixes = { ...entityPrefixes }; // Create a copy
31
+ // Rebuild reverse mapping for efficient lookup (case-insensitive for prefix matching)
32
+ this.prefixToEntityType = Object.entries(this.entityPrefixes).reduce((acc, [type, prefix]) => {
33
+ acc[prefix.toUpperCase()] = type; // Store prefix in uppercase for consistent lookup
34
+ // Consider if lowercase or original case mapping is also needed based on expected input.
35
+ // For now, assuming prefixes are matched case-insensitively by uppercasing input prefix.
36
+ return acc;
37
+ }, {});
38
+ }
39
+ /**
40
+ * Retrieves a copy of the current entity prefix configuration.
41
+ * @returns {EntityPrefixConfig} The current entity prefix configuration.
42
+ */
43
+ getEntityPrefixes() {
44
+ return { ...this.entityPrefixes };
45
+ }
46
+ /**
47
+ * Generates a cryptographically secure random string of a specified length
48
+ * from a given character set.
49
+ * @param {number} [length=IdGenerator.DEFAULT_LENGTH] - The desired length of the random string.
50
+ * @param {string} [charset=IdGenerator.DEFAULT_CHARSET] - The character set to use for generation.
51
+ * @returns {string} A random string.
52
+ */
53
+ generateRandomString(length = IdGenerator.DEFAULT_LENGTH, charset = IdGenerator.DEFAULT_CHARSET) {
54
+ if (length <= 0) {
55
+ return "";
56
+ }
57
+ const bytes = randomBytes(length);
58
+ let result = "";
59
+ for (let i = 0; i < length; i++) {
60
+ result += charset[bytes[i] % charset.length];
61
+ }
62
+ return result;
63
+ }
64
+ /**
65
+ * Generates a unique ID, optionally with a specified prefix.
66
+ * @param {string} [prefix] - An optional prefix for the ID.
67
+ * @param {IdGenerationOptions} [options={}] - Optional parameters for customizing
68
+ * the length, separator, and charset of the random part of the ID.
69
+ * @returns {string} A unique identifier string.
70
+ */
71
+ generate(prefix, options = {}) {
72
+ const { length = IdGenerator.DEFAULT_LENGTH, separator = IdGenerator.DEFAULT_SEPARATOR, charset = IdGenerator.DEFAULT_CHARSET, } = options;
73
+ const randomPart = this.generateRandomString(length, charset);
74
+ return prefix ? `${prefix}${separator}${randomPart}` : randomPart;
75
+ }
76
+ /**
77
+ * Generates a unique ID for a specified entity type, using its configured prefix.
78
+ * The format is typically `PREFIX_RANDOMPART`.
79
+ * @param {string} entityType - The type of entity for which to generate an ID (must be registered
80
+ * via `setEntityPrefixes` or constructor).
81
+ * @param {IdGenerationOptions} [options={}] - Optional parameters for customizing the ID generation.
82
+ * @returns {string} A unique identifier string for the entity (e.g., "PROJ_A6B3J0").
83
+ * @throws {McpError} If the `entityType` is not registered (i.e., no prefix is configured for it).
84
+ */
85
+ generateForEntity(entityType, options = {}) {
86
+ const prefix = this.entityPrefixes[entityType];
87
+ if (!prefix) {
88
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Unknown entity type: "${entityType}". No prefix configured.`);
89
+ }
90
+ return this.generate(prefix, options);
91
+ }
92
+ /**
93
+ * Validates if a given ID string matches the expected format for a specified entity type,
94
+ * including its prefix, separator, and random part characteristics.
95
+ * @param {string} id - The ID string to validate.
96
+ * @param {string} entityType - The expected entity type of the ID.
97
+ * @param {IdGenerationOptions} [options={}] - Optional parameters to specify the expected
98
+ * length and separator if they differ from defaults for this validation.
99
+ * @returns {boolean} `true` if the ID is valid for the entity type, `false` otherwise.
100
+ */
101
+ isValid(id, entityType, options = {}) {
102
+ const prefix = this.entityPrefixes[entityType];
103
+ if (!prefix) {
104
+ return false; // Cannot validate if entity type or prefix is unknown
105
+ }
106
+ const { length = IdGenerator.DEFAULT_LENGTH, separator = IdGenerator.DEFAULT_SEPARATOR,
107
+ // charset is not used for regex validation but for generation
108
+ } = options;
109
+ // Regex assumes default charset (A-Z, 0-9). If charset is customizable for validation,
110
+ // the regex would need to be dynamically built or options.charset used.
111
+ // For now, it matches the default generation charset.
112
+ const pattern = new RegExp(`^${prefix}${separator}[A-Z0-9]{${length}}$`);
113
+ return pattern.test(id);
114
+ }
115
+ /**
116
+ * Strips the prefix from a prefixed ID string.
117
+ * @param {string} id - The ID string (e.g., "PROJ_A6B3J0").
118
+ * @param {string} [separator=IdGenerator.DEFAULT_SEPARATOR] - The separator used in the ID.
119
+ * @returns {string} The part of the ID after the first separator, or the original ID if no separator is found.
120
+ */
121
+ stripPrefix(id, separator = IdGenerator.DEFAULT_SEPARATOR) {
122
+ const parts = id.split(separator);
123
+ return parts.length > 1 ? parts.slice(1).join(separator) : id; // Handle cases with multiple separators in random part
124
+ }
125
+ /**
126
+ * Determines the entity type from a prefixed ID string.
127
+ * @param {string} id - The ID string (e.g., "PROJ_A6B3J0").
128
+ * @param {string} [separator=IdGenerator.DEFAULT_SEPARATOR] - The separator used in the ID.
129
+ * @returns {string} The determined entity type.
130
+ * @throws {McpError} If the ID format is invalid or the prefix does not map to a known entity type.
131
+ */
132
+ getEntityType(id, separator = IdGenerator.DEFAULT_SEPARATOR) {
133
+ const parts = id.split(separator);
134
+ if (parts.length < 2 || !parts[0]) {
135
+ // Need at least a prefix and a random part
136
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid ID format: "${id}". Expected format like "PREFIX${separator}RANDOMPART".`);
137
+ }
138
+ const inputPrefix = parts[0].toUpperCase(); // Match prefix case-insensitively
139
+ const entityType = this.prefixToEntityType[inputPrefix];
140
+ if (!entityType) {
141
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Unknown entity type for prefix: "${parts[0]}" in ID "${id}".`);
142
+ }
143
+ return entityType;
144
+ }
145
+ /**
146
+ * Normalizes an entity ID to ensure the prefix matches the configured case
147
+ * (if specific casing is important for the system) and the random part is uppercase.
148
+ * This implementation assumes prefixes are stored/used consistently and focuses on random part casing.
149
+ * @param {string} id - The ID to normalize.
150
+ * @param {string} [separator=IdGenerator.DEFAULT_SEPARATOR] - The separator used in the ID.
151
+ * @returns {string} The normalized ID string.
152
+ * @throws {McpError} If the entity type cannot be determined from the ID.
153
+ */
154
+ normalize(id, separator = IdGenerator.DEFAULT_SEPARATOR) {
155
+ // This will throw if entity type is not found or ID format is wrong
156
+ const entityType = this.getEntityType(id, separator);
157
+ const configuredPrefix = this.entityPrefixes[entityType]; // Get the canonical prefix
158
+ const parts = id.split(separator);
159
+ const randomPart = parts.slice(1).join(separator); // Re-join if separator was in random part
160
+ return `${configuredPrefix}${separator}${randomPart.toUpperCase()}`;
161
+ }
162
+ }
163
+ /** Default character set for the random part of generated IDs (uppercase alphanumeric). */
164
+ IdGenerator.DEFAULT_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
165
+ /** Default separator used between a prefix and the random part of an ID. */
166
+ IdGenerator.DEFAULT_SEPARATOR = "_";
167
+ /** Default length for the random part of generated IDs. */
168
+ IdGenerator.DEFAULT_LENGTH = 6;
169
+ /**
170
+ * A default, shared instance of the `IdGenerator`.
171
+ * This instance can be configured with entity prefixes at application startup
172
+ * or used directly for generating unprefixed random IDs or UUIDs.
173
+ *
174
+ * Example:
175
+ * ```typescript
176
+ * import { idGenerator, generateUUID } from './idGenerator';
177
+ *
178
+ * // Configure prefixes (optional, typically at app start)
179
+ * idGenerator.setEntityPrefixes({ user: 'USR', order: 'ORD' });
180
+ *
181
+ * const userId = idGenerator.generateForEntity('user'); // e.g., USR_X7V2L9
182
+ * const simpleId = idGenerator.generate(); // e.g., K3P8A1
183
+ * const standardUuid = generateUUID(); // e.g., '123e4567-e89b-12d3-a456-426614174000'
184
+ * ```
185
+ */
186
+ export const idGenerator = new IdGenerator();
187
+ /**
188
+ * Generates a standard Version 4 UUID (Universally Unique Identifier).
189
+ * Uses the `crypto.randomUUID()` method for cryptographically strong randomness.
190
+ * @returns {string} A UUID string (e.g., "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx").
191
+ */
192
+ export const generateUUID = () => {
193
+ return cryptoRandomUUID();
194
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./sanitization.js";
2
+ export * from "./rateLimiter.js";
3
+ export * from "./idGenerator.js";
@@ -0,0 +1,3 @@
1
+ export * from "./sanitization.js";
2
+ export * from "./rateLimiter.js";
3
+ export * from "./idGenerator.js";
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @fileoverview Provides a generic rate limiter class to manage request rates
3
+ * based on configurable time windows and request counts. It supports custom
4
+ * key generation, periodic cleanup of expired entries, and skipping rate
5
+ * limiting in development environments.
6
+ * @module src/utils/security/rateLimiter
7
+ */
8
+ import { RequestContext } from "../internal/index.js";
9
+ /**
10
+ * Configuration options for the {@link RateLimiter}.
11
+ */
12
+ export interface RateLimitConfig {
13
+ /** Time window in milliseconds during which requests are counted. */
14
+ windowMs: number;
15
+ /** Maximum number of requests allowed from a single key within the `windowMs`. */
16
+ maxRequests: number;
17
+ /**
18
+ * Custom error message template when rate limit is exceeded.
19
+ * Use `{waitTime}` as a placeholder for the remaining seconds until reset.
20
+ * Defaults to "Rate limit exceeded. Please try again in {waitTime} seconds."
21
+ */
22
+ errorMessage?: string;
23
+ /**
24
+ * If `true`, rate limiting checks will be skipped if `process.env.NODE_ENV` is 'development'.
25
+ * Defaults to `false`.
26
+ */
27
+ skipInDevelopment?: boolean;
28
+ /**
29
+ * An optional function to generate a unique key for rate limiting based on an identifier
30
+ * and optional request context. If not provided, the raw identifier is used as the key.
31
+ * @param identifier - The base identifier (e.g., IP address, user ID).
32
+ * @param context - Optional request context.
33
+ * @returns A string to be used as the rate limiting key.
34
+ */
35
+ keyGenerator?: (identifier: string, context?: RequestContext) => string;
36
+ /**
37
+ * Interval in milliseconds for cleaning up expired rate limit entries from memory.
38
+ * Defaults to 5 minutes. Set to `0` or `null` to disable automatic cleanup.
39
+ */
40
+ cleanupInterval?: number | null;
41
+ }
42
+ /**
43
+ * Represents an individual entry in the rate limiter's tracking store.
44
+ * @internal
45
+ */
46
+ export interface RateLimitEntry {
47
+ /** The current count of requests within the window. */
48
+ count: number;
49
+ /** The timestamp (milliseconds since epoch) when the current window resets. */
50
+ resetTime: number;
51
+ }
52
+ /**
53
+ * A generic rate limiter class that can be used to control the frequency of
54
+ * operations or requests from various sources. It stores request counts in memory.
55
+ */
56
+ export declare class RateLimiter {
57
+ private limits;
58
+ private cleanupTimer;
59
+ private currentConfig;
60
+ /**
61
+ * Default configuration values for the rate limiter.
62
+ */
63
+ private static DEFAULT_CONFIG;
64
+ /**
65
+ * Creates a new `RateLimiter` instance.
66
+ * @param {Partial<RateLimitConfig>} [initialConfig={}] - Optional initial configuration
67
+ * to override default settings.
68
+ */
69
+ constructor(initialConfig?: Partial<RateLimitConfig>);
70
+ /**
71
+ * Starts the periodic cleanup timer for expired rate limit entries.
72
+ * If a timer already exists, it's cleared and restarted.
73
+ * @private
74
+ */
75
+ private startCleanupTimer;
76
+ /**
77
+ * Removes expired entries from the rate limit store to free up memory.
78
+ * This method is called periodically by the cleanup timer.
79
+ * @private
80
+ */
81
+ private cleanupExpiredEntries;
82
+ /**
83
+ * Updates the rate limiter's configuration.
84
+ * @param {Partial<RateLimitConfig>} newConfig - Partial configuration object
85
+ * with new settings to apply.
86
+ */
87
+ configure(newConfig: Partial<RateLimitConfig>): void;
88
+ /**
89
+ * Retrieves a copy of the current rate limiter configuration.
90
+ * @returns {RateLimitConfig} The current configuration.
91
+ */
92
+ getConfig(): RateLimitConfig;
93
+ /**
94
+ * Resets all rate limits, clearing all tracked keys and their counts.
95
+ * @param {RequestContext} [context] - Optional context for logging the reset operation.
96
+ */
97
+ reset(context?: RequestContext): void;
98
+ /**
99
+ * Checks if a request identified by a key exceeds the configured rate limit.
100
+ * If the limit is exceeded, an `McpError` is thrown.
101
+ *
102
+ * @param {string} identifier - A unique string identifying the source of the request
103
+ * (e.g., IP address, user ID, session ID).
104
+ * @param {RequestContext} [context] - Optional request context for logging and potentially
105
+ * for use by a custom `keyGenerator`.
106
+ * @throws {McpError} If the rate limit is exceeded for the given key.
107
+ * The error will have `BaseErrorCode.RATE_LIMITED`.
108
+ */
109
+ check(identifier: string, context?: RequestContext): void;
110
+ /**
111
+ * Retrieves the current rate limit status for a given key.
112
+ * @param {string} key - The rate limit key (as generated by `keyGenerator` or the raw identifier).
113
+ * @returns {{ current: number; limit: number; remaining: number; resetTime: number } | null}
114
+ * An object with current status, or `null` if the key is not currently tracked (or has expired).
115
+ * `resetTime` is a Unix timestamp (milliseconds).
116
+ */
117
+ getStatus(key: string): {
118
+ current: number;
119
+ limit: number;
120
+ remaining: number;
121
+ resetTime: number;
122
+ } | null;
123
+ /**
124
+ * Stops the cleanup timer and clears all rate limit entries.
125
+ * This should be called if the rate limiter instance is no longer needed,
126
+ * to prevent resource leaks (though `unref` on the timer helps).
127
+ * @param {RequestContext} [context] - Optional context for logging the disposal.
128
+ */
129
+ dispose(context?: RequestContext): void;
130
+ }
131
+ /**
132
+ * A default, shared instance of the `RateLimiter`.
133
+ * This instance is configured with default settings (e.g., 100 requests per 15 minutes).
134
+ * It can be reconfigured using `rateLimiter.configure()`.
135
+ *
136
+ * Example:
137
+ * ```typescript
138
+ * import { rateLimiter, RequestContext } from './rateLimiter';
139
+ * import { requestContextService } from '../internal';
140
+ *
141
+ * const context: RequestContext = requestContextService.createRequestContext({ operation: 'MyApiCall' });
142
+ * const userIp = '123.45.67.89';
143
+ *
144
+ * try {
145
+ * rateLimiter.check(userIp, context);
146
+ * // Proceed with operation
147
+ * } catch (e) {
148
+ * if (e instanceof McpError && e.code === BaseErrorCode.RATE_LIMITED) {
149
+ * console.error("Rate limit hit:", e.message);
150
+ * } else {
151
+ * // Handle other errors
152
+ * }
153
+ * }
154
+ * ```
155
+ */
156
+ export declare const rateLimiter: RateLimiter;