obsidian-mcp-server 2.0.7 → 3.1.0
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/CLAUDE.md +364 -0
- package/Dockerfile +99 -0
- package/LICENSE +4 -6
- package/README.md +246 -206
- package/changelog/3.0.x/3.0.0.md +102 -0
- package/changelog/3.1.x/3.1.0.md +26 -0
- package/changelog/template.md +51 -0
- package/dist/config/server-config.d.ts +19 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +55 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -295
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts +8 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.js +8 -0
- package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +36 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +9 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts +23 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.js +47 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +13 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +30 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts +21 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js +38 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts +45 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.js +66 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +120 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +582 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +40 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +42 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +58 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +66 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +19 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js +43 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +92 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +245 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +13 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js +38 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +273 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +13 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +38 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +68 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +178 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +77 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +172 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +22 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +88 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +81 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +83 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +167 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +76 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +244 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +48 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +100 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -0
- package/dist/services/obsidian/frontmatter-ops.d.ts +34 -0
- package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -0
- package/dist/services/obsidian/frontmatter-ops.js +230 -0
- package/dist/services/obsidian/frontmatter-ops.js.map +1 -0
- package/dist/services/obsidian/obsidian-service.d.ts +83 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -0
- package/dist/services/obsidian/obsidian-service.js +435 -0
- package/dist/services/obsidian/obsidian-service.js.map +1 -0
- package/dist/services/obsidian/section-extractor.d.ts +13 -0
- package/dist/services/obsidian/section-extractor.d.ts.map +1 -0
- package/dist/services/obsidian/section-extractor.js +124 -0
- package/dist/services/obsidian/section-extractor.js.map +1 -0
- package/dist/services/obsidian/types.d.ts +91 -0
- package/dist/services/obsidian/types.d.ts.map +1 -0
- package/dist/services/obsidian/types.js +7 -0
- package/dist/services/obsidian/types.js.map +1 -0
- package/package.json +63 -69
- package/server.json +167 -0
- package/CHANGELOG.md +0 -124
- package/dist/config/index.d.ts +0 -41
- package/dist/config/index.js +0 -191
- package/dist/mcp-server/server.d.ts +0 -33
- package/dist/mcp-server/server.js +0 -211
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.d.ts +0 -51
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.js +0 -168
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.d.ts +0 -19
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.js +0 -91
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +0 -77
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +0 -341
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +0 -18
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +0 -69
- package/dist/mcp-server/tools/obsidianListNotesTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianListNotesTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianListNotesTool/logic.d.ts +0 -68
- package/dist/mcp-server/tools/obsidianListNotesTool/logic.js +0 -215
- package/dist/mcp-server/tools/obsidianListNotesTool/registration.d.ts +0 -23
- package/dist/mcp-server/tools/obsidianListNotesTool/registration.js +0 -98
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +0 -2
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +0 -42
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +0 -152
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +0 -52
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +0 -2
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +0 -28
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +0 -161
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +0 -52
- package/dist/mcp-server/tools/obsidianReadNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianReadNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianReadNoteTool/logic.d.ts +0 -87
- package/dist/mcp-server/tools/obsidianReadNoteTool/logic.js +0 -216
- package/dist/mcp-server/tools/obsidianReadNoteTool/registration.d.ts +0 -20
- package/dist/mcp-server/tools/obsidianReadNoteTool/registration.js +0 -101
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +0 -255
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +0 -583
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +0 -22
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +0 -111
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.d.ts +0 -183
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.js +0 -490
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.d.ts +0 -21
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.js +0 -108
- package/dist/mcp-server/transports/auth/core/authContext.d.ts +0 -33
- package/dist/mcp-server/transports/auth/core/authContext.js +0 -24
- package/dist/mcp-server/transports/auth/core/authTypes.d.ts +0 -17
- package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
- package/dist/mcp-server/transports/auth/core/authUtils.d.ts +0 -18
- package/dist/mcp-server/transports/auth/core/authUtils.js +0 -45
- package/dist/mcp-server/transports/auth/index.d.ts +0 -10
- package/dist/mcp-server/transports/auth/index.js +0 -9
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
- package/dist/mcp-server/transports/httpErrorHandler.d.ts +0 -26
- package/dist/mcp-server/transports/httpErrorHandler.js +0 -73
- package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
- package/dist/mcp-server/transports/httpTransport.js +0 -208
- package/dist/mcp-server/transports/stdioTransport.d.ts +0 -42
- package/dist/mcp-server/transports/stdioTransport.js +0 -63
- package/dist/services/obsidianRestAPI/index.d.ts +0 -15
- package/dist/services/obsidianRestAPI/index.js +0 -17
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +0 -38
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +0 -62
- package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +0 -22
- package/dist/services/obsidianRestAPI/methods/commandMethods.js +0 -31
- package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +0 -16
- package/dist/services/obsidianRestAPI/methods/openMethods.js +0 -21
- package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +0 -37
- package/dist/services/obsidianRestAPI/methods/patchMethods.js +0 -94
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +0 -42
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +0 -66
- package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +0 -25
- package/dist/services/obsidianRestAPI/methods/searchMethods.js +0 -36
- package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +0 -58
- package/dist/services/obsidianRestAPI/methods/vaultMethods.js +0 -144
- package/dist/services/obsidianRestAPI/service.d.ts +0 -195
- package/dist/services/obsidianRestAPI/service.js +0 -379
- package/dist/services/obsidianRestAPI/types.d.ts +0 -127
- package/dist/services/obsidianRestAPI/types.js +0 -7
- package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +0 -4
- package/dist/services/obsidianRestAPI/vaultCache/index.js +0 -4
- package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +0 -88
- package/dist/services/obsidianRestAPI/vaultCache/service.js +0 -299
- package/dist/types-global/errors.d.ts +0 -73
- package/dist/types-global/errors.js +0 -71
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.js +0 -13
- package/dist/utils/internal/asyncUtils.d.ts +0 -54
- package/dist/utils/internal/asyncUtils.js +0 -101
- package/dist/utils/internal/errorHandler.d.ts +0 -176
- package/dist/utils/internal/errorHandler.js +0 -351
- package/dist/utils/internal/index.d.ts +0 -4
- package/dist/utils/internal/index.js +0 -4
- package/dist/utils/internal/logger.d.ts +0 -141
- package/dist/utils/internal/logger.js +0 -406
- package/dist/utils/internal/requestContext.d.ts +0 -83
- package/dist/utils/internal/requestContext.js +0 -72
- package/dist/utils/metrics/index.d.ts +0 -1
- package/dist/utils/metrics/index.js +0 -1
- package/dist/utils/metrics/tokenCounter.d.ts +0 -27
- package/dist/utils/metrics/tokenCounter.js +0 -128
- package/dist/utils/obsidian/index.d.ts +0 -5
- package/dist/utils/obsidian/index.js +0 -5
- package/dist/utils/obsidian/obsidianApiUtils.d.ts +0 -14
- package/dist/utils/obsidian/obsidianApiUtils.js +0 -29
- package/dist/utils/obsidian/obsidianStatUtils.d.ts +0 -68
- package/dist/utils/obsidian/obsidianStatUtils.js +0 -143
- package/dist/utils/parsing/dateParser.d.ts +0 -56
- package/dist/utils/parsing/dateParser.js +0 -104
- package/dist/utils/parsing/index.d.ts +0 -2
- package/dist/utils/parsing/index.js +0 -3
- package/dist/utils/parsing/jsonParser.d.ts +0 -80
- package/dist/utils/parsing/jsonParser.js +0 -133
- package/dist/utils/security/idGenerator.d.ts +0 -140
- package/dist/utils/security/idGenerator.js +0 -194
- package/dist/utils/security/index.d.ts +0 -3
- package/dist/utils/security/index.js +0 -3
- package/dist/utils/security/rateLimiter.d.ts +0 -156
- package/dist/utils/security/rateLimiter.js +0 -235
- package/dist/utils/security/sanitization.d.ts +0 -244
- package/dist/utils/security/sanitization.js +0 -599
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { dump } from "js-yaml";
|
|
3
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
4
|
-
import { logger, retryWithDelay, } from "../../../utils/index.js";
|
|
5
|
-
import { sanitization } from "../../../utils/security/sanitization.js";
|
|
6
|
-
// ====================================================================================
|
|
7
|
-
// Schema Definitions
|
|
8
|
-
// ====================================================================================
|
|
9
|
-
const ManageTagsInputSchemaBase = z.object({
|
|
10
|
-
filePath: z
|
|
11
|
-
.string()
|
|
12
|
-
.min(1)
|
|
13
|
-
.describe("The vault-relative path to the target note (e.g., 'Journal/2024-06-12.md')."),
|
|
14
|
-
operation: z
|
|
15
|
-
.enum(["add", "remove", "list"])
|
|
16
|
-
.describe("The tag operation to perform: 'add' to include new tags, 'remove' to delete existing tags, or 'list' to view all current tags."),
|
|
17
|
-
tags: z
|
|
18
|
-
.array(z.string())
|
|
19
|
-
.describe("An array of tag names to be processed. The '#' prefix should be omitted (e.g., use 'project/active', not '#project/active')."),
|
|
20
|
-
});
|
|
21
|
-
export const ObsidianManageTagsInputSchemaShape = ManageTagsInputSchemaBase.shape;
|
|
22
|
-
export const ManageTagsInputSchema = ManageTagsInputSchemaBase;
|
|
23
|
-
// ====================================================================================
|
|
24
|
-
// Core Logic Function
|
|
25
|
-
// ====================================================================================
|
|
26
|
-
export const processObsidianManageTags = async (params, context, obsidianService, vaultCacheService) => {
|
|
27
|
-
logger.debug(`Processing obsidian_manage_tags request`, {
|
|
28
|
-
...context,
|
|
29
|
-
...params,
|
|
30
|
-
});
|
|
31
|
-
const { filePath, operation, tags: inputTags } = params;
|
|
32
|
-
const sanitizedTags = inputTags.map((t) => sanitization.sanitizeTagName(t));
|
|
33
|
-
const shouldRetryNotFound = (err) => err instanceof McpError && err.code === BaseErrorCode.NOT_FOUND;
|
|
34
|
-
const getFileWithRetry = async (opContext, format) => {
|
|
35
|
-
return await retryWithDelay(() => obsidianService.getFileContent(filePath, format, opContext), {
|
|
36
|
-
operationName: `getFileContentForTagManagement`,
|
|
37
|
-
context: opContext,
|
|
38
|
-
maxRetries: 3,
|
|
39
|
-
delayMs: 300,
|
|
40
|
-
shouldRetry: shouldRetryNotFound,
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
const initialNote = (await getFileWithRetry(context, "json"));
|
|
44
|
-
const currentTags = initialNote.tags;
|
|
45
|
-
switch (operation) {
|
|
46
|
-
case "list": {
|
|
47
|
-
return {
|
|
48
|
-
success: true,
|
|
49
|
-
message: "Successfully listed all tags.",
|
|
50
|
-
currentTags: currentTags,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
case "add": {
|
|
54
|
-
const tagsToAdd = sanitizedTags.filter((t) => !currentTags.includes(t));
|
|
55
|
-
if (tagsToAdd.length === 0) {
|
|
56
|
-
return {
|
|
57
|
-
success: true,
|
|
58
|
-
message: "No new tags to add; all provided tags already exist in the note.",
|
|
59
|
-
currentTags: currentTags,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
const frontmatter = initialNote.frontmatter ?? {};
|
|
63
|
-
const frontmatterTags = Array.isArray(frontmatter.tags)
|
|
64
|
-
? frontmatter.tags
|
|
65
|
-
: [];
|
|
66
|
-
const newFrontmatterTags = [
|
|
67
|
-
...new Set([...frontmatterTags, ...tagsToAdd]),
|
|
68
|
-
];
|
|
69
|
-
frontmatter.tags = newFrontmatterTags;
|
|
70
|
-
const noteContent = (await getFileWithRetry(context, "markdown"));
|
|
71
|
-
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
|
|
72
|
-
const match = noteContent.match(frontmatterRegex);
|
|
73
|
-
const newFrontmatterString = dump(frontmatter);
|
|
74
|
-
let newContent;
|
|
75
|
-
if (match) {
|
|
76
|
-
newContent = noteContent.replace(frontmatterRegex, `---\n${newFrontmatterString}---\n`);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
newContent = `---\n${newFrontmatterString}---\n\n${noteContent}`;
|
|
80
|
-
}
|
|
81
|
-
await retryWithDelay(() => obsidianService.updateFileContent(filePath, newContent, context), {
|
|
82
|
-
operationName: `updateFileForTagAdd`,
|
|
83
|
-
context,
|
|
84
|
-
maxRetries: 3,
|
|
85
|
-
delayMs: 300,
|
|
86
|
-
shouldRetry: shouldRetryNotFound,
|
|
87
|
-
});
|
|
88
|
-
if (vaultCacheService) {
|
|
89
|
-
await vaultCacheService.updateCacheForFile(filePath, context);
|
|
90
|
-
}
|
|
91
|
-
const finalTags = [...new Set([...currentTags, ...tagsToAdd])];
|
|
92
|
-
return {
|
|
93
|
-
success: true,
|
|
94
|
-
message: `Successfully added tags: ${tagsToAdd.join(", ")}.`,
|
|
95
|
-
currentTags: finalTags,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
case "remove": {
|
|
99
|
-
const tagsToRemove = sanitizedTags.filter((t) => currentTags.includes(t));
|
|
100
|
-
if (tagsToRemove.length === 0) {
|
|
101
|
-
return {
|
|
102
|
-
success: true,
|
|
103
|
-
message: "No tags to remove; none of the provided tags exist in the note.",
|
|
104
|
-
currentTags: currentTags,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
let noteContent = (await getFileWithRetry(context, "markdown"));
|
|
108
|
-
const frontmatter = initialNote.frontmatter ?? {};
|
|
109
|
-
let frontmatterTags = Array.isArray(frontmatter.tags)
|
|
110
|
-
? frontmatter.tags
|
|
111
|
-
: [];
|
|
112
|
-
const newFrontmatterTags = frontmatterTags.filter((t) => !tagsToRemove.includes(t));
|
|
113
|
-
let frontmatterModified = newFrontmatterTags.length !== frontmatterTags.length;
|
|
114
|
-
if (frontmatterModified) {
|
|
115
|
-
frontmatter.tags = newFrontmatterTags;
|
|
116
|
-
if (newFrontmatterTags.length === 0) {
|
|
117
|
-
delete frontmatter.tags;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
|
|
121
|
-
const match = noteContent.match(frontmatterRegex);
|
|
122
|
-
if (frontmatterModified && match) {
|
|
123
|
-
const newFrontmatterString = Object.keys(frontmatter).length > 0 ? dump(frontmatter) : "";
|
|
124
|
-
if (newFrontmatterString) {
|
|
125
|
-
noteContent = noteContent.replace(frontmatterRegex, `---\n${newFrontmatterString}---\n`);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
noteContent = noteContent.replace(frontmatterRegex, "");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
let inlineModified = false;
|
|
132
|
-
for (const tag of tagsToRemove) {
|
|
133
|
-
const regex = new RegExp(`(^|[^\\w-#])#${tag}\\b`, "g");
|
|
134
|
-
if (regex.test(noteContent)) {
|
|
135
|
-
noteContent = noteContent.replace(regex, "$1");
|
|
136
|
-
inlineModified = true;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (frontmatterModified || inlineModified) {
|
|
140
|
-
await retryWithDelay(() => obsidianService.updateFileContent(filePath, noteContent, context), {
|
|
141
|
-
operationName: `updateFileContentForTagRemove`,
|
|
142
|
-
context,
|
|
143
|
-
maxRetries: 3,
|
|
144
|
-
delayMs: 300,
|
|
145
|
-
shouldRetry: shouldRetryNotFound,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (vaultCacheService) {
|
|
149
|
-
await vaultCacheService.updateCacheForFile(filePath, context);
|
|
150
|
-
}
|
|
151
|
-
const finalTags = currentTags.filter((t) => !tagsToRemove.includes(t));
|
|
152
|
-
return {
|
|
153
|
-
success: true,
|
|
154
|
-
message: `Successfully removed tags: ${tagsToRemove.join(", ")}.`,
|
|
155
|
-
currentTags: finalTags,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
default:
|
|
159
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid operation: ${operation}`, context);
|
|
160
|
-
}
|
|
161
|
-
};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { ObsidianRestApiService, VaultCacheService } from "../../../services/obsidianRestAPI/index.js";
|
|
3
|
-
export declare const registerObsidianManageTagsTool: (server: McpServer, obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined) => Promise<void>;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
2
|
-
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
3
|
-
import { ManageTagsInputSchema, ObsidianManageTagsInputSchemaShape, processObsidianManageTags, } from "./logic.js";
|
|
4
|
-
export const registerObsidianManageTagsTool = async (server, obsidianService, vaultCacheService) => {
|
|
5
|
-
const toolName = "obsidian_manage_tags";
|
|
6
|
-
const toolDescription = "Manages tags for a specified note, handling them in both the YAML frontmatter and inline content. Supports adding, removing, and listing tags to provide a comprehensive tag management solution.";
|
|
7
|
-
const registrationContext = requestContextService.createRequestContext({
|
|
8
|
-
operation: "RegisterObsidianManageTagsTool",
|
|
9
|
-
toolName: toolName,
|
|
10
|
-
module: "ObsidianManageTagsRegistration",
|
|
11
|
-
});
|
|
12
|
-
logger.info(`Attempting to register tool: ${toolName}`, registrationContext);
|
|
13
|
-
await ErrorHandler.tryCatch(async () => {
|
|
14
|
-
server.tool(toolName, toolDescription, ObsidianManageTagsInputSchemaShape, async (params) => {
|
|
15
|
-
const handlerContext = requestContextService.createRequestContext({
|
|
16
|
-
parentContext: registrationContext,
|
|
17
|
-
operation: "HandleObsidianManageTagsRequest",
|
|
18
|
-
toolName: toolName,
|
|
19
|
-
params: params,
|
|
20
|
-
});
|
|
21
|
-
logger.debug(`Handling '${toolName}' request`, handlerContext);
|
|
22
|
-
return await ErrorHandler.tryCatch(async () => {
|
|
23
|
-
const validatedParams = ManageTagsInputSchema.parse(params);
|
|
24
|
-
const response = await processObsidianManageTags(validatedParams, handlerContext, obsidianService, vaultCacheService);
|
|
25
|
-
logger.debug(`'${toolName}' processed successfully`, handlerContext);
|
|
26
|
-
return {
|
|
27
|
-
content: [
|
|
28
|
-
{
|
|
29
|
-
type: "text",
|
|
30
|
-
text: JSON.stringify(response, null, 2),
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
isError: false,
|
|
34
|
-
};
|
|
35
|
-
}, {
|
|
36
|
-
operation: `processing ${toolName} handler`,
|
|
37
|
-
context: handlerContext,
|
|
38
|
-
input: params,
|
|
39
|
-
errorMapper: (error) => new McpError(error instanceof McpError
|
|
40
|
-
? error.code
|
|
41
|
-
: BaseErrorCode.INTERNAL_ERROR, `Error processing ${toolName} tool: ${error instanceof Error ? error.message : "Unknown error"}`, { ...handlerContext }),
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
logger.info(`Tool registered successfully: ${toolName}`, registrationContext);
|
|
45
|
-
}, {
|
|
46
|
-
operation: `registering tool ${toolName}`,
|
|
47
|
-
context: registrationContext,
|
|
48
|
-
errorCode: BaseErrorCode.INTERNAL_ERROR,
|
|
49
|
-
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 }),
|
|
50
|
-
critical: true,
|
|
51
|
-
});
|
|
52
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Barrel file for the 'obsidian_read_note' MCP tool.
|
|
3
|
-
*
|
|
4
|
-
* This file serves as the public entry point for the obsidian_read_note tool module.
|
|
5
|
-
* It re-exports the primary registration function (`registerObsidianReadNoteTool`)
|
|
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 { registerObsidianReadNoteTool } from "./registration.js";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Barrel file for the 'obsidian_read_note' MCP tool.
|
|
3
|
-
*
|
|
4
|
-
* This file serves as the public entry point for the obsidian_read_note tool module.
|
|
5
|
-
* It re-exports the primary registration function (`registerObsidianReadNoteTool`)
|
|
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 { registerObsidianReadNoteTool } from "./registration.js";
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { NoteJson, 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_read_note' tool.
|
|
6
|
-
*/
|
|
7
|
-
export declare const ObsidianReadNoteInputSchema: z.ZodObject<{
|
|
8
|
-
/**
|
|
9
|
-
* The vault-relative path to the target file (e.g., "Folder/My Note.md").
|
|
10
|
-
* Must include the file extension. The tool first attempts a case-sensitive match.
|
|
11
|
-
* If not found, it attempts a case-insensitive fallback search within the same directory.
|
|
12
|
-
*/
|
|
13
|
-
filePath: z.ZodString;
|
|
14
|
-
/**
|
|
15
|
-
* Specifies the desired format for the returned content.
|
|
16
|
-
* 'markdown' returns the raw file content as a string.
|
|
17
|
-
* 'json' returns a structured NoteJson object containing content, parsed frontmatter, tags, and file metadata (stat).
|
|
18
|
-
* Defaults to 'markdown'.
|
|
19
|
-
*/
|
|
20
|
-
format: z.ZodOptional<z.ZodDefault<z.ZodEnum<["markdown", "json"]>>>;
|
|
21
|
-
/**
|
|
22
|
-
* If true and the requested format is 'markdown', includes formatted file statistics
|
|
23
|
-
* (creation time, modification time, token count estimate) in the response's 'stat' field.
|
|
24
|
-
* Defaults to false. This flag is ignored if the format is 'json', as stats are always included within the NoteJson object itself (and also added to the top-level 'stat' field in the response).
|
|
25
|
-
*/
|
|
26
|
-
includeStat: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
27
|
-
}, "strip", z.ZodTypeAny, {
|
|
28
|
-
filePath: string;
|
|
29
|
-
includeStat: boolean;
|
|
30
|
-
format?: "markdown" | "json" | undefined;
|
|
31
|
-
}, {
|
|
32
|
-
filePath: string;
|
|
33
|
-
format?: "markdown" | "json" | undefined;
|
|
34
|
-
includeStat?: boolean | undefined;
|
|
35
|
-
}>;
|
|
36
|
-
/**
|
|
37
|
-
* TypeScript type inferred from the input schema (`ObsidianReadNoteInputSchema`).
|
|
38
|
-
* Represents the validated input parameters used within the core processing logic.
|
|
39
|
-
*/
|
|
40
|
-
export type ObsidianReadNoteInput = z.infer<typeof ObsidianReadNoteInputSchema>;
|
|
41
|
-
/**
|
|
42
|
-
* Represents the structure of file statistics after formatting, including
|
|
43
|
-
* human-readable timestamps and an estimated token count.
|
|
44
|
-
*/
|
|
45
|
-
type FormattedStat = {
|
|
46
|
-
/** Creation time formatted as a standard date-time string (e.g., "05:29:00 PM | 05-03-2025"). */
|
|
47
|
-
createdTime: string;
|
|
48
|
-
/** Last modified time formatted as a standard date-time string (e.g., "05:29:00 PM | 05-03-2025"). */
|
|
49
|
-
modifiedTime: string;
|
|
50
|
-
/** Estimated token count of the file content (using tiktoken 'gpt-4o'). */
|
|
51
|
-
tokenCountEstimate: number;
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* Defines the structure of the successful response returned by the `processObsidianReadNote` function.
|
|
55
|
-
* This object is typically serialized to JSON and sent back to the client.
|
|
56
|
-
*/
|
|
57
|
-
export interface ObsidianReadNoteResponse {
|
|
58
|
-
/**
|
|
59
|
-
* The content of the file in the requested format.
|
|
60
|
-
* If format='markdown', this is a string.
|
|
61
|
-
* If format='json', this is a NoteJson object (which also contains the content string and stats).
|
|
62
|
-
*/
|
|
63
|
-
content: string | NoteJson;
|
|
64
|
-
/**
|
|
65
|
-
* Optional formatted file statistics.
|
|
66
|
-
* Included if format='json', or if format='markdown' and includeStat=true.
|
|
67
|
-
*/
|
|
68
|
-
stats?: FormattedStat;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Processes the core logic for reading a file from the Obsidian vault.
|
|
72
|
-
*
|
|
73
|
-
* It attempts to read the file using the provided path (case-sensitive first,
|
|
74
|
-
* then case-insensitive fallback). It always fetches the full NoteJson object
|
|
75
|
-
* internally to access file statistics. Finally, it formats the response
|
|
76
|
-
* according to the requested format ('markdown' or 'json') and the 'includeStat' flag.
|
|
77
|
-
*
|
|
78
|
-
* @param {ObsidianReadNoteInput} params - The validated input parameters.
|
|
79
|
-
* @param {RequestContext} context - The request context for logging and correlation.
|
|
80
|
-
* @param {ObsidianRestApiService} obsidianService - An instance of the Obsidian REST API service.
|
|
81
|
-
* @returns {Promise<ObsidianReadNoteResponse>} A promise resolving to the structured success response
|
|
82
|
-
* containing the file content and optionally formatted statistics.
|
|
83
|
-
* @throws {McpError} Throws an McpError if the file cannot be found (even with fallback),
|
|
84
|
-
* if there's an ambiguous fallback match, or if any other API interaction fails.
|
|
85
|
-
*/
|
|
86
|
-
export declare const processObsidianReadNote: (params: ObsidianReadNoteInput, context: RequestContext, obsidianService: ObsidianRestApiService) => Promise<ObsidianReadNoteResponse>;
|
|
87
|
-
export {};
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import path from "node:path"; // Using POSIX path functions for vault path manipulation
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
4
|
-
import { createFormattedStatWithTokenCount, logger, retryWithDelay, } from "../../../utils/index.js";
|
|
5
|
-
// ====================================================================================
|
|
6
|
-
// Schema Definitions for Input Validation
|
|
7
|
-
// ====================================================================================
|
|
8
|
-
/**
|
|
9
|
-
* Defines the allowed formats for the returned file content.
|
|
10
|
-
* - 'markdown': Returns the raw Markdown content as a string.
|
|
11
|
-
* - 'json': Returns a structured NoteJson object including content, frontmatter, tags, and stats.
|
|
12
|
-
*/
|
|
13
|
-
const ReadNoteFormatSchema = z
|
|
14
|
-
.enum(["markdown", "json"])
|
|
15
|
-
.default("markdown")
|
|
16
|
-
.describe("Specifies the format for the returned content ('markdown' or 'json'). Defaults to 'markdown'.");
|
|
17
|
-
/**
|
|
18
|
-
* Zod schema for validating the input parameters of the 'obsidian_read_note' tool.
|
|
19
|
-
*/
|
|
20
|
-
export const ObsidianReadNoteInputSchema = z
|
|
21
|
-
.object({
|
|
22
|
-
/**
|
|
23
|
-
* The vault-relative path to the target file (e.g., "Folder/My Note.md").
|
|
24
|
-
* Must include the file extension. The tool first attempts a case-sensitive match.
|
|
25
|
-
* If not found, it attempts a case-insensitive fallback search within the same directory.
|
|
26
|
-
*/
|
|
27
|
-
filePath: z
|
|
28
|
-
.string()
|
|
29
|
-
.min(1, "filePath cannot be empty")
|
|
30
|
-
.describe('The vault-relative path to the target file (e.g., "developer/github/tips.md"). Tries case-sensitive first, then case-insensitive fallback.'),
|
|
31
|
-
/**
|
|
32
|
-
* Specifies the desired format for the returned content.
|
|
33
|
-
* 'markdown' returns the raw file content as a string.
|
|
34
|
-
* 'json' returns a structured NoteJson object containing content, parsed frontmatter, tags, and file metadata (stat).
|
|
35
|
-
* Defaults to 'markdown'.
|
|
36
|
-
*/
|
|
37
|
-
format: ReadNoteFormatSchema.optional() // Optional, defaults to 'markdown' via ReadNoteFormatSchema
|
|
38
|
-
.describe("Format for the returned content ('markdown' or 'json'). Defaults to 'markdown'."),
|
|
39
|
-
/**
|
|
40
|
-
* If true and the requested format is 'markdown', includes formatted file statistics
|
|
41
|
-
* (creation time, modification time, token count estimate) in the response's 'stat' field.
|
|
42
|
-
* Defaults to false. This flag is ignored if the format is 'json', as stats are always included within the NoteJson object itself (and also added to the top-level 'stat' field in the response).
|
|
43
|
-
*/
|
|
44
|
-
includeStat: z
|
|
45
|
-
.boolean()
|
|
46
|
-
.optional()
|
|
47
|
-
.default(false)
|
|
48
|
-
.describe("If true and format is 'markdown', includes file stats in the response. Defaults to false. Ignored if format is 'json'."),
|
|
49
|
-
})
|
|
50
|
-
.describe("Retrieves the content and optionally metadata of a specific file within the connected Obsidian vault. Supports case-insensitive path fallback.");
|
|
51
|
-
// ====================================================================================
|
|
52
|
-
// Core Logic Function
|
|
53
|
-
// ====================================================================================
|
|
54
|
-
/**
|
|
55
|
-
* Processes the core logic for reading a file from the Obsidian vault.
|
|
56
|
-
*
|
|
57
|
-
* It attempts to read the file using the provided path (case-sensitive first,
|
|
58
|
-
* then case-insensitive fallback). It always fetches the full NoteJson object
|
|
59
|
-
* internally to access file statistics. Finally, it formats the response
|
|
60
|
-
* according to the requested format ('markdown' or 'json') and the 'includeStat' flag.
|
|
61
|
-
*
|
|
62
|
-
* @param {ObsidianReadNoteInput} params - The validated input parameters.
|
|
63
|
-
* @param {RequestContext} context - The request context for logging and correlation.
|
|
64
|
-
* @param {ObsidianRestApiService} obsidianService - An instance of the Obsidian REST API service.
|
|
65
|
-
* @returns {Promise<ObsidianReadNoteResponse>} A promise resolving to the structured success response
|
|
66
|
-
* containing the file content and optionally formatted statistics.
|
|
67
|
-
* @throws {McpError} Throws an McpError if the file cannot be found (even with fallback),
|
|
68
|
-
* if there's an ambiguous fallback match, or if any other API interaction fails.
|
|
69
|
-
*/
|
|
70
|
-
export const processObsidianReadNote = async (params, context, obsidianService) => {
|
|
71
|
-
const { filePath: originalFilePath, format: requestedFormat, includeStat, } = params;
|
|
72
|
-
let effectiveFilePath = originalFilePath; // Track the actual path used (might change during fallback)
|
|
73
|
-
logger.debug(`Processing obsidian_read_note request for path: ${originalFilePath}`, { ...context, format: requestedFormat, includeStat });
|
|
74
|
-
const shouldRetryNotFound = (err) => err instanceof McpError && err.code === BaseErrorCode.NOT_FOUND;
|
|
75
|
-
try {
|
|
76
|
-
let noteJson;
|
|
77
|
-
// --- Step 1: Read File Content (always fetch JSON internally) ---
|
|
78
|
-
const readContext = { ...context, operation: "readFileAsJson" };
|
|
79
|
-
try {
|
|
80
|
-
// Attempt 1: Read using the provided path (case-sensitive)
|
|
81
|
-
logger.debug(`Attempting to read file as JSON (case-sensitive): ${originalFilePath}`, readContext);
|
|
82
|
-
noteJson = await retryWithDelay(() => obsidianService.getFileContent(originalFilePath, "json", readContext), {
|
|
83
|
-
operationName: "readFileWithRetry",
|
|
84
|
-
context: readContext,
|
|
85
|
-
maxRetries: 3,
|
|
86
|
-
delayMs: 300,
|
|
87
|
-
shouldRetry: shouldRetryNotFound,
|
|
88
|
-
});
|
|
89
|
-
effectiveFilePath = originalFilePath; // Confirm exact path worked
|
|
90
|
-
logger.debug(`Successfully read file as JSON using exact path: ${originalFilePath}`, readContext);
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
// Attempt 2: Case-insensitive fallback if initial read failed with NOT_FOUND
|
|
94
|
-
if (error instanceof McpError && error.code === BaseErrorCode.NOT_FOUND) {
|
|
95
|
-
logger.info(`File not found with exact path: ${originalFilePath}. Attempting case-insensitive fallback.`, readContext);
|
|
96
|
-
const fallbackContext = {
|
|
97
|
-
...readContext,
|
|
98
|
-
subOperation: "caseInsensitiveFallback",
|
|
99
|
-
};
|
|
100
|
-
try {
|
|
101
|
-
// Use POSIX path functions as vault paths are typically /-separated
|
|
102
|
-
const dirname = path.posix.dirname(originalFilePath);
|
|
103
|
-
const filenameLower = path.posix
|
|
104
|
-
.basename(originalFilePath)
|
|
105
|
-
.toLowerCase();
|
|
106
|
-
// Handle case where the file is in the vault root (dirname is '.')
|
|
107
|
-
const dirToList = dirname === "." ? "/" : dirname;
|
|
108
|
-
logger.debug(`Listing directory for fallback: ${dirToList}`, fallbackContext);
|
|
109
|
-
const filesInDir = await retryWithDelay(() => obsidianService.listFiles(dirToList, fallbackContext), {
|
|
110
|
-
operationName: "listFilesForReadFallback",
|
|
111
|
-
context: fallbackContext,
|
|
112
|
-
maxRetries: 3,
|
|
113
|
-
delayMs: 300,
|
|
114
|
-
shouldRetry: shouldRetryNotFound,
|
|
115
|
-
});
|
|
116
|
-
// Filter directory listing for files matching the lowercase filename
|
|
117
|
-
const matches = filesInDir.filter((f) => !f.endsWith("/") && // Ensure it's a file, not a directory entry ending in /
|
|
118
|
-
path.posix.basename(f).toLowerCase() === filenameLower);
|
|
119
|
-
if (matches.length === 1) {
|
|
120
|
-
// Found exactly one case-insensitive match
|
|
121
|
-
const correctFilename = path.posix.basename(matches[0]);
|
|
122
|
-
effectiveFilePath = path.posix.join(dirname, correctFilename); // Construct the correct path
|
|
123
|
-
logger.info(`Found case-insensitive match: ${effectiveFilePath}. Retrying read as JSON.`, fallbackContext);
|
|
124
|
-
// Retry reading the file content using the corrected path
|
|
125
|
-
noteJson = await retryWithDelay(() => obsidianService.getFileContent(effectiveFilePath, "json", fallbackContext), {
|
|
126
|
-
operationName: "readFileWithFallbackRetry",
|
|
127
|
-
context: fallbackContext,
|
|
128
|
-
maxRetries: 3,
|
|
129
|
-
delayMs: 300,
|
|
130
|
-
shouldRetry: shouldRetryNotFound,
|
|
131
|
-
});
|
|
132
|
-
logger.debug(`Successfully read file as JSON using fallback path: ${effectiveFilePath}`, fallbackContext);
|
|
133
|
-
}
|
|
134
|
-
else if (matches.length > 1) {
|
|
135
|
-
// Ambiguous match: Multiple files match case-insensitively
|
|
136
|
-
logger.error(`Case-insensitive fallback failed: Multiple matches found for ${filenameLower} in ${dirToList}.`, { ...fallbackContext, matches });
|
|
137
|
-
throw new McpError(BaseErrorCode.CONFLICT, // Use CONFLICT for ambiguity
|
|
138
|
-
`File read failed: Ambiguous case-insensitive matches for '${originalFilePath}'. Found: [${matches.join(", ")}]`, fallbackContext);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
// No match found even with fallback
|
|
142
|
-
logger.error(`Case-insensitive fallback failed: No match found for ${filenameLower} in ${dirToList}.`, fallbackContext);
|
|
143
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `File not found: '${originalFilePath}' (case-insensitive fallback also failed).`, fallbackContext);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
catch (fallbackError) {
|
|
147
|
-
// Catch errors specifically from the fallback logic
|
|
148
|
-
if (fallbackError instanceof McpError)
|
|
149
|
-
throw fallbackError; // Re-throw known errors
|
|
150
|
-
// Wrap unexpected fallback errors
|
|
151
|
-
const errorMessage = `Unexpected error during case-insensitive fallback for ${originalFilePath}`;
|
|
152
|
-
logger.error(errorMessage, fallbackError instanceof Error ? fallbackError : undefined, fallbackContext);
|
|
153
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `${errorMessage}: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`, fallbackContext);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
// Re-throw errors from the initial read attempt that were not NOT_FOUND
|
|
158
|
-
throw error;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// --- Step 2: Format the Response ---
|
|
162
|
-
const formatContext = {
|
|
163
|
-
...context,
|
|
164
|
-
operation: "formatResponse",
|
|
165
|
-
effectiveFilePath,
|
|
166
|
-
};
|
|
167
|
-
logger.debug(`Formatting response. Requested format: ${requestedFormat}, Include stat: ${includeStat}`, formatContext);
|
|
168
|
-
// Generate formatted statistics using the utility function.
|
|
169
|
-
// Provide the content string for token counting. Handle cases where stat might be missing.
|
|
170
|
-
const formattedStatResult = noteJson.stat
|
|
171
|
-
? await createFormattedStatWithTokenCount(noteJson.stat, noteJson.content ?? "", formatContext) // Await the async utility
|
|
172
|
-
: undefined;
|
|
173
|
-
// Ensure stat is undefined if the utility returned null (e.g., token counting failed)
|
|
174
|
-
const formattedStat = formattedStatResult === null ? undefined : formattedStatResult;
|
|
175
|
-
// Initialize the response object
|
|
176
|
-
const response = {
|
|
177
|
-
content: "", // Placeholder, will be set based on format
|
|
178
|
-
// stat is added conditionally below
|
|
179
|
-
};
|
|
180
|
-
// Populate response based on requested format
|
|
181
|
-
if (requestedFormat === "json") {
|
|
182
|
-
// Return the full NoteJson object. Its internal 'stat' will remain numeric.
|
|
183
|
-
// The formatted stats are provided in the top-level 'response.stats'.
|
|
184
|
-
response.content = noteJson;
|
|
185
|
-
response.stats = formattedStat; // Always include formatted stat at top level for JSON format
|
|
186
|
-
logger.debug(`Response format set to JSON, including full NoteJson (with original numeric stat) and top-level formatted stat.`, formatContext);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
// 'markdown' format
|
|
190
|
-
response.content = noteJson.content ?? ""; // Extract the markdown content string
|
|
191
|
-
if (includeStat && formattedStat) {
|
|
192
|
-
response.stats = formattedStat; // Include formatted stats only if requested for markdown
|
|
193
|
-
logger.debug(`Response format set to markdown, including formatted stat as requested.`, formatContext);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
logger.debug(`Response format set to markdown, excluding stat (includeStat=${includeStat}).`, formatContext);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
logger.debug(`Successfully processed read request for ${effectiveFilePath}.`, context);
|
|
200
|
-
return response;
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
// Catch any errors that propagated up (e.g., from initial read, fallback, or unexpected issues)
|
|
204
|
-
if (error instanceof McpError) {
|
|
205
|
-
// Log known McpErrors that reached this top level
|
|
206
|
-
logger.error(`McpError during file read process for ${originalFilePath}: ${error.message}`, error, context);
|
|
207
|
-
throw error; // Re-throw McpError
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
// Wrap unexpected errors in a generic McpError
|
|
211
|
-
const errorMessage = `Unexpected error processing read request for ${originalFilePath}`;
|
|
212
|
-
logger.error(errorMessage, error instanceof Error ? error : undefined, context);
|
|
213
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `${errorMessage}: ${error instanceof Error ? error.message : String(error)}`, context);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { ObsidianRestApiService } from "../../../services/obsidianRestAPI/index.js";
|
|
3
|
-
/**
|
|
4
|
-
* Registers the 'obsidian_read_note' tool with the MCP server.
|
|
5
|
-
*
|
|
6
|
-
* This tool retrieves the content and optionally metadata of a specified file
|
|
7
|
-
* within the user's Obsidian vault. It supports specifying the output format
|
|
8
|
-
* ('markdown' or 'json') and includes a case-insensitive fallback mechanism
|
|
9
|
-
* if the exact file path is not found initially.
|
|
10
|
-
*
|
|
11
|
-
* The response is a JSON string containing the file content in the requested format
|
|
12
|
-
* and optionally formatted file statistics (timestamps, token count).
|
|
13
|
-
*
|
|
14
|
-
* @param {McpServer} server - The MCP server instance to register the tool with.
|
|
15
|
-
* @param {ObsidianRestApiService} obsidianService - An instance of the Obsidian REST API service
|
|
16
|
-
* used to interact with the user's Obsidian vault.
|
|
17
|
-
* @returns {Promise<void>} A promise that resolves when the tool registration is complete or rejects on error.
|
|
18
|
-
* @throws {McpError} Throws an McpError if registration fails critically.
|
|
19
|
-
*/
|
|
20
|
-
export declare const registerObsidianReadNoteTool: (server: McpServer, obsidianService: ObsidianRestApiService) => Promise<void>;
|