obsidian-mcp-server 1.4.1 → 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/README.md +28 -8
- 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 +10 -9
- 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/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Obsidian MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
|
-
[](https://modelcontextprotocol.io/)
|
|
5
|
+
[]()
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
|
-
[]()
|
|
8
8
|
[](https://github.com/cyanheads/obsidian-mcp-server)
|
|
9
9
|
|
|
10
10
|
A Model Context Protocol server designed for LLMs to interact with Obsidian vaults. Built with TypeScript and featuring secure API communication, efficient file operations, and comprehensive search capabilities, it enables AI assistants to seamlessly manage knowledge bases through a clean, flexible tool interface.
|
|
@@ -16,21 +16,25 @@ Requires the Local REST API plugin in Obsidian.
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
18
|
### File Operations
|
|
19
|
+
|
|
19
20
|
- Atomic file/directory operations with validation
|
|
20
21
|
- Resource monitoring and cleanup
|
|
21
22
|
- Error handling and graceful failure
|
|
22
23
|
|
|
23
24
|
### Search System
|
|
25
|
+
|
|
24
26
|
- Full-text search with configurable context
|
|
25
27
|
- Advanced JsonLogic queries for files, tags, and metadata
|
|
26
28
|
- Support for glob patterns and frontmatter fields
|
|
27
29
|
|
|
28
30
|
### Property Management
|
|
31
|
+
|
|
29
32
|
- YAML frontmatter parsing and intelligent merging
|
|
30
33
|
- Automatic timestamps (created by Obsidian, modified by server)
|
|
31
34
|
- Custom field support
|
|
32
35
|
|
|
33
36
|
### Security & Performance
|
|
37
|
+
|
|
34
38
|
- API key auth with rate limiting and SSL options
|
|
35
39
|
- Resource monitoring and health checks
|
|
36
40
|
- Graceful shutdown handling
|
|
@@ -41,6 +45,7 @@ Note: Requires Node.js
|
|
|
41
45
|
|
|
42
46
|
1. Enable Local REST API plugin in Obsidian
|
|
43
47
|
2. Clone and build:
|
|
48
|
+
|
|
44
49
|
```bash
|
|
45
50
|
git clone git@github.com:cyanheads/obsidian-mcp-server.git
|
|
46
51
|
cd obsidian-mcp-server
|
|
@@ -49,6 +54,7 @@ npm run build
|
|
|
49
54
|
```
|
|
50
55
|
|
|
51
56
|
Or install from npm:
|
|
57
|
+
|
|
52
58
|
```bash
|
|
53
59
|
npm install obsidian-mcp-server
|
|
54
60
|
```
|
|
@@ -84,24 +90,29 @@ Add to your MCP client settings (e.g., `claude_desktop_config.json` or `cline_mc
|
|
|
84
90
|
Environment Variables:
|
|
85
91
|
|
|
86
92
|
Required:
|
|
93
|
+
|
|
87
94
|
- `OBSIDIAN_API_KEY`: Your API key from Obsidian's Local REST API plugin settings
|
|
88
95
|
|
|
89
96
|
Connection Settings:
|
|
97
|
+
|
|
90
98
|
- `VERIFY_SSL`: Enable SSL certificate verification (default: false) # This must be set to false for self-signed certificates. If you are running locally or don't understand what this means, this should be set to false.
|
|
91
99
|
- `OBSIDIAN_PROTOCOL`: Protocol to use (default: "https")
|
|
92
100
|
- `OBSIDIAN_HOST`: Host address (default: "127.0.0.1")
|
|
93
101
|
- `OBSIDIAN_PORT`: Port number (default: 27124)
|
|
94
102
|
|
|
95
103
|
Request Limits:
|
|
104
|
+
|
|
96
105
|
- `REQUEST_TIMEOUT`: Request timeout in milliseconds (default: 5000)
|
|
97
106
|
- `MAX_CONTENT_LENGTH`: Maximum response content length in bytes (default: 52428800 [50MB])
|
|
98
107
|
- `MAX_BODY_LENGTH`: Maximum request body length in bytes (default: 52428800 [50MB])
|
|
99
108
|
|
|
100
109
|
Rate Limiting:
|
|
110
|
+
|
|
101
111
|
- `RATE_LIMIT_WINDOW_MS`: Rate limit window in milliseconds (default: 900000 [15 minutes])
|
|
102
112
|
- `RATE_LIMIT_MAX_REQUESTS`: Maximum requests per window (default: 200)
|
|
103
113
|
|
|
104
114
|
Tool Execution:
|
|
115
|
+
|
|
105
116
|
- `TOOL_TIMEOUT_MS`: Tool execution timeout in milliseconds (default: 60000 [1 minute])
|
|
106
117
|
|
|
107
118
|
## Project Structure
|
|
@@ -124,22 +135,25 @@ src/
|
|
|
124
135
|
## Tools
|
|
125
136
|
|
|
126
137
|
### File Management
|
|
138
|
+
|
|
127
139
|
```typescript
|
|
128
140
|
// List vault contents
|
|
129
|
-
obsidian_list_files_in_vault: {
|
|
141
|
+
obsidian_list_files_in_vault: {
|
|
142
|
+
}
|
|
130
143
|
|
|
131
144
|
// List directory contents
|
|
132
145
|
obsidian_list_files_in_dir: {
|
|
133
|
-
dirpath: string
|
|
146
|
+
dirpath: string; // Path relative to vault root
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
// Get file contents
|
|
137
150
|
obsidian_get_file_contents: {
|
|
138
|
-
filepath: string
|
|
151
|
+
filepath: string; // Path relative to vault root
|
|
139
152
|
}
|
|
140
153
|
```
|
|
141
154
|
|
|
142
155
|
### Search Operations
|
|
156
|
+
|
|
143
157
|
```typescript
|
|
144
158
|
// Text search with context
|
|
145
159
|
obsidian_find_in_file: {
|
|
@@ -171,6 +185,7 @@ obsidian_get_tags: {
|
|
|
171
185
|
```
|
|
172
186
|
|
|
173
187
|
### Content Modification
|
|
188
|
+
|
|
174
189
|
```typescript
|
|
175
190
|
// Append to file
|
|
176
191
|
obsidian_append_content: {
|
|
@@ -186,6 +201,7 @@ obsidian_patch_content: {
|
|
|
186
201
|
```
|
|
187
202
|
|
|
188
203
|
### Property Management
|
|
204
|
+
|
|
189
205
|
```typescript
|
|
190
206
|
// Get note properties
|
|
191
207
|
obsidian_get_properties: {
|
|
@@ -199,8 +215,8 @@ obsidian_update_properties: {
|
|
|
199
215
|
title?: string,
|
|
200
216
|
author?: string,
|
|
201
217
|
// Note: created/modified timestamps are managed automatically
|
|
202
|
-
type?: Array<"concept" | "architecture" | "specification" |
|
|
203
|
-
"protocol" | "api" | "research" | "implementation" |
|
|
218
|
+
type?: Array<"concept" | "architecture" | "specification" |
|
|
219
|
+
"protocol" | "api" | "research" | "implementation" |
|
|
204
220
|
"guide" | "reference">,
|
|
205
221
|
tags?: string[], // Must start with #
|
|
206
222
|
status?: Array<"draft" | "in-progress" | "review" | "complete">,
|
|
@@ -219,21 +235,25 @@ obsidian_update_properties: {
|
|
|
219
235
|
## Best Practices
|
|
220
236
|
|
|
221
237
|
### File Operations
|
|
238
|
+
|
|
222
239
|
- Use atomic operations with validation
|
|
223
240
|
- Handle errors and monitor performance
|
|
224
241
|
|
|
225
242
|
### Search Implementation
|
|
243
|
+
|
|
226
244
|
- Use appropriate search tool for the task:
|
|
227
245
|
- obsidian_find_in_file for text search
|
|
228
246
|
- obsidian_complex_search for metadata/tag filtering
|
|
229
247
|
- Keep context size reasonable (default: 10 chars)
|
|
230
248
|
|
|
231
249
|
### Property Management
|
|
250
|
+
|
|
232
251
|
- Use appropriate types and validate updates
|
|
233
252
|
- Handle arrays and custom fields properly
|
|
234
253
|
- Never set timestamps (managed automatically)
|
|
235
254
|
|
|
236
255
|
### Error Prevention
|
|
256
|
+
|
|
237
257
|
- Validate inputs and handle errors gracefully
|
|
238
258
|
- Monitor patterns and respect rate limits
|
|
239
259
|
|
package/build/mcp/handlers.js
CHANGED
|
@@ -5,10 +5,32 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSche
|
|
|
5
5
|
import { ObsidianError } from "../utils/errors.js";
|
|
6
6
|
import { validateToolArguments } from "../utils/validation.js";
|
|
7
7
|
import { rateLimiter } from "../utils/rate-limiting.js";
|
|
8
|
-
import { createLogger } from "../utils/logging.js";
|
|
9
|
-
import { DEFAULT_TIMEOUT_CONFIG } from "./types.js";
|
|
8
|
+
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
9
|
+
import { DEFAULT_TIMEOUT_CONFIG, McpErrorCode } from "./types.js";
|
|
10
10
|
// Create a logger for request handlers
|
|
11
11
|
const logger = createLogger('McpHandlers');
|
|
12
|
+
/**
|
|
13
|
+
* Helper function to safely mask sensitive data
|
|
14
|
+
*/
|
|
15
|
+
function maskSensitiveData(data) {
|
|
16
|
+
if (!data)
|
|
17
|
+
return {};
|
|
18
|
+
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth', 'credential'];
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const [key, value] of Object.entries(data)) {
|
|
21
|
+
const isSensitive = sensitiveFields.some(field => key.toLowerCase().includes(field.toLowerCase()));
|
|
22
|
+
if (isSensitive) {
|
|
23
|
+
result[key] = '********';
|
|
24
|
+
}
|
|
25
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
26
|
+
result[key] = maskSensitiveData(value);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
result[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
12
34
|
/**
|
|
13
35
|
* Set up tool listing handler
|
|
14
36
|
* @param server The MCP server instance
|
|
@@ -17,11 +39,27 @@ const logger = createLogger('McpHandlers');
|
|
|
17
39
|
export function setupToolListingHandler(server, toolHandlers) {
|
|
18
40
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
19
41
|
logger.debug('Handling ListToolsRequest');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
42
|
+
// Start performance timing
|
|
43
|
+
logger.startTimer('list_tools');
|
|
44
|
+
try {
|
|
45
|
+
const tools = [];
|
|
46
|
+
for (const handler of toolHandlers.values()) {
|
|
47
|
+
tools.push(handler.getToolDescription());
|
|
48
|
+
}
|
|
49
|
+
// Log success and timing information
|
|
50
|
+
const elapsedMs = logger.endTimer('list_tools', 'Listed tools');
|
|
51
|
+
logger.logOperationResult(true, 'list_tools', elapsedMs, {
|
|
52
|
+
toolCount: tools.length
|
|
53
|
+
});
|
|
54
|
+
return { tools };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Log failure with timing information
|
|
58
|
+
const elapsedMs = logger.endTimer('list_tools', 'Failed to list tools');
|
|
59
|
+
logger.logOperationResult(false, 'list_tools', elapsedMs);
|
|
60
|
+
logger.error('Failed to list available tools', error instanceof Error ? error : undefined);
|
|
61
|
+
throw error;
|
|
23
62
|
}
|
|
24
|
-
return { tools };
|
|
25
63
|
});
|
|
26
64
|
}
|
|
27
65
|
/**
|
|
@@ -32,26 +70,53 @@ export function setupToolListingHandler(server, toolHandlers) {
|
|
|
32
70
|
export function setupToolCallingHandler(server, toolHandlers) {
|
|
33
71
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
34
72
|
const { name, arguments: args } = request.params;
|
|
35
|
-
|
|
73
|
+
const operationId = `call_tool_${name}_${Date.now()}`;
|
|
74
|
+
logger.debug(`Handling CallToolRequest for tool: ${name}`, {
|
|
75
|
+
toolName: name,
|
|
76
|
+
operationId
|
|
77
|
+
});
|
|
78
|
+
// Start performance timing
|
|
79
|
+
logger.startTimer(operationId);
|
|
80
|
+
// Handle unknown tool
|
|
36
81
|
const handler = toolHandlers.get(name);
|
|
37
82
|
if (!handler) {
|
|
38
|
-
|
|
39
|
-
|
|
83
|
+
const errorInfo = {
|
|
84
|
+
toolName: name,
|
|
85
|
+
errorCode: McpErrorCode.NOT_FOUND,
|
|
86
|
+
errorCategory: ErrorCategoryType.CATEGORY_VALIDATION
|
|
87
|
+
};
|
|
88
|
+
logger.error(`Unknown tool requested: ${name}`, errorInfo);
|
|
89
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
90
|
+
logger.logOperationResult(false, 'call_tool', elapsedMs, errorInfo);
|
|
91
|
+
throw new ObsidianError(`Unknown tool: ${name}`, McpErrorCode.NOT_FOUND);
|
|
40
92
|
}
|
|
41
93
|
// Check rate limit
|
|
42
94
|
try {
|
|
43
95
|
rateLimiter.enforceRateLimit(name);
|
|
44
96
|
}
|
|
45
97
|
catch (error) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
98
|
+
const errorInfo = {
|
|
99
|
+
toolName: name,
|
|
100
|
+
errorCode: McpErrorCode.RATE_LIMIT_EXCEEDED,
|
|
101
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
102
|
+
};
|
|
103
|
+
logger.warn(`Rate limit exceeded for tool: ${name}`, errorInfo);
|
|
104
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
105
|
+
logger.logOperationResult(false, 'call_tool', elapsedMs, errorInfo);
|
|
106
|
+
throw new ObsidianError(`Rate limit exceeded for tool: ${name}`, McpErrorCode.RATE_LIMIT_EXCEEDED);
|
|
49
107
|
}
|
|
50
108
|
// Add timeout handling
|
|
51
109
|
const timeoutMs = DEFAULT_TIMEOUT_CONFIG.toolExecutionMs;
|
|
52
110
|
const timeoutPromise = new Promise((_, reject) => {
|
|
53
111
|
setTimeout(() => {
|
|
54
|
-
|
|
112
|
+
const errorInfo = {
|
|
113
|
+
toolName: name,
|
|
114
|
+
timeoutMs,
|
|
115
|
+
errorCode: McpErrorCode.TIMEOUT,
|
|
116
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
117
|
+
};
|
|
118
|
+
logger.error(`Tool execution timed out after ${timeoutMs}ms`, errorInfo);
|
|
119
|
+
reject(new ObsidianError(`Tool execution timed out after ${timeoutMs}ms`, McpErrorCode.TIMEOUT));
|
|
55
120
|
}, timeoutMs);
|
|
56
121
|
});
|
|
57
122
|
try {
|
|
@@ -59,24 +124,48 @@ export function setupToolCallingHandler(server, toolHandlers) {
|
|
|
59
124
|
const toolDescription = handler.getToolDescription();
|
|
60
125
|
const validationResult = validateToolArguments(args, toolDescription.inputSchema);
|
|
61
126
|
if (!validationResult.valid) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
127
|
+
const errorInfo = {
|
|
128
|
+
toolName: name,
|
|
129
|
+
validationErrors: validationResult.errors,
|
|
130
|
+
providedArgs: maskSensitiveData(args),
|
|
131
|
+
errorCode: McpErrorCode.BAD_REQUEST,
|
|
132
|
+
errorCategory: ErrorCategoryType.CATEGORY_VALIDATION
|
|
133
|
+
};
|
|
134
|
+
logger.error(`Invalid tool arguments for ${name}:`, errorInfo);
|
|
135
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
136
|
+
logger.logOperationResult(false, 'call_tool', elapsedMs, errorInfo);
|
|
137
|
+
throw new ObsidianError(`Invalid tool arguments: ${validationResult.errors.join(', ')}`, McpErrorCode.BAD_REQUEST);
|
|
65
138
|
}
|
|
66
139
|
// Log the tool execution
|
|
67
|
-
logger.info(`Executing tool: ${name}
|
|
140
|
+
logger.info(`Executing tool: ${name}`, {
|
|
141
|
+
toolName: name,
|
|
142
|
+
args: maskSensitiveData(args)
|
|
143
|
+
});
|
|
68
144
|
// Race between tool execution and timeout
|
|
69
145
|
const content = await Promise.race([
|
|
70
146
|
handler.runTool(args),
|
|
71
147
|
timeoutPromise
|
|
72
148
|
]);
|
|
73
|
-
|
|
149
|
+
// Log successful execution
|
|
150
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
151
|
+
logger.logOperationResult(true, 'call_tool', elapsedMs, {
|
|
152
|
+
toolName: name,
|
|
153
|
+
contentLength: content.reduce((sum, item) => {
|
|
154
|
+
return sum + (item.type === 'text' ? item.text.length : 0);
|
|
155
|
+
}, 0)
|
|
156
|
+
});
|
|
74
157
|
return { content };
|
|
75
158
|
}
|
|
76
159
|
catch (error) {
|
|
160
|
+
// Handle ObsidianError
|
|
77
161
|
if (error instanceof ObsidianError) {
|
|
78
162
|
// Check if the operation actually succeeded despite the error
|
|
79
|
-
if (error.errorCode ===
|
|
163
|
+
if (error.errorCode === McpErrorCode.SUCCESS_NO_CONTENT) {
|
|
164
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
165
|
+
logger.logOperationResult(true, 'call_tool', elapsedMs, {
|
|
166
|
+
toolName: name,
|
|
167
|
+
status: 'success_no_content'
|
|
168
|
+
});
|
|
80
169
|
return {
|
|
81
170
|
content: [{
|
|
82
171
|
type: "text",
|
|
@@ -84,21 +173,31 @@ export function setupToolCallingHandler(server, toolHandlers) {
|
|
|
84
173
|
}]
|
|
85
174
|
};
|
|
86
175
|
}
|
|
176
|
+
// Log failure for other ObsidianErrors
|
|
177
|
+
const errorInfo = {
|
|
178
|
+
toolName: name,
|
|
179
|
+
errorMessage: error.message,
|
|
180
|
+
errorCode: error.errorCode,
|
|
181
|
+
errorCategory: ErrorCategoryType.CATEGORY_BUSINESS_LOGIC,
|
|
182
|
+
details: error.details ? JSON.stringify(error.details) : undefined
|
|
183
|
+
};
|
|
184
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
185
|
+
logger.logOperationResult(false, 'call_tool', elapsedMs, errorInfo);
|
|
87
186
|
throw error;
|
|
88
187
|
}
|
|
89
|
-
// Enhanced error logging
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{
|
|
188
|
+
// Enhanced error logging for other errors
|
|
189
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
190
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
191
|
+
const errorInfo = {
|
|
192
|
+
toolName: name,
|
|
193
|
+
errorMessage,
|
|
194
|
+
errorStack,
|
|
195
|
+
errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
|
|
196
|
+
};
|
|
197
|
+
logger.error("Tool execution error:", errorInfo);
|
|
198
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
199
|
+
logger.logOperationResult(false, 'call_tool', elapsedMs, errorInfo);
|
|
200
|
+
throw new ObsidianError(`Tool '${name}' execution failed: ${errorMessage}`, McpErrorCode.INTERNAL_SERVER_ERROR, { originalError: errorMessage, stack: errorStack });
|
|
102
201
|
}
|
|
103
202
|
});
|
|
104
203
|
}
|
|
@@ -110,9 +209,24 @@ export function setupToolCallingHandler(server, toolHandlers) {
|
|
|
110
209
|
export function setupResourceListingHandler(server, resources) {
|
|
111
210
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
112
211
|
logger.debug('Handling ListResourcesRequest');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
212
|
+
// Start performance timing
|
|
213
|
+
logger.startTimer('list_resources');
|
|
214
|
+
try {
|
|
215
|
+
const resourceList = Object.values(resources).map(resource => resource.getResourceDescription());
|
|
216
|
+
// Log success with timing information
|
|
217
|
+
const elapsedMs = logger.endTimer('list_resources', 'Listed resources');
|
|
218
|
+
logger.logOperationResult(true, 'list_resources', elapsedMs, {
|
|
219
|
+
resourceCount: resourceList.length
|
|
220
|
+
});
|
|
221
|
+
return { resources: resourceList };
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
// Log failure with timing information
|
|
225
|
+
const elapsedMs = logger.endTimer('list_resources', 'Failed to list resources');
|
|
226
|
+
logger.logOperationResult(false, 'list_resources', elapsedMs);
|
|
227
|
+
logger.error('Failed to list available resources', error instanceof Error ? error : undefined);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
116
230
|
});
|
|
117
231
|
}
|
|
118
232
|
/**
|
|
@@ -123,16 +237,51 @@ export function setupResourceListingHandler(server, resources) {
|
|
|
123
237
|
export function setupResourceReadingHandler(server, resources) {
|
|
124
238
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
125
239
|
const uri = request.params.uri;
|
|
126
|
-
|
|
240
|
+
const operationId = `read_resource_${Date.now()}`;
|
|
241
|
+
logger.debug(`Handling ReadResourceRequest for URI: ${uri}`, {
|
|
242
|
+
resourceUri: uri,
|
|
243
|
+
operationId
|
|
244
|
+
});
|
|
245
|
+
// Start performance timing
|
|
246
|
+
logger.startTimer(operationId);
|
|
127
247
|
const resource = resources[uri];
|
|
128
|
-
if (resource) {
|
|
248
|
+
if (!resource) {
|
|
249
|
+
const errorInfo = {
|
|
250
|
+
resourceUri: uri,
|
|
251
|
+
errorCode: McpErrorCode.NOT_FOUND,
|
|
252
|
+
errorCategory: ErrorCategoryType.CATEGORY_DATA_ACCESS
|
|
253
|
+
};
|
|
254
|
+
logger.error(`Resource not found: ${uri}`, errorInfo);
|
|
255
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
256
|
+
logger.logOperationResult(false, 'read_resource', elapsedMs, errorInfo);
|
|
257
|
+
throw new ObsidianError(`Resource not found: ${uri}`, McpErrorCode.NOT_FOUND);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
129
260
|
logger.debug(`Found resource for URI: ${uri}`);
|
|
130
|
-
|
|
131
|
-
|
|
261
|
+
const contents = await resource.getContent();
|
|
262
|
+
// Log success with timing information
|
|
263
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
264
|
+
logger.logOperationResult(true, 'read_resource', elapsedMs, {
|
|
265
|
+
resourceUri: uri,
|
|
266
|
+
contentItems: contents.length
|
|
267
|
+
});
|
|
268
|
+
return { contents };
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
// Log failure with timing information
|
|
272
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
273
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
274
|
+
const errorInfo = {
|
|
275
|
+
resourceUri: uri,
|
|
276
|
+
errorMessage,
|
|
277
|
+
errorStack,
|
|
278
|
+
errorCategory: ErrorCategoryType.CATEGORY_DATA_ACCESS
|
|
132
279
|
};
|
|
280
|
+
logger.error(`Error reading resource: ${uri}`, errorInfo);
|
|
281
|
+
const elapsedMs = logger.endTimer(operationId);
|
|
282
|
+
logger.logOperationResult(false, 'read_resource', elapsedMs, errorInfo);
|
|
283
|
+
throw new ObsidianError(`Failed to read resource: ${errorMessage}`, McpErrorCode.INTERNAL_SERVER_ERROR, { originalError: errorMessage, stack: errorStack });
|
|
133
284
|
}
|
|
134
|
-
logger.error(`Resource not found: ${uri}`);
|
|
135
|
-
throw new ObsidianError(`Resource not found: ${uri}`, 40400); // 40400 = Not found
|
|
136
285
|
});
|
|
137
286
|
}
|
|
138
287
|
//# sourceMappingURL=handlers.js.map
|