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,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ObsidianRestApiService } from "../../../services/obsidianRestAPI/index.js";
|
|
3
|
+
import { VaultCacheService } from "../../../services/obsidianRestAPI/vaultCache/index.js";
|
|
4
|
+
import { RequestContext } from "../../../utils/index.js";
|
|
5
|
+
declare const ObsidianGlobalSearchInputSchema: z.ZodObject<{
|
|
6
|
+
query: z.ZodString;
|
|
7
|
+
searchInPath: z.ZodOptional<z.ZodString>;
|
|
8
|
+
contextLength: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9
|
+
modified_since: z.ZodOptional<z.ZodString>;
|
|
10
|
+
modified_until: z.ZodOptional<z.ZodString>;
|
|
11
|
+
useRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
12
|
+
caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
|
+
pageSize: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
14
|
+
page: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
15
|
+
maxMatchesPerFile: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
caseSensitive: boolean;
|
|
18
|
+
query: string;
|
|
19
|
+
contextLength: number;
|
|
20
|
+
useRegex: boolean;
|
|
21
|
+
pageSize: number;
|
|
22
|
+
page: number;
|
|
23
|
+
maxMatchesPerFile: number;
|
|
24
|
+
searchInPath?: string | undefined;
|
|
25
|
+
modified_since?: string | undefined;
|
|
26
|
+
modified_until?: string | undefined;
|
|
27
|
+
}, {
|
|
28
|
+
query: string;
|
|
29
|
+
caseSensitive?: boolean | undefined;
|
|
30
|
+
searchInPath?: string | undefined;
|
|
31
|
+
contextLength?: number | undefined;
|
|
32
|
+
modified_since?: string | undefined;
|
|
33
|
+
modified_until?: string | undefined;
|
|
34
|
+
useRegex?: boolean | undefined;
|
|
35
|
+
pageSize?: number | undefined;
|
|
36
|
+
page?: number | undefined;
|
|
37
|
+
maxMatchesPerFile?: number | undefined;
|
|
38
|
+
}>;
|
|
39
|
+
export declare const ObsidianGlobalSearchInputSchemaShape: {
|
|
40
|
+
query: z.ZodString;
|
|
41
|
+
searchInPath: z.ZodOptional<z.ZodString>;
|
|
42
|
+
contextLength: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
43
|
+
modified_since: z.ZodOptional<z.ZodString>;
|
|
44
|
+
modified_until: z.ZodOptional<z.ZodString>;
|
|
45
|
+
useRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
46
|
+
caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
47
|
+
pageSize: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
48
|
+
page: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
49
|
+
maxMatchesPerFile: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
50
|
+
};
|
|
51
|
+
export type ObsidianGlobalSearchInput = z.infer<typeof ObsidianGlobalSearchInputSchema>;
|
|
52
|
+
export interface MatchContext {
|
|
53
|
+
context: string;
|
|
54
|
+
matchText?: string;
|
|
55
|
+
position?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface GlobalSearchResult {
|
|
58
|
+
path: string;
|
|
59
|
+
filename: string;
|
|
60
|
+
matches: MatchContext[];
|
|
61
|
+
modifiedTime: string;
|
|
62
|
+
createdTime: string;
|
|
63
|
+
numericMtime: number;
|
|
64
|
+
}
|
|
65
|
+
export interface ObsidianGlobalSearchResponse {
|
|
66
|
+
success: boolean;
|
|
67
|
+
message: string;
|
|
68
|
+
results: GlobalSearchResult[];
|
|
69
|
+
totalFilesFound: number;
|
|
70
|
+
totalMatchesFound: number;
|
|
71
|
+
currentPage: number;
|
|
72
|
+
pageSize: number;
|
|
73
|
+
totalPages: number;
|
|
74
|
+
alsoFoundInFiles?: string[];
|
|
75
|
+
}
|
|
76
|
+
export declare const processObsidianGlobalSearch: (params: ObsidianGlobalSearchInput, context: RequestContext, obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined) => Promise<ObsidianGlobalSearchResponse>;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import path from "node:path/posix";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
4
|
+
// Import formatTimestamp utility
|
|
5
|
+
import { config } from "../../../config/index.js";
|
|
6
|
+
import { dateParser, formatTimestamp, logger, retryWithDelay, sanitizeInputForLogging, } from "../../../utils/index.js";
|
|
7
|
+
// ====================================================================================
|
|
8
|
+
// Schema Definitions (Updated for Pagination, Match Limit, and Path Filter)
|
|
9
|
+
// ====================================================================================
|
|
10
|
+
const ObsidianGlobalSearchInputSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
query: z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe("The search query (text or regex pattern)."),
|
|
16
|
+
searchInPath: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Optional vault-relative path to recursively search within (e.g., 'Notes/Projects'). If omitted, searches the entire vault."),
|
|
20
|
+
contextLength: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.positive()
|
|
24
|
+
.optional()
|
|
25
|
+
.default(100)
|
|
26
|
+
.describe("Characters of context around matches."),
|
|
27
|
+
modified_since: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Filter files modified *since* this date/time (e.g., '2 weeks ago', '2024-01-15')."),
|
|
31
|
+
modified_until: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Filter files modified *until* this date/time (e.g., 'today', '2024-03-20 17:00')."),
|
|
35
|
+
useRegex: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.optional()
|
|
38
|
+
.default(false)
|
|
39
|
+
.describe("Treat 'query' as regex. Defaults to false."),
|
|
40
|
+
caseSensitive: z
|
|
41
|
+
.boolean()
|
|
42
|
+
.optional()
|
|
43
|
+
.default(false)
|
|
44
|
+
.describe("Perform case-sensitive search. Defaults to false."),
|
|
45
|
+
pageSize: z
|
|
46
|
+
.number()
|
|
47
|
+
.int()
|
|
48
|
+
.positive()
|
|
49
|
+
.optional()
|
|
50
|
+
.default(50)
|
|
51
|
+
.describe("Maximum number of result files per page. Defaults to 50."),
|
|
52
|
+
page: z
|
|
53
|
+
.number()
|
|
54
|
+
.int()
|
|
55
|
+
.positive()
|
|
56
|
+
.optional()
|
|
57
|
+
.default(1)
|
|
58
|
+
.describe("Page number of results to return. Defaults to 1."),
|
|
59
|
+
maxMatchesPerFile: z
|
|
60
|
+
.number()
|
|
61
|
+
.int()
|
|
62
|
+
.positive()
|
|
63
|
+
.optional()
|
|
64
|
+
.default(5)
|
|
65
|
+
.describe("Maximum number of matches to show per file. Defaults to 5."),
|
|
66
|
+
})
|
|
67
|
+
.describe("Performs search across vault content using text or regex. Supports filtering by modification date, directory path, pagination, and limiting matches per file.");
|
|
68
|
+
export const ObsidianGlobalSearchInputSchemaShape = ObsidianGlobalSearchInputSchema.shape;
|
|
69
|
+
// ====================================================================================
|
|
70
|
+
// Helper Function (findMatchesInContent - for Cache Fallback)
|
|
71
|
+
// ====================================================================================
|
|
72
|
+
// Removed lineNumber calculation and return
|
|
73
|
+
function findMatchesInContent(content, query, useRegex, caseSensitive, contextLength, context) {
|
|
74
|
+
const matches = [];
|
|
75
|
+
let regex;
|
|
76
|
+
const operation = "findMatchesInContent";
|
|
77
|
+
const opContext = { ...context, operation };
|
|
78
|
+
try {
|
|
79
|
+
const flags = `g${caseSensitive ? "" : "i"}`;
|
|
80
|
+
regex = useRegex
|
|
81
|
+
? new RegExp(query, flags)
|
|
82
|
+
: new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
const errorMsg = `[${operation}] Invalid regex pattern: ${query}`;
|
|
86
|
+
logger.error(errorMsg, e instanceof Error ? e : undefined, opContext);
|
|
87
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid regex pattern: ${query}`, opContext);
|
|
88
|
+
}
|
|
89
|
+
let match;
|
|
90
|
+
// Removed line number calculation logic
|
|
91
|
+
while ((match = regex.exec(content)) !== null) {
|
|
92
|
+
const matchIndex = match.index;
|
|
93
|
+
const matchText = match[0];
|
|
94
|
+
const startIndex = Math.max(0, matchIndex - contextLength);
|
|
95
|
+
const endIndex = Math.min(content.length, matchIndex + matchText.length + contextLength);
|
|
96
|
+
const contextSnippet = content.substring(startIndex, endIndex);
|
|
97
|
+
// Find position *within* the snippet for consistency with API fallback
|
|
98
|
+
const positionInSnippet = contextSnippet
|
|
99
|
+
.toLowerCase()
|
|
100
|
+
.indexOf(matchText.toLowerCase());
|
|
101
|
+
matches.push({
|
|
102
|
+
// lineNumber removed
|
|
103
|
+
context: contextSnippet,
|
|
104
|
+
matchText: matchText, // Included for cache search
|
|
105
|
+
position: positionInSnippet >= 0 ? positionInSnippet : 0, // Included for cache search
|
|
106
|
+
});
|
|
107
|
+
if (matchText.length === 0)
|
|
108
|
+
regex.lastIndex++;
|
|
109
|
+
}
|
|
110
|
+
return matches;
|
|
111
|
+
}
|
|
112
|
+
// ====================================================================================
|
|
113
|
+
// Core Logic Function (API-First with Cache Fallback)
|
|
114
|
+
// ====================================================================================
|
|
115
|
+
const API_SEARCH_TIMEOUT_MS = config.obsidianApiSearchTimeoutMs;
|
|
116
|
+
export const processObsidianGlobalSearch = async (params, context, obsidianService, vaultCacheService) => {
|
|
117
|
+
const operation = "processObsidianGlobalSearch";
|
|
118
|
+
const opContext = { ...context, operation };
|
|
119
|
+
logger.info(`Processing obsidian_global_search request: "${params.query}" (API-first)`, { ...opContext, params: sanitizeInputForLogging(params) });
|
|
120
|
+
let sinceDate = null;
|
|
121
|
+
let untilDate = null;
|
|
122
|
+
let strategyMessage = "";
|
|
123
|
+
let allFilteredResults = []; // Store all results matching filters before pagination
|
|
124
|
+
let totalMatchesCount = 0; // Total matches across all files before limiting per file
|
|
125
|
+
// Normalize searchInPath: remove leading/trailing slashes and ensure it ends with a slash if not empty
|
|
126
|
+
const searchPathPrefix = params.searchInPath
|
|
127
|
+
? params.searchInPath.replace(/^\/+|\/+$/g, "") +
|
|
128
|
+
(params.searchInPath === "/" ? "" : "/")
|
|
129
|
+
: ""; // Empty string means search entire vault
|
|
130
|
+
// 1. Parse Date Filters
|
|
131
|
+
const dateParseContext = { ...opContext, subOperation: "parseDates" };
|
|
132
|
+
try {
|
|
133
|
+
if (params.modified_since)
|
|
134
|
+
sinceDate = await dateParser.parseToDate(params.modified_since, dateParseContext);
|
|
135
|
+
if (params.modified_until)
|
|
136
|
+
untilDate = await dateParser.parseToDate(params.modified_until, dateParseContext);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const errMsg = `Invalid date format provided`;
|
|
140
|
+
logger.error(errMsg, error instanceof Error ? error : undefined, dateParseContext);
|
|
141
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, errMsg, dateParseContext);
|
|
142
|
+
}
|
|
143
|
+
// 2. Attempt API Search with Retries and Timeout
|
|
144
|
+
let apiFailedOrTimedOut = false;
|
|
145
|
+
try {
|
|
146
|
+
strategyMessage = `Attempting live API search with retries (timeout: ${API_SEARCH_TIMEOUT_MS / 1000}s per attempt). `;
|
|
147
|
+
const apiSearchContext = {
|
|
148
|
+
...opContext,
|
|
149
|
+
subOperation: "searchApiSimpleWithRetry",
|
|
150
|
+
};
|
|
151
|
+
const apiResults = await retryWithDelay(async () => {
|
|
152
|
+
logger.info(`Calling obsidianService.searchSimple for query: "${params.query}"`, apiSearchContext);
|
|
153
|
+
const apiCallPromise = obsidianService.searchSimple(params.query, params.contextLength, apiSearchContext);
|
|
154
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`API search timed out after ${API_SEARCH_TIMEOUT_MS}ms`)), API_SEARCH_TIMEOUT_MS));
|
|
155
|
+
return await Promise.race([apiCallPromise, timeoutPromise]);
|
|
156
|
+
}, {
|
|
157
|
+
operationName: "obsidianService.searchSimple",
|
|
158
|
+
context: apiSearchContext,
|
|
159
|
+
maxRetries: 2, // Total of 3 attempts
|
|
160
|
+
delayMs: 500,
|
|
161
|
+
shouldRetry: (err) => {
|
|
162
|
+
// Retry on any error during the API call phase
|
|
163
|
+
logger.warning(`API search attempt failed. Retrying...`, {
|
|
164
|
+
...apiSearchContext,
|
|
165
|
+
error: err instanceof Error ? err.message : String(err),
|
|
166
|
+
});
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
strategyMessage += `API search successful, returned ${apiResults.length} potential files. `;
|
|
171
|
+
logger.info(`API searchSimple returned ${apiResults.length} files with potential matches.`, apiSearchContext);
|
|
172
|
+
// Process API results (fetch stats for date filtering and inclusion)
|
|
173
|
+
const fetchStatsContext = {
|
|
174
|
+
...opContext,
|
|
175
|
+
subOperation: "fetchStatsForApiResults",
|
|
176
|
+
};
|
|
177
|
+
let processedCount = 0;
|
|
178
|
+
for (const apiResult of apiResults) {
|
|
179
|
+
const filePathFromApi = apiResult.filename; // API uses 'filename' for the full path
|
|
180
|
+
// Apply path filter
|
|
181
|
+
if (searchPathPrefix && !filePathFromApi.startsWith(searchPathPrefix)) {
|
|
182
|
+
continue; // Skip if file is not in the specified path
|
|
183
|
+
}
|
|
184
|
+
let mtime;
|
|
185
|
+
let ctime;
|
|
186
|
+
// Fetch stats regardless of date filtering to include in results
|
|
187
|
+
try {
|
|
188
|
+
const noteJson = (await obsidianService.getFileContent(filePathFromApi, "json", fetchStatsContext));
|
|
189
|
+
mtime = noteJson.stat.mtime;
|
|
190
|
+
ctime = noteJson.stat.ctime; // Get ctime
|
|
191
|
+
// Apply date filtering if needed
|
|
192
|
+
if ((sinceDate && mtime < sinceDate.getTime()) ||
|
|
193
|
+
(untilDate && mtime > untilDate.getTime())) {
|
|
194
|
+
continue; // Skip due to date filter
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (statError) {
|
|
198
|
+
logger.warning(`Failed to fetch stats for file ${filePathFromApi}. Skipping file. Error: ${statError instanceof Error ? statError.message : String(statError)}`, fetchStatsContext);
|
|
199
|
+
continue; // Skip if stats cannot be fetched
|
|
200
|
+
}
|
|
201
|
+
// Transform SimpleSearchMatch[] to MatchContext[] - OMITTING matchText and position
|
|
202
|
+
const transformedMatches = [];
|
|
203
|
+
for (const apiMatch of apiResult.matches) {
|
|
204
|
+
transformedMatches.push({
|
|
205
|
+
// lineNumber removed
|
|
206
|
+
context: apiMatch.context, // Use the context provided by the API
|
|
207
|
+
// matchText and position are omitted as they cannot be reliably determined from API result
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// Apply match limit per file
|
|
211
|
+
const limitedMatches = transformedMatches.slice(0, params.maxMatchesPerFile);
|
|
212
|
+
// Only add if we actually found matches after transformation/filtering
|
|
213
|
+
if (limitedMatches.length > 0) {
|
|
214
|
+
allFilteredResults.push({
|
|
215
|
+
// Add to the unfiltered list first
|
|
216
|
+
path: filePathFromApi,
|
|
217
|
+
filename: path.basename(filePathFromApi),
|
|
218
|
+
matches: limitedMatches, // Use limited matches
|
|
219
|
+
modifiedTime: formatTimestamp(mtime, fetchStatsContext), // Format mtime
|
|
220
|
+
createdTime: formatTimestamp(ctime, fetchStatsContext), // Format ctime
|
|
221
|
+
numericMtime: mtime, // Store numeric mtime
|
|
222
|
+
});
|
|
223
|
+
totalMatchesCount += transformedMatches.length; // Count *all* matches before limiting for total count
|
|
224
|
+
processedCount++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
strategyMessage += `Processed ${processedCount} files matching all filters (including path: '${searchPathPrefix || "entire vault"}'). `;
|
|
228
|
+
}
|
|
229
|
+
catch (apiError) {
|
|
230
|
+
// API call failed or timed out internally
|
|
231
|
+
apiFailedOrTimedOut = true;
|
|
232
|
+
strategyMessage += `API search failed or timed out (${apiError instanceof Error ? apiError.message : String(apiError)}). `;
|
|
233
|
+
logger.warning(strategyMessage, {
|
|
234
|
+
...opContext,
|
|
235
|
+
subOperation: "apiSearchFailedOrTimedOut",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// 3. Fallback to Cache if API Failed/Timed Out
|
|
239
|
+
if (apiFailedOrTimedOut) {
|
|
240
|
+
if (vaultCacheService && vaultCacheService.isReady()) {
|
|
241
|
+
strategyMessage += "Falling back to in-memory cache. ";
|
|
242
|
+
logger.info("API search failed/timed out. Falling back to in-memory cache.", opContext);
|
|
243
|
+
const cache = vaultCacheService.getCache();
|
|
244
|
+
const cacheSearchContext = {
|
|
245
|
+
...opContext,
|
|
246
|
+
subOperation: "searchCacheFallback",
|
|
247
|
+
};
|
|
248
|
+
allFilteredResults = []; // Reset results for cache search
|
|
249
|
+
totalMatchesCount = 0;
|
|
250
|
+
let processedCount = 0;
|
|
251
|
+
for (const [filePath, cacheEntry] of cache.entries()) {
|
|
252
|
+
// Apply path filter
|
|
253
|
+
if (searchPathPrefix && !filePath.startsWith(searchPathPrefix)) {
|
|
254
|
+
continue; // Skip if file is not in the specified path
|
|
255
|
+
}
|
|
256
|
+
const mtime = cacheEntry.mtime; // Get mtime from cache
|
|
257
|
+
// Apply date filtering
|
|
258
|
+
if ((sinceDate && mtime < sinceDate.getTime()) ||
|
|
259
|
+
(untilDate && mtime > untilDate.getTime())) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const matches = findMatchesInContent(cacheEntry.content, params.query, params.useRegex, params.caseSensitive, params.contextLength, cacheSearchContext);
|
|
264
|
+
// Apply match limit per file
|
|
265
|
+
const limitedMatches = matches.slice(0, params.maxMatchesPerFile);
|
|
266
|
+
if (limitedMatches.length > 0) {
|
|
267
|
+
let ctime = null;
|
|
268
|
+
// Attempt to fetch ctime as cache likely doesn't have it
|
|
269
|
+
try {
|
|
270
|
+
const noteJson = (await obsidianService.getFileContent(filePath, "json", cacheSearchContext));
|
|
271
|
+
ctime = noteJson.stat.ctime;
|
|
272
|
+
}
|
|
273
|
+
catch (statError) {
|
|
274
|
+
logger.warning(`Failed to fetch ctime for cached file ${filePath} during fallback. Error: ${statError instanceof Error ? statError.message : String(statError)}`, cacheSearchContext);
|
|
275
|
+
// Proceed without ctime if fetch fails
|
|
276
|
+
}
|
|
277
|
+
allFilteredResults.push({
|
|
278
|
+
// Add to unfiltered list
|
|
279
|
+
path: filePath,
|
|
280
|
+
filename: path.basename(filePath),
|
|
281
|
+
modifiedTime: formatTimestamp(mtime, cacheSearchContext), // Format mtime
|
|
282
|
+
createdTime: formatTimestamp(ctime ?? mtime, cacheSearchContext), // Format ctime (or mtime fallback)
|
|
283
|
+
matches: limitedMatches, // Use limited matches
|
|
284
|
+
numericMtime: mtime, // Store numeric mtime from cache
|
|
285
|
+
});
|
|
286
|
+
totalMatchesCount += matches.length; // Count *all* matches before limiting
|
|
287
|
+
processedCount++;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (matchError) {
|
|
291
|
+
logger.warning(`Error matching content in cached file ${filePath} during fallback: ${matchError instanceof Error ? matchError.message : String(matchError)}`, cacheSearchContext);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
strategyMessage += `Searched ${cache.size} cached files, processed ${processedCount} matching all filters (including path: '${searchPathPrefix || "entire vault"}'). `;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// This block now handles both "cache disabled" and "cache not ready"
|
|
298
|
+
const reason = vaultCacheService ? "is not ready" : "is disabled";
|
|
299
|
+
strategyMessage += `Cache not available (${reason}), unable to fallback. `;
|
|
300
|
+
logger.error(`API search failed and cache ${reason}. Cannot perform search.`, opContext);
|
|
301
|
+
// Throw a specific error because the tool cannot function without a data source.
|
|
302
|
+
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Live API search failed and the cache is currently ${reason}. Please ensure the Obsidian REST API is running and reachable, and that the cache is enabled and has had time to build.`, opContext);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// 4. Apply Pagination and Sorting
|
|
306
|
+
const totalFilesFound = allFilteredResults.length;
|
|
307
|
+
const pageSize = params.pageSize;
|
|
308
|
+
const currentPage = params.page;
|
|
309
|
+
const totalPages = Math.ceil(totalFilesFound / pageSize);
|
|
310
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
311
|
+
const endIndex = startIndex + pageSize;
|
|
312
|
+
// Sort results by numeric modified time (descending) *before* pagination
|
|
313
|
+
allFilteredResults.sort((a, b) => {
|
|
314
|
+
return b.numericMtime - a.numericMtime; // Descending
|
|
315
|
+
});
|
|
316
|
+
const paginatedResults = allFilteredResults.slice(startIndex, endIndex);
|
|
317
|
+
// 5. Determine alsoFoundInFiles
|
|
318
|
+
let alsoFoundInFiles = undefined;
|
|
319
|
+
if (totalPages > 1) {
|
|
320
|
+
const paginatedFilePaths = new Set(paginatedResults.map((r) => r.path));
|
|
321
|
+
alsoFoundInFiles = allFilteredResults
|
|
322
|
+
.filter((r) => !paginatedFilePaths.has(r.path)) // Get files not on the current page
|
|
323
|
+
.map((r) => r.filename); // Then get their filenames
|
|
324
|
+
alsoFoundInFiles = [...new Set(alsoFoundInFiles)]; // Ensure unique filenames in the final list
|
|
325
|
+
}
|
|
326
|
+
// 6. Construct Final Response
|
|
327
|
+
const finalMessage = `${strategyMessage}Found ${totalMatchesCount} matches across ${totalFilesFound} files matching all criteria. Returning page ${currentPage} of ${totalPages} (${paginatedResults.length} files on this page, page size ${pageSize}, max matches per file ${params.maxMatchesPerFile}). Results sorted by modification date (newest first).`;
|
|
328
|
+
const response = {
|
|
329
|
+
success: true, // Indicate overall tool success, even if fallback was used or results are empty
|
|
330
|
+
message: finalMessage,
|
|
331
|
+
results: paginatedResults,
|
|
332
|
+
totalFilesFound: totalFilesFound,
|
|
333
|
+
totalMatchesFound: totalMatchesCount,
|
|
334
|
+
currentPage: currentPage,
|
|
335
|
+
pageSize: pageSize,
|
|
336
|
+
totalPages: totalPages,
|
|
337
|
+
alsoFoundInFiles: alsoFoundInFiles, // Add the list here
|
|
338
|
+
};
|
|
339
|
+
logger.info(`Global search processing completed. ${finalMessage}`, opContext);
|
|
340
|
+
return response;
|
|
341
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ObsidianGlobalSearchToolRegistration
|
|
3
|
+
* @description Registers the 'obsidian_global_search' tool with the MCP server.
|
|
4
|
+
* This tool allows searching the Obsidian vault using text/regex queries with optional date filters.
|
|
5
|
+
*/
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import type { ObsidianRestApiService } from "../../../services/obsidianRestAPI/index.js";
|
|
8
|
+
import type { VaultCacheService } from "../../../services/obsidianRestAPI/vaultCache/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Registers the 'obsidian_global_search' tool with the MCP server instance.
|
|
11
|
+
*
|
|
12
|
+
* @param {McpServer} server - The MCP server instance.
|
|
13
|
+
* @param {ObsidianRestApiService} obsidianService - The instance of the Obsidian REST API service.
|
|
14
|
+
* @param {VaultCacheService} vaultCacheService - The instance of the Vault Cache service.
|
|
15
|
+
* @returns {Promise<void>} A promise that resolves when the tool is registered.
|
|
16
|
+
* @throws {McpError} If registration fails critically.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerObsidianGlobalSearchTool(server: McpServer, obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ObsidianGlobalSearchToolRegistration
|
|
3
|
+
* @description Registers the 'obsidian_global_search' tool with the MCP server.
|
|
4
|
+
* This tool allows searching the Obsidian vault using text/regex queries with optional date filters.
|
|
5
|
+
*/
|
|
6
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
7
|
+
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
8
|
+
import { ObsidianGlobalSearchInputSchemaShape, processObsidianGlobalSearch, } from "./logic.js"; // Ensure '.js' extension
|
|
9
|
+
/**
|
|
10
|
+
* Registers the 'obsidian_global_search' tool with the MCP server instance.
|
|
11
|
+
*
|
|
12
|
+
* @param {McpServer} server - The MCP server instance.
|
|
13
|
+
* @param {ObsidianRestApiService} obsidianService - The instance of the Obsidian REST API service.
|
|
14
|
+
* @param {VaultCacheService} vaultCacheService - The instance of the Vault Cache service.
|
|
15
|
+
* @returns {Promise<void>} A promise that resolves when the tool is registered.
|
|
16
|
+
* @throws {McpError} If registration fails critically.
|
|
17
|
+
*/
|
|
18
|
+
export async function registerObsidianGlobalSearchTool(server, obsidianService, vaultCacheService) {
|
|
19
|
+
const toolName = "obsidian_global_search";
|
|
20
|
+
const toolDescription = `Performs search across the Obsidian vault using text or regex, primarily relying on the Obsidian REST API's simple search. Supports filtering by modification date, optionally restricting search to a specific directory path (recursively), pagination (page, pageSize), and limiting matches shown per file (maxMatchesPerFile). Returns a JSON object containing success status, a message, pagination details (currentPage, pageSize, totalPages), total file/match counts (before pagination), and an array of results. Each result includes the file path, filename, creation timestamp (ctime), modification timestamp (mtime), and an array of match context snippets (limited by maxMatchesPerFile). If there are multiple pages of results, it also includes an 'alsoFoundInFiles' array listing filenames found on other pages.`;
|
|
21
|
+
const registrationContext = requestContextService.createRequestContext({
|
|
22
|
+
operation: "RegisterObsidianGlobalSearchTool",
|
|
23
|
+
toolName: toolName,
|
|
24
|
+
module: "ObsidianGlobalSearchRegistration",
|
|
25
|
+
});
|
|
26
|
+
logger.info(`Attempting to register tool: ${toolName}`, registrationContext);
|
|
27
|
+
await ErrorHandler.tryCatch(async () => {
|
|
28
|
+
server.tool(toolName, toolDescription, ObsidianGlobalSearchInputSchemaShape, async (params, handlerInvocationContext) => {
|
|
29
|
+
const handlerContext = requestContextService.createRequestContext({
|
|
30
|
+
operation: "HandleObsidianGlobalSearchRequest",
|
|
31
|
+
toolName: toolName,
|
|
32
|
+
paramsSummary: {
|
|
33
|
+
useRegex: params.useRegex,
|
|
34
|
+
caseSensitive: params.caseSensitive,
|
|
35
|
+
pageSize: params.pageSize,
|
|
36
|
+
page: params.page,
|
|
37
|
+
maxMatchesPerFile: params.maxMatchesPerFile,
|
|
38
|
+
searchInPath: params.searchInPath,
|
|
39
|
+
hasDateFilter: !!(params.modified_since || params.modified_until),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
logger.debug(`Handling '${toolName}' request`, handlerContext);
|
|
43
|
+
return await ErrorHandler.tryCatch(async () => {
|
|
44
|
+
const response = await processObsidianGlobalSearch(params, handlerContext, obsidianService, vaultCacheService);
|
|
45
|
+
logger.debug(`'${toolName}' processed successfully`, handlerContext);
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: JSON.stringify(response, null, 2),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
isError: false,
|
|
54
|
+
};
|
|
55
|
+
}, {
|
|
56
|
+
operation: `executing tool ${toolName}`,
|
|
57
|
+
context: handlerContext,
|
|
58
|
+
errorCode: BaseErrorCode.INTERNAL_ERROR,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
logger.info(`Tool registered successfully: ${toolName}`, registrationContext);
|
|
62
|
+
}, {
|
|
63
|
+
operation: `registering tool ${toolName}`,
|
|
64
|
+
context: registrationContext,
|
|
65
|
+
errorCode: BaseErrorCode.INTERNAL_ERROR,
|
|
66
|
+
errorMapper: (error) => new McpError(error instanceof McpError ? error.code : BaseErrorCode.INTERNAL_ERROR, `Failed to register tool '${toolName}': ${error instanceof Error ? error.message : "Unknown error"}`, { ...registrationContext }),
|
|
67
|
+
critical: true,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for the 'obsidian_list_files' MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* This file serves as the public entry point for the obsidian_list_files tool module.
|
|
5
|
+
* It re-exports the primary registration function (`registerObsidianListFilesTool`)
|
|
6
|
+
* from the './registration.js' module. This pattern simplifies imports for consumers
|
|
7
|
+
* of the tool, allowing them to import necessary components from a single location.
|
|
8
|
+
*
|
|
9
|
+
* Consumers (like the main server setup) should import the registration function
|
|
10
|
+
* from this file to integrate the tool into the MCP server instance.
|
|
11
|
+
*/
|
|
12
|
+
export { registerObsidianListFilesTool } from "./registration.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for the 'obsidian_list_files' MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* This file serves as the public entry point for the obsidian_list_files tool module.
|
|
5
|
+
* It re-exports the primary registration function (`registerObsidianListFilesTool`)
|
|
6
|
+
* from the './registration.js' module. This pattern simplifies imports for consumers
|
|
7
|
+
* of the tool, allowing them to import necessary components from a single location.
|
|
8
|
+
*
|
|
9
|
+
* Consumers (like the main server setup) should import the registration function
|
|
10
|
+
* from this file to integrate the tool into the MCP server instance.
|
|
11
|
+
*/
|
|
12
|
+
export { registerObsidianListFilesTool } from "./registration.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ObsidianRestApiService } from "../../../services/obsidianRestAPI/index.js";
|
|
3
|
+
import { RequestContext } from "../../../utils/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for validating the input parameters of the 'obsidian_list_files' tool.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ObsidianListFilesInputSchema: z.ZodObject<{
|
|
8
|
+
/**
|
|
9
|
+
* The vault-relative path to the directory whose contents should be listed.
|
|
10
|
+
* Examples: "Attachments/Images", "Projects", "" (for vault root), "/" (for vault root).
|
|
11
|
+
* The path is treated as case-sensitive by the underlying Obsidian API.
|
|
12
|
+
*/
|
|
13
|
+
dirPath: z.ZodString;
|
|
14
|
+
/**
|
|
15
|
+
* Optional array of file extensions (including the leading dot) to filter the results.
|
|
16
|
+
* Only files matching one of these extensions will be included. Directories are always included regardless of this filter.
|
|
17
|
+
* Example: [".md", ".png"]
|
|
18
|
+
*/
|
|
19
|
+
fileExtensionFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
20
|
+
/**
|
|
21
|
+
* Optional JavaScript-compatible regular expression pattern string to filter results by name.
|
|
22
|
+
* Only files and directories whose names match the regex will be included.
|
|
23
|
+
* Example: "^\\d{4}-\\d{2}-\\d{2}" (matches names starting with YYYY-MM-DD)
|
|
24
|
+
*/
|
|
25
|
+
nameRegexFilter: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
dirPath: string;
|
|
28
|
+
fileExtensionFilter?: string[] | undefined;
|
|
29
|
+
nameRegexFilter?: string | null | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
dirPath: string;
|
|
32
|
+
fileExtensionFilter?: string[] | undefined;
|
|
33
|
+
nameRegexFilter?: string | null | undefined;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* TypeScript type inferred from the input schema (`ObsidianListFilesInputSchema`).
|
|
37
|
+
* Represents the validated input parameters used within the core processing logic.
|
|
38
|
+
*/
|
|
39
|
+
export type ObsidianListFilesInput = z.infer<typeof ObsidianListFilesInputSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* Defines the structure of the successful response returned by the `processObsidianListFiles` function.
|
|
42
|
+
* This object is typically serialized to JSON and sent back to the client.
|
|
43
|
+
*/
|
|
44
|
+
export interface ObsidianListFilesResponse {
|
|
45
|
+
/** The vault-relative path of the directory whose contents were listed (normalized, e.g., "/" for root). */
|
|
46
|
+
directoryPath: string;
|
|
47
|
+
/** A string representation of the directory contents formatted as a simple tree structure. */
|
|
48
|
+
tree: string;
|
|
49
|
+
/** The total number of files and directories included in the formatted tree after filtering. */
|
|
50
|
+
totalEntries: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Processes the core logic for listing files and directories within a specified
|
|
54
|
+
* directory in the Obsidian vault. Applies optional filters and formats the output.
|
|
55
|
+
*
|
|
56
|
+
* @param {ObsidianListFilesInput} params - The validated input parameters.
|
|
57
|
+
* @param {RequestContext} context - The request context for logging and correlation.
|
|
58
|
+
* @param {ObsidianRestApiService} obsidianService - An instance of the Obsidian REST API service.
|
|
59
|
+
* @returns {Promise<ObsidianListFilesResponse>} A promise resolving to the structured success response
|
|
60
|
+
* containing the listed directory path, a formatted tree string, and the total entry count.
|
|
61
|
+
* @throws {McpError} Throws an McpError if the directory cannot be listed (e.g., not found)
|
|
62
|
+
* or if any other API interaction or validation fails.
|
|
63
|
+
*/
|
|
64
|
+
export declare const processObsidianListFiles: (params: ObsidianListFilesInput, context: RequestContext, obsidianService: ObsidianRestApiService) => Promise<ObsidianListFilesResponse>;
|