obsidian-mcp-server 1.5.8 → 2.0.2
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 +248 -104
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.js +191 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.js +296 -18
- 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 -8
- package/dist/utils/index.js +13 -9
- 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 +58 -37
- package/dist/index.js.map +0 -1
- package/dist/mcp/handlers.d.ts +0 -29
- package/dist/mcp/handlers.js +0 -305
- package/dist/mcp/handlers.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -6
- package/dist/mcp/index.js +0 -7
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -18
- package/dist/mcp/server.js +0 -240
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -70
- package/dist/mcp/types.js +0 -49
- package/dist/mcp/types.js.map +0 -1
- package/dist/obsidian/client.d.ts +0 -109
- package/dist/obsidian/client.js +0 -403
- package/dist/obsidian/client.js.map +0 -1
- package/dist/obsidian/errors.d.ts +0 -28
- package/dist/obsidian/errors.js +0 -75
- package/dist/obsidian/errors.js.map +0 -1
- package/dist/obsidian/index.d.ts +0 -6
- package/dist/obsidian/index.js +0 -7
- package/dist/obsidian/index.js.map +0 -1
- package/dist/obsidian/types.d.ts +0 -107
- package/dist/obsidian/types.js +0 -12
- package/dist/obsidian/types.js.map +0 -1
- package/dist/resources/index.d.ts +0 -13
- package/dist/resources/index.js +0 -15
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/tags.d.ts +0 -39
- package/dist/resources/tags.js +0 -257
- package/dist/resources/tags.js.map +0 -1
- package/dist/resources/types.d.ts +0 -27
- package/dist/resources/types.js +0 -5
- package/dist/resources/types.js.map +0 -1
- package/dist/tools/base.d.ts +0 -46
- package/dist/tools/base.js +0 -88
- package/dist/tools/base.js.map +0 -1
- package/dist/tools/files/content.d.ts +0 -58
- package/dist/tools/files/content.js +0 -171
- package/dist/tools/files/content.js.map +0 -1
- package/dist/tools/files/index.d.ts +0 -14
- package/dist/tools/files/index.js +0 -22
- package/dist/tools/files/index.js.map +0 -1
- package/dist/tools/files/list.d.ts +0 -35
- package/dist/tools/files/list.js +0 -133
- package/dist/tools/files/list.js.map +0 -1
- package/dist/tools/index.d.ts +0 -21
- package/dist/tools/index.js +0 -31
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/properties/index.d.ts +0 -14
- package/dist/tools/properties/index.js +0 -19
- package/dist/tools/properties/index.js.map +0 -1
- package/dist/tools/properties/manager.d.ts +0 -62
- package/dist/tools/properties/manager.js +0 -302
- package/dist/tools/properties/manager.js.map +0 -1
- package/dist/tools/properties/tools.d.ts +0 -47
- package/dist/tools/properties/tools.js +0 -239
- package/dist/tools/properties/tools.js.map +0 -1
- package/dist/tools/properties/types.d.ts +0 -141
- package/dist/tools/properties/types.js +0 -70
- package/dist/tools/properties/types.js.map +0 -1
- package/dist/tools/search/complex.d.ts +0 -38
- package/dist/tools/search/complex.js +0 -270
- package/dist/tools/search/complex.js.map +0 -1
- package/dist/tools/search/index.d.ts +0 -14
- package/dist/tools/search/index.js +0 -20
- package/dist/tools/search/index.js.map +0 -1
- package/dist/tools/search/simple.d.ts +0 -25
- package/dist/tools/search/simple.js +0 -127
- package/dist/tools/search/simple.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -24
- package/dist/utils/errors.js +0 -59
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/idGenerator.d.ts +0 -15
- package/dist/utils/idGenerator.js +0 -21
- package/dist/utils/idGenerator.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logging.d.ts +0 -245
- package/dist/utils/logging.js +0 -417
- package/dist/utils/logging.js.map +0 -1
- package/dist/utils/rate-limiting.d.ts +0 -50
- package/dist/utils/rate-limiting.js +0 -94
- package/dist/utils/rate-limiting.js.map +0 -1
- package/dist/utils/tokenization.d.ts +0 -28
- package/dist/utils/tokenization.js +0 -75
- package/dist/utils/tokenization.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -22
- package/dist/utils/validation.js +0 -92
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from "fs";
|
|
3
|
+
import path, { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
dotenv.config();
|
|
7
|
+
// --- Determine Project Root ---
|
|
8
|
+
/**
|
|
9
|
+
* Finds the project root directory by searching upwards for package.json.
|
|
10
|
+
* @param startDir The directory to start searching from.
|
|
11
|
+
* @returns The absolute path to the project root, or throws an error if not found.
|
|
12
|
+
*/
|
|
13
|
+
const findProjectRoot = (startDir) => {
|
|
14
|
+
let currentDir = startDir;
|
|
15
|
+
while (true) {
|
|
16
|
+
const packageJsonPath = join(currentDir, "package.json");
|
|
17
|
+
if (existsSync(packageJsonPath)) {
|
|
18
|
+
return currentDir;
|
|
19
|
+
}
|
|
20
|
+
const parentDir = dirname(currentDir);
|
|
21
|
+
if (parentDir === currentDir) {
|
|
22
|
+
// Reached the root of the filesystem without finding package.json
|
|
23
|
+
throw new Error(`Could not find project root (package.json) starting from ${startDir}`);
|
|
24
|
+
}
|
|
25
|
+
currentDir = parentDir;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
let projectRoot;
|
|
29
|
+
try {
|
|
30
|
+
// For ESM, __dirname is not available directly.
|
|
31
|
+
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
projectRoot = findProjectRoot(currentModuleDir);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(`FATAL: Error determining project root: ${error.message}`);
|
|
36
|
+
projectRoot = process.cwd();
|
|
37
|
+
console.warn(`Warning: Using process.cwd() (${projectRoot}) as fallback project root.`);
|
|
38
|
+
}
|
|
39
|
+
// --- End Determine Project Root ---
|
|
40
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
41
|
+
let pkg = { name: "obsidian-mcp-server", version: "0.0.0" };
|
|
42
|
+
try {
|
|
43
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (process.stderr.isTTY) {
|
|
47
|
+
console.error("Warning: Could not read package.json for default config values. Using hardcoded defaults.", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Zod schema for validating environment variables.
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
const EnvSchema = z.object({
|
|
55
|
+
MCP_SERVER_NAME: z.string().optional(),
|
|
56
|
+
MCP_SERVER_VERSION: z.string().optional(),
|
|
57
|
+
MCP_LOG_LEVEL: z.string().default("info"),
|
|
58
|
+
LOGS_DIR: z.string().default(path.join(projectRoot, "logs")),
|
|
59
|
+
NODE_ENV: z.string().default("development"),
|
|
60
|
+
MCP_TRANSPORT_TYPE: z.enum(["stdio", "http"]).default("stdio"),
|
|
61
|
+
MCP_HTTP_PORT: z.coerce.number().int().positive().default(3010),
|
|
62
|
+
MCP_HTTP_HOST: z.string().default("127.0.0.1"),
|
|
63
|
+
MCP_ALLOWED_ORIGINS: z.string().optional(),
|
|
64
|
+
MCP_AUTH_MODE: z.enum(["jwt", "oauth"]).optional(),
|
|
65
|
+
MCP_AUTH_SECRET_KEY: z
|
|
66
|
+
.string()
|
|
67
|
+
.min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long for security")
|
|
68
|
+
.optional(),
|
|
69
|
+
OAUTH_ISSUER_URL: z.string().url().optional(),
|
|
70
|
+
OAUTH_AUDIENCE: z.string().optional(),
|
|
71
|
+
OAUTH_JWKS_URI: z.string().url().optional(),
|
|
72
|
+
// --- Obsidian Specific Config ---
|
|
73
|
+
OBSIDIAN_API_KEY: z.string().min(1, "OBSIDIAN_API_KEY cannot be empty"),
|
|
74
|
+
OBSIDIAN_BASE_URL: z.string().url().default("http://127.0.0.1:27123"),
|
|
75
|
+
OBSIDIAN_VERIFY_SSL: z
|
|
76
|
+
.string()
|
|
77
|
+
.transform((val) => val.toLowerCase() === "true")
|
|
78
|
+
.default("false"),
|
|
79
|
+
OBSIDIAN_CACHE_REFRESH_INTERVAL_MIN: z.coerce
|
|
80
|
+
.number()
|
|
81
|
+
.int()
|
|
82
|
+
.positive()
|
|
83
|
+
.default(10),
|
|
84
|
+
OBSIDIAN_ENABLE_CACHE: z
|
|
85
|
+
.string()
|
|
86
|
+
.transform((val) => val.toLowerCase() === "true")
|
|
87
|
+
.default("true"),
|
|
88
|
+
OBSIDIAN_API_SEARCH_TIMEOUT_MS: z.coerce
|
|
89
|
+
.number()
|
|
90
|
+
.int()
|
|
91
|
+
.positive()
|
|
92
|
+
.default(30000),
|
|
93
|
+
});
|
|
94
|
+
const parsedEnv = EnvSchema.safeParse(process.env);
|
|
95
|
+
if (!parsedEnv.success) {
|
|
96
|
+
const errorDetails = parsedEnv.error.flatten().fieldErrors;
|
|
97
|
+
if (process.stderr.isTTY) {
|
|
98
|
+
console.error("❌ Invalid environment variables:", errorDetails);
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Invalid environment configuration. Please check your .env file or environment variables. Details: ${JSON.stringify(errorDetails)}`);
|
|
101
|
+
}
|
|
102
|
+
const env = parsedEnv.data;
|
|
103
|
+
// --- Directory Ensurance Function ---
|
|
104
|
+
const ensureDirectory = (dirPath, rootDir, dirName) => {
|
|
105
|
+
const resolvedDirPath = path.isAbsolute(dirPath)
|
|
106
|
+
? dirPath
|
|
107
|
+
: path.resolve(rootDir, dirPath);
|
|
108
|
+
if (!resolvedDirPath.startsWith(rootDir + path.sep) &&
|
|
109
|
+
resolvedDirPath !== rootDir) {
|
|
110
|
+
if (process.stderr.isTTY) {
|
|
111
|
+
console.error(`Error: ${dirName} path "${dirPath}" resolves to "${resolvedDirPath}", which is outside the project boundary "${rootDir}".`);
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (!existsSync(resolvedDirPath)) {
|
|
116
|
+
try {
|
|
117
|
+
mkdirSync(resolvedDirPath, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (process.stderr.isTTY) {
|
|
121
|
+
console.error(`Error creating ${dirName} directory at ${resolvedDirPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
try {
|
|
128
|
+
if (!statSync(resolvedDirPath).isDirectory()) {
|
|
129
|
+
if (process.stderr.isTTY) {
|
|
130
|
+
console.error(`Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`);
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (statError) {
|
|
136
|
+
if (process.stderr.isTTY) {
|
|
137
|
+
console.error(`Error accessing ${dirName} path ${resolvedDirPath}: ${statError.message}`);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return resolvedDirPath;
|
|
143
|
+
};
|
|
144
|
+
// --- End Directory Ensurance Function ---
|
|
145
|
+
const validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs");
|
|
146
|
+
if (!validatedLogsPath) {
|
|
147
|
+
if (process.stderr.isTTY) {
|
|
148
|
+
console.error("FATAL: Logs directory configuration is invalid or could not be created. Please check permissions and path. Exiting.");
|
|
149
|
+
}
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Main application configuration object.
|
|
154
|
+
*/
|
|
155
|
+
export const config = {
|
|
156
|
+
pkg,
|
|
157
|
+
mcpServerName: env.MCP_SERVER_NAME || pkg.name,
|
|
158
|
+
mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
|
|
159
|
+
logLevel: env.MCP_LOG_LEVEL,
|
|
160
|
+
logsPath: validatedLogsPath,
|
|
161
|
+
environment: env.NODE_ENV,
|
|
162
|
+
mcpTransportType: env.MCP_TRANSPORT_TYPE,
|
|
163
|
+
mcpHttpPort: env.MCP_HTTP_PORT,
|
|
164
|
+
mcpHttpHost: env.MCP_HTTP_HOST,
|
|
165
|
+
mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",")
|
|
166
|
+
.map((origin) => origin.trim())
|
|
167
|
+
.filter(Boolean),
|
|
168
|
+
mcpAuthMode: env.MCP_AUTH_MODE,
|
|
169
|
+
mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY,
|
|
170
|
+
oauthIssuerUrl: env.OAUTH_ISSUER_URL,
|
|
171
|
+
oauthAudience: env.OAUTH_AUDIENCE,
|
|
172
|
+
oauthJwksUri: env.OAUTH_JWKS_URI,
|
|
173
|
+
obsidianApiKey: env.OBSIDIAN_API_KEY,
|
|
174
|
+
obsidianBaseUrl: env.OBSIDIAN_BASE_URL,
|
|
175
|
+
obsidianVerifySsl: env.OBSIDIAN_VERIFY_SSL,
|
|
176
|
+
obsidianCacheRefreshIntervalMin: env.OBSIDIAN_CACHE_REFRESH_INTERVAL_MIN,
|
|
177
|
+
obsidianEnableCache: env.OBSIDIAN_ENABLE_CACHE,
|
|
178
|
+
obsidianApiSearchTimeoutMs: env.OBSIDIAN_API_SEARCH_TIMEOUT_MS,
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* The configured logging level for the application.
|
|
182
|
+
* Exported separately for convenience (e.g., logger initialization).
|
|
183
|
+
* @type {string}
|
|
184
|
+
*/
|
|
185
|
+
export const logLevel = config.logLevel;
|
|
186
|
+
/**
|
|
187
|
+
* The configured runtime environment for the application.
|
|
188
|
+
* Exported separately for convenience.
|
|
189
|
+
* @type {string}
|
|
190
|
+
*/
|
|
191
|
+
export const environment = config.environment;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,20 +1,298 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { config, environment } from "./config/index.js"; // This loads .env via dotenv.config()
|
|
4
|
+
import { initializeAndStartServer } from "./mcp-server/server.js";
|
|
5
|
+
import { requestContextService, retryWithDelay } from "./utils/index.js";
|
|
6
|
+
import { logger } from "./utils/internal/logger.js"; // Import logger instance early
|
|
7
|
+
// Import Services
|
|
8
|
+
import { ObsidianRestApiService } from "./services/obsidianRestAPI/index.js";
|
|
9
|
+
import { VaultCacheService } from "./services/obsidianRestAPI/vaultCache/index.js"; // Import VaultCacheService
|
|
10
|
+
/**
|
|
11
|
+
* The main MCP server instance (only stored globally for stdio shutdown).
|
|
12
|
+
* @type {McpServer | undefined}
|
|
13
|
+
*/
|
|
14
|
+
let server;
|
|
15
|
+
/**
|
|
16
|
+
* The main HTTP server instance (only stored globally for http shutdown).
|
|
17
|
+
* @type {ServerType | undefined}
|
|
18
|
+
*/
|
|
19
|
+
let httpServerInstance;
|
|
20
|
+
/**
|
|
21
|
+
* Shared Obsidian REST API service instance.
|
|
22
|
+
* @type {ObsidianRestApiService | undefined}
|
|
23
|
+
*/
|
|
24
|
+
let obsidianService;
|
|
25
|
+
/**
|
|
26
|
+
* Shared Vault Cache service instance.
|
|
27
|
+
* @type {VaultCacheService | undefined}
|
|
28
|
+
*/
|
|
29
|
+
let vaultCacheService;
|
|
30
|
+
/**
|
|
31
|
+
* Gracefully shuts down the main MCP server.
|
|
32
|
+
* Handles process termination signals (SIGTERM, SIGINT) and critical errors.
|
|
33
|
+
*
|
|
34
|
+
* @param signal - The signal or event name that triggered the shutdown (e.g., "SIGTERM", "uncaughtException").
|
|
35
|
+
*/
|
|
36
|
+
const shutdown = async (signal) => {
|
|
37
|
+
// Define context for the shutdown operation
|
|
38
|
+
const shutdownContext = requestContextService.createRequestContext({
|
|
39
|
+
operation: "Shutdown",
|
|
40
|
+
signal,
|
|
41
|
+
});
|
|
42
|
+
logger.info(`Received ${signal}. Starting graceful shutdown...`, shutdownContext);
|
|
43
|
+
try {
|
|
44
|
+
// Stop cache refresh timer first
|
|
45
|
+
if (config.obsidianEnableCache && vaultCacheService) {
|
|
46
|
+
vaultCacheService.stopPeriodicRefresh();
|
|
47
|
+
}
|
|
48
|
+
// Close the main MCP server (only relevant for stdio)
|
|
49
|
+
if (server) {
|
|
50
|
+
logger.info("Closing main MCP server (stdio)...", shutdownContext);
|
|
51
|
+
await server.close();
|
|
52
|
+
logger.info("Main MCP server (stdio) closed successfully", shutdownContext);
|
|
53
|
+
}
|
|
54
|
+
// Close the main HTTP server instance (if it exists)
|
|
55
|
+
if (httpServerInstance) {
|
|
56
|
+
logger.info("Closing main HTTP server...", shutdownContext);
|
|
57
|
+
await new Promise((resolve, reject) => {
|
|
58
|
+
httpServerInstance.close((err) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
logger.error("Error closing HTTP server", err, shutdownContext);
|
|
61
|
+
reject(err);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
logger.info("Main HTTP server closed successfully", shutdownContext);
|
|
65
|
+
resolve();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (!server && !httpServerInstance) {
|
|
70
|
+
logger.warning("No server instance (Stdio or HTTP) found to close during shutdown.", shutdownContext);
|
|
71
|
+
}
|
|
72
|
+
// Add any other necessary cleanup here (e.g., closing database connections if added later)
|
|
73
|
+
logger.info("Graceful shutdown completed successfully", shutdownContext);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// Handle any errors during shutdown
|
|
78
|
+
logger.error("Critical error during shutdown", error instanceof Error ? error : undefined, {
|
|
79
|
+
...shutdownContext, // Spread the existing RequestContext
|
|
80
|
+
// error field is handled by logger.error's second argument
|
|
81
|
+
});
|
|
82
|
+
process.exit(1); // Exit with error code if shutdown fails
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Initializes and starts the main MCP server.
|
|
87
|
+
* Sets up request context, initializes the server instance, starts the transport,
|
|
88
|
+
* and registers signal handlers for graceful shutdown and error handling.
|
|
89
|
+
*/
|
|
90
|
+
const start = async () => {
|
|
91
|
+
// --- Logger Initialization (Moved here AFTER config/dotenv is loaded) ---
|
|
92
|
+
const validMcpLogLevels = [
|
|
93
|
+
"debug",
|
|
94
|
+
"info",
|
|
95
|
+
"notice",
|
|
96
|
+
"warning",
|
|
97
|
+
"error",
|
|
98
|
+
"crit",
|
|
99
|
+
"alert",
|
|
100
|
+
"emerg",
|
|
101
|
+
];
|
|
102
|
+
// Read level from config (which read from env var or default)
|
|
103
|
+
const initialLogLevelConfig = config.logLevel;
|
|
104
|
+
// Validate the configured log level
|
|
105
|
+
let validatedMcpLogLevel = "info"; // Default to 'info'
|
|
106
|
+
if (validMcpLogLevels.includes(initialLogLevelConfig)) {
|
|
107
|
+
validatedMcpLogLevel = initialLogLevelConfig;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Use console.warn here as logger isn't initialized yet
|
|
111
|
+
console.warn(`Invalid MCP_LOG_LEVEL "${initialLogLevelConfig}" provided via config/env. Defaulting to "info".`);
|
|
112
|
+
}
|
|
113
|
+
// Initialize the logger with the validated MCP level and wait for it to complete.
|
|
114
|
+
await logger.initialize(validatedMcpLogLevel);
|
|
115
|
+
// Log initialization message using the logger itself (will go to file/console)
|
|
116
|
+
logger.info(`Logger initialized by start(). MCP logging level: ${validatedMcpLogLevel}`);
|
|
117
|
+
// --- End Logger Initialization ---
|
|
118
|
+
// Log that config is loaded (this was previously done earlier)
|
|
119
|
+
logger.debug("Configuration loaded successfully", requestContextService.createRequestContext({
|
|
120
|
+
configLoaded: true,
|
|
121
|
+
configSummary: {
|
|
122
|
+
serverName: config.mcpServerName,
|
|
123
|
+
transport: config.mcpTransportType,
|
|
124
|
+
logLevel: config.logLevel,
|
|
125
|
+
},
|
|
126
|
+
}));
|
|
127
|
+
// Create application-level request context using the service instance
|
|
128
|
+
// Use the validated transport type from the config object
|
|
129
|
+
const transportType = config.mcpTransportType;
|
|
130
|
+
const startupContext = requestContextService.createRequestContext({
|
|
131
|
+
operation: `ServerStartup_${transportType}`, // Include transport in operation name
|
|
132
|
+
appName: config.mcpServerName,
|
|
133
|
+
appVersion: config.mcpServerVersion,
|
|
134
|
+
environment: environment,
|
|
12
135
|
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
136
|
+
logger.info(`Starting ${config.mcpServerName} v${config.mcpServerVersion} (Transport: ${transportType})...`, startupContext);
|
|
137
|
+
try {
|
|
138
|
+
// --- Instantiate Shared Services ---
|
|
139
|
+
logger.debug("Instantiating shared services...", startupContext);
|
|
140
|
+
obsidianService = new ObsidianRestApiService(); // Instantiate Obsidian Service
|
|
141
|
+
// --- Perform Initial Obsidian API Status Check ---
|
|
142
|
+
try {
|
|
143
|
+
logger.info("Performing initial Obsidian API status check with retries...", startupContext);
|
|
144
|
+
const status = await retryWithDelay(async () => {
|
|
145
|
+
if (!obsidianService) {
|
|
146
|
+
// This case should not happen in practice, but it satisfies the type checker.
|
|
147
|
+
throw new Error("Obsidian service not initialized.");
|
|
148
|
+
}
|
|
149
|
+
const checkStatusContext = {
|
|
150
|
+
...startupContext,
|
|
151
|
+
operation: "checkStatusAttempt",
|
|
152
|
+
};
|
|
153
|
+
const currentStatus = await obsidianService.checkStatus(checkStatusContext);
|
|
154
|
+
if (currentStatus?.service !== "Obsidian Local REST API" ||
|
|
155
|
+
!currentStatus?.authenticated) {
|
|
156
|
+
// Throw an error to trigger a retry
|
|
157
|
+
throw new Error(`Obsidian API status check failed or indicates authentication issue. Status: ${JSON.stringify(currentStatus)}`);
|
|
158
|
+
}
|
|
159
|
+
return currentStatus;
|
|
160
|
+
}, {
|
|
161
|
+
operationName: "initialObsidianApiCheck",
|
|
162
|
+
context: startupContext,
|
|
163
|
+
maxRetries: 5, // Retry up to 5 times
|
|
164
|
+
delayMs: 3000, // Wait 3 seconds between retries
|
|
165
|
+
});
|
|
166
|
+
logger.info("Obsidian API status check successful.", {
|
|
167
|
+
...startupContext,
|
|
168
|
+
obsidianVersion: status.versions.obsidian,
|
|
169
|
+
pluginVersion: status.versions.self,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (statusError) {
|
|
173
|
+
logger.error("Critical error during initial Obsidian API status check after multiple retries. Check OBSIDIAN_BASE_URL, OBSIDIAN_API_KEY, and plugin status.", {
|
|
174
|
+
...startupContext,
|
|
175
|
+
error: statusError instanceof Error
|
|
176
|
+
? statusError.message
|
|
177
|
+
: String(statusError),
|
|
178
|
+
stack: statusError instanceof Error ? statusError.stack : undefined,
|
|
179
|
+
});
|
|
180
|
+
// Re-throw the final error to be caught by the main startup catch block, which will exit the process.
|
|
181
|
+
throw statusError;
|
|
182
|
+
}
|
|
183
|
+
// --- End Status Check ---
|
|
184
|
+
if (config.obsidianEnableCache) {
|
|
185
|
+
vaultCacheService = new VaultCacheService(obsidianService); // Instantiate Cache Service, passing Obsidian Service
|
|
186
|
+
logger.info("Vault cache is enabled and service is instantiated.", startupContext);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
logger.info("Vault cache is disabled by configuration.", startupContext);
|
|
190
|
+
}
|
|
191
|
+
logger.info("Shared services instantiated.", startupContext);
|
|
192
|
+
// --- End Service Instantiation ---
|
|
193
|
+
// Initialize the server instance and start the selected transport
|
|
194
|
+
logger.debug("Initializing and starting MCP server transport", startupContext);
|
|
195
|
+
// Start the server transport. Services are instantiated here and passed down.
|
|
196
|
+
// For stdio, this returns the McpServer instance.
|
|
197
|
+
// For http, it returns the http.Server instance.
|
|
198
|
+
const serverOrHttpInstance = await initializeAndStartServer(obsidianService, vaultCacheService);
|
|
199
|
+
if (transportType === "stdio" &&
|
|
200
|
+
serverOrHttpInstance instanceof McpServer) {
|
|
201
|
+
server = serverOrHttpInstance; // Store McpServer for stdio
|
|
202
|
+
logger.debug("Stored McpServer instance for stdio transport.", startupContext);
|
|
203
|
+
}
|
|
204
|
+
else if (transportType === "http" && serverOrHttpInstance) {
|
|
205
|
+
// The instance is of ServerType (http.Server or https.Server)
|
|
206
|
+
httpServerInstance = serverOrHttpInstance; // Store ServerType for http transport
|
|
207
|
+
logger.debug("Stored http.Server instance for http transport.", startupContext);
|
|
208
|
+
}
|
|
209
|
+
else if (transportType === "http") {
|
|
210
|
+
// This case should ideally not be reached if startHttpTransport always returns an http.Server
|
|
211
|
+
logger.warning("HTTP transport selected, but initializeAndStartServer did not return an http.Server instance.", startupContext);
|
|
212
|
+
}
|
|
213
|
+
// If initializeAndStartServer failed, it would have thrown an error,
|
|
214
|
+
// and execution would jump to the outer catch block.
|
|
215
|
+
logger.info(`${config.mcpServerName} is running with ${transportType} transport`, {
|
|
216
|
+
...startupContext,
|
|
217
|
+
startTime: new Date().toISOString(),
|
|
218
|
+
});
|
|
219
|
+
// --- Trigger Background Cache Build ---
|
|
220
|
+
if (config.obsidianEnableCache && vaultCacheService) {
|
|
221
|
+
// Start building the cache, but don't wait for it to finish.
|
|
222
|
+
// The server will be operational while the cache builds.
|
|
223
|
+
// Tools needing the cache should check its readiness state.
|
|
224
|
+
logger.info("Triggering background vault cache build...", startupContext);
|
|
225
|
+
// No 'await' here - run in background
|
|
226
|
+
vaultCacheService
|
|
227
|
+
.buildVaultCache()
|
|
228
|
+
.then(() => {
|
|
229
|
+
// Once the initial build is done, start the periodic refresh
|
|
230
|
+
vaultCacheService?.startPeriodicRefresh();
|
|
231
|
+
})
|
|
232
|
+
.catch((cacheBuildError) => {
|
|
233
|
+
// Log errors during the background build process
|
|
234
|
+
logger.error("Error occurred during background vault cache build", {
|
|
235
|
+
...startupContext, // Use startup context for correlation
|
|
236
|
+
operation: "BackgroundCacheBuild",
|
|
237
|
+
error: cacheBuildError instanceof Error
|
|
238
|
+
? cacheBuildError.message
|
|
239
|
+
: String(cacheBuildError),
|
|
240
|
+
stack: cacheBuildError instanceof Error
|
|
241
|
+
? cacheBuildError.stack
|
|
242
|
+
: undefined,
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// --- End Cache Build Trigger ---
|
|
247
|
+
// --- Signal and Error Handling Setup ---
|
|
248
|
+
// Handle process signals for graceful shutdown
|
|
249
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
250
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
251
|
+
// Handle uncaught exceptions
|
|
252
|
+
process.on("uncaughtException", async (error) => {
|
|
253
|
+
const errorContext = {
|
|
254
|
+
...startupContext, // Include base context for correlation
|
|
255
|
+
event: "uncaughtException",
|
|
256
|
+
error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
258
|
+
};
|
|
259
|
+
logger.error("Uncaught exception detected. Initiating shutdown...", errorContext);
|
|
260
|
+
// Attempt graceful shutdown; shutdown() handles its own errors.
|
|
261
|
+
await shutdown("uncaughtException");
|
|
262
|
+
// If shutdown fails internally, it will call process.exit(1).
|
|
263
|
+
// If shutdown succeeds, it calls process.exit(0).
|
|
264
|
+
// If shutdown itself throws unexpectedly *before* exiting, this process might terminate abruptly,
|
|
265
|
+
// but the core shutdown logic is handled within shutdown().
|
|
266
|
+
});
|
|
267
|
+
// Handle unhandled promise rejections
|
|
268
|
+
process.on("unhandledRejection", async (reason) => {
|
|
269
|
+
const rejectionContext = {
|
|
270
|
+
...startupContext, // Include base context for correlation
|
|
271
|
+
event: "unhandledRejection",
|
|
272
|
+
reason: reason instanceof Error ? reason.message : String(reason),
|
|
273
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
274
|
+
};
|
|
275
|
+
logger.error("Unhandled promise rejection detected. Initiating shutdown...", rejectionContext);
|
|
276
|
+
// Attempt graceful shutdown; shutdown() handles its own errors.
|
|
277
|
+
await shutdown("unhandledRejection");
|
|
278
|
+
// Similar logic as uncaughtException: shutdown handles its exit codes.
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
// Handle critical startup errors (already logged by ErrorHandler or caught above)
|
|
283
|
+
// Log the final failure context, including error details, before exiting
|
|
284
|
+
logger.error("Critical error during startup, exiting.", {
|
|
285
|
+
...startupContext,
|
|
286
|
+
finalErrorContext: "Startup Failure",
|
|
287
|
+
error: error instanceof Error ? error.message : String(error),
|
|
288
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
289
|
+
});
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
// --- Async IIFE to allow top-level await ---
|
|
294
|
+
// This remains necessary because start() is async
|
|
295
|
+
(async () => {
|
|
296
|
+
// Start the application
|
|
297
|
+
await start();
|
|
298
|
+
})(); // End async IIFE
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main entry point for the MCP (Model Context Protocol) server.
|
|
3
|
+
* This file orchestrates the server's lifecycle:
|
|
4
|
+
* 1. Initializes the core `McpServer` instance (from `@modelcontextprotocol/sdk`) with its identity and capabilities.
|
|
5
|
+
* 2. Registers available resources and tools, making them discoverable and usable by clients.
|
|
6
|
+
* 3. Selects and starts the appropriate communication transport (stdio or Streamable HTTP)
|
|
7
|
+
* based on configuration.
|
|
8
|
+
* 4. Handles top-level error management during startup.
|
|
9
|
+
*
|
|
10
|
+
* MCP Specification References:
|
|
11
|
+
* - Lifecycle: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/lifecycle.mdx
|
|
12
|
+
* - Overview (Capabilities): https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/index.mdx
|
|
13
|
+
* - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx
|
|
14
|
+
* @module src/mcp-server/server
|
|
15
|
+
*/
|
|
16
|
+
import { ServerType } from "@hono/node-server";
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { ObsidianRestApiService } from "../services/obsidianRestAPI/index.js";
|
|
19
|
+
import { VaultCacheService } from "../services/obsidianRestAPI/vaultCache/index.js";
|
|
20
|
+
/**
|
|
21
|
+
* Main application entry point. Initializes services and starts the MCP server.
|
|
22
|
+
* Orchestrates server startup, transport selection, and top-level error handling.
|
|
23
|
+
*
|
|
24
|
+
* MCP Spec Relevance:
|
|
25
|
+
* - Manages server startup, leading to a server ready for MCP messages.
|
|
26
|
+
* - Handles critical startup failures, ensuring appropriate process exit.
|
|
27
|
+
*
|
|
28
|
+
* @param {ObsidianRestApiService} obsidianService - The shared Obsidian REST API service instance, instantiated by the caller (e.g., index.ts).
|
|
29
|
+
* @param {VaultCacheService | undefined} vaultCacheService - The shared Vault Cache service instance, instantiated by the caller (e.g., index.ts).
|
|
30
|
+
* @returns {Promise<void | McpServer>} For 'stdio', resolves with `McpServer`. For 'http', runs indefinitely.
|
|
31
|
+
* Rejects on critical failure, leading to process exit.
|
|
32
|
+
*/
|
|
33
|
+
export declare function initializeAndStartServer(obsidianService: ObsidianRestApiService, vaultCacheService: VaultCacheService | undefined): Promise<void | McpServer | ServerType>;
|