obsidian-mcp-server 1.5.7 → 2.0.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/README.md +249 -101
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.js +191 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +298 -0
- package/dist/mcp-server/server.d.ts +33 -0
- package/dist/mcp-server/server.js +211 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/logic.d.ts +51 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/logic.js +168 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/registration.d.ts +19 -0
- package/dist/mcp-server/tools/obsidianDeleteFileTool/registration.js +91 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +77 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +341 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +18 -0
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +69 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/logic.d.ts +64 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/logic.js +179 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/registration.d.ts +19 -0
- package/dist/mcp-server/tools/obsidianListFilesTool/registration.js +96 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +2 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +42 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +152 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +52 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +2 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +28 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +161 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +3 -0
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +52 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/logic.d.ts +87 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/logic.js +216 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/registration.d.ts +20 -0
- package/dist/mcp-server/tools/obsidianReadFileTool/registration.js +101 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +255 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +583 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +22 -0
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +111 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/index.d.ts +12 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/index.js +12 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/logic.d.ts +183 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/logic.js +490 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/registration.d.ts +21 -0
- package/dist/mcp-server/tools/obsidianUpdateFileTool/registration.js +108 -0
- package/dist/mcp-server/transports/authentication/authContext.d.ts +33 -0
- package/dist/mcp-server/transports/authentication/authContext.js +24 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +30 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
- package/dist/mcp-server/transports/authentication/authUtils.d.ts +18 -0
- package/dist/mcp-server/transports/authentication/authUtils.js +45 -0
- package/dist/mcp-server/transports/authentication/oauthMiddleware.d.ts +24 -0
- package/dist/mcp-server/transports/authentication/oauthMiddleware.js +109 -0
- package/dist/mcp-server/transports/authentication/types.d.ts +17 -0
- package/dist/mcp-server/transports/authentication/types.js +5 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +24 -0
- package/dist/mcp-server/transports/httpTransport.js +496 -0
- package/dist/mcp-server/transports/stdioTransport.d.ts +42 -0
- package/dist/mcp-server/transports/stdioTransport.js +63 -0
- package/dist/services/obsidianRestAPI/index.d.ts +15 -0
- package/dist/services/obsidianRestAPI/index.js +17 -0
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +38 -0
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +62 -0
- package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +22 -0
- package/dist/services/obsidianRestAPI/methods/commandMethods.js +31 -0
- package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +16 -0
- package/dist/services/obsidianRestAPI/methods/openMethods.js +21 -0
- package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +37 -0
- package/dist/services/obsidianRestAPI/methods/patchMethods.js +94 -0
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +42 -0
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +66 -0
- package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +25 -0
- package/dist/services/obsidianRestAPI/methods/searchMethods.js +36 -0
- package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +58 -0
- package/dist/services/obsidianRestAPI/methods/vaultMethods.js +144 -0
- package/dist/services/obsidianRestAPI/service.d.ts +195 -0
- package/dist/services/obsidianRestAPI/service.js +379 -0
- package/dist/services/obsidianRestAPI/types.d.ts +127 -0
- package/dist/services/obsidianRestAPI/types.js +7 -0
- package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +4 -0
- package/dist/services/obsidianRestAPI/vaultCache/index.js +4 -0
- package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +88 -0
- package/dist/services/obsidianRestAPI/vaultCache/service.js +299 -0
- package/dist/types-global/errors.d.ts +73 -0
- package/dist/types-global/errors.js +71 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/internal/asyncUtils.d.ts +54 -0
- package/dist/utils/internal/asyncUtils.js +101 -0
- package/dist/utils/internal/errorHandler.d.ts +176 -0
- package/dist/utils/internal/errorHandler.js +351 -0
- package/dist/utils/internal/index.d.ts +4 -0
- package/dist/utils/internal/index.js +4 -0
- package/dist/utils/internal/logger.d.ts +141 -0
- package/dist/utils/internal/logger.js +406 -0
- package/dist/utils/internal/requestContext.d.ts +83 -0
- package/dist/utils/internal/requestContext.js +72 -0
- package/dist/utils/metrics/index.d.ts +1 -0
- package/dist/utils/metrics/index.js +1 -0
- package/dist/utils/metrics/tokenCounter.d.ts +27 -0
- package/dist/utils/metrics/tokenCounter.js +128 -0
- package/dist/utils/obsidian/index.d.ts +5 -0
- package/dist/utils/obsidian/index.js +5 -0
- package/dist/utils/obsidian/obsidianApiUtils.d.ts +14 -0
- package/dist/utils/obsidian/obsidianApiUtils.js +29 -0
- package/dist/utils/obsidian/obsidianStatUtils.d.ts +68 -0
- package/dist/utils/obsidian/obsidianStatUtils.js +143 -0
- package/dist/utils/parsing/dateParser.d.ts +56 -0
- package/dist/utils/parsing/dateParser.js +104 -0
- package/dist/utils/parsing/index.d.ts +2 -0
- package/dist/utils/parsing/index.js +3 -0
- package/dist/utils/parsing/jsonParser.d.ts +80 -0
- package/dist/utils/parsing/jsonParser.js +133 -0
- package/dist/utils/security/idGenerator.d.ts +140 -0
- package/dist/utils/security/idGenerator.js +194 -0
- package/dist/utils/security/index.d.ts +3 -0
- package/dist/utils/security/index.js +3 -0
- package/dist/utils/security/rateLimiter.d.ts +156 -0
- package/dist/utils/security/rateLimiter.js +235 -0
- package/dist/utils/security/sanitization.d.ts +244 -0
- package/dist/utils/security/sanitization.js +599 -0
- package/package.json +72 -40
- package/.github/workflows/publish.yml +0 -35
- package/Dockerfile +0 -63
- package/build/index.js +0 -20
- package/build/mcp/handlers.js +0 -305
- package/build/mcp/index.js +0 -7
- package/build/mcp/server.js +0 -240
- package/build/mcp/types.js +0 -49
- package/build/obsidian/client.js +0 -403
- package/build/obsidian/errors.js +0 -75
- package/build/obsidian/index.js +0 -7
- package/build/obsidian/types.js +0 -12
- package/build/resources/index.js +0 -15
- package/build/resources/tags.js +0 -257
- package/build/resources/types.js +0 -5
- package/build/tools/base.js +0 -88
- package/build/tools/files/content.js +0 -171
- package/build/tools/files/index.js +0 -22
- package/build/tools/files/list.js +0 -133
- package/build/tools/index.js +0 -31
- package/build/tools/properties/index.js +0 -19
- package/build/tools/properties/manager.js +0 -302
- package/build/tools/properties/tools.js +0 -239
- package/build/tools/properties/types.js +0 -70
- package/build/tools/search/complex.js +0 -270
- package/build/tools/search/index.js +0 -20
- package/build/tools/search/simple.js +0 -127
- package/build/utils/errors.js +0 -59
- package/build/utils/idGenerator.js +0 -21
- package/build/utils/index.js +0 -9
- package/build/utils/logging.js +0 -417
- package/build/utils/rate-limiting.js +0 -94
- package/build/utils/tokenization.js +0 -75
- package/build/utils/validation.js +0 -92
- package/debug.js +0 -98
- package/docs/tree.md +0 -67
- package/env.json +0 -42
- package/mcp-client-config.example.json +0 -23
- package/repomix.config.json +0 -20
- package/scripts/clean.ts +0 -103
- package/scripts/tree.ts +0 -599
- package/src/index.ts +0 -23
- package/src/mcp/handlers.ts +0 -410
- package/src/mcp/index.ts +0 -7
- package/src/mcp/server.ts +0 -288
- package/src/mcp/types.ts +0 -112
- package/src/obsidian/client.ts +0 -505
- package/src/obsidian/errors.ts +0 -105
- package/src/obsidian/index.ts +0 -7
- package/src/obsidian/types.ts +0 -124
- package/src/resources/index.ts +0 -17
- package/src/resources/tags.ts +0 -283
- package/src/resources/types.ts +0 -30
- package/src/tools/base.ts +0 -123
- package/src/tools/files/content.ts +0 -206
- package/src/tools/files/index.ts +0 -31
- package/src/tools/files/list.ts +0 -150
- package/src/tools/index.ts +0 -38
- package/src/tools/properties/index.ts +0 -21
- package/src/tools/properties/manager.ts +0 -370
- package/src/tools/properties/tools.ts +0 -280
- package/src/tools/properties/types.ts +0 -117
- package/src/tools/search/complex.ts +0 -303
- package/src/tools/search/index.ts +0 -22
- package/src/tools/search/simple.ts +0 -147
- package/src/utils/errors.ts +0 -69
- package/src/utils/idGenerator.ts +0 -22
- package/src/utils/index.ts +0 -8
- package/src/utils/logging.ts +0 -558
- package/src/utils/rate-limiting.ts +0 -114
- package/src/utils/tokenization.ts +0 -85
- package/src/utils/validation.ts +0 -105
- package/tsconfig.json +0 -17
package/src/mcp/types.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP server type definitions
|
|
3
|
-
*/
|
|
4
|
-
import { TextContent, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { ErrorCategoryType, LogLevel, StandardizedErrorObject } from "../utils/logging.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* MCP server configuration
|
|
9
|
-
*/
|
|
10
|
-
export interface McpServerConfig extends Record<string, unknown> {
|
|
11
|
-
name: string;
|
|
12
|
-
version: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Timeout configuration for tool execution
|
|
17
|
-
*/
|
|
18
|
-
export interface TimeoutConfig {
|
|
19
|
-
toolExecutionMs: number; // Default: 60000 (60 seconds)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Map of resources by URI
|
|
24
|
-
*/
|
|
25
|
-
export interface ResourceMap {
|
|
26
|
-
[uri: string]: {
|
|
27
|
-
getContent(): Promise<TextContent[]>;
|
|
28
|
-
getResourceDescription(): any;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Server capabilities configuration
|
|
34
|
-
*/
|
|
35
|
-
export interface ServerCapabilities {
|
|
36
|
-
tools: Record<string, Tool>;
|
|
37
|
-
resources: ResourceMap;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Operation result interface following MCP best practices
|
|
42
|
-
*/
|
|
43
|
-
export interface OperationResultSuccess<DataType> {
|
|
44
|
-
resultSuccessful: true;
|
|
45
|
-
resultData: DataType;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface OperationResultFailure {
|
|
49
|
-
resultSuccessful: false;
|
|
50
|
-
resultError: StandardizedErrorObject;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type OperationResult<DataType> =
|
|
54
|
-
| OperationResultSuccess<DataType>
|
|
55
|
-
| OperationResultFailure;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Error codes used in the MCP server
|
|
59
|
-
*/
|
|
60
|
-
export enum McpErrorCode {
|
|
61
|
-
// Server errors (5xxxx)
|
|
62
|
-
INTERNAL_SERVER_ERROR = 50000,
|
|
63
|
-
SERVICE_UNAVAILABLE = 50300,
|
|
64
|
-
TIMEOUT = 50800,
|
|
65
|
-
|
|
66
|
-
// Client errors (4xxxx)
|
|
67
|
-
BAD_REQUEST = 40000,
|
|
68
|
-
UNAUTHORIZED = 40100,
|
|
69
|
-
FORBIDDEN = 40300,
|
|
70
|
-
NOT_FOUND = 40400,
|
|
71
|
-
METHOD_NOT_ALLOWED = 40500,
|
|
72
|
-
RATE_LIMIT_EXCEEDED = 40900,
|
|
73
|
-
|
|
74
|
-
// Success with special handling
|
|
75
|
-
SUCCESS_NO_CONTENT = 20400
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Default timeout configuration
|
|
80
|
-
*/
|
|
81
|
-
export const DEFAULT_TIMEOUT_CONFIG: TimeoutConfig = {
|
|
82
|
-
toolExecutionMs: parseInt(process.env.TOOL_TIMEOUT_MS ?? '60000') // 60 second default timeout
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Helper functions for creating operation results
|
|
87
|
-
*/
|
|
88
|
-
export function createSuccessResult<DataType>(data: DataType): OperationResultSuccess<DataType> {
|
|
89
|
-
return {
|
|
90
|
-
resultSuccessful: true,
|
|
91
|
-
resultData: data
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function createFailureResult(
|
|
96
|
-
message: string,
|
|
97
|
-
code: string | McpErrorCode,
|
|
98
|
-
category: ErrorCategoryType = ErrorCategoryType.CATEGORY_UNKNOWN,
|
|
99
|
-
context?: Record<string, unknown>
|
|
100
|
-
): OperationResultFailure {
|
|
101
|
-
return {
|
|
102
|
-
resultSuccessful: false,
|
|
103
|
-
resultError: {
|
|
104
|
-
errorMessage: message,
|
|
105
|
-
errorCode: typeof code === 'string' ? code : String(code),
|
|
106
|
-
errorCategory: category,
|
|
107
|
-
errorSeverity: LogLevel.ERROR,
|
|
108
|
-
errorTimestamp: new Date().toISOString(),
|
|
109
|
-
errorContext: context
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
}
|
package/src/obsidian/client.ts
DELETED
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Obsidian REST API client implementation
|
|
3
|
-
*/
|
|
4
|
-
import type { AxiosInstance, AxiosRequestConfig } from "axios";
|
|
5
|
-
import axios from "axios";
|
|
6
|
-
import { readFileSync } from "fs";
|
|
7
|
-
import { Agent } from "node:https";
|
|
8
|
-
import { dirname, join } from "path";
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
import { ObsidianError } from '../utils/errors.js';
|
|
12
|
-
import { createLogger } from '../utils/logging.js';
|
|
13
|
-
import { sanitizeHeader, validateFilePath } from '../utils/validation.js';
|
|
14
|
-
import {
|
|
15
|
-
createMissingAPIKeyMessage,
|
|
16
|
-
handleAxiosError
|
|
17
|
-
} from './errors.js';
|
|
18
|
-
import {
|
|
19
|
-
DEFAULT_OBSIDIAN_CONFIG,
|
|
20
|
-
JsonLogicQuery,
|
|
21
|
-
NoteJson,
|
|
22
|
-
ObsidianCommand,
|
|
23
|
-
ObsidianConfig,
|
|
24
|
-
ObsidianFile,
|
|
25
|
-
ObsidianServerConfig,
|
|
26
|
-
ObsidianStatus,
|
|
27
|
-
PeriodType,
|
|
28
|
-
SearchResponse,
|
|
29
|
-
SimpleSearchResult
|
|
30
|
-
} from './types.js';
|
|
31
|
-
|
|
32
|
-
// Logger for the ObsidianClient
|
|
33
|
-
const logger = createLogger('ObsidianClient');
|
|
34
|
-
|
|
35
|
-
// Get package version for user agent
|
|
36
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
37
|
-
const __dirname = dirname(__filename);
|
|
38
|
-
const VERSION = (() => {
|
|
39
|
-
try {
|
|
40
|
-
// Look for package.json in the same directory as the built files
|
|
41
|
-
const packagePath = join(__dirname, '..', '..', 'package.json');
|
|
42
|
-
const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
43
|
-
return pkg.version;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
// Try alternative location for development
|
|
46
|
-
try {
|
|
47
|
-
const devPackagePath = join(__dirname, '..', '..', '..', 'package.json');
|
|
48
|
-
const pkg = JSON.parse(readFileSync(devPackagePath, 'utf-8'));
|
|
49
|
-
return pkg.version;
|
|
50
|
-
} catch (devError) {
|
|
51
|
-
logger.warn('Could not read package.json version, using fallback');
|
|
52
|
-
return '1.1.0'; // Fallback version
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
})();
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Client for interacting with the Obsidian Local REST API
|
|
59
|
-
*/
|
|
60
|
-
export class ObsidianClient {
|
|
61
|
-
private client: AxiosInstance;
|
|
62
|
-
private config: Required<ObsidianConfig> & ObsidianServerConfig;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Create a new ObsidianClient
|
|
66
|
-
* @param config Configuration for the client
|
|
67
|
-
*/
|
|
68
|
-
constructor(config: ObsidianConfig) {
|
|
69
|
-
if (!config.apiKey) {
|
|
70
|
-
throw new ObsidianError(
|
|
71
|
-
createMissingAPIKeyMessage(),
|
|
72
|
-
40100 // Unauthorized
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Determine if we're in a development environment
|
|
77
|
-
const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
|
78
|
-
|
|
79
|
-
// Read environment variables with fallbacks
|
|
80
|
-
const envConfig = {
|
|
81
|
-
protocol: process.env.OBSIDIAN_PROTOCOL as "http" | "https" || DEFAULT_OBSIDIAN_CONFIG.protocol,
|
|
82
|
-
host: process.env.OBSIDIAN_HOST || DEFAULT_OBSIDIAN_CONFIG.host,
|
|
83
|
-
port: parseInt(process.env.OBSIDIAN_PORT || String(DEFAULT_OBSIDIAN_CONFIG.port)),
|
|
84
|
-
verifySSL: process.env.VERIFY_SSL ? process.env.VERIFY_SSL === 'true' : (isDev ? false : true),
|
|
85
|
-
timeout: parseInt(process.env.REQUEST_TIMEOUT || '5000'),
|
|
86
|
-
maxContentLength: parseInt(process.env.MAX_CONTENT_LENGTH || String(50 * 1024 * 1024)),
|
|
87
|
-
maxBodyLength: parseInt(process.env.MAX_BODY_LENGTH || String(50 * 1024 * 1024))
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Combine defaults with provided config and environment variables
|
|
91
|
-
this.config = {
|
|
92
|
-
protocol: envConfig.protocol,
|
|
93
|
-
host: envConfig.host,
|
|
94
|
-
port: envConfig.port,
|
|
95
|
-
verifySSL: config.verifySSL ?? envConfig.verifySSL,
|
|
96
|
-
apiKey: config.apiKey,
|
|
97
|
-
timeout: config.timeout ?? envConfig.timeout,
|
|
98
|
-
maxContentLength: config.maxContentLength ?? envConfig.maxContentLength,
|
|
99
|
-
maxBodyLength: config.maxBodyLength ?? envConfig.maxBodyLength
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// Configure HTTPS agent
|
|
103
|
-
const httpsAgent = new Agent({
|
|
104
|
-
rejectUnauthorized: this.config.verifySSL
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const axiosConfig: AxiosRequestConfig = {
|
|
108
|
-
baseURL: this.getBaseUrl(),
|
|
109
|
-
headers: {
|
|
110
|
-
...this.getHeaders(),
|
|
111
|
-
// Security headers
|
|
112
|
-
'X-Content-Type-Options': 'nosniff',
|
|
113
|
-
'X-Frame-Options': 'DENY',
|
|
114
|
-
'X-XSS-Protection': '1; mode=block',
|
|
115
|
-
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
|
|
116
|
-
},
|
|
117
|
-
validateStatus: (status) => status >= 200 && status < 300,
|
|
118
|
-
timeout: this.config.timeout,
|
|
119
|
-
maxRedirects: 5,
|
|
120
|
-
maxContentLength: this.config.maxContentLength,
|
|
121
|
-
maxBodyLength: this.config.maxBodyLength,
|
|
122
|
-
httpsAgent,
|
|
123
|
-
// Additional security configurations
|
|
124
|
-
xsrfCookieName: 'XSRF-TOKEN',
|
|
125
|
-
xsrfHeaderName: 'X-XSRF-TOKEN',
|
|
126
|
-
withCredentials: true,
|
|
127
|
-
decompress: true
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
if (!this.config.verifySSL) {
|
|
131
|
-
logger.warn(
|
|
132
|
-
"WARNING: SSL verification is disabled. While this works for development, it's not recommended for production.\n" +
|
|
133
|
-
"To properly configure SSL certificates:\n" +
|
|
134
|
-
"1. Go to Obsidian Settings > Local REST API\n" +
|
|
135
|
-
"2. Under 'How to Access', copy the certificate\n" +
|
|
136
|
-
"3. For Windows users:\n" +
|
|
137
|
-
" - Open 'certmgr.msc' (Windows Certificate Manager)\n" +
|
|
138
|
-
" - Go to 'Trusted Root Certification Authorities' > 'Certificates'\n" +
|
|
139
|
-
" - Right-click > 'All Tasks' > 'Import' and follow the wizard\n" +
|
|
140
|
-
" - Select the certificate file you copied from Obsidian\n" +
|
|
141
|
-
"4. For other systems:\n" +
|
|
142
|
-
" - macOS: Add to Keychain Access\n" +
|
|
143
|
-
" - Linux: Add to ca-certificates"
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
this.client = axios.create(axiosConfig);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Get the base URL for the Obsidian API
|
|
152
|
-
*/
|
|
153
|
-
private getBaseUrl(): string {
|
|
154
|
-
return `${this.config.protocol}://${this.config.host}:${this.config.port}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Get headers for requests to the Obsidian API
|
|
159
|
-
*/
|
|
160
|
-
private getHeaders(): Record<string, string> {
|
|
161
|
-
const headers: Record<string, string> = {
|
|
162
|
-
Authorization: `Bearer ${this.config.apiKey}`,
|
|
163
|
-
'Accept': 'application/json',
|
|
164
|
-
'User-Agent': `obsidian-mcp-server/${VERSION}`
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Sanitize headers
|
|
168
|
-
return Object.fromEntries(
|
|
169
|
-
Object.entries(headers).map(([key, value]) => [
|
|
170
|
-
key,
|
|
171
|
-
sanitizeHeader(value)
|
|
172
|
-
])
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Safely execute an API request with error handling
|
|
178
|
-
*/
|
|
179
|
-
private async safeRequest<T>(operation: () => Promise<T>): Promise<T> {
|
|
180
|
-
try {
|
|
181
|
-
return await operation();
|
|
182
|
-
} catch (error) {
|
|
183
|
-
if (axios.isAxiosError(error)) {
|
|
184
|
-
throw handleAxiosError(error, this.config.host, this.config.port);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (error instanceof Error) {
|
|
188
|
-
throw new ObsidianError(error.message, 50000, error);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
throw new ObsidianError("Unknown error occurred", 50000, error);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* List all files in the vault
|
|
197
|
-
*/
|
|
198
|
-
async listFilesInVault(): Promise<ObsidianFile[]> {
|
|
199
|
-
return this.safeRequest(async () => {
|
|
200
|
-
logger.debug('Listing all files in vault');
|
|
201
|
-
const response = await this.client.get<{ files: ObsidianFile[] }>("/vault/");
|
|
202
|
-
return response.data.files;
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* List files in a specific directory
|
|
208
|
-
*/
|
|
209
|
-
async listFilesInDir(dirpath: string): Promise<ObsidianFile[]> {
|
|
210
|
-
validateFilePath(dirpath);
|
|
211
|
-
return this.safeRequest(async () => {
|
|
212
|
-
logger.debug(`Listing files in directory: ${dirpath}`);
|
|
213
|
-
const response = await this.client.get<{ files: ObsidianFile[] }>(`/vault/${dirpath}/`);
|
|
214
|
-
return response.data.files;
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Get the contents of a file
|
|
220
|
-
*/
|
|
221
|
-
async getFileContents(filepath: string): Promise<string> {
|
|
222
|
-
validateFilePath(filepath);
|
|
223
|
-
return this.safeRequest(async () => {
|
|
224
|
-
logger.debug(`Getting contents of file: ${filepath}`);
|
|
225
|
-
const response = await this.client.get<string>(`/vault/${filepath}`);
|
|
226
|
-
return response.data;
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Search for a string across all files
|
|
232
|
-
*/
|
|
233
|
-
async search(query: string, contextLength: number = 100): Promise<SimpleSearchResult[]> {
|
|
234
|
-
return this.safeRequest(async () => {
|
|
235
|
-
logger.debug(`Searching for: ${query} with context length: ${contextLength}`);
|
|
236
|
-
const response = await this.client.post<SimpleSearchResult[]>(
|
|
237
|
-
"/search/simple/",
|
|
238
|
-
null,
|
|
239
|
-
{ params: { query, contextLength } }
|
|
240
|
-
);
|
|
241
|
-
return response.data;
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Append content to a file
|
|
247
|
-
*/
|
|
248
|
-
async appendContent(filepath: string, content: string): Promise<void> {
|
|
249
|
-
validateFilePath(filepath);
|
|
250
|
-
if (!content || typeof content !== 'string') {
|
|
251
|
-
throw new ObsidianError('Invalid content: Content must be a non-empty string', 40003);
|
|
252
|
-
}
|
|
253
|
-
return this.safeRequest(async () => {
|
|
254
|
-
logger.debug(`Appending content to file: ${filepath}`);
|
|
255
|
-
await this.client.post(
|
|
256
|
-
`/vault/${filepath}`,
|
|
257
|
-
content,
|
|
258
|
-
{
|
|
259
|
-
headers: {
|
|
260
|
-
"Content-Type": "text/markdown"
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Update the entire content of a file
|
|
269
|
-
*/
|
|
270
|
-
async updateContent(filepath: string, content: string): Promise<void> {
|
|
271
|
-
validateFilePath(filepath);
|
|
272
|
-
if (!content || typeof content !== 'string') {
|
|
273
|
-
throw new ObsidianError('Invalid content: Content must be a non-empty string', 40003);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return this.safeRequest(async () => {
|
|
277
|
-
logger.debug(`Updating content of file: ${filepath}`);
|
|
278
|
-
await this.client.put(
|
|
279
|
-
`/vault/${filepath}`,
|
|
280
|
-
content,
|
|
281
|
-
{
|
|
282
|
-
headers: {
|
|
283
|
-
"Content-Type": "text/markdown"
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
);
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Execute a complex search using JsonLogic query
|
|
292
|
-
*/
|
|
293
|
-
async searchJson(query: JsonLogicQuery): Promise<SearchResponse[]> {
|
|
294
|
-
return this.safeRequest(async () => {
|
|
295
|
-
logger.debug(`Executing JSON search with query: ${JSON.stringify(query)}`);
|
|
296
|
-
const isTagSearch = JSON.stringify(query).includes('"contains"') &&
|
|
297
|
-
JSON.stringify(query).includes('"#"');
|
|
298
|
-
|
|
299
|
-
const response = await this.client.post(
|
|
300
|
-
"/search/",
|
|
301
|
-
query,
|
|
302
|
-
{
|
|
303
|
-
headers: {
|
|
304
|
-
"Content-Type": "application/vnd.olrapi.jsonlogic+json",
|
|
305
|
-
"Accept": "application/vnd.olrapi.note+json"
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
return response.data as SearchResponse[];
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Get server status
|
|
316
|
-
*/
|
|
317
|
-
async getStatus(): Promise<ObsidianStatus> {
|
|
318
|
-
return this.safeRequest(async () => {
|
|
319
|
-
logger.debug('Getting server status');
|
|
320
|
-
const response = await this.client.get<ObsidianStatus>("/");
|
|
321
|
-
return response.data;
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* List available commands
|
|
327
|
-
*/
|
|
328
|
-
async listCommands(): Promise<ObsidianCommand[]> {
|
|
329
|
-
return this.safeRequest(async () => {
|
|
330
|
-
logger.debug('Listing commands');
|
|
331
|
-
const response = await this.client.get<{commands: ObsidianCommand[]}>("/commands/");
|
|
332
|
-
return response.data.commands;
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Execute a command by ID
|
|
338
|
-
*/
|
|
339
|
-
async executeCommand(commandId: string): Promise<void> {
|
|
340
|
-
return this.safeRequest(async () => {
|
|
341
|
-
logger.debug(`Executing command: ${commandId}`);
|
|
342
|
-
await this.client.post(`/commands/${commandId}/`);
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Open a file in Obsidian
|
|
348
|
-
*/
|
|
349
|
-
async openFile(filepath: string, newLeaf: boolean = false): Promise<void> {
|
|
350
|
-
validateFilePath(filepath);
|
|
351
|
-
return this.safeRequest(async () => {
|
|
352
|
-
logger.debug(`Opening file: ${filepath}, newLeaf: ${newLeaf}`);
|
|
353
|
-
await this.client.post(`/open/${filepath}`, null, {
|
|
354
|
-
params: { newLeaf }
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Get the currently active file
|
|
361
|
-
*/
|
|
362
|
-
async getActiveFile(): Promise<NoteJson> {
|
|
363
|
-
return this.safeRequest(async () => {
|
|
364
|
-
logger.debug('Getting active file');
|
|
365
|
-
const response = await this.client.get<NoteJson>("/active/", {
|
|
366
|
-
headers: {
|
|
367
|
-
"Accept": "application/vnd.olrapi.note+json"
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
return response.data;
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Update the active file
|
|
376
|
-
*/
|
|
377
|
-
async updateActiveFile(content: string): Promise<void> {
|
|
378
|
-
return this.safeRequest(async () => {
|
|
379
|
-
logger.debug('Updating active file');
|
|
380
|
-
await this.client.put("/active/", content, {
|
|
381
|
-
headers: {
|
|
382
|
-
"Content-Type": "text/markdown"
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Delete the active file
|
|
390
|
-
*/
|
|
391
|
-
async deleteActiveFile(): Promise<void> {
|
|
392
|
-
return this.safeRequest(async () => {
|
|
393
|
-
logger.debug('Deleting active file');
|
|
394
|
-
await this.client.delete("/active/");
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Patch the active file
|
|
400
|
-
*/
|
|
401
|
-
async patchActiveFile(
|
|
402
|
-
operation: "append" | "prepend" | "replace",
|
|
403
|
-
targetType: "heading" | "block" | "frontmatter",
|
|
404
|
-
target: string,
|
|
405
|
-
content: string,
|
|
406
|
-
options?: {
|
|
407
|
-
delimiter?: string;
|
|
408
|
-
trimWhitespace?: boolean;
|
|
409
|
-
contentType?: "text/markdown" | "application/json";
|
|
410
|
-
}
|
|
411
|
-
): Promise<void> {
|
|
412
|
-
return this.safeRequest(async () => {
|
|
413
|
-
logger.debug(`Patching active file: ${operation} ${targetType} "${target}"`);
|
|
414
|
-
const headers: Record<string, string> = {
|
|
415
|
-
"Operation": operation,
|
|
416
|
-
"Target-Type": targetType,
|
|
417
|
-
"Target": target,
|
|
418
|
-
"Content-Type": options?.contentType || "text/markdown"
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
if (options?.delimiter) {
|
|
422
|
-
headers["Target-Delimiter"] = options.delimiter;
|
|
423
|
-
}
|
|
424
|
-
if (options?.trimWhitespace !== undefined) {
|
|
425
|
-
headers["Trim-Target-Whitespace"] = options.trimWhitespace.toString();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
await this.client.patch("/active/", content, { headers });
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Get a periodic note (e.g., daily, weekly)
|
|
434
|
-
*/
|
|
435
|
-
async getPeriodicNote(period: PeriodType["type"]): Promise<NoteJson> {
|
|
436
|
-
return this.safeRequest(async () => {
|
|
437
|
-
logger.debug(`Getting ${period} periodic note`);
|
|
438
|
-
const response = await this.client.get<NoteJson>(`/periodic/${period}/`, {
|
|
439
|
-
headers: {
|
|
440
|
-
"Accept": "application/vnd.olrapi.note+json"
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
return response.data;
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Update a periodic note
|
|
449
|
-
*/
|
|
450
|
-
async updatePeriodicNote(period: PeriodType["type"], content: string): Promise<void> {
|
|
451
|
-
return this.safeRequest(async () => {
|
|
452
|
-
logger.debug(`Updating ${period} periodic note`);
|
|
453
|
-
await this.client.put(`/periodic/${period}/`, content, {
|
|
454
|
-
headers: {
|
|
455
|
-
"Content-Type": "text/markdown"
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Delete a periodic note
|
|
463
|
-
*/
|
|
464
|
-
async deletePeriodicNote(period: PeriodType["type"]): Promise<void> {
|
|
465
|
-
return this.safeRequest(async () => {
|
|
466
|
-
logger.debug(`Deleting ${period} periodic note`);
|
|
467
|
-
await this.client.delete(`/periodic/${period}/`);
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Patch a periodic note
|
|
473
|
-
*/
|
|
474
|
-
async patchPeriodicNote(
|
|
475
|
-
period: PeriodType["type"],
|
|
476
|
-
operation: "append" | "prepend" | "replace",
|
|
477
|
-
targetType: "heading" | "block" | "frontmatter",
|
|
478
|
-
target: string,
|
|
479
|
-
content: string,
|
|
480
|
-
options?: {
|
|
481
|
-
delimiter?: string;
|
|
482
|
-
trimWhitespace?: boolean;
|
|
483
|
-
contentType?: "text/markdown" | "application/json";
|
|
484
|
-
}
|
|
485
|
-
): Promise<void> {
|
|
486
|
-
return this.safeRequest(async () => {
|
|
487
|
-
logger.debug(`Patching ${period} periodic note: ${operation} ${targetType} "${target}"`);
|
|
488
|
-
const headers: Record<string, string> = {
|
|
489
|
-
"Operation": operation,
|
|
490
|
-
"Target-Type": targetType,
|
|
491
|
-
"Target": target,
|
|
492
|
-
"Content-Type": options?.contentType || "text/markdown"
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
if (options?.delimiter) {
|
|
496
|
-
headers["Target-Delimiter"] = options.delimiter;
|
|
497
|
-
}
|
|
498
|
-
if (options?.trimWhitespace !== undefined) {
|
|
499
|
-
headers["Trim-Target-Whitespace"] = options.trimWhitespace.toString();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
await this.client.patch(`/periodic/${period}/`, content, { headers });
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
}
|