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.
- 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 +1 -1
- 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/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
|