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.
- package/README.md +248 -104
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.js +191 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.js +296 -18
- package/dist/mcp-server/server.d.ts +33 -0
- package/dist/mcp-server/server.js +211 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/logic.d.ts +51 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/logic.js +168 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/registration.d.ts +19 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/registration.js +91 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +77 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +341 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +18 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +69 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/logic.d.ts +64 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/logic.js +179 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/registration.d.ts +19 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/registration.js +96 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +2 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +42 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +152 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +52 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +2 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +28 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +161 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +52 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/logic.d.ts +87 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/logic.js +216 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/registration.d.ts +20 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/registration.js +101 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +255 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +583 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +22 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +111 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/logic.d.ts +183 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/logic.js +490 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/registration.d.ts +21 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/registration.js +108 -0
- package/dist/mcp-server/transports/authentication/authContext.d.ts +33 -0
- package/dist/mcp-server/transports/authentication/authContext.js +24 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +30 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
- package/dist/mcp-server/transports/authentication/authUtils.d.ts +18 -0
- package/dist/mcp-server/transports/authentication/authUtils.js +45 -0
- package/dist/mcp-server/transports/authentication/oauthMiddleware.d.ts +24 -0
- package/dist/mcp-server/transports/authentication/oauthMiddleware.js +109 -0
- package/dist/mcp-server/transports/authentication/types.d.ts +17 -0
- package/dist/mcp-server/transports/authentication/types.js +5 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +24 -0
- package/dist/mcp-server/transports/httpTransport.js +496 -0
- package/dist/mcp-server/transports/stdioTransport.d.ts +42 -0
- package/dist/mcp-server/transports/stdioTransport.js +63 -0
- package/dist/services/obsidianRestAPI/index.d.ts +15 -0
- package/dist/services/obsidianRestAPI/index.js +17 -0
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +38 -0
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +62 -0
- package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +22 -0
- package/dist/services/obsidianRestAPI/methods/commandMethods.js +31 -0
- package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +16 -0
- package/dist/services/obsidianRestAPI/methods/openMethods.js +21 -0
- package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +37 -0
- package/dist/services/obsidianRestAPI/methods/patchMethods.js +94 -0
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +42 -0
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +66 -0
- package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +25 -0
- package/dist/services/obsidianRestAPI/methods/searchMethods.js +36 -0
- package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +58 -0
- package/dist/services/obsidianRestAPI/methods/vaultMethods.js +144 -0
- package/dist/services/obsidianRestAPI/service.d.ts +195 -0
- package/dist/services/obsidianRestAPI/service.js +379 -0
- package/dist/services/obsidianRestAPI/types.d.ts +127 -0
- package/dist/services/obsidianRestAPI/types.js +7 -0
- package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +4 -0
- package/dist/services/obsidianRestAPI/vaultCache/index.js +4 -0
- package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +88 -0
- package/dist/services/obsidianRestAPI/vaultCache/service.js +299 -0
- package/dist/types-global/errors.d.ts +73 -0
- package/dist/types-global/errors.js +71 -0
- package/dist/utils/index.d.ts +5 -8
- package/dist/utils/index.js +13 -9
- package/dist/utils/internal/asyncUtils.d.ts +54 -0
- package/dist/utils/internal/asyncUtils.js +101 -0
- package/dist/utils/internal/errorHandler.d.ts +176 -0
- package/dist/utils/internal/errorHandler.js +351 -0
- package/dist/utils/internal/index.d.ts +4 -0
- package/dist/utils/internal/index.js +4 -0
- package/dist/utils/internal/logger.d.ts +141 -0
- package/dist/utils/internal/logger.js +406 -0
- package/dist/utils/internal/requestContext.d.ts +83 -0
- package/dist/utils/internal/requestContext.js +72 -0
- package/dist/utils/metrics/index.d.ts +1 -0
- package/dist/utils/metrics/index.js +1 -0
- package/dist/utils/metrics/tokenCounter.d.ts +27 -0
- package/dist/utils/metrics/tokenCounter.js +128 -0
- package/dist/utils/obsidian/index.d.ts +5 -0
- package/dist/utils/obsidian/index.js +5 -0
- package/dist/utils/obsidian/obsidianApiUtils.d.ts +14 -0
- package/dist/utils/obsidian/obsidianApiUtils.js +29 -0
- package/dist/utils/obsidian/obsidianStatUtils.d.ts +68 -0
- package/dist/utils/obsidian/obsidianStatUtils.js +143 -0
- package/dist/utils/parsing/dateParser.d.ts +56 -0
- package/dist/utils/parsing/dateParser.js +104 -0
- package/dist/utils/parsing/index.d.ts +2 -0
- package/dist/utils/parsing/index.js +3 -0
- package/dist/utils/parsing/jsonParser.d.ts +80 -0
- package/dist/utils/parsing/jsonParser.js +133 -0
- package/dist/utils/security/idGenerator.d.ts +140 -0
- package/dist/utils/security/idGenerator.js +194 -0
- package/dist/utils/security/index.d.ts +3 -0
- package/dist/utils/security/index.js +3 -0
- package/dist/utils/security/rateLimiter.d.ts +156 -0
- package/dist/utils/security/rateLimiter.js +235 -0
- package/dist/utils/security/sanitization.d.ts +244 -0
- package/dist/utils/security/sanitization.js +599 -0
- package/package.json +58 -37
- package/dist/index.js.map +0 -1
- package/dist/mcp/handlers.d.ts +0 -29
- package/dist/mcp/handlers.js +0 -305
- package/dist/mcp/handlers.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -6
- package/dist/mcp/index.js +0 -7
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -18
- package/dist/mcp/server.js +0 -240
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -70
- package/dist/mcp/types.js +0 -49
- package/dist/mcp/types.js.map +0 -1
- package/dist/obsidian/client.d.ts +0 -109
- package/dist/obsidian/client.js +0 -403
- package/dist/obsidian/client.js.map +0 -1
- package/dist/obsidian/errors.d.ts +0 -28
- package/dist/obsidian/errors.js +0 -75
- package/dist/obsidian/errors.js.map +0 -1
- package/dist/obsidian/index.d.ts +0 -6
- package/dist/obsidian/index.js +0 -7
- package/dist/obsidian/index.js.map +0 -1
- package/dist/obsidian/types.d.ts +0 -107
- package/dist/obsidian/types.js +0 -12
- package/dist/obsidian/types.js.map +0 -1
- package/dist/resources/index.d.ts +0 -13
- package/dist/resources/index.js +0 -15
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/tags.d.ts +0 -39
- package/dist/resources/tags.js +0 -257
- package/dist/resources/tags.js.map +0 -1
- package/dist/resources/types.d.ts +0 -27
- package/dist/resources/types.js +0 -5
- package/dist/resources/types.js.map +0 -1
- package/dist/tools/base.d.ts +0 -46
- package/dist/tools/base.js +0 -88
- package/dist/tools/base.js.map +0 -1
- package/dist/tools/files/content.d.ts +0 -58
- package/dist/tools/files/content.js +0 -171
- package/dist/tools/files/content.js.map +0 -1
- package/dist/tools/files/index.d.ts +0 -14
- package/dist/tools/files/index.js +0 -22
- package/dist/tools/files/index.js.map +0 -1
- package/dist/tools/files/list.d.ts +0 -35
- package/dist/tools/files/list.js +0 -133
- package/dist/tools/files/list.js.map +0 -1
- package/dist/tools/index.d.ts +0 -21
- package/dist/tools/index.js +0 -31
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/properties/index.d.ts +0 -14
- package/dist/tools/properties/index.js +0 -19
- package/dist/tools/properties/index.js.map +0 -1
- package/dist/tools/properties/manager.d.ts +0 -62
- package/dist/tools/properties/manager.js +0 -302
- package/dist/tools/properties/manager.js.map +0 -1
- package/dist/tools/properties/tools.d.ts +0 -47
- package/dist/tools/properties/tools.js +0 -239
- package/dist/tools/properties/tools.js.map +0 -1
- package/dist/tools/properties/types.d.ts +0 -141
- package/dist/tools/properties/types.js +0 -70
- package/dist/tools/properties/types.js.map +0 -1
- package/dist/tools/search/complex.d.ts +0 -38
- package/dist/tools/search/complex.js +0 -270
- package/dist/tools/search/complex.js.map +0 -1
- package/dist/tools/search/index.d.ts +0 -14
- package/dist/tools/search/index.js +0 -20
- package/dist/tools/search/index.js.map +0 -1
- package/dist/tools/search/simple.d.ts +0 -25
- package/dist/tools/search/simple.js +0 -127
- package/dist/tools/search/simple.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -24
- package/dist/utils/errors.js +0 -59
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/idGenerator.d.ts +0 -15
- package/dist/utils/idGenerator.js +0 -21
- package/dist/utils/idGenerator.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logging.d.ts +0 -245
- package/dist/utils/logging.js +0 -417
- package/dist/utils/logging.js.map +0 -1
- package/dist/utils/rate-limiting.d.ts +0 -50
- package/dist/utils/rate-limiting.js +0 -94
- package/dist/utils/rate-limiting.js.map +0 -1
- package/dist/utils/tokenization.d.ts +0 -28
- package/dist/utils/tokenization.js +0 -75
- package/dist/utils/tokenization.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -22
- package/dist/utils/validation.js +0 -92
- 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,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;
|