obsidian-mcp-server 1.4.0 → 1.5.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/LICENSE +2 -1
- package/README.md +62 -83
- package/build/mcp/handlers.js +190 -41
- package/build/mcp/server.js +188 -72
- package/build/mcp/types.js +42 -0
- package/build/resources/tags.js +44 -4
- package/build/tools/base.js +16 -6
- package/build/tools/properties/manager.js +7 -7
- package/build/tools/properties/tools.js +51 -4
- package/build/tools/search/complex.js +55 -4
- package/build/utils/logging.js +348 -50
- package/debug.js +99 -0
- package/package.json +14 -15
- package/src/mcp/handlers.ts +251 -49
- package/src/mcp/server.ts +207 -78
- package/src/mcp/types.ts +69 -1
- package/src/resources/tags.ts +49 -4
- package/src/tools/base.ts +20 -9
- package/src/tools/properties/manager.ts +8 -8
- package/src/tools/properties/tools.ts +59 -4
- package/src/tools/search/complex.ts +60 -4
- package/src/utils/logging.ts +466 -54
package/build/mcp/server.js
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
import { config } from "dotenv";
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import { createLogger } from "../utils/logging.js";
|
|
7
|
+
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
8
8
|
import { rateLimiter } from "../utils/rate-limiting.js";
|
|
9
9
|
import { createTagResource } from "../resources/index.js";
|
|
10
10
|
import { createToolHandlers, createToolHandlerMap } from "../tools/index.js";
|
|
11
11
|
import { ObsidianClient } from "../obsidian/client.js";
|
|
12
12
|
import { setupToolListingHandler, setupToolCallingHandler, setupResourceListingHandler, setupResourceReadingHandler } from "./handlers.js";
|
|
13
|
+
import { McpErrorCode } from "./types.js";
|
|
13
14
|
// Create a logger for the server
|
|
14
15
|
const logger = createLogger('McpServer');
|
|
15
16
|
// Load environment variables
|
|
@@ -18,52 +19,116 @@ config();
|
|
|
18
19
|
* Initialize MCP server components
|
|
19
20
|
*/
|
|
20
21
|
export async function initializeServer() {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
maxContentLength: parseInt(process.env.MAX_CONTENT_LENGTH || String(50 * 1024 * 1024)),
|
|
33
|
-
maxBodyLength: parseInt(process.env.MAX_BODY_LENGTH || String(50 * 1024 * 1024))
|
|
34
|
-
});
|
|
35
|
-
// Initialize tool handlers
|
|
36
|
-
logger.info('Initializing tool handlers');
|
|
37
|
-
const toolHandlers = createToolHandlers(client);
|
|
38
|
-
const toolHandlerMap = createToolHandlerMap(toolHandlers);
|
|
39
|
-
// Initialize resources
|
|
40
|
-
logger.info('Initializing resources');
|
|
41
|
-
const tagResource = createTagResource(client);
|
|
42
|
-
const resources = {
|
|
43
|
-
[tagResource.getResourceDescription().uri]: tagResource
|
|
44
|
-
};
|
|
45
|
-
// Create MCP server
|
|
46
|
-
const serverConfig = {
|
|
47
|
-
name: "obsidian-mcp-server",
|
|
48
|
-
version: process.env.npm_package_version ?? "1.1.0" // Use version from package.json
|
|
49
|
-
};
|
|
50
|
-
logger.info(`Creating MCP server: ${serverConfig.name} v${serverConfig.version}`);
|
|
51
|
-
const server = new Server(serverConfig, {
|
|
52
|
-
capabilities: {
|
|
53
|
-
tools: {},
|
|
54
|
-
resources
|
|
22
|
+
// Start initialization timer
|
|
23
|
+
logger.startTimer('server_init');
|
|
24
|
+
try {
|
|
25
|
+
// Verify API key exists
|
|
26
|
+
const API_KEY = process.env.OBSIDIAN_API_KEY;
|
|
27
|
+
if (!API_KEY) {
|
|
28
|
+
logger.error("Missing API key", {
|
|
29
|
+
errorCategory: ErrorCategoryType.CATEGORY_AUTHENTICATION,
|
|
30
|
+
errorCode: McpErrorCode.UNAUTHORIZED
|
|
31
|
+
});
|
|
32
|
+
throw new Error("OBSIDIAN_API_KEY environment variable is required");
|
|
55
33
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
// Initialize Obsidian client with environment configuration
|
|
35
|
+
logger.info('Initializing Obsidian client', {
|
|
36
|
+
verifySSL: process.env.VERIFY_SSL === 'true',
|
|
37
|
+
timeout: parseInt(process.env.REQUEST_TIMEOUT || '5000'),
|
|
38
|
+
maxContentLength: parseInt(process.env.MAX_CONTENT_LENGTH || String(50 * 1024 * 1024)),
|
|
39
|
+
maxBodyLength: parseInt(process.env.MAX_BODY_LENGTH || String(50 * 1024 * 1024))
|
|
40
|
+
});
|
|
41
|
+
const client = new ObsidianClient({
|
|
42
|
+
apiKey: API_KEY,
|
|
43
|
+
verifySSL: process.env.VERIFY_SSL === 'true',
|
|
44
|
+
timeout: parseInt(process.env.REQUEST_TIMEOUT || '5000'),
|
|
45
|
+
maxContentLength: parseInt(process.env.MAX_CONTENT_LENGTH || String(50 * 1024 * 1024)),
|
|
46
|
+
maxBodyLength: parseInt(process.env.MAX_BODY_LENGTH || String(50 * 1024 * 1024))
|
|
47
|
+
});
|
|
48
|
+
// Initialize tool handlers
|
|
49
|
+
logger.startTimer('init_tools');
|
|
50
|
+
logger.info('Initializing tool handlers');
|
|
51
|
+
const toolHandlers = createToolHandlers(client);
|
|
52
|
+
const toolHandlerMap = createToolHandlerMap(toolHandlers);
|
|
53
|
+
const toolsElapsedMs = logger.endTimer('init_tools');
|
|
54
|
+
logger.info(`Initialized ${toolHandlerMap.size} tool handlers`, {
|
|
55
|
+
processingTimeMs: toolsElapsedMs,
|
|
56
|
+
toolCount: toolHandlerMap.size
|
|
57
|
+
});
|
|
58
|
+
// Initialize resources
|
|
59
|
+
logger.startTimer('init_resources');
|
|
60
|
+
logger.info('Initializing resources');
|
|
61
|
+
const tagResource = createTagResource(client);
|
|
62
|
+
const resources = {
|
|
63
|
+
[tagResource.getResourceDescription().uri]: tagResource
|
|
64
|
+
};
|
|
65
|
+
const resourcesElapsedMs = logger.endTimer('init_resources');
|
|
66
|
+
logger.info(`Initialized ${Object.keys(resources).length} resources`, {
|
|
67
|
+
processingTimeMs: resourcesElapsedMs,
|
|
68
|
+
resourceCount: Object.keys(resources).length
|
|
69
|
+
});
|
|
70
|
+
// Create MCP server
|
|
71
|
+
const serverConfig = {
|
|
72
|
+
name: "obsidian-mcp-server",
|
|
73
|
+
version: process.env.npm_package_version ?? "1.1.0" // Use version from package.json
|
|
74
|
+
};
|
|
75
|
+
logger.info(`Creating MCP server: ${serverConfig.name} v${serverConfig.version}`, {
|
|
76
|
+
serverName: serverConfig.name,
|
|
77
|
+
serverVersion: serverConfig.version
|
|
78
|
+
});
|
|
79
|
+
const server = new Server(serverConfig, {
|
|
80
|
+
capabilities: {
|
|
81
|
+
tools: {},
|
|
82
|
+
resources
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// Set up request handlers
|
|
86
|
+
logger.startTimer('setup_handlers');
|
|
87
|
+
setupToolListingHandler(server, toolHandlerMap);
|
|
88
|
+
setupToolCallingHandler(server, toolHandlerMap);
|
|
89
|
+
setupResourceListingHandler(server, resources);
|
|
90
|
+
setupResourceReadingHandler(server, resources);
|
|
91
|
+
const handlersElapsedMs = logger.endTimer('setup_handlers');
|
|
92
|
+
logger.info('Request handlers configured', { processingTimeMs: handlersElapsedMs });
|
|
93
|
+
// Set up enhanced error handler
|
|
94
|
+
server.onerror = (error) => {
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
logger.error("MCP Server Error", {
|
|
97
|
+
name: error.name,
|
|
98
|
+
message: error.message,
|
|
99
|
+
stack: error.stack,
|
|
100
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
logger.error("MCP Server Error (Unknown)", {
|
|
105
|
+
error: String(error),
|
|
106
|
+
errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
// Log successful initialization
|
|
111
|
+
const totalElapsedMs = logger.endTimer('server_init');
|
|
112
|
+
logger.logOperationResult(true, 'server_initialization', totalElapsedMs, {
|
|
113
|
+
toolCount: toolHandlerMap.size,
|
|
114
|
+
resourceCount: Object.keys(resources).length
|
|
115
|
+
});
|
|
116
|
+
return server;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Log initialization failure
|
|
120
|
+
const totalElapsedMs = logger.endTimer('server_init');
|
|
121
|
+
logger.logOperationResult(false, 'server_initialization', totalElapsedMs);
|
|
122
|
+
// Enhance error reporting
|
|
123
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
124
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
125
|
+
logger.error("Failed to initialize server", {
|
|
126
|
+
error: errorMessage,
|
|
127
|
+
stack: errorStack,
|
|
128
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
129
|
+
});
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
67
132
|
}
|
|
68
133
|
/**
|
|
69
134
|
* Set up graceful shutdown handling
|
|
@@ -72,43 +137,78 @@ export async function initializeServer() {
|
|
|
72
137
|
*/
|
|
73
138
|
export function setupShutdownHandling(server, cleanupHandlers = []) {
|
|
74
139
|
// Cleanup function for graceful shutdown
|
|
75
|
-
const cleanup = async () => {
|
|
76
|
-
logger.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
140
|
+
const cleanup = async (signal) => {
|
|
141
|
+
logger.startTimer('server_shutdown');
|
|
142
|
+
logger.info('Shutting down server...', { signal });
|
|
143
|
+
try {
|
|
144
|
+
// Run cleanup handlers
|
|
145
|
+
logger.debug('Running cleanup handlers', { handlersCount: cleanupHandlers.length });
|
|
146
|
+
for (const handler of cleanupHandlers) {
|
|
147
|
+
try {
|
|
148
|
+
await handler();
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
152
|
+
logger.error('Error during cleanup handler execution', {
|
|
153
|
+
error: errorMessage,
|
|
154
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
155
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
156
|
+
});
|
|
157
|
+
}
|
|
84
158
|
}
|
|
159
|
+
// Dispose rate limiter
|
|
160
|
+
logger.debug('Disposing rate limiter');
|
|
161
|
+
rateLimiter.dispose();
|
|
162
|
+
// Close server
|
|
163
|
+
logger.debug('Closing MCP server connection');
|
|
164
|
+
await server.close();
|
|
165
|
+
const elapsedMs = logger.endTimer('server_shutdown');
|
|
166
|
+
logger.logOperationResult(true, 'server_shutdown', elapsedMs);
|
|
167
|
+
logger.info('Server shutdown completed successfully');
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const elapsedMs = logger.endTimer('server_shutdown');
|
|
172
|
+
logger.logOperationResult(false, 'server_shutdown', elapsedMs);
|
|
173
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
174
|
+
logger.error('Failed to shut down server gracefully', {
|
|
175
|
+
error: errorMessage,
|
|
176
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
177
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
178
|
+
});
|
|
179
|
+
process.exit(1);
|
|
85
180
|
}
|
|
86
|
-
// Dispose rate limiter
|
|
87
|
-
rateLimiter.dispose();
|
|
88
|
-
// Close server
|
|
89
|
-
await server.close();
|
|
90
|
-
process.exit(0);
|
|
91
181
|
};
|
|
92
182
|
// Handle various termination signals
|
|
93
|
-
process.on('SIGINT', cleanup); // Ctrl+C on all platforms
|
|
94
|
-
process.on('SIGTERM', cleanup); // Termination request
|
|
183
|
+
process.on('SIGINT', () => cleanup('SIGINT')); // Ctrl+C on all platforms
|
|
184
|
+
process.on('SIGTERM', () => cleanup('SIGTERM')); // Termination request
|
|
95
185
|
if (process.platform === 'win32') {
|
|
96
186
|
// Windows-specific handling
|
|
97
|
-
process.on('SIGHUP', cleanup); // Terminal closed
|
|
187
|
+
process.on('SIGHUP', () => cleanup('SIGHUP')); // Terminal closed
|
|
98
188
|
}
|
|
99
189
|
else {
|
|
100
190
|
// Unix-specific signals
|
|
101
|
-
process.on('SIGUSR1', cleanup);
|
|
102
|
-
process.on('SIGUSR2', cleanup);
|
|
191
|
+
process.on('SIGUSR1', () => cleanup('SIGUSR1'));
|
|
192
|
+
process.on('SIGUSR2', () => cleanup('SIGUSR2'));
|
|
103
193
|
}
|
|
104
194
|
// Handle uncaught errors
|
|
105
195
|
process.on('uncaughtException', async (error) => {
|
|
106
|
-
logger.error('Uncaught exception
|
|
107
|
-
|
|
196
|
+
logger.error('Uncaught exception', {
|
|
197
|
+
name: error.name,
|
|
198
|
+
message: error.message,
|
|
199
|
+
stack: error.stack,
|
|
200
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
201
|
+
});
|
|
202
|
+
await cleanup('uncaughtException');
|
|
108
203
|
});
|
|
109
|
-
process.on('unhandledRejection', async (
|
|
110
|
-
|
|
111
|
-
|
|
204
|
+
process.on('unhandledRejection', async (reason, promise) => {
|
|
205
|
+
const errorMessage = reason instanceof Error ? reason.message : String(reason);
|
|
206
|
+
logger.error('Unhandled promise rejection', {
|
|
207
|
+
reason: errorMessage,
|
|
208
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
209
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
210
|
+
});
|
|
211
|
+
await cleanup('unhandledRejection');
|
|
112
212
|
});
|
|
113
213
|
}
|
|
114
214
|
/**
|
|
@@ -116,15 +216,31 @@ export function setupShutdownHandling(server, cleanupHandlers = []) {
|
|
|
116
216
|
*/
|
|
117
217
|
export async function run() {
|
|
118
218
|
try {
|
|
219
|
+
logger.info("Starting Obsidian MCP server");
|
|
220
|
+
logger.startTimer('server_startup');
|
|
119
221
|
const server = await initializeServer();
|
|
120
222
|
setupShutdownHandling(server, []);
|
|
121
223
|
// Connect to transport
|
|
224
|
+
logger.debug("Connecting to stdio transport");
|
|
122
225
|
const transport = new StdioServerTransport();
|
|
123
226
|
await server.connect(transport);
|
|
124
|
-
logger.
|
|
227
|
+
const elapsedMs = logger.endTimer('server_startup');
|
|
228
|
+
logger.logOperationResult(true, 'server_startup', elapsedMs);
|
|
229
|
+
logger.info("Obsidian MCP server running on stdio", {
|
|
230
|
+
serverName: "obsidian-mcp-server",
|
|
231
|
+
serverVersion: process.env.npm_package_version ?? "1.1.0",
|
|
232
|
+
transport: "stdio"
|
|
233
|
+
});
|
|
125
234
|
}
|
|
126
235
|
catch (error) {
|
|
127
|
-
logger.
|
|
236
|
+
const elapsedMs = logger.endTimer('server_startup', 'Server startup failed');
|
|
237
|
+
logger.logOperationResult(false, 'server_startup', elapsedMs);
|
|
238
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
239
|
+
logger.error("Failed to start server", {
|
|
240
|
+
error: errorMessage,
|
|
241
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
242
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
243
|
+
});
|
|
128
244
|
process.exit(1);
|
|
129
245
|
}
|
|
130
246
|
}
|
package/build/mcp/types.js
CHANGED
|
@@ -1,7 +1,49 @@
|
|
|
1
|
+
import { ErrorCategoryType, LogLevel } from "../utils/logging.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error codes used in the MCP server
|
|
4
|
+
*/
|
|
5
|
+
export var McpErrorCode;
|
|
6
|
+
(function (McpErrorCode) {
|
|
7
|
+
// Server errors (5xxxx)
|
|
8
|
+
McpErrorCode[McpErrorCode["INTERNAL_SERVER_ERROR"] = 50000] = "INTERNAL_SERVER_ERROR";
|
|
9
|
+
McpErrorCode[McpErrorCode["SERVICE_UNAVAILABLE"] = 50300] = "SERVICE_UNAVAILABLE";
|
|
10
|
+
McpErrorCode[McpErrorCode["TIMEOUT"] = 50800] = "TIMEOUT";
|
|
11
|
+
// Client errors (4xxxx)
|
|
12
|
+
McpErrorCode[McpErrorCode["BAD_REQUEST"] = 40000] = "BAD_REQUEST";
|
|
13
|
+
McpErrorCode[McpErrorCode["UNAUTHORIZED"] = 40100] = "UNAUTHORIZED";
|
|
14
|
+
McpErrorCode[McpErrorCode["FORBIDDEN"] = 40300] = "FORBIDDEN";
|
|
15
|
+
McpErrorCode[McpErrorCode["NOT_FOUND"] = 40400] = "NOT_FOUND";
|
|
16
|
+
McpErrorCode[McpErrorCode["METHOD_NOT_ALLOWED"] = 40500] = "METHOD_NOT_ALLOWED";
|
|
17
|
+
McpErrorCode[McpErrorCode["RATE_LIMIT_EXCEEDED"] = 40900] = "RATE_LIMIT_EXCEEDED";
|
|
18
|
+
// Success with special handling
|
|
19
|
+
McpErrorCode[McpErrorCode["SUCCESS_NO_CONTENT"] = 20400] = "SUCCESS_NO_CONTENT";
|
|
20
|
+
})(McpErrorCode || (McpErrorCode = {}));
|
|
1
21
|
/**
|
|
2
22
|
* Default timeout configuration
|
|
3
23
|
*/
|
|
4
24
|
export const DEFAULT_TIMEOUT_CONFIG = {
|
|
5
25
|
toolExecutionMs: parseInt(process.env.TOOL_TIMEOUT_MS ?? '60000') // 60 second default timeout
|
|
6
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* Helper functions for creating operation results
|
|
29
|
+
*/
|
|
30
|
+
export function createSuccessResult(data) {
|
|
31
|
+
return {
|
|
32
|
+
resultSuccessful: true,
|
|
33
|
+
resultData: data
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function createFailureResult(message, code, category = ErrorCategoryType.CATEGORY_UNKNOWN, context) {
|
|
37
|
+
return {
|
|
38
|
+
resultSuccessful: false,
|
|
39
|
+
resultError: {
|
|
40
|
+
errorMessage: message,
|
|
41
|
+
errorCode: typeof code === 'string' ? code : String(code),
|
|
42
|
+
errorCategory: category,
|
|
43
|
+
errorSeverity: LogLevel.ERROR,
|
|
44
|
+
errorTimestamp: new Date().toISOString(),
|
|
45
|
+
errorContext: context
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
7
49
|
//# sourceMappingURL=types.js.map
|
package/build/resources/tags.js
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { PropertyManager } from "../tools/properties/manager.js";
|
|
2
2
|
import { sep } from "path";
|
|
3
|
-
import { createLogger } from "../utils/logging.js";
|
|
3
|
+
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
4
4
|
// Create a logger for tag resources
|
|
5
5
|
const logger = createLogger('TagResource');
|
|
6
|
+
/**
|
|
7
|
+
* Helper function to safely convert any error to an object
|
|
8
|
+
*/
|
|
9
|
+
function errorToObject(error) {
|
|
10
|
+
if (error instanceof Error) {
|
|
11
|
+
return {
|
|
12
|
+
name: error.name,
|
|
13
|
+
message: error.message,
|
|
14
|
+
stack: error.stack,
|
|
15
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
else if (typeof error === 'object' && error !== null) {
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return {
|
|
23
|
+
error: String(error),
|
|
24
|
+
errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
6
28
|
/**
|
|
7
29
|
* Resource for providing tags used in the Obsidian vault
|
|
8
30
|
*/
|
|
@@ -33,6 +55,7 @@ export class TagResource {
|
|
|
33
55
|
* Initialize the tag cache
|
|
34
56
|
*/
|
|
35
57
|
async initializeCache() {
|
|
58
|
+
logger.startTimer('init_tag_cache');
|
|
36
59
|
try {
|
|
37
60
|
logger.info('Initializing tag cache');
|
|
38
61
|
// Get all markdown files using platform-agnostic path pattern
|
|
@@ -56,15 +79,22 @@ export class TagResource {
|
|
|
56
79
|
}
|
|
57
80
|
}
|
|
58
81
|
catch (error) {
|
|
59
|
-
logger.error(`Failed to process file ${result.filename}:`, error);
|
|
82
|
+
logger.error(`Failed to process file ${result.filename}:`, errorToObject(error));
|
|
60
83
|
}
|
|
61
84
|
}
|
|
62
85
|
this.isInitialized = true;
|
|
63
86
|
this.lastUpdate = Date.now();
|
|
87
|
+
const elapsedMs = logger.endTimer('init_tag_cache');
|
|
88
|
+
logger.logOperationResult(true, 'initialize_tag_cache', elapsedMs, {
|
|
89
|
+
tagCount: this.tagCache.size,
|
|
90
|
+
fileCount: results.length
|
|
91
|
+
});
|
|
64
92
|
logger.info(`Tag cache initialized with ${this.tagCache.size} unique tags`);
|
|
65
93
|
}
|
|
66
94
|
catch (error) {
|
|
67
|
-
logger.
|
|
95
|
+
const elapsedMs = logger.endTimer('init_tag_cache');
|
|
96
|
+
logger.logOperationResult(false, 'initialize_tag_cache', elapsedMs);
|
|
97
|
+
logger.error("Failed to initialize tag cache:", errorToObject(error));
|
|
68
98
|
throw error;
|
|
69
99
|
}
|
|
70
100
|
}
|
|
@@ -91,6 +121,7 @@ export class TagResource {
|
|
|
91
121
|
* Get the content for the resource
|
|
92
122
|
*/
|
|
93
123
|
async getContent() {
|
|
124
|
+
logger.startTimer('get_tags_content');
|
|
94
125
|
try {
|
|
95
126
|
if (!this.isInitialized) {
|
|
96
127
|
logger.info('Tag cache not initialized, initializing now');
|
|
@@ -117,6 +148,13 @@ export class TagResource {
|
|
|
117
148
|
}
|
|
118
149
|
};
|
|
119
150
|
logger.debug(`Returning tag resource with ${response.tags.length} tags`);
|
|
151
|
+
const elapsedMs = logger.endTimer('get_tags_content');
|
|
152
|
+
logger.logOperationResult(true, 'get_tags', elapsedMs, {
|
|
153
|
+
tagCount: response.tags.length,
|
|
154
|
+
totalOccurrences: response.metadata.totalOccurrences,
|
|
155
|
+
uniqueTags: response.metadata.uniqueTags,
|
|
156
|
+
scannedFiles: response.metadata.scannedFiles
|
|
157
|
+
});
|
|
120
158
|
return [{
|
|
121
159
|
type: "text",
|
|
122
160
|
text: JSON.stringify(response, null, 2),
|
|
@@ -124,7 +162,9 @@ export class TagResource {
|
|
|
124
162
|
}];
|
|
125
163
|
}
|
|
126
164
|
catch (error) {
|
|
127
|
-
logger.
|
|
165
|
+
const elapsedMs = logger.endTimer('get_tags_content');
|
|
166
|
+
logger.logOperationResult(false, 'get_tags', elapsedMs);
|
|
167
|
+
logger.error("Failed to get tags:", errorToObject(error));
|
|
128
168
|
throw error;
|
|
129
169
|
}
|
|
130
170
|
}
|
package/build/tools/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ObsidianError } from "../utils/errors.js";
|
|
2
2
|
import { tokenCounter } from "../utils/tokenization.js";
|
|
3
|
-
import { createLogger } from "../utils/logging.js";
|
|
3
|
+
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
4
|
+
import { McpErrorCode } from "../mcp/types.js";
|
|
4
5
|
// Create a logger for tool operations
|
|
5
6
|
const logger = createLogger('Tools');
|
|
6
7
|
/**
|
|
@@ -51,7 +52,12 @@ export class BaseToolHandler {
|
|
|
51
52
|
const truncatedText = tokenCounter.truncateToTokenLimit(text);
|
|
52
53
|
const finalTokenCount = tokenCounter.countTokens(truncatedText);
|
|
53
54
|
if (originalTokenCount > finalTokenCount) {
|
|
54
|
-
logger.debug(`[${this.name}] Response truncated
|
|
55
|
+
logger.debug(`[${this.name}] Response truncated`, {
|
|
56
|
+
toolName: this.name,
|
|
57
|
+
originalTokens: originalTokenCount,
|
|
58
|
+
truncatedTokens: finalTokenCount,
|
|
59
|
+
truncationPercent: Math.round((1 - finalTokenCount / originalTokenCount) * 100)
|
|
60
|
+
});
|
|
55
61
|
}
|
|
56
62
|
return [{
|
|
57
63
|
type: "text",
|
|
@@ -67,12 +73,16 @@ export class BaseToolHandler {
|
|
|
67
73
|
if (error instanceof ObsidianError) {
|
|
68
74
|
throw error;
|
|
69
75
|
}
|
|
76
|
+
logger.error(`Tool '${this.name}' execution failed`, {
|
|
77
|
+
toolName: this.name,
|
|
78
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
79
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
80
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
81
|
+
});
|
|
70
82
|
if (error instanceof Error) {
|
|
71
|
-
throw new ObsidianError(`Tool '${this.name}' execution failed: ${error.message}`,
|
|
72
|
-
{ originalError: error.stack });
|
|
83
|
+
throw new ObsidianError(`Tool '${this.name}' execution failed: ${error.message}`, McpErrorCode.INTERNAL_SERVER_ERROR, { originalError: error.stack });
|
|
73
84
|
}
|
|
74
|
-
throw new ObsidianError(`Tool '${this.name}' execution failed with unknown error`,
|
|
75
|
-
{ error });
|
|
85
|
+
throw new ObsidianError(`Tool '${this.name}' execution failed with unknown error`, McpErrorCode.INTERNAL_SERVER_ERROR, { error: String(error) });
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
//# sourceMappingURL=base.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Property manager for Obsidian notes
|
|
3
3
|
*/
|
|
4
|
-
import { parse, stringify } from 'yaml';
|
|
5
4
|
import { EOL } from 'os';
|
|
5
|
+
import { parse, stringify } from 'yaml';
|
|
6
6
|
import { createLogger } from '../../utils/logging.js';
|
|
7
7
|
import { ObsidianPropertiesSchema, PropertyUpdateSchema } from './types.js';
|
|
8
8
|
// Create a logger for property operations
|
|
@@ -37,14 +37,14 @@ export class PropertyManager {
|
|
|
37
37
|
// Validate against schema
|
|
38
38
|
const result = ObsidianPropertiesSchema.safeParse(properties);
|
|
39
39
|
if (!result.success) {
|
|
40
|
-
logger.warn('Property validation warnings:', result.error);
|
|
40
|
+
logger.warn('Property validation warnings:', { validationError: result.error });
|
|
41
41
|
// Return the properties with fixed tags
|
|
42
42
|
return properties;
|
|
43
43
|
}
|
|
44
44
|
return result.data;
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
|
-
logger.error('Error parsing properties:', error);
|
|
47
|
+
logger.error('Error parsing properties:', error instanceof Error ? error : { error: String(error) });
|
|
48
48
|
return {};
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -62,7 +62,7 @@ export class PropertyManager {
|
|
|
62
62
|
return `---${EOL}${yaml}---${EOL}`;
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
65
|
-
logger.error('Error generating properties:', error);
|
|
65
|
+
logger.error('Error generating properties:', error instanceof Error ? error : { error: String(error) });
|
|
66
66
|
throw error;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -134,7 +134,7 @@ export class PropertyManager {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
catch (error) {
|
|
137
|
-
logger.error(`Failed to get properties from ${filepath}:`, error);
|
|
137
|
+
logger.error(`Failed to get properties from ${filepath}:`, error instanceof Error ? error : { error: String(error) });
|
|
138
138
|
return {
|
|
139
139
|
success: false,
|
|
140
140
|
message: `Failed to get properties: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -154,7 +154,7 @@ export class PropertyManager {
|
|
|
154
154
|
// Validate new properties
|
|
155
155
|
const validation = this.validateProperties(newProperties);
|
|
156
156
|
if (!validation.valid) {
|
|
157
|
-
logger.warn(`Invalid properties for ${filepath}:`, validation.errors);
|
|
157
|
+
logger.warn(`Invalid properties for ${filepath}:`, { errors: validation.errors });
|
|
158
158
|
return {
|
|
159
159
|
success: false,
|
|
160
160
|
message: 'Invalid properties',
|
|
@@ -182,7 +182,7 @@ export class PropertyManager {
|
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
184
|
catch (error) {
|
|
185
|
-
logger.error(`Failed to update properties for ${filepath}:`, error);
|
|
185
|
+
logger.error(`Failed to update properties for ${filepath}:`, error instanceof Error ? error : { error: String(error) });
|
|
186
186
|
return {
|
|
187
187
|
success: false,
|
|
188
188
|
message: `Failed to update properties: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { BaseToolHandler } from "../base.js";
|
|
2
2
|
import { PropertyManager } from "./manager.js";
|
|
3
|
-
import { createLogger } from "../../utils/logging.js";
|
|
3
|
+
import { createLogger, ErrorCategoryType } from "../../utils/logging.js";
|
|
4
4
|
// Create a logger for property tools
|
|
5
5
|
const logger = createLogger('PropertyTools');
|
|
6
|
+
/**
|
|
7
|
+
* Helper function to safely convert any error to an object
|
|
8
|
+
*/
|
|
9
|
+
function errorToObject(error) {
|
|
10
|
+
if (error instanceof Error) {
|
|
11
|
+
return {
|
|
12
|
+
name: error.name,
|
|
13
|
+
message: error.message,
|
|
14
|
+
stack: error.stack,
|
|
15
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
else if (typeof error === 'object' && error !== null) {
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return {
|
|
23
|
+
error: String(error),
|
|
24
|
+
errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
6
28
|
/**
|
|
7
29
|
* Tool names for property operations
|
|
8
30
|
*/
|
|
@@ -51,13 +73,23 @@ export class GetPropertiesToolHandler extends BaseToolHandler {
|
|
|
51
73
|
};
|
|
52
74
|
}
|
|
53
75
|
async runTool(args) {
|
|
76
|
+
logger.startTimer(`get_properties_${args.filepath}`);
|
|
54
77
|
try {
|
|
55
78
|
logger.debug(`Getting properties from: ${args.filepath}`);
|
|
56
79
|
const result = await this.propertyManager.getProperties(args.filepath);
|
|
80
|
+
const elapsedMs = logger.endTimer(`get_properties_${args.filepath}`);
|
|
81
|
+
logger.logOperationResult(true, 'get_properties', elapsedMs, {
|
|
82
|
+
filepath: args.filepath,
|
|
83
|
+
propertyCount: Object.keys(result.properties || {}).length
|
|
84
|
+
});
|
|
57
85
|
return this.createResponse(result);
|
|
58
86
|
}
|
|
59
87
|
catch (error) {
|
|
60
|
-
logger.
|
|
88
|
+
const elapsedMs = logger.endTimer(`get_properties_${args.filepath}`);
|
|
89
|
+
logger.logOperationResult(false, 'get_properties', elapsedMs, {
|
|
90
|
+
filepath: args.filepath
|
|
91
|
+
});
|
|
92
|
+
logger.error(`Error getting properties from ${args.filepath}:`, errorToObject(error));
|
|
61
93
|
return this.handleError(error);
|
|
62
94
|
}
|
|
63
95
|
}
|
|
@@ -177,13 +209,28 @@ export class UpdatePropertiesToolHandler extends BaseToolHandler {
|
|
|
177
209
|
};
|
|
178
210
|
}
|
|
179
211
|
async runTool(args) {
|
|
212
|
+
logger.startTimer(`update_properties_${args.filepath}`);
|
|
180
213
|
try {
|
|
181
|
-
logger.debug(`Updating properties for: ${args.filepath}
|
|
214
|
+
logger.debug(`Updating properties for: ${args.filepath}`, {
|
|
215
|
+
filepath: args.filepath,
|
|
216
|
+
propertyCount: Object.keys(args.properties || {}).length,
|
|
217
|
+
replace: args.replace || false
|
|
218
|
+
});
|
|
182
219
|
const result = await this.propertyManager.updateProperties(args.filepath, args.properties, args.replace);
|
|
220
|
+
const elapsedMs = logger.endTimer(`update_properties_${args.filepath}`);
|
|
221
|
+
logger.logOperationResult(true, 'update_properties', elapsedMs, {
|
|
222
|
+
filepath: args.filepath,
|
|
223
|
+
propertyCount: Object.keys(args.properties || {}).length,
|
|
224
|
+
success: result.success
|
|
225
|
+
});
|
|
183
226
|
return this.createResponse(result);
|
|
184
227
|
}
|
|
185
228
|
catch (error) {
|
|
186
|
-
logger.
|
|
229
|
+
const elapsedMs = logger.endTimer(`update_properties_${args.filepath}`);
|
|
230
|
+
logger.logOperationResult(false, 'update_properties', elapsedMs, {
|
|
231
|
+
filepath: args.filepath
|
|
232
|
+
});
|
|
233
|
+
logger.error(`Error updating properties for ${args.filepath}:`, errorToObject(error));
|
|
187
234
|
return this.handleError(error);
|
|
188
235
|
}
|
|
189
236
|
}
|