obsidian-mcp-server 1.3.0 → 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.
Files changed (77) hide show
  1. package/build/index.js +12 -3
  2. package/build/mcp/handlers.js +138 -0
  3. package/build/mcp/index.js +7 -0
  4. package/build/mcp/server.js +131 -0
  5. package/build/mcp/types.js +7 -0
  6. package/build/{obsidian.js → obsidian/client.js} +116 -96
  7. package/build/obsidian/errors.js +75 -0
  8. package/build/obsidian/index.js +7 -0
  9. package/build/obsidian/types.js +12 -0
  10. package/build/resources/index.js +15 -0
  11. package/build/{resources.js → resources/tags.js} +31 -5
  12. package/build/resources/types.js +5 -0
  13. package/build/tools/base.js +78 -0
  14. package/build/tools/files/content.js +171 -0
  15. package/build/tools/files/index.js +22 -0
  16. package/build/tools/files/list.js +133 -0
  17. package/build/tools/index.js +31 -0
  18. package/build/tools/properties/index.js +19 -0
  19. package/build/{properties.js → tools/properties/manager.js} +40 -8
  20. package/build/{propertyTools.js → tools/properties/tools.js} +22 -6
  21. package/build/{propertyTypes.js → tools/properties/types.js} +17 -5
  22. package/build/tools/search/complex.js +203 -0
  23. package/build/tools/search/index.js +20 -0
  24. package/build/tools/search/simple.js +127 -0
  25. package/build/utils/errors.js +59 -0
  26. package/build/utils/index.js +9 -0
  27. package/build/utils/logging.js +119 -0
  28. package/build/utils/rate-limiting.js +94 -0
  29. package/build/utils/tokenization.js +62 -0
  30. package/build/utils/validation.js +88 -0
  31. package/examples/README.md +48 -0
  32. package/examples/append-content.md +63 -0
  33. package/examples/complex-search.md +117 -0
  34. package/examples/find-in-file.md +94 -0
  35. package/examples/get-file-contents.md +72 -0
  36. package/examples/get-properties.md +89 -0
  37. package/examples/list-files-in-dir.md +55 -0
  38. package/examples/list-files-in-vault.md +53 -0
  39. package/examples/patch-content.md +60 -0
  40. package/examples/update-properties.md +126 -0
  41. package/package.json +1 -1
  42. package/src/index.ts +13 -3
  43. package/src/mcp/handlers.ts +183 -0
  44. package/src/mcp/index.ts +6 -0
  45. package/src/mcp/server.ts +162 -0
  46. package/src/mcp/types.ts +46 -0
  47. package/src/{obsidian.ts → obsidian/client.ts} +132 -125
  48. package/src/obsidian/errors.ts +105 -0
  49. package/src/obsidian/index.ts +6 -0
  50. package/src/obsidian/types.ts +124 -0
  51. package/src/resources/index.ts +17 -0
  52. package/src/{resources.ts → resources/tags.ts} +38 -6
  53. package/src/resources/types.ts +30 -0
  54. package/src/tools/base.ts +112 -0
  55. package/src/tools/files/content.ts +206 -0
  56. package/src/tools/files/index.ts +31 -0
  57. package/src/tools/files/list.ts +150 -0
  58. package/src/tools/index.ts +38 -0
  59. package/src/tools/properties/index.ts +21 -0
  60. package/src/{properties.ts → tools/properties/manager.ts} +41 -8
  61. package/src/{propertyTools.ts → tools/properties/tools.ts} +33 -7
  62. package/src/{propertyTypes.ts → tools/properties/types.ts} +29 -5
  63. package/src/tools/search/complex.ts +231 -0
  64. package/src/tools/search/index.ts +22 -0
  65. package/src/tools/search/simple.ts +147 -0
  66. package/src/utils/errors.ts +69 -0
  67. package/src/utils/index.ts +8 -0
  68. package/src/utils/logging.ts +146 -0
  69. package/src/utils/rate-limiting.ts +114 -0
  70. package/src/utils/tokenization.ts +71 -0
  71. package/src/utils/validation.ts +95 -0
  72. package/build/server.js +0 -263
  73. package/build/tools.js +0 -845
  74. package/build/types.js +0 -37
  75. package/src/server.ts +0 -335
  76. package/src/tools.ts +0 -926
  77. package/src/types.ts +0 -184
package/build/index.js CHANGED
@@ -1,11 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { run } from "./server.js";
2
+ import { run } from "./mcp/server.js";
3
+ import { createLogger } from "./utils/logging.js";
4
+ // Create a logger for the main entry point
5
+ const logger = createLogger('Main');
3
6
  // Main entry point
4
7
  if (import.meta.url === `file://${process.argv[1]}`) {
8
+ logger.info("Starting Obsidian MCP Server");
5
9
  run().catch((error) => {
6
- console.error("Failed to start server:", error);
10
+ logger.error("Failed to start server:", error);
7
11
  process.exit(1);
8
12
  });
9
13
  }
10
- export { run };
14
+ // Export the run function and other important modules
15
+ export { run } from "./mcp/server.js";
16
+ export * from "./obsidian/index.js";
17
+ export * from "./tools/index.js";
18
+ export * from "./resources/index.js";
19
+ export * from "./utils/index.js";
11
20
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,138 @@
1
+ /**
2
+ * MCP server request handlers
3
+ */
4
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { ObsidianError } from "../utils/errors.js";
6
+ import { validateToolArguments } from "../utils/validation.js";
7
+ import { rateLimiter } from "../utils/rate-limiting.js";
8
+ import { createLogger } from "../utils/logging.js";
9
+ import { DEFAULT_TIMEOUT_CONFIG } from "./types.js";
10
+ // Create a logger for request handlers
11
+ const logger = createLogger('McpHandlers');
12
+ /**
13
+ * Set up tool listing handler
14
+ * @param server The MCP server instance
15
+ * @param toolHandlers The tool handlers to register
16
+ */
17
+ export function setupToolListingHandler(server, toolHandlers) {
18
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
19
+ logger.debug('Handling ListToolsRequest');
20
+ const tools = [];
21
+ for (const handler of toolHandlers.values()) {
22
+ tools.push(handler.getToolDescription());
23
+ }
24
+ return { tools };
25
+ });
26
+ }
27
+ /**
28
+ * Set up tool calling handler
29
+ * @param server The MCP server instance
30
+ * @param toolHandlers The tool handlers to register
31
+ */
32
+ export function setupToolCallingHandler(server, toolHandlers) {
33
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
34
+ const { name, arguments: args } = request.params;
35
+ logger.debug(`Handling CallToolRequest for tool: ${name}`);
36
+ const handler = toolHandlers.get(name);
37
+ if (!handler) {
38
+ logger.error(`Unknown tool requested: ${name}`);
39
+ throw new ObsidianError(`Unknown tool: ${name}`, 40400); // 40400 = Not found
40
+ }
41
+ // Check rate limit
42
+ try {
43
+ rateLimiter.enforceRateLimit(name);
44
+ }
45
+ catch (error) {
46
+ // If rate limit is exceeded, log and rethrow the error
47
+ logger.warn(`Rate limit exceeded for tool: ${name}`);
48
+ throw error;
49
+ }
50
+ // Add timeout handling
51
+ const timeoutMs = DEFAULT_TIMEOUT_CONFIG.toolExecutionMs;
52
+ const timeoutPromise = new Promise((_, reject) => {
53
+ setTimeout(() => {
54
+ reject(new ObsidianError(`Tool execution timed out after ${timeoutMs}ms`, 40800)); // 40800 = Request timeout
55
+ }, timeoutMs);
56
+ });
57
+ try {
58
+ // Validate arguments against tool's schema
59
+ const toolDescription = handler.getToolDescription();
60
+ const validationResult = validateToolArguments(args, toolDescription.inputSchema);
61
+ if (!validationResult.valid) {
62
+ logger.error(`Invalid tool arguments for ${name}:`, validationResult.errors);
63
+ throw new ObsidianError(`Invalid tool arguments: ${validationResult.errors.join(', ')}`, 40000 // 40000 = Bad request
64
+ );
65
+ }
66
+ // Log the tool execution
67
+ logger.info(`Executing tool: ${name}`);
68
+ // Race between tool execution and timeout
69
+ const content = await Promise.race([
70
+ handler.runTool(args),
71
+ timeoutPromise
72
+ ]);
73
+ logger.debug(`Tool execution completed successfully: ${name}`);
74
+ return { content };
75
+ }
76
+ catch (error) {
77
+ if (error instanceof ObsidianError) {
78
+ // Check if the operation actually succeeded despite the error
79
+ if (error.errorCode === 20400) { // 20400 = Success with no content
80
+ return {
81
+ content: [{
82
+ type: "text",
83
+ text: "Operation completed successfully"
84
+ }]
85
+ };
86
+ }
87
+ throw error;
88
+ }
89
+ // Enhanced error logging
90
+ logger.error("Tool execution error:", {
91
+ name: error instanceof Error ? error.name : 'Unknown',
92
+ message: error instanceof Error ? error.message : String(error),
93
+ stack: error instanceof Error ? error.stack : undefined,
94
+ toolName: name
95
+ });
96
+ if (error instanceof Error) {
97
+ throw new ObsidianError(`Tool '${name}' execution failed: ${error.message}`, 50000, // 50000 = Internal server error
98
+ { originalError: error.stack });
99
+ }
100
+ throw new ObsidianError("Tool execution failed with unknown error", 50000, // 50000 = Internal server error
101
+ { error });
102
+ }
103
+ });
104
+ }
105
+ /**
106
+ * Set up resource listing handler
107
+ * @param server The MCP server instance
108
+ * @param resources The resources to register
109
+ */
110
+ export function setupResourceListingHandler(server, resources) {
111
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
112
+ logger.debug('Handling ListResourcesRequest');
113
+ return {
114
+ resources: Object.values(resources).map(resource => resource.getResourceDescription())
115
+ };
116
+ });
117
+ }
118
+ /**
119
+ * Set up resource reading handler
120
+ * @param server The MCP server instance
121
+ * @param resources The resources to register
122
+ */
123
+ export function setupResourceReadingHandler(server, resources) {
124
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
125
+ const uri = request.params.uri;
126
+ logger.debug(`Handling ReadResourceRequest for URI: ${uri}`);
127
+ const resource = resources[uri];
128
+ if (resource) {
129
+ logger.debug(`Found resource for URI: ${uri}`);
130
+ return {
131
+ contents: await resource.getContent()
132
+ };
133
+ }
134
+ logger.error(`Resource not found: ${uri}`);
135
+ throw new ObsidianError(`Resource not found: ${uri}`, 40400); // 40400 = Not found
136
+ });
137
+ }
138
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP module exports
3
+ */
4
+ export * from './types.js';
5
+ export * from './handlers.js';
6
+ export * from './server.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,131 @@
1
+ /**
2
+ * MCP server implementation
3
+ */
4
+ import { config } from "dotenv";
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { createLogger } from "../utils/logging.js";
8
+ import { rateLimiter } from "../utils/rate-limiting.js";
9
+ import { createTagResource } from "../resources/index.js";
10
+ import { createToolHandlers, createToolHandlerMap } from "../tools/index.js";
11
+ import { ObsidianClient } from "../obsidian/client.js";
12
+ import { setupToolListingHandler, setupToolCallingHandler, setupResourceListingHandler, setupResourceReadingHandler } from "./handlers.js";
13
+ // Create a logger for the server
14
+ const logger = createLogger('McpServer');
15
+ // Load environment variables
16
+ config();
17
+ /**
18
+ * Initialize MCP server components
19
+ */
20
+ 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
55
+ }
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;
67
+ }
68
+ /**
69
+ * Set up graceful shutdown handling
70
+ * @param server The MCP server instance
71
+ * @param cleanupHandlers Additional cleanup handlers to run on shutdown
72
+ */
73
+ export function setupShutdownHandling(server, cleanupHandlers = []) {
74
+ // 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);
84
+ }
85
+ }
86
+ // Dispose rate limiter
87
+ rateLimiter.dispose();
88
+ // Close server
89
+ await server.close();
90
+ process.exit(0);
91
+ };
92
+ // Handle various termination signals
93
+ process.on('SIGINT', cleanup); // Ctrl+C on all platforms
94
+ process.on('SIGTERM', cleanup); // Termination request
95
+ if (process.platform === 'win32') {
96
+ // Windows-specific handling
97
+ process.on('SIGHUP', cleanup); // Terminal closed
98
+ }
99
+ else {
100
+ // Unix-specific signals
101
+ process.on('SIGUSR1', cleanup);
102
+ process.on('SIGUSR2', cleanup);
103
+ }
104
+ // Handle uncaught errors
105
+ process.on('uncaughtException', async (error) => {
106
+ logger.error('Uncaught exception:', error);
107
+ await cleanup();
108
+ });
109
+ process.on('unhandledRejection', async (error) => {
110
+ logger.error('Unhandled rejection:', error);
111
+ await cleanup();
112
+ });
113
+ }
114
+ /**
115
+ * Run the MCP server
116
+ */
117
+ export async function run() {
118
+ try {
119
+ const server = await initializeServer();
120
+ setupShutdownHandling(server, []);
121
+ // Connect to transport
122
+ const transport = new StdioServerTransport();
123
+ await server.connect(transport);
124
+ logger.info("Obsidian MCP server running on stdio");
125
+ }
126
+ catch (error) {
127
+ logger.error("Failed to start server:", error);
128
+ process.exit(1);
129
+ }
130
+ }
131
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Default timeout configuration
3
+ */
4
+ export const DEFAULT_TIMEOUT_CONFIG = {
5
+ toolExecutionMs: parseInt(process.env.TOOL_TIMEOUT_MS ?? '60000') // 60 second default timeout
6
+ };
7
+ //# sourceMappingURL=types.js.map