obsidian-mcp-server 1.2.6 → 1.4.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 +54 -10
- package/build/index.js +12 -3
- package/build/mcp/handlers.js +138 -0
- package/build/mcp/index.js +7 -0
- package/build/mcp/server.js +131 -0
- package/build/mcp/types.js +7 -0
- package/build/{obsidian.js → obsidian/client.js} +156 -104
- package/build/obsidian/errors.js +75 -0
- package/build/obsidian/index.js +7 -0
- package/build/obsidian/types.js +12 -0
- package/build/resources/index.js +15 -0
- package/build/{resources.js → resources/tags.js} +35 -14
- package/build/resources/types.js +5 -0
- package/build/tools/base.js +78 -0
- package/build/tools/files/content.js +171 -0
- package/build/tools/files/index.js +22 -0
- package/build/tools/files/list.js +133 -0
- package/build/tools/index.js +31 -0
- package/build/tools/properties/index.js +19 -0
- package/build/{properties.js → tools/properties/manager.js} +50 -13
- package/build/{propertyTools.js → tools/properties/tools.js} +22 -6
- package/build/{propertyTypes.js → tools/properties/types.js} +19 -7
- package/build/tools/search/complex.js +203 -0
- package/build/tools/search/index.js +20 -0
- package/build/tools/search/simple.js +127 -0
- package/build/utils/errors.js +59 -0
- package/build/utils/index.js +9 -0
- package/build/utils/logging.js +119 -0
- package/build/utils/rate-limiting.js +94 -0
- package/build/utils/tokenization.js +62 -0
- package/build/utils/validation.js +88 -0
- package/examples/README.md +48 -0
- package/examples/append-content.md +63 -0
- package/examples/complex-search.md +117 -0
- package/examples/find-in-file.md +94 -0
- package/examples/get-file-contents.md +72 -0
- package/examples/get-properties.md +89 -0
- package/examples/list-files-in-dir.md +55 -0
- package/examples/list-files-in-vault.md +53 -0
- package/examples/patch-content.md +60 -0
- package/examples/update-properties.md +126 -0
- package/mcp-client-config.example.json +23 -0
- package/package.json +4 -3
- package/src/index.ts +13 -3
- package/src/mcp/handlers.ts +183 -0
- package/src/mcp/index.ts +6 -0
- package/src/mcp/server.ts +162 -0
- package/src/mcp/types.ts +46 -0
- package/src/{obsidian.ts → obsidian/client.ts} +174 -134
- package/src/obsidian/errors.ts +105 -0
- package/src/obsidian/index.ts +6 -0
- package/src/obsidian/types.ts +124 -0
- package/src/resources/index.ts +17 -0
- package/src/{resources.ts → resources/tags.ts} +42 -16
- package/src/resources/types.ts +30 -0
- package/src/tools/base.ts +112 -0
- package/src/tools/files/content.ts +206 -0
- package/src/tools/files/index.ts +31 -0
- package/src/tools/files/list.ts +150 -0
- package/src/tools/index.ts +38 -0
- package/src/tools/properties/index.ts +21 -0
- package/src/{properties.ts → tools/properties/manager.ts} +54 -13
- package/src/{propertyTools.ts → tools/properties/tools.ts} +33 -7
- package/src/{propertyTypes.ts → tools/properties/types.ts} +31 -7
- package/src/tools/search/complex.ts +231 -0
- package/src/tools/search/index.ts +22 -0
- package/src/tools/search/simple.ts +147 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logging.ts +146 -0
- package/src/utils/rate-limiting.ts +114 -0
- package/src/utils/tokenization.ts +71 -0
- package/src/utils/validation.ts +95 -0
- package/build/server.js +0 -238
- package/build/tools.js +0 -863
- package/build/types.js +0 -37
- package/src/server.ts +0 -308
- package/src/tools.ts +0 -946
- package/src/types.ts +0 -184
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities for the Obsidian MCP Server
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Error class for Obsidian MCP Server specific errors
|
|
6
|
+
*/
|
|
7
|
+
export class ObsidianError extends Error {
|
|
8
|
+
details;
|
|
9
|
+
errorCode;
|
|
10
|
+
constructor(message, errorCode = 50000, // Default server error code
|
|
11
|
+
details) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.details = details;
|
|
14
|
+
this.name = "ObsidianError";
|
|
15
|
+
// Ensure 5-digit error code
|
|
16
|
+
if (errorCode < 10000 || errorCode > 99999) {
|
|
17
|
+
// Convert HTTP status codes to 5-digit codes
|
|
18
|
+
// 4xx -> 4xxxx
|
|
19
|
+
// 5xx -> 5xxxx
|
|
20
|
+
this.errorCode = errorCode < 1000 ? errorCode * 100 : 50000;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.errorCode = errorCode;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Convert to API error format
|
|
27
|
+
toApiError() {
|
|
28
|
+
return {
|
|
29
|
+
errorCode: this.errorCode,
|
|
30
|
+
message: this.message
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Maps HTTP status codes to internal error codes
|
|
36
|
+
*/
|
|
37
|
+
export function getErrorCodeFromStatus(status) {
|
|
38
|
+
switch (status) {
|
|
39
|
+
case 400: return 40000; // Bad request
|
|
40
|
+
case 401: return 40100; // Unauthorized
|
|
41
|
+
case 403: return 40300; // Forbidden
|
|
42
|
+
case 404: return 40400; // Not found
|
|
43
|
+
case 405: return 40500; // Method not allowed
|
|
44
|
+
case 409: return 40900; // Conflict
|
|
45
|
+
case 429: return 42900; // Too many requests
|
|
46
|
+
case 500: return 50000; // Internal server error
|
|
47
|
+
case 501: return 50100; // Not implemented
|
|
48
|
+
case 502: return 50200; // Bad gateway
|
|
49
|
+
case 503: return 50300; // Service unavailable
|
|
50
|
+
case 504: return 50400; // Gateway timeout
|
|
51
|
+
default:
|
|
52
|
+
if (status >= 400 && status < 500)
|
|
53
|
+
return 40000 + (status - 400) * 100;
|
|
54
|
+
if (status >= 500 && status < 600)
|
|
55
|
+
return 50000 + (status - 500) * 100;
|
|
56
|
+
return 50000;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utilities for the Obsidian MCP Server
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Log levels
|
|
6
|
+
*/
|
|
7
|
+
export var LogLevel;
|
|
8
|
+
(function (LogLevel) {
|
|
9
|
+
LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
|
|
10
|
+
LogLevel[LogLevel["WARN"] = 1] = "WARN";
|
|
11
|
+
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
|
12
|
+
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
|
|
13
|
+
LogLevel[LogLevel["TRACE"] = 4] = "TRACE";
|
|
14
|
+
})(LogLevel || (LogLevel = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Default logger configuration
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_CONFIG = {
|
|
19
|
+
level: process.env.NODE_ENV === 'production'
|
|
20
|
+
? LogLevel.INFO
|
|
21
|
+
: LogLevel.DEBUG,
|
|
22
|
+
includeTimestamps: true,
|
|
23
|
+
includeLevel: true
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Simple logger for MCP server operations
|
|
27
|
+
*/
|
|
28
|
+
export class Logger {
|
|
29
|
+
name;
|
|
30
|
+
config;
|
|
31
|
+
constructor(name, config = {}) {
|
|
32
|
+
this.name = name;
|
|
33
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Internal method to format and output a log message
|
|
37
|
+
*/
|
|
38
|
+
log(level, message, ...args) {
|
|
39
|
+
if (level > this.config.level)
|
|
40
|
+
return;
|
|
41
|
+
const parts = [];
|
|
42
|
+
// Add timestamp if configured
|
|
43
|
+
if (this.config.includeTimestamps) {
|
|
44
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
45
|
+
}
|
|
46
|
+
// Add level if configured
|
|
47
|
+
if (this.config.includeLevel) {
|
|
48
|
+
const levelStr = LogLevel[level] || 'UNKNOWN';
|
|
49
|
+
parts.push(`[${levelStr}]`);
|
|
50
|
+
}
|
|
51
|
+
// Add component name
|
|
52
|
+
parts.push(`[${this.name}]`);
|
|
53
|
+
// Add message
|
|
54
|
+
parts.push(message);
|
|
55
|
+
// Output to console
|
|
56
|
+
const output = parts.join(' ');
|
|
57
|
+
switch (level) {
|
|
58
|
+
case LogLevel.ERROR:
|
|
59
|
+
console.error(output, ...args);
|
|
60
|
+
break;
|
|
61
|
+
case LogLevel.WARN:
|
|
62
|
+
console.warn(output, ...args);
|
|
63
|
+
break;
|
|
64
|
+
case LogLevel.INFO:
|
|
65
|
+
console.info(output, ...args);
|
|
66
|
+
break;
|
|
67
|
+
case LogLevel.DEBUG:
|
|
68
|
+
case LogLevel.TRACE:
|
|
69
|
+
default:
|
|
70
|
+
console.debug(output, ...args);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Log an error message
|
|
76
|
+
*/
|
|
77
|
+
error(message, ...args) {
|
|
78
|
+
this.log(LogLevel.ERROR, message, ...args);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Log a warning message
|
|
82
|
+
*/
|
|
83
|
+
warn(message, ...args) {
|
|
84
|
+
this.log(LogLevel.WARN, message, ...args);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Log an info message
|
|
88
|
+
*/
|
|
89
|
+
info(message, ...args) {
|
|
90
|
+
this.log(LogLevel.INFO, message, ...args);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Log a debug message
|
|
94
|
+
*/
|
|
95
|
+
debug(message, ...args) {
|
|
96
|
+
this.log(LogLevel.DEBUG, message, ...args);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Log a trace message
|
|
100
|
+
*/
|
|
101
|
+
trace(message, ...args) {
|
|
102
|
+
this.log(LogLevel.TRACE, message, ...args);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Set the log level
|
|
106
|
+
*/
|
|
107
|
+
setLevel(level) {
|
|
108
|
+
this.config.level = level;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create and return a namespaced logger
|
|
113
|
+
*/
|
|
114
|
+
export function createLogger(name, config) {
|
|
115
|
+
return new Logger(name, config);
|
|
116
|
+
}
|
|
117
|
+
// Create a global root logger
|
|
118
|
+
export const rootLogger = createLogger('mcp-server');
|
|
119
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limiting utilities for the Obsidian MCP Server
|
|
3
|
+
*/
|
|
4
|
+
import { ObsidianError } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Default rate limit configuration
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_RATE_LIMIT_CONFIG = {
|
|
9
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
10
|
+
maxRequests: 200
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* RateLimit manages request rate limiting for API endpoints
|
|
14
|
+
*/
|
|
15
|
+
export class RateLimiter {
|
|
16
|
+
config;
|
|
17
|
+
requestCounts = new Map();
|
|
18
|
+
cleanupInterval;
|
|
19
|
+
constructor(config = DEFAULT_RATE_LIMIT_CONFIG) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
// Clean up expired rate limit entries periodically
|
|
22
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 60000); // Clean up every minute
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a request is within rate limits
|
|
26
|
+
* @param key Identifier for the rate limit bucket (e.g., toolName)
|
|
27
|
+
* @returns Whether the request is allowed
|
|
28
|
+
*/
|
|
29
|
+
checkRateLimit(key) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const requestInfo = this.requestCounts.get(key);
|
|
32
|
+
if (!requestInfo || now > requestInfo.resetTime) {
|
|
33
|
+
// Reset counter for new window
|
|
34
|
+
this.requestCounts.set(key, {
|
|
35
|
+
count: 1,
|
|
36
|
+
resetTime: now + this.config.windowMs
|
|
37
|
+
});
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (requestInfo.count >= this.config.maxRequests) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
requestInfo.count++;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check rate limit and throw an error if exceeded
|
|
48
|
+
* @param key Identifier for the rate limit bucket
|
|
49
|
+
* @throws ObsidianError if rate limit is exceeded
|
|
50
|
+
*/
|
|
51
|
+
enforceRateLimit(key) {
|
|
52
|
+
if (!this.checkRateLimit(key)) {
|
|
53
|
+
throw new ObsidianError(`Rate limit exceeded for ${key}. Please try again later.`, 42900 // 42900 = Rate limit exceeded
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get information about current rate limit status
|
|
59
|
+
* @param key Identifier for the rate limit bucket
|
|
60
|
+
* @returns Rate limit information or null if no requests have been made
|
|
61
|
+
*/
|
|
62
|
+
getRateLimitInfo(key) {
|
|
63
|
+
const requestInfo = this.requestCounts.get(key);
|
|
64
|
+
if (!requestInfo) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
remaining: Math.max(0, this.config.maxRequests - requestInfo.count),
|
|
69
|
+
resetTime: requestInfo.resetTime
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Clean up expired rate limit entries
|
|
74
|
+
*/
|
|
75
|
+
cleanup() {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
for (const [key, info] of this.requestCounts.entries()) {
|
|
78
|
+
if (now > info.resetTime) {
|
|
79
|
+
this.requestCounts.delete(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Clean up resources (e.g., when shutting down)
|
|
85
|
+
*/
|
|
86
|
+
dispose() {
|
|
87
|
+
if (this.cleanupInterval) {
|
|
88
|
+
clearInterval(this.cleanupInterval);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Export a singleton instance with default configuration
|
|
93
|
+
export const rateLimiter = new RateLimiter();
|
|
94
|
+
//# sourceMappingURL=rate-limiting.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token counting utilities for the Obsidian MCP Server
|
|
3
|
+
*/
|
|
4
|
+
import { encoding_for_model } from "tiktoken";
|
|
5
|
+
// Load token limits from environment or use defaults
|
|
6
|
+
export const MAX_TOKENS = parseInt(process.env.MAX_TOKENS ?? '20000');
|
|
7
|
+
export const TRUNCATION_MESSAGE = "\n\n[Response truncated due to length]";
|
|
8
|
+
/**
|
|
9
|
+
* Handles token counting and text truncation to stay within token limits
|
|
10
|
+
*/
|
|
11
|
+
export class TokenCounter {
|
|
12
|
+
tokenizer = encoding_for_model("gpt-4"); // This is strictly for token counting, not for LLM inference
|
|
13
|
+
isShuttingDown = false;
|
|
14
|
+
constructor() {
|
|
15
|
+
// Clean up tokenizer when process exits
|
|
16
|
+
const cleanup = () => {
|
|
17
|
+
if (!this.isShuttingDown) {
|
|
18
|
+
this.isShuttingDown = true;
|
|
19
|
+
if (this.tokenizer) {
|
|
20
|
+
this.tokenizer.free();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
process.on('exit', cleanup);
|
|
25
|
+
process.on('SIGINT', cleanup);
|
|
26
|
+
process.on('SIGTERM', cleanup);
|
|
27
|
+
process.on('uncaughtException', cleanup);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Count the number of tokens in a string
|
|
31
|
+
*/
|
|
32
|
+
countTokens(text) {
|
|
33
|
+
return this.tokenizer.encode(text).length;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Truncate text to stay within token limit
|
|
37
|
+
*/
|
|
38
|
+
truncateToTokenLimit(text, limit = MAX_TOKENS) {
|
|
39
|
+
const tokens = this.tokenizer.encode(text);
|
|
40
|
+
if (tokens.length <= limit) {
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
// Reserve tokens for truncation message
|
|
44
|
+
const messageTokens = this.tokenizer.encode(TRUNCATION_MESSAGE);
|
|
45
|
+
const availableTokens = limit - messageTokens.length;
|
|
46
|
+
// Decode truncated tokens back to text
|
|
47
|
+
const truncatedText = this.tokenizer.decode(tokens.slice(0, availableTokens));
|
|
48
|
+
return truncatedText + TRUNCATION_MESSAGE;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clean up resources
|
|
52
|
+
*/
|
|
53
|
+
cleanup() {
|
|
54
|
+
if (this.tokenizer && !this.isShuttingDown) {
|
|
55
|
+
this.isShuttingDown = true;
|
|
56
|
+
this.tokenizer.free();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Export a singleton instance
|
|
61
|
+
export const tokenCounter = new TokenCounter();
|
|
62
|
+
//# sourceMappingURL=tokenization.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for the Obsidian MCP Server
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validates a file path to prevent path traversal attacks and other security issues
|
|
6
|
+
* @param filepath The path to validate
|
|
7
|
+
* @throws Error if the path is invalid
|
|
8
|
+
*/
|
|
9
|
+
export function validateFilePath(filepath) {
|
|
10
|
+
// Prevent path traversal attacks
|
|
11
|
+
const normalizedPath = filepath.replace(/\\/g, '/');
|
|
12
|
+
if (normalizedPath.includes('../') || normalizedPath.includes('..\\')) {
|
|
13
|
+
throw new Error('Invalid file path: Path traversal not allowed');
|
|
14
|
+
}
|
|
15
|
+
// Additional path validations
|
|
16
|
+
if (normalizedPath.startsWith('/') || /^[a-zA-Z]:/.test(normalizedPath)) {
|
|
17
|
+
throw new Error('Invalid file path: Absolute paths not allowed');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sanitizes a header value to prevent header injection attacks
|
|
22
|
+
* @param value The header value to sanitize
|
|
23
|
+
* @returns The sanitized header value
|
|
24
|
+
*/
|
|
25
|
+
export function sanitizeHeader(value) {
|
|
26
|
+
// Remove any potentially harmful characters from header values
|
|
27
|
+
return value.replace(/[^\w\s\-\._~:/?#\[\]@!$&'()*+,;=]/g, '');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates tool arguments against a JSON schema
|
|
31
|
+
* @param args The arguments to validate
|
|
32
|
+
* @param schema The JSON schema to validate against
|
|
33
|
+
* @returns Validation result with errors if any
|
|
34
|
+
*/
|
|
35
|
+
export function validateToolArguments(args, schema) {
|
|
36
|
+
if (typeof args !== 'object' || args === null) {
|
|
37
|
+
return { valid: false, errors: ['Arguments must be an object'] };
|
|
38
|
+
}
|
|
39
|
+
const errors = [];
|
|
40
|
+
const required = schema.required || [];
|
|
41
|
+
// Check required fields
|
|
42
|
+
for (const field of required) {
|
|
43
|
+
if (!(field in args)) {
|
|
44
|
+
errors.push(`Missing required field: ${field}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Check field types
|
|
48
|
+
const properties = schema.properties || {};
|
|
49
|
+
for (const [key, value] of Object.entries(args)) {
|
|
50
|
+
const propSchema = properties[key];
|
|
51
|
+
if (!propSchema) {
|
|
52
|
+
errors.push(`Unknown field: ${key}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Skip validation for undefined optional fields
|
|
56
|
+
if (value === undefined && !required.includes(key)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Type validation
|
|
60
|
+
if (propSchema.type === 'string' && typeof value !== 'string') {
|
|
61
|
+
errors.push(`Field ${key} must be a string`);
|
|
62
|
+
}
|
|
63
|
+
else if (propSchema.type === 'number' && typeof value !== 'number') {
|
|
64
|
+
errors.push(`Field ${key} must be a number`);
|
|
65
|
+
}
|
|
66
|
+
else if (propSchema.type === 'boolean' && typeof value !== 'boolean') {
|
|
67
|
+
errors.push(`Field ${key} must be a boolean`);
|
|
68
|
+
}
|
|
69
|
+
else if (propSchema.type === 'array' && !Array.isArray(value)) {
|
|
70
|
+
errors.push(`Field ${key} must be an array`);
|
|
71
|
+
}
|
|
72
|
+
// Enum validation
|
|
73
|
+
if (propSchema.enum && value !== undefined && !propSchema.enum.includes(value)) {
|
|
74
|
+
errors.push(`Field ${key} must be one of: ${propSchema.enum.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
// Format validation for paths
|
|
77
|
+
if (propSchema.format === 'path' && typeof value === 'string') {
|
|
78
|
+
try {
|
|
79
|
+
validateFilePath(value);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
errors.push(`Field ${key}: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { valid: errors.length === 0, errors };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Obsidian MCP Server Tool Examples
|
|
2
|
+
|
|
3
|
+
This directory contains example requests and responses for each tool provided by the obsidian-mcp-server. These examples demonstrate the capabilities and expected output format of each tool.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### 1. [List Files in Vault](list-files-in-vault.md)
|
|
8
|
+
Lists all files and directories in the root directory of your Obsidian vault. Returns a hierarchical structure of files and folders, including metadata like file type.
|
|
9
|
+
|
|
10
|
+
### 2. [List Files in Directory](list-files-in-dir.md)
|
|
11
|
+
Lists all files and directories that exist in a specific Obsidian directory. Returns a hierarchical structure showing files, folders, and their relationships.
|
|
12
|
+
|
|
13
|
+
### 3. [Get File Contents](get-file-contents.md)
|
|
14
|
+
Return the content of a single file in your vault. Supports markdown files, text files, and other readable formats.
|
|
15
|
+
|
|
16
|
+
### 4. [Find in File](find-in-file.md)
|
|
17
|
+
Full-text search across all files in the vault. Returns matching files with surrounding context for each match.
|
|
18
|
+
|
|
19
|
+
### 5. [Append Content](append-content.md)
|
|
20
|
+
Append content to a new or existing file in the vault. Useful for adding new sections, notes, or updates to existing documents.
|
|
21
|
+
|
|
22
|
+
### 6. [Patch Content](patch-content.md)
|
|
23
|
+
Update the entire content of an existing note or create a new one. Provides complete control over file contents.
|
|
24
|
+
|
|
25
|
+
### 7. [Complex Search](complex-search.md)
|
|
26
|
+
File path pattern matching using JsonLogic queries. Supports operations like glob pattern matching and variable access.
|
|
27
|
+
|
|
28
|
+
### 8. [Get Properties](get-properties.md)
|
|
29
|
+
Get properties (title, tags, status, etc.) from an Obsidian note's YAML frontmatter. Returns all available properties including custom fields.
|
|
30
|
+
|
|
31
|
+
### 9. [Update Properties](update-properties.md)
|
|
32
|
+
Update properties in an Obsidian note's YAML frontmatter. Intelligently merges arrays and handles custom fields.
|
|
33
|
+
|
|
34
|
+
## Resources
|
|
35
|
+
|
|
36
|
+
### Direct Resources
|
|
37
|
+
- obsidian://tags - List of all tags used across the Obsidian vault with their usage counts
|
|
38
|
+
|
|
39
|
+
## Production Readiness
|
|
40
|
+
|
|
41
|
+
All tools have been tested and demonstrate:
|
|
42
|
+
- Proper input validation and error handling
|
|
43
|
+
- Comprehensive and well-structured responses
|
|
44
|
+
- Consistent output formatting
|
|
45
|
+
- Practical applicability to real-world Obsidian vault management
|
|
46
|
+
- Deep integration with Obsidian's features and capabilities
|
|
47
|
+
|
|
48
|
+
The examples serve as both documentation and test cases, showing the expected behavior and quality of responses for each tool.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Append Content Tool Example
|
|
2
|
+
|
|
3
|
+
## Request 1 (Append to Existing Note)
|
|
4
|
+
```json
|
|
5
|
+
{
|
|
6
|
+
"filepath": "Projects/Project Alpha/Notes/Meeting Notes.md",
|
|
7
|
+
"content": "\n\n## Team Meeting - 2024-01-25\n\n### Discussion Points\n- Reviewed project timeline\n- Discussed technical challenges\n- Assigned new tasks\n\n### Action Items\n- [ ] Update documentation\n- [ ] Schedule follow-up meeting\n- [ ] Share progress report"
|
|
8
|
+
}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Response 1
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"success": true,
|
|
15
|
+
"message": "Content appended successfully"
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Request 2 (Create New Note)
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"filepath": "Daily Notes/2024-01-25.md",
|
|
23
|
+
"content": "---\ntitle: Daily Note - January 25, 2024\ntags: [daily-notes]\ndate: 2024-01-25\n---\n\n# Daily Notes\n\n## Tasks\n- [ ] Review project updates\n- [ ] Team meeting at 2 PM\n- [ ] Update documentation\n\n## Notes\n- Started work on new feature\n- Discussed timeline with team\n- Reviewed technical specifications"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Response 2
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"success": true,
|
|
31
|
+
"message": "File created and content appended successfully"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Example Use Cases
|
|
36
|
+
|
|
37
|
+
1. **Meeting Notes**
|
|
38
|
+
- Add new meeting minutes to existing notes
|
|
39
|
+
- Create structured meeting summaries
|
|
40
|
+
- Track action items and decisions
|
|
41
|
+
|
|
42
|
+
2. **Daily Notes**
|
|
43
|
+
- Create daily journal entries
|
|
44
|
+
- Add to existing daily logs
|
|
45
|
+
- Maintain consistent note structure
|
|
46
|
+
|
|
47
|
+
3. **Project Documentation**
|
|
48
|
+
- Add new sections to documentation
|
|
49
|
+
- Update progress logs
|
|
50
|
+
- Append new requirements or specifications
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
- Can append to existing files or create new ones
|
|
54
|
+
- Preserves existing content in the file
|
|
55
|
+
- Maintains proper markdown formatting
|
|
56
|
+
- Supports YAML frontmatter
|
|
57
|
+
- Handles various content types:
|
|
58
|
+
* Meeting notes
|
|
59
|
+
* Daily logs
|
|
60
|
+
* Task lists
|
|
61
|
+
* Documentation updates
|
|
62
|
+
- File path is relative to vault root
|
|
63
|
+
- Creates parent directories if needed
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Complex Search Tool Example
|
|
2
|
+
|
|
3
|
+
## Request 1 (Simple Glob Pattern)
|
|
4
|
+
```json
|
|
5
|
+
{
|
|
6
|
+
"query": {
|
|
7
|
+
"glob": ["*.md", {"var": "path"}]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Response 1
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"matches": [
|
|
16
|
+
"README.md",
|
|
17
|
+
"Projects/Project Alpha/Documentation/Requirements.md",
|
|
18
|
+
"Daily Notes/2024-01-25.md",
|
|
19
|
+
"Templates/Meeting Note.md"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Request 2 (Complex Pattern)
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"query": {
|
|
28
|
+
"and": [
|
|
29
|
+
{
|
|
30
|
+
"glob": ["Projects/**/*.md", {"var": "path"}]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"in": [
|
|
34
|
+
"Documentation",
|
|
35
|
+
{"var": "path"}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Response 2
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"matches": [
|
|
47
|
+
"Projects/Project Alpha/Documentation/Requirements.md",
|
|
48
|
+
"Projects/Project Alpha/Documentation/Architecture.md",
|
|
49
|
+
"Projects/Project Beta/Documentation/API.md"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Example Use Cases
|
|
55
|
+
|
|
56
|
+
1. **File Organization**
|
|
57
|
+
- Find files by pattern matching
|
|
58
|
+
- Filter by path components
|
|
59
|
+
- Combine multiple search criteria
|
|
60
|
+
|
|
61
|
+
2. **Content Management**
|
|
62
|
+
- Locate files in specific directories
|
|
63
|
+
- Filter by file extensions
|
|
64
|
+
- Find files matching complex patterns
|
|
65
|
+
|
|
66
|
+
3. **Project Navigation**
|
|
67
|
+
- Search within project directories
|
|
68
|
+
- Find documentation files
|
|
69
|
+
- Filter by file location
|
|
70
|
+
|
|
71
|
+
## Notes
|
|
72
|
+
- Uses JsonLogic for query construction
|
|
73
|
+
- Supports glob pattern matching
|
|
74
|
+
- Can combine multiple conditions
|
|
75
|
+
- Available operations:
|
|
76
|
+
* glob: Pattern matching for paths
|
|
77
|
+
* var: Variable access (path)
|
|
78
|
+
* and/or: Logical operations
|
|
79
|
+
* in: Membership testing
|
|
80
|
+
- Path is relative to vault root
|
|
81
|
+
- Returns matching file paths
|
|
82
|
+
- Useful for complex file filtering
|
|
83
|
+
|
|
84
|
+
## Common Patterns
|
|
85
|
+
|
|
86
|
+
1. **Find All Markdown Files**
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"glob": ["**/*.md", {"var": "path"}]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. **Files in Specific Directory**
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"glob": ["Projects/Project Alpha/**/*", {"var": "path"}]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
3. **Multiple Extensions**
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"or": [
|
|
104
|
+
{"glob": ["**/*.md", {"var": "path"}]},
|
|
105
|
+
{"glob": ["**/*.txt", {"var": "path"}]}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
4. **Exclude Pattern**
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"and": [
|
|
114
|
+
{"glob": ["**/*.md", {"var": "path"}]},
|
|
115
|
+
{"not": {"glob": ["**/Archive/**", {"var": "path"}]}}
|
|
116
|
+
]
|
|
117
|
+
}
|