obsidian-mcp-server 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -1
- package/README.md +36 -77
- 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} +116 -96
- 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} +31 -5
- 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} +40 -8
- package/build/{propertyTools.js → tools/properties/tools.js} +22 -6
- package/build/{propertyTypes.js → tools/properties/types.js} +17 -5
- 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/package.json +12 -14
- 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} +132 -125
- 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} +38 -6
- 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} +41 -8
- package/src/{propertyTools.ts → tools/properties/tools.ts} +33 -7
- package/src/{propertyTypes.ts → tools/properties/types.ts} +29 -5
- 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 -263
- package/build/tools.js +0 -845
- package/build/types.js +0 -37
- package/src/server.ts +0 -335
- package/src/tools.ts +0 -926
- package/src/types.ts +0 -184
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Obsidian MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://www.typescriptlang.org/)
|
|
4
|
+
[](https://modelcontextprotocol.io/)
|
|
5
|
+
[]()
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[]()
|
|
8
8
|
[](https://github.com/cyanheads/obsidian-mcp-server)
|
|
@@ -18,6 +18,7 @@ Requires the Local REST API plugin in Obsidian.
|
|
|
18
18
|
### File Operations
|
|
19
19
|
- Atomic file/directory operations with validation
|
|
20
20
|
- Resource monitoring and cleanup
|
|
21
|
+
- Error handling and graceful failure
|
|
21
22
|
|
|
22
23
|
### Search System
|
|
23
24
|
- Full-text search with configurable context
|
|
@@ -32,12 +33,14 @@ Requires the Local REST API plugin in Obsidian.
|
|
|
32
33
|
### Security & Performance
|
|
33
34
|
- API key auth with rate limiting and SSL options
|
|
34
35
|
- Resource monitoring and health checks
|
|
36
|
+
- Graceful shutdown handling
|
|
35
37
|
|
|
36
38
|
## Installation
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
Note: Requires Node.js
|
|
41
|
+
|
|
42
|
+
1. Enable Local REST API plugin in Obsidian
|
|
43
|
+
2. Clone and build:
|
|
41
44
|
```bash
|
|
42
45
|
git clone git@github.com:cyanheads/obsidian-mcp-server.git
|
|
43
46
|
cd obsidian-mcp-server
|
|
@@ -84,7 +87,7 @@ Required:
|
|
|
84
87
|
- `OBSIDIAN_API_KEY`: Your API key from Obsidian's Local REST API plugin settings
|
|
85
88
|
|
|
86
89
|
Connection Settings:
|
|
87
|
-
- `VERIFY_SSL`: Enable SSL certificate verification (default: false
|
|
90
|
+
- `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.
|
|
88
91
|
- `OBSIDIAN_PROTOCOL`: Protocol to use (default: "https")
|
|
89
92
|
- `OBSIDIAN_HOST`: Host address (default: "127.0.0.1")
|
|
90
93
|
- `OBSIDIAN_PORT`: Port number (default: 27124)
|
|
@@ -101,46 +104,22 @@ Rate Limiting:
|
|
|
101
104
|
Tool Execution:
|
|
102
105
|
- `TOOL_TIMEOUT_MS`: Tool execution timeout in milliseconds (default: 60000 [1 minute])
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
For Windows Users:
|
|
107
|
-
1. Development Setup (Not Recommended for Production):
|
|
108
|
-
- Set `VERIFY_SSL` to "false"
|
|
109
|
-
- Set `OBSIDIAN_PROTOCOL` to "http"
|
|
110
|
-
- Enable "Non-encrypted (HTTP) Server" in Obsidian's Local REST API settings
|
|
111
|
-
|
|
112
|
-
2. Production Setup (Recommended):
|
|
113
|
-
- Set `VERIFY_SSL` to "true"
|
|
114
|
-
- Get the certificate from Obsidian Settings > Local REST API > 'How to Access'
|
|
115
|
-
- Open Windows Certificate Manager (certmgr.msc)
|
|
116
|
-
- Navigate to "Trusted Root Certification Authorities" > "Certificates"
|
|
117
|
-
- Right-click > "All Tasks" > "Import" and select the certificate file
|
|
107
|
+
## Project Structure
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
- macOS: Add certificate to Keychain Access
|
|
121
|
-
- Linux: Add to ca-certificates
|
|
109
|
+
The project follows a modular architecture with clear separation of concerns:
|
|
122
110
|
|
|
123
|
-
Additional configuration options:
|
|
124
|
-
```typescript
|
|
125
|
-
interface ObsidianConfig {
|
|
126
|
-
apiKey: string; // Required: API key for authentication
|
|
127
|
-
verifySSL?: boolean; // Optional: Enable SSL verification
|
|
128
|
-
timeout?: number; // Optional: Request timeout in ms
|
|
129
|
-
maxContentLength?: number;// Optional: Max response content length
|
|
130
|
-
maxBodyLength?: number; // Optional: Max request body length
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
interface RateLimitConfig {
|
|
134
|
-
windowMs: number; // Time window for rate limiting
|
|
135
|
-
maxRequests: number; // Max requests per window
|
|
136
|
-
}
|
|
137
111
|
```
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
112
|
+
src/
|
|
113
|
+
├── index.ts # Main entry point
|
|
114
|
+
├── mcp/ # MCP server implementation
|
|
115
|
+
├── obsidian/ # Obsidian API client and types
|
|
116
|
+
├── resources/ # MCP resource implementations
|
|
117
|
+
├── tools/ # MCP tool implementations
|
|
118
|
+
│ ├── files/ # File operations tools
|
|
119
|
+
│ ├── search/ # Search tools
|
|
120
|
+
│ └── properties/ # Property management tools
|
|
121
|
+
└── utils/ # Shared utilities
|
|
122
|
+
```
|
|
144
123
|
|
|
145
124
|
## Tools
|
|
146
125
|
|
|
@@ -184,6 +163,11 @@ obsidian_complex_search: {
|
|
|
184
163
|
// {"in": ["#mytag", {"var": "frontmatter.tags"}]}
|
|
185
164
|
// ]}
|
|
186
165
|
}
|
|
166
|
+
|
|
167
|
+
// Get all tags in vault or directory
|
|
168
|
+
obsidian_get_tags: {
|
|
169
|
+
path?: string // Optional: limit to specific directory
|
|
170
|
+
}
|
|
187
171
|
```
|
|
188
172
|
|
|
189
173
|
### Content Modification
|
|
@@ -201,41 +185,8 @@ obsidian_patch_content: {
|
|
|
201
185
|
}
|
|
202
186
|
```
|
|
203
187
|
|
|
204
|
-
### Command Management
|
|
205
|
-
```typescript
|
|
206
|
-
// List available commands
|
|
207
|
-
obsidian_list_commands: {}
|
|
208
|
-
|
|
209
|
-
// Execute a command
|
|
210
|
-
obsidian_execute_command: {
|
|
211
|
-
commandId: string // Command ID to execute
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### File Navigation
|
|
216
|
-
```typescript
|
|
217
|
-
// Open a file in Obsidian
|
|
218
|
-
obsidian_open_file: {
|
|
219
|
-
filepath: string, // Path relative to vault root
|
|
220
|
-
newLeaf?: boolean // Open in new leaf (default: false)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Get active file content
|
|
224
|
-
obsidian_get_active_file: {}
|
|
225
|
-
|
|
226
|
-
// Get periodic note content
|
|
227
|
-
obsidian_get_periodic_note: {
|
|
228
|
-
period: "daily" | "weekly" | "monthly" | "quarterly" | "yearly"
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
188
|
### Property Management
|
|
233
189
|
```typescript
|
|
234
|
-
// Get all tags in vault or directory
|
|
235
|
-
obsidian_get_tags: {
|
|
236
|
-
path?: string // Optional: limit to specific directory
|
|
237
|
-
}
|
|
238
|
-
|
|
239
190
|
// Get note properties
|
|
240
191
|
obsidian_get_properties: {
|
|
241
192
|
filepath: string // Path relative to vault root
|
|
@@ -286,6 +237,14 @@ obsidian_update_properties: {
|
|
|
286
237
|
- Validate inputs and handle errors gracefully
|
|
287
238
|
- Monitor patterns and respect rate limits
|
|
288
239
|
|
|
240
|
+
## Resources
|
|
241
|
+
|
|
242
|
+
The MCP server exposes the following resources:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
obsidian://tags # List of all tags used across the vault
|
|
246
|
+
```
|
|
247
|
+
|
|
289
248
|
## Contributing
|
|
290
249
|
|
|
291
250
|
1. Fork the repository
|
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
|
-
|
|
10
|
+
logger.error("Failed to start server:", error);
|
|
7
11
|
process.exit(1);
|
|
8
12
|
});
|
|
9
13
|
}
|
|
10
|
-
|
|
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,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
|