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.
@@ -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
- // Verify API key exists
22
- const API_KEY = process.env.OBSIDIAN_API_KEY;
23
- if (!API_KEY) {
24
- throw new Error("OBSIDIAN_API_KEY environment variable is required");
25
- }
26
- // Initialize Obsidian client with environment configuration
27
- logger.info('Initializing Obsidian client');
28
- const client = new ObsidianClient({
29
- apiKey: API_KEY,
30
- verifySSL: process.env.VERIFY_SSL === 'true',
31
- timeout: parseInt(process.env.REQUEST_TIMEOUT || '5000'),
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
- // Set up request handlers
58
- setupToolListingHandler(server, toolHandlerMap);
59
- setupToolCallingHandler(server, toolHandlerMap);
60
- setupResourceListingHandler(server, resources);
61
- setupResourceReadingHandler(server, resources);
62
- // Set up error handler
63
- server.onerror = (error) => {
64
- logger.error("[MCP Error]", error);
65
- };
66
- return server;
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.info('Shutting down server...');
77
- // Run cleanup handlers
78
- for (const handler of cleanupHandlers) {
79
- try {
80
- await handler();
81
- }
82
- catch (error) {
83
- logger.error('Error during cleanup:', error);
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:', error);
107
- await cleanup();
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 (error) => {
110
- logger.error('Unhandled rejection:', error);
111
- await cleanup();
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.info("Obsidian MCP server running on stdio");
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.error("Failed to start server:", error);
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
  }
@@ -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
@@ -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.error("Failed to initialize tag cache:", error);
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.error("Failed to get tags:", error);
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
  }
@@ -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:`, `original tokens=${originalTokenCount}`, `truncated tokens=${finalTokenCount}`);
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}`, 50000, // Internal server error
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`, 50000, // Internal server 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.error(`Error getting properties from ${args.filepath}:`, error);
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.error(`Error updating properties for ${args.filepath}:`, error);
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
  }