obsidian-mcp-server 1.5.7 → 2.0.1
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 +249 -101
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.js +191 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +298 -0
- 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 -0
- package/dist/utils/index.js +13 -0
- 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 +72 -40
- package/.github/workflows/publish.yml +0 -35
- package/Dockerfile +0 -63
- package/build/index.js +0 -20
- package/build/mcp/handlers.js +0 -305
- package/build/mcp/index.js +0 -7
- package/build/mcp/server.js +0 -240
- package/build/mcp/types.js +0 -49
- package/build/obsidian/client.js +0 -403
- package/build/obsidian/errors.js +0 -75
- package/build/obsidian/index.js +0 -7
- package/build/obsidian/types.js +0 -12
- package/build/resources/index.js +0 -15
- package/build/resources/tags.js +0 -257
- package/build/resources/types.js +0 -5
- package/build/tools/base.js +0 -88
- package/build/tools/files/content.js +0 -171
- package/build/tools/files/index.js +0 -22
- package/build/tools/files/list.js +0 -133
- package/build/tools/index.js +0 -31
- package/build/tools/properties/index.js +0 -19
- package/build/tools/properties/manager.js +0 -302
- package/build/tools/properties/tools.js +0 -239
- package/build/tools/properties/types.js +0 -70
- package/build/tools/search/complex.js +0 -270
- package/build/tools/search/index.js +0 -20
- package/build/tools/search/simple.js +0 -127
- package/build/utils/errors.js +0 -59
- package/build/utils/idGenerator.js +0 -21
- package/build/utils/index.js +0 -9
- package/build/utils/logging.js +0 -417
- package/build/utils/rate-limiting.js +0 -94
- package/build/utils/tokenization.js +0 -75
- package/build/utils/validation.js +0 -92
- package/debug.js +0 -98
- package/docs/tree.md +0 -67
- package/env.json +0 -42
- package/mcp-client-config.example.json +0 -23
- package/repomix.config.json +0 -20
- package/scripts/clean.ts +0 -103
- package/scripts/tree.ts +0 -599
- package/src/index.ts +0 -23
- package/src/mcp/handlers.ts +0 -410
- package/src/mcp/index.ts +0 -7
- package/src/mcp/server.ts +0 -288
- package/src/mcp/types.ts +0 -112
- package/src/obsidian/client.ts +0 -505
- package/src/obsidian/errors.ts +0 -105
- package/src/obsidian/index.ts +0 -7
- package/src/obsidian/types.ts +0 -124
- package/src/resources/index.ts +0 -17
- package/src/resources/tags.ts +0 -283
- package/src/resources/types.ts +0 -30
- package/src/tools/base.ts +0 -123
- package/src/tools/files/content.ts +0 -206
- package/src/tools/files/index.ts +0 -31
- package/src/tools/files/list.ts +0 -150
- package/src/tools/index.ts +0 -38
- package/src/tools/properties/index.ts +0 -21
- package/src/tools/properties/manager.ts +0 -370
- package/src/tools/properties/tools.ts +0 -280
- package/src/tools/properties/types.ts +0 -117
- package/src/tools/search/complex.ts +0 -303
- package/src/tools/search/index.ts +0 -22
- package/src/tools/search/simple.ts +0 -147
- package/src/utils/errors.ts +0 -69
- package/src/utils/idGenerator.ts +0 -22
- package/src/utils/index.ts +0 -8
- package/src/utils/logging.ts +0 -558
- package/src/utils/rate-limiting.ts +0 -114
- package/src/utils/tokenization.ts +0 -85
- package/src/utils/validation.ts +0 -105
- package/tsconfig.json +0 -17
package/src/obsidian/errors.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error handling for Obsidian client
|
|
3
|
-
*/
|
|
4
|
-
import { AxiosError } from "axios";
|
|
5
|
-
import type { ApiError } from '../utils/errors.js';
|
|
6
|
-
import { ObsidianError, getErrorCodeFromStatus } from '../utils/errors.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Helper function to create a descriptive error message for SSL certificate issues
|
|
10
|
-
*/
|
|
11
|
-
export function createSSLErrorMessage(error: Error, config: { verifySSL: boolean }): string {
|
|
12
|
-
return (
|
|
13
|
-
`SSL certificate verification failed. You have two options:\n\n` +
|
|
14
|
-
`Option 1 - Enable HTTP (not recommended for production):\n` +
|
|
15
|
-
`1. Go to Obsidian Settings > Local REST API\n` +
|
|
16
|
-
`2. Enable "Enable Non-encrypted (HTTP) Server"\n` +
|
|
17
|
-
`3. Update your client config to use "http" protocol\n\n` +
|
|
18
|
-
`Option 2 - Configure HTTPS (recommended):\n` +
|
|
19
|
-
`1. Go to Obsidian Settings > Local REST API\n` +
|
|
20
|
-
`2. Under 'How to Access', copy the certificate\n` +
|
|
21
|
-
`3. Add the certificate to your system's trusted certificates:\n` +
|
|
22
|
-
` - On macOS: Add to Keychain Access\n` +
|
|
23
|
-
` - On Windows: Add to Certificate Manager\n` +
|
|
24
|
-
` - On Linux: Add to ca-certificates\n` +
|
|
25
|
-
` For development only: Set verifySSL: false in client config\n\n` +
|
|
26
|
-
`Original error: ${error.message}`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Helper function to create a descriptive error message for connection refused issues
|
|
32
|
-
*/
|
|
33
|
-
export function createConnectionRefusedMessage(host: string, port: number): string {
|
|
34
|
-
return (
|
|
35
|
-
`Connection refused. To fix this:\n` +
|
|
36
|
-
`1. Ensure Obsidian is running\n` +
|
|
37
|
-
`2. Verify the 'Local REST API' plugin is enabled in Obsidian Settings\n` +
|
|
38
|
-
`3. Check that you're using the correct host (${host}) and port (${port})\n` +
|
|
39
|
-
`4. Make sure HTTPS is enabled in the plugin settings`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Helper function to create a descriptive error message for authentication failures
|
|
45
|
-
*/
|
|
46
|
-
export function createAuthFailedMessage(): string {
|
|
47
|
-
return (
|
|
48
|
-
`Authentication failed. To fix this:\n` +
|
|
49
|
-
`1. Go to Obsidian Settings > Local REST API\n` +
|
|
50
|
-
`2. Copy your API key from the settings\n` +
|
|
51
|
-
`3. Update your configuration with the new API key\n` +
|
|
52
|
-
`Note: The API key changes when you regenerate certificates`
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Helper function to create a descriptive error message for missing API key
|
|
58
|
-
*/
|
|
59
|
-
export function createMissingAPIKeyMessage(): string {
|
|
60
|
-
return (
|
|
61
|
-
`Missing API key. To fix this:\n` +
|
|
62
|
-
`1. Install the 'Local REST API' plugin in Obsidian\n` +
|
|
63
|
-
`2. Enable the plugin in Obsidian Settings\n` +
|
|
64
|
-
`3. Copy your API key from Obsidian Settings > Local REST API\n` +
|
|
65
|
-
`4. Provide the API key in your configuration`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Helper function to handle Axios errors consistently
|
|
71
|
-
*/
|
|
72
|
-
export function handleAxiosError(error: AxiosError<ApiError>, host: string, port: number): ObsidianError {
|
|
73
|
-
const response = error.response;
|
|
74
|
-
const errorData = response?.data;
|
|
75
|
-
|
|
76
|
-
// Handle common connection errors with helpful messages
|
|
77
|
-
if (error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
78
|
-
return new ObsidianError(
|
|
79
|
-
createSSLErrorMessage(error, { verifySSL: true }),
|
|
80
|
-
50001, // SSL error code
|
|
81
|
-
{ code: error.code }
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (error.code === 'ECONNREFUSED') {
|
|
86
|
-
return new ObsidianError(
|
|
87
|
-
createConnectionRefusedMessage(host, port),
|
|
88
|
-
50002, // Connection refused
|
|
89
|
-
{ code: error.code }
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (response?.status === 401) {
|
|
94
|
-
return new ObsidianError(
|
|
95
|
-
createAuthFailedMessage(),
|
|
96
|
-
40100, // Unauthorized
|
|
97
|
-
{ code: error.code }
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// For other errors, use API error code if available
|
|
102
|
-
const errorCode = errorData?.errorCode ?? getErrorCodeFromStatus(response?.status ?? 500);
|
|
103
|
-
const message = errorData?.message ?? error.message ?? "Unknown error";
|
|
104
|
-
return new ObsidianError(message, errorCode, errorData);
|
|
105
|
-
}
|
package/src/obsidian/index.ts
DELETED
package/src/obsidian/types.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definitions for Obsidian client
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Configuration for the Obsidian client
|
|
7
|
-
*/
|
|
8
|
-
export interface ObsidianConfig {
|
|
9
|
-
apiKey: string;
|
|
10
|
-
verifySSL?: boolean;
|
|
11
|
-
timeout?: number;
|
|
12
|
-
maxContentLength?: number;
|
|
13
|
-
maxBodyLength?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Server configuration for Obsidian
|
|
18
|
-
*/
|
|
19
|
-
export interface ObsidianServerConfig {
|
|
20
|
-
protocol: "http" | "https";
|
|
21
|
-
host: string;
|
|
22
|
-
port: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Default configuration for Obsidian server
|
|
27
|
-
*/
|
|
28
|
-
export const DEFAULT_OBSIDIAN_CONFIG: ObsidianServerConfig = {
|
|
29
|
-
protocol: "https", // HTTPS required by default in Obsidian REST API plugin
|
|
30
|
-
host: "127.0.0.1",
|
|
31
|
-
port: 27124
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* JSON representation of an Obsidian note
|
|
36
|
-
*/
|
|
37
|
-
export interface NoteJson {
|
|
38
|
-
content: string;
|
|
39
|
-
frontmatter: Record<string, unknown>;
|
|
40
|
-
path: string;
|
|
41
|
-
stat: {
|
|
42
|
-
ctime: number;
|
|
43
|
-
mtime: number;
|
|
44
|
-
size: number;
|
|
45
|
-
};
|
|
46
|
-
tags: string[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Obsidian file/folder information
|
|
51
|
-
*/
|
|
52
|
-
export interface ObsidianFile {
|
|
53
|
-
path: string;
|
|
54
|
-
type: "file" | "folder";
|
|
55
|
-
children?: ObsidianFile[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Obsidian command
|
|
60
|
-
*/
|
|
61
|
-
export interface ObsidianCommand {
|
|
62
|
-
id: string;
|
|
63
|
-
name: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Obsidian server status
|
|
68
|
-
*/
|
|
69
|
-
export interface ObsidianStatus {
|
|
70
|
-
authenticated: boolean;
|
|
71
|
-
ok: string;
|
|
72
|
-
service: string;
|
|
73
|
-
versions: {
|
|
74
|
-
obsidian: string;
|
|
75
|
-
self: string;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Period type for periodic notes
|
|
81
|
-
*/
|
|
82
|
-
export interface PeriodType {
|
|
83
|
-
type: "daily" | "weekly" | "monthly" | "quarterly" | "yearly";
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Search match with context
|
|
88
|
-
*/
|
|
89
|
-
export interface SearchMatch {
|
|
90
|
-
context: string;
|
|
91
|
-
match: {
|
|
92
|
-
start: number;
|
|
93
|
-
end: number;
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Standard search result
|
|
99
|
-
*/
|
|
100
|
-
export interface SearchResult {
|
|
101
|
-
filename: string;
|
|
102
|
-
result: unknown;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Simple search result with context
|
|
107
|
-
*/
|
|
108
|
-
export interface SimpleSearchResult {
|
|
109
|
-
filename: string;
|
|
110
|
-
score: number;
|
|
111
|
-
matches: SearchMatch[];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Union type for search responses
|
|
116
|
-
*/
|
|
117
|
-
export type SearchResponse = SearchResult | SimpleSearchResult;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* JSON Logic query for complex searches
|
|
121
|
-
*/
|
|
122
|
-
export interface JsonLogicQuery {
|
|
123
|
-
[operator: string]: unknown;
|
|
124
|
-
}
|
package/src/resources/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resources module exports
|
|
3
|
-
*/
|
|
4
|
-
export * from './tags.js';
|
|
5
|
-
export * from './types.js';
|
|
6
|
-
|
|
7
|
-
import { ObsidianClient } from '../obsidian/client.js';
|
|
8
|
-
import { TagResource } from './tags.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create and return the tag resource
|
|
12
|
-
* @param client The ObsidianClient instance
|
|
13
|
-
* @returns The tag resource
|
|
14
|
-
*/
|
|
15
|
-
export function createTagResource(client: ObsidianClient): TagResource {
|
|
16
|
-
return new TagResource(client);
|
|
17
|
-
}
|
package/src/resources/tags.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tag resource implementation
|
|
3
|
-
*/
|
|
4
|
-
import { Resource, TextContent } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import pLimit from 'p-limit'; // Import p-limit
|
|
6
|
-
import { sep } from "path";
|
|
7
|
-
import { ObsidianClient } from "../obsidian/client.js";
|
|
8
|
-
import { JsonLogicQuery } from "../obsidian/types.js";
|
|
9
|
-
import { PropertyManager } from "../tools/properties/manager.js";
|
|
10
|
-
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
11
|
-
import { TagResponse } from "./types.js";
|
|
12
|
-
|
|
13
|
-
// Create a logger for tag resources
|
|
14
|
-
const logger = createLogger('TagResource');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Helper function to safely convert any error to an object
|
|
18
|
-
*/
|
|
19
|
-
function errorToObject(error: unknown): Record<string, unknown> {
|
|
20
|
-
if (error instanceof Error) {
|
|
21
|
-
return {
|
|
22
|
-
name: error.name,
|
|
23
|
-
message: error.message,
|
|
24
|
-
stack: error.stack,
|
|
25
|
-
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
26
|
-
};
|
|
27
|
-
} else if (typeof error === 'object' && error !== null) {
|
|
28
|
-
return error as Record<string, unknown>;
|
|
29
|
-
} else {
|
|
30
|
-
return {
|
|
31
|
-
error: String(error),
|
|
32
|
-
errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Resource for providing tags used in the Obsidian vault
|
|
39
|
-
*/
|
|
40
|
-
export class TagResource {
|
|
41
|
-
private tagCache: Map<string, Set<string>> = new Map();
|
|
42
|
-
private propertyManager: PropertyManager;
|
|
43
|
-
private isInitialized = false;
|
|
44
|
-
private isUpdating = false; // Flag to prevent concurrent updates
|
|
45
|
-
private lastUpdate = 0;
|
|
46
|
-
private updateInterval = 5000; // 5 seconds
|
|
47
|
-
|
|
48
|
-
constructor(private client: ObsidianClient) {
|
|
49
|
-
this.propertyManager = new PropertyManager(client);
|
|
50
|
-
this.initializeCache();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get resource description for the MCP server
|
|
55
|
-
*/
|
|
56
|
-
getResourceDescription(): Resource {
|
|
57
|
-
return {
|
|
58
|
-
uri: "obsidian://tags",
|
|
59
|
-
name: "Obsidian Tags",
|
|
60
|
-
description: "List of all tags used across the Obsidian vault with their usage counts",
|
|
61
|
-
mimeType: "application/json"
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Initialize the tag cache
|
|
67
|
-
*/
|
|
68
|
-
private async initializeCache() {
|
|
69
|
-
logger.startTimer('init_tag_cache');
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
logger.info('Initializing tag cache');
|
|
73
|
-
|
|
74
|
-
// Get all markdown files using platform-agnostic path pattern
|
|
75
|
-
const query: JsonLogicQuery = {
|
|
76
|
-
"glob": [`**${sep}*.md`.replace(/\\/g, '/'), { "var": "path" }]
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const results = await this.client.searchJson(query);
|
|
80
|
-
this.tagCache.clear();
|
|
81
|
-
|
|
82
|
-
// Create a limiter with a concurrency of 3 (reduced from 10)
|
|
83
|
-
const limit = pLimit(3);
|
|
84
|
-
|
|
85
|
-
// Create promises for processing each file with concurrency limiting
|
|
86
|
-
const processingPromises = results
|
|
87
|
-
.filter(result => 'filename' in result) // Ensure filename exists
|
|
88
|
-
.map((result) => limit(async () => { // Wrap the async function with the limiter
|
|
89
|
-
const filename = result.filename;
|
|
90
|
-
try {
|
|
91
|
-
// This call is now rate-limited
|
|
92
|
-
const content = await this.client.getFileContents(filename);
|
|
93
|
-
// Only extract tags from frontmatter YAML
|
|
94
|
-
const parseResult = this.propertyManager.parseProperties(content);
|
|
95
|
-
// Handle potential parsing errors before accessing properties
|
|
96
|
-
if (parseResult.error) {
|
|
97
|
-
logger.warn(`Skipping tags for ${filename} due to parsing error: ${parseResult.error.message}`);
|
|
98
|
-
return { filename, tags: [] }; // Return empty tags on error
|
|
99
|
-
}
|
|
100
|
-
return { filename, tags: parseResult.properties.tags || [] };
|
|
101
|
-
} catch (error) {
|
|
102
|
-
logger.error(`Failed to process file ${filename}:`, errorToObject(error));
|
|
103
|
-
return { filename, error: true }; // Mark as failed
|
|
104
|
-
}
|
|
105
|
-
})); // Close the limiter wrapper
|
|
106
|
-
|
|
107
|
-
// Execute promises in parallel and wait for all to settle
|
|
108
|
-
const processedResults = await Promise.allSettled(processingPromises);
|
|
109
|
-
|
|
110
|
-
// Populate the cache from settled results
|
|
111
|
-
processedResults.forEach(settledResult => {
|
|
112
|
-
// Check if the promise was fulfilled and didn't encounter a processing error
|
|
113
|
-
if (settledResult.status === 'fulfilled' && !settledResult.value.error) {
|
|
114
|
-
const { filename, tags } = settledResult.value;
|
|
115
|
-
// Ensure tags is an array before iterating
|
|
116
|
-
if (tags && Array.isArray(tags)) {
|
|
117
|
-
tags.forEach((tag: string) => {
|
|
118
|
-
this.addTag(tag, filename);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
} else if (settledResult.status === 'rejected') {
|
|
122
|
-
// Log unexpected rejections from the async map function itself
|
|
123
|
-
logger.error(`Unexpected error during file processing setup:`, errorToObject(settledResult.reason));
|
|
124
|
-
}
|
|
125
|
-
// Errors during getFileContents/parseProperties are already logged within the map function
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
this.isInitialized = true;
|
|
129
|
-
this.lastUpdate = Date.now();
|
|
130
|
-
|
|
131
|
-
const elapsedMs = logger.endTimer('init_tag_cache');
|
|
132
|
-
logger.logOperationResult(true, 'initialize_tag_cache', elapsedMs, {
|
|
133
|
-
tagCount: this.tagCache.size,
|
|
134
|
-
fileCount: results.length
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
logger.info(`Tag cache initialized with ${this.tagCache.size} unique tags`);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
const elapsedMs = logger.endTimer('init_tag_cache');
|
|
140
|
-
logger.logOperationResult(false, 'initialize_tag_cache', elapsedMs);
|
|
141
|
-
logger.error("Failed to initialize tag cache:", errorToObject(error));
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Add a tag to the cache
|
|
148
|
-
*/
|
|
149
|
-
private addTag(tag: string, filepath: string) {
|
|
150
|
-
if (!this.tagCache.has(tag)) {
|
|
151
|
-
this.tagCache.set(tag, new Set());
|
|
152
|
-
}
|
|
153
|
-
this.tagCache.get(tag)!.add(filepath);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Update the cache if needed, preventing race conditions.
|
|
158
|
-
*/
|
|
159
|
-
private async updateCacheIfNeeded() {
|
|
160
|
-
const now = Date.now();
|
|
161
|
-
// Check if cache is fresh enough
|
|
162
|
-
if (now - this.lastUpdate <= this.updateInterval) {
|
|
163
|
-
return; // Cache is up-to-date
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Check if an update is already in progress
|
|
167
|
-
if (this.isUpdating) {
|
|
168
|
-
logger.debug('Cache update already in progress, skipping redundant update.');
|
|
169
|
-
// Optionally, wait for the ongoing update instead of returning immediately
|
|
170
|
-
// For now, we return to avoid complexity, potentially serving slightly stale data.
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Acquire the update lock
|
|
175
|
-
this.isUpdating = true;
|
|
176
|
-
logger.debug('Acquired cache update lock.');
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
// Double-check the update condition after acquiring the lock
|
|
180
|
-
// to handle cases where another process finished updating
|
|
181
|
-
// while this one was waiting for the lock (though less likely with a simple flag).
|
|
182
|
-
const nowAfterLock = Date.now();
|
|
183
|
-
if (nowAfterLock - this.lastUpdate > this.updateInterval) {
|
|
184
|
-
logger.info('Tag cache needs update, refreshing...');
|
|
185
|
-
await this.initializeCache(); // This method updates this.lastUpdate internally
|
|
186
|
-
} else {
|
|
187
|
-
logger.debug('Cache was updated by another process while waiting for lock.');
|
|
188
|
-
}
|
|
189
|
-
} catch (error) {
|
|
190
|
-
// Log error during update, but don't necessarily block other operations
|
|
191
|
-
logger.error('Error during cache update:', errorToObject(error));
|
|
192
|
-
// Decide if the error should be re-thrown or handled gracefully
|
|
193
|
-
// For now, we log and continue, allowing the lock to be released.
|
|
194
|
-
} finally {
|
|
195
|
-
// Release the update lock
|
|
196
|
-
this.isUpdating = false;
|
|
197
|
-
logger.debug('Released cache update lock.');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Get the content for the resource, optionally filtering by path.
|
|
203
|
-
* Note: The path filtering logic assumes the tool handler passes the path correctly.
|
|
204
|
-
*/
|
|
205
|
-
// TODO: Verify how the path argument is passed from the tool handler
|
|
206
|
-
async getContent(filterPath?: string): Promise<TextContent[]> {
|
|
207
|
-
logger.startTimer('get_tags_content');
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
if (!this.isInitialized) {
|
|
211
|
-
logger.info('Tag cache not initialized, initializing now');
|
|
212
|
-
await this.initializeCache();
|
|
213
|
-
} else {
|
|
214
|
-
await this.updateCacheIfNeeded();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let filteredTagEntries: { name: string; files: Set<string> }[] = [];
|
|
218
|
-
|
|
219
|
-
// Filter tags based on the provided path
|
|
220
|
-
if (filterPath) {
|
|
221
|
-
// Normalize path to ensure it ends with a separator for accurate startsWith check
|
|
222
|
-
const normalizedFilterPath = filterPath.endsWith(sep) ? filterPath : filterPath + sep;
|
|
223
|
-
logger.debug(`Filtering tags for path: ${normalizedFilterPath}`);
|
|
224
|
-
|
|
225
|
-
filteredTagEntries = Array.from(this.tagCache.entries())
|
|
226
|
-
.map(([name, files]) => {
|
|
227
|
-
// Filter files within each tag entry
|
|
228
|
-
const relevantFiles = Array.from(files).filter(file => file.startsWith(normalizedFilterPath));
|
|
229
|
-
return { name, files: new Set(relevantFiles) };
|
|
230
|
-
})
|
|
231
|
-
.filter(tagEntry => tagEntry.files.size > 0); // Keep only tags that exist in the filtered path
|
|
232
|
-
|
|
233
|
-
logger.debug(`Found ${filteredTagEntries.length} tags after filtering.`);
|
|
234
|
-
} else {
|
|
235
|
-
// If no path provided, use all tags from the cache
|
|
236
|
-
filteredTagEntries = Array.from(this.tagCache.entries()).map(([name, files]) => ({ name, files }));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const response: TagResponse = {
|
|
240
|
-
tags: filteredTagEntries
|
|
241
|
-
.map(({ name, files }) => ({ // Map from the potentially filtered structure
|
|
242
|
-
name,
|
|
243
|
-
count: files.size,
|
|
244
|
-
files: Array.from(files).sort()
|
|
245
|
-
}))
|
|
246
|
-
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)), // Sort filtered results
|
|
247
|
-
metadata: {
|
|
248
|
-
// Note: Metadata currently reflects the entire vault cache, even when filtered.
|
|
249
|
-
// Adjust calculation here if path-specific metadata is desired.
|
|
250
|
-
totalOccurrences: Array.from(this.tagCache.values())
|
|
251
|
-
.reduce((sum, files) => sum + files.size, 0),
|
|
252
|
-
uniqueTags: this.tagCache.size,
|
|
253
|
-
scannedFiles: new Set(
|
|
254
|
-
Array.from(this.tagCache.values())
|
|
255
|
-
.flatMap(files => Array.from(files))
|
|
256
|
-
).size,
|
|
257
|
-
lastUpdate: this.lastUpdate
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
logger.debug(`Returning tag resource with ${response.tags.length} tags`);
|
|
262
|
-
|
|
263
|
-
const elapsedMs = logger.endTimer('get_tags_content');
|
|
264
|
-
logger.logOperationResult(true, 'get_tags', elapsedMs, {
|
|
265
|
-
tagCount: response.tags.length,
|
|
266
|
-
totalOccurrences: response.metadata.totalOccurrences,
|
|
267
|
-
uniqueTags: response.metadata.uniqueTags,
|
|
268
|
-
scannedFiles: response.metadata.scannedFiles
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
return [{
|
|
272
|
-
type: "text",
|
|
273
|
-
text: JSON.stringify(response, null, 2),
|
|
274
|
-
uri: this.getResourceDescription().uri
|
|
275
|
-
}];
|
|
276
|
-
} catch (error) {
|
|
277
|
-
const elapsedMs = logger.endTimer('get_tags_content');
|
|
278
|
-
logger.logOperationResult(false, 'get_tags', elapsedMs);
|
|
279
|
-
logger.error("Failed to get tags:", errorToObject(error));
|
|
280
|
-
throw error;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
package/src/resources/types.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resource types for the MCP server
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Tag information structure
|
|
7
|
-
*/
|
|
8
|
-
export interface TagInfo {
|
|
9
|
-
name: string;
|
|
10
|
-
count: number;
|
|
11
|
-
files: string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Tag metadata structure
|
|
16
|
-
*/
|
|
17
|
-
export interface TagMetadata {
|
|
18
|
-
totalOccurrences: number;
|
|
19
|
-
uniqueTags: number;
|
|
20
|
-
scannedFiles: number;
|
|
21
|
-
lastUpdate: number; // Timestamp of last cache update
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Tag resource response structure
|
|
26
|
-
*/
|
|
27
|
-
export interface TagResponse {
|
|
28
|
-
tags: TagInfo[];
|
|
29
|
-
metadata: TagMetadata;
|
|
30
|
-
}
|
package/src/tools/base.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for all tool handlers
|
|
3
|
-
*/
|
|
4
|
-
import { TextContent, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { McpErrorCode } from "../mcp/types.js";
|
|
6
|
-
import { ObsidianClient } from "../obsidian/client.js";
|
|
7
|
-
import { ObsidianError } from "../utils/errors.js";
|
|
8
|
-
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
9
|
-
import { tokenCounter } from "../utils/tokenization.js";
|
|
10
|
-
|
|
11
|
-
// Create a logger for tool operations
|
|
12
|
-
const logger = createLogger('Tools');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Interface that all tool handlers must implement
|
|
16
|
-
*/
|
|
17
|
-
export interface ToolHandler<T = any> {
|
|
18
|
-
name: string;
|
|
19
|
-
getToolDescription(): Tool;
|
|
20
|
-
runTool(args: T): Promise<Array<TextContent>>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Base class for all tool handlers with common functionality
|
|
25
|
-
*/
|
|
26
|
-
export abstract class BaseToolHandler<T = Record<string, unknown>> implements ToolHandler<T> {
|
|
27
|
-
/**
|
|
28
|
-
* Create a new tool handler
|
|
29
|
-
* @param name The name of the tool
|
|
30
|
-
* @param client The ObsidianClient instance
|
|
31
|
-
*/
|
|
32
|
-
constructor(
|
|
33
|
-
public readonly name: string,
|
|
34
|
-
protected client: ObsidianClient
|
|
35
|
-
) {}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get the tool description for MCP server
|
|
39
|
-
*/
|
|
40
|
-
abstract getToolDescription(): Tool;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Run the tool with the provided arguments
|
|
44
|
-
*/
|
|
45
|
-
abstract runTool(args: T): Promise<Array<TextContent>>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Create a standardized response from any content type
|
|
49
|
-
* @param content The content to format for response
|
|
50
|
-
* @returns An array of TextContent objects
|
|
51
|
-
*/
|
|
52
|
-
protected createResponse(content: unknown): TextContent[] {
|
|
53
|
-
let text: string;
|
|
54
|
-
|
|
55
|
-
// Handle different content types
|
|
56
|
-
if (typeof content === 'string') {
|
|
57
|
-
text = content;
|
|
58
|
-
} else if (content instanceof Buffer) {
|
|
59
|
-
text = content.toString('utf-8');
|
|
60
|
-
} else if (Array.isArray(content) && content.every(item => typeof item === 'string')) {
|
|
61
|
-
text = content.join('\n');
|
|
62
|
-
} else if (content instanceof Error) {
|
|
63
|
-
text = `Error: ${content.message}\n${content.stack || ''}`;
|
|
64
|
-
} else {
|
|
65
|
-
try {
|
|
66
|
-
text = JSON.stringify(content, null, 2);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
text = String(content);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Count tokens and truncate if necessary
|
|
73
|
-
const originalTokenCount = tokenCounter.countTokens(text);
|
|
74
|
-
const truncatedText = tokenCounter.truncateToTokenLimit(text);
|
|
75
|
-
const finalTokenCount = tokenCounter.countTokens(truncatedText);
|
|
76
|
-
|
|
77
|
-
if (originalTokenCount > finalTokenCount) {
|
|
78
|
-
logger.debug(`[${this.name}] Response truncated`, {
|
|
79
|
-
toolName: this.name,
|
|
80
|
-
originalTokens: originalTokenCount,
|
|
81
|
-
truncatedTokens: finalTokenCount,
|
|
82
|
-
truncationPercent: Math.round((1 - finalTokenCount / originalTokenCount) * 100)
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return [{
|
|
87
|
-
type: "text",
|
|
88
|
-
text: truncatedText
|
|
89
|
-
}];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Standard error handling for tool execution
|
|
94
|
-
* @param error The error to handle
|
|
95
|
-
* @throws ObsidianError
|
|
96
|
-
*/
|
|
97
|
-
protected handleError(error: unknown): never {
|
|
98
|
-
if (error instanceof ObsidianError) {
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
logger.error(`Tool '${this.name}' execution failed`, {
|
|
103
|
-
toolName: this.name,
|
|
104
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
105
|
-
errorStack: error instanceof Error ? error.stack : undefined,
|
|
106
|
-
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
if (error instanceof Error) {
|
|
110
|
-
throw new ObsidianError(
|
|
111
|
-
`Tool '${this.name}' execution failed: ${error.message}`,
|
|
112
|
-
McpErrorCode.INTERNAL_SERVER_ERROR,
|
|
113
|
-
{ originalError: error.stack }
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
throw new ObsidianError(
|
|
118
|
-
`Tool '${this.name}' execution failed with unknown error`,
|
|
119
|
-
McpErrorCode.INTERNAL_SERVER_ERROR,
|
|
120
|
-
{ error: String(error) }
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|