mcp-obsidian-cli 1.0.1 → 1.2.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/lib/helpers.js +62 -0
- package/package.json +7 -2
- package/prompts/obsidian-bases.md +380 -0
- package/prompts/obsidian-canvas.md +312 -0
- package/prompts/obsidian-cli.md +115 -0
- package/prompts/obsidian-markdown.md +224 -0
- package/server.js +131 -135
- package/.claude/settings.local.json +0 -15
package/server.js
CHANGED
|
@@ -22,43 +22,30 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
22
22
|
import { z } from "zod";
|
|
23
23
|
import { execFile } from "node:child_process";
|
|
24
24
|
import { promisify } from "node:util";
|
|
25
|
-
import { readFileSync,
|
|
26
|
-
import { load as yamlLoad } from "js-yaml";
|
|
25
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
27
26
|
import { homedir } from "node:os";
|
|
28
|
-
import { join } from "node:path";
|
|
27
|
+
import { join, dirname } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
29
|
import { exec } from "node:child_process";
|
|
30
|
+
import { loadConfig, parseArgs, text, errorResult } from "./lib/helpers.js";
|
|
30
31
|
|
|
31
32
|
const execFileAsync = promisify(execFile);
|
|
32
33
|
const execAsync = promisify(exec);
|
|
33
34
|
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const PROMPTS_DIR = join(__dirname, "prompts");
|
|
37
|
+
|
|
38
|
+
const promptContent = {
|
|
39
|
+
"obsidian-cli": readFileSync(join(PROMPTS_DIR, "obsidian-cli.md"), "utf8"),
|
|
40
|
+
"obsidian-markdown": readFileSync(join(PROMPTS_DIR, "obsidian-markdown.md"), "utf8"),
|
|
41
|
+
"obsidian-bases": readFileSync(join(PROMPTS_DIR, "obsidian-bases.md"), "utf8"),
|
|
42
|
+
"obsidian-canvas": readFileSync(join(PROMPTS_DIR, "obsidian-canvas.md"), "utf8"),
|
|
43
|
+
};
|
|
44
|
+
|
|
34
45
|
const configBase = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
35
46
|
const CONFIG_DIR = join(configBase, "mcp-obsidian-cli");
|
|
36
47
|
const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
|
|
37
48
|
|
|
38
|
-
function loadConfig() {
|
|
39
|
-
const defaults = { vault: "", cliPath: "obsidian", timeoutMs: 15000 };
|
|
40
|
-
let config = { ...defaults };
|
|
41
|
-
|
|
42
|
-
if (existsSync(CONFIG_FILE)) {
|
|
43
|
-
try {
|
|
44
|
-
const content = readFileSync(CONFIG_FILE, "utf8");
|
|
45
|
-
const fileConfig = yamlLoad(content);
|
|
46
|
-
if (fileConfig) {
|
|
47
|
-
if (fileConfig.vault) config.vault = fileConfig.vault;
|
|
48
|
-
if (fileConfig.cliPath) config.cliPath = fileConfig.cliPath;
|
|
49
|
-
if (fileConfig.timeoutMs) config.timeoutMs = fileConfig.timeoutMs;
|
|
50
|
-
}
|
|
51
|
-
} catch (err) {
|
|
52
|
-
console.error("Warning: failed to load config file:", err.message);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (process.env.OBSIDIAN_VAULT) config.vault = process.env.OBSIDIAN_VAULT;
|
|
57
|
-
if (process.env.OBSIDIAN_CLI_PATH) config.cliPath = process.env.OBSIDIAN_CLI_PATH;
|
|
58
|
-
if (process.env.OBSIDIAN_TIMEOUT_MS) config.timeoutMs = parseInt(process.env.OBSIDIAN_TIMEOUT_MS, 10);
|
|
59
|
-
|
|
60
|
-
return config;
|
|
61
|
-
}
|
|
62
49
|
|
|
63
50
|
const KNOWN_CLI_PATHS = [
|
|
64
51
|
"/Applications/Obsidian.app/Contents/MacOS/obsidian",
|
|
@@ -89,35 +76,20 @@ async function resolveCliPath(configured) {
|
|
|
89
76
|
return configured;
|
|
90
77
|
}
|
|
91
78
|
|
|
92
|
-
const config = loadConfig();
|
|
79
|
+
const config = loadConfig(CONFIG_FILE);
|
|
93
80
|
const CLI = await resolveCliPath(config.cliPath);
|
|
94
81
|
const VAULT = config.vault;
|
|
95
82
|
const TIMEOUT_MS = config.timeoutMs;
|
|
96
83
|
|
|
97
84
|
async function checkObsidianRunning() {
|
|
98
85
|
try {
|
|
99
|
-
const { stdout
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
stdout.includes("Checking for update") ||
|
|
107
|
-
stdout.includes("App is up to date") ||
|
|
108
|
-
stdout.includes("Latest version is");
|
|
109
|
-
if (hasStartupMsg) {
|
|
110
|
-
return { running: false, version: null };
|
|
111
|
-
}
|
|
112
|
-
if (stdout.includes("(installer")) {
|
|
113
|
-
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
114
|
-
if (match) {
|
|
115
|
-
return { running: true, version: match[1] };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return { running: false, version: null };
|
|
119
|
-
} catch (err) {
|
|
120
|
-
return { running: false, version: null };
|
|
86
|
+
const { stdout } = await execAsync(
|
|
87
|
+
"ps aux | grep -i obsidian | grep -v grep | grep -v Helper",
|
|
88
|
+
{ timeout: 2000 }
|
|
89
|
+
);
|
|
90
|
+
return stdout.includes("/Applications/Obsidian.app");
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
121
93
|
}
|
|
122
94
|
}
|
|
123
95
|
|
|
@@ -129,8 +101,8 @@ async function checkObsidianRunning() {
|
|
|
129
101
|
* Run the Obsidian CLI with the given argument string.
|
|
130
102
|
* Returns { stdout, stderr } or throws on non-zero exit / timeout.
|
|
131
103
|
*/
|
|
132
|
-
async function run(
|
|
133
|
-
const args = parseArgs(
|
|
104
|
+
async function run(input) {
|
|
105
|
+
const args = Array.isArray(input) ? [...input] : parseArgs(input);
|
|
134
106
|
if (VAULT) args.push(`vault=${VAULT}`);
|
|
135
107
|
|
|
136
108
|
try {
|
|
@@ -166,35 +138,10 @@ async function run(argString) {
|
|
|
166
138
|
}
|
|
167
139
|
}
|
|
168
140
|
|
|
169
|
-
/**
|
|
170
|
-
* Minimal arg parser: splits on whitespace but respects key="value with spaces".
|
|
171
|
-
*/
|
|
172
|
-
function parseArgs(str) {
|
|
173
|
-
const args = [];
|
|
174
|
-
const re = /(?:[^\s"]+|"[^"]*")+/g;
|
|
175
|
-
let m;
|
|
176
|
-
while ((m = re.exec(str)) !== null) {
|
|
177
|
-
args.push(m[0]);
|
|
178
|
-
}
|
|
179
|
-
return args;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Standard MCP text result. */
|
|
183
|
-
function text(content) {
|
|
184
|
-
return { content: [{ type: "text", text: content }] };
|
|
185
|
-
}
|
|
186
141
|
|
|
187
|
-
/**
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
content: [{ type: "text", text: content }],
|
|
191
|
-
isError: true,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Run CLI, return MCP result. */
|
|
196
|
-
async function runTool(argString) {
|
|
197
|
-
const { stdout, stderr, error } = await run(argString);
|
|
142
|
+
/** Run CLI, return MCP result. Accepts a command string or an args array. */
|
|
143
|
+
async function runTool(input) {
|
|
144
|
+
const { stdout, stderr, error } = await run(input);
|
|
198
145
|
if (error) {
|
|
199
146
|
return errorResult(error.message, error.type);
|
|
200
147
|
}
|
|
@@ -210,7 +157,7 @@ async function runTool(argString) {
|
|
|
210
157
|
|
|
211
158
|
const server = new McpServer({
|
|
212
159
|
name: "obsidian-mcp",
|
|
213
|
-
version: "1.
|
|
160
|
+
version: "1.2.0",
|
|
214
161
|
capabilities: { tools: {} },
|
|
215
162
|
});
|
|
216
163
|
|
|
@@ -236,71 +183,73 @@ Examples:
|
|
|
236
183
|
|
|
237
184
|
server.tool(
|
|
238
185
|
"obsidian_daily_read",
|
|
239
|
-
"Read today's daily note contents.",
|
|
186
|
+
"Read today's daily note contents.\n\nReturns the full markdown content of today's daily note. Returns an error if no daily note exists for today.",
|
|
240
187
|
{},
|
|
241
188
|
async () => runTool("daily:read"),
|
|
242
189
|
);
|
|
243
190
|
|
|
244
191
|
server.tool(
|
|
245
192
|
"obsidian_daily_path",
|
|
246
|
-
"Get the file path of today's daily note.",
|
|
193
|
+
"Get the file path of today's daily note.\n\nReturns the vault-relative path (e.g. 'Daily/2026-04-03.md'). Useful for constructing paths for other tools.",
|
|
247
194
|
{},
|
|
248
195
|
async () => runTool("daily:path"),
|
|
249
196
|
);
|
|
250
197
|
|
|
251
198
|
server.tool(
|
|
252
199
|
"obsidian_daily_append",
|
|
253
|
-
"Append content to today's daily note
|
|
200
|
+
"Append content to today's daily note.\n\nParameters:\n content (required) — markdown text to append at the end of today's daily note\n\nExamples:\n obsidian_daily_append({ content: \"- Meeting with team at 3pm\" })\n obsidian_daily_append({ content: \"> [!tip] Remember\\n> Review PR before EOD\" })",
|
|
254
201
|
{ content: z.string().describe("Content to append") },
|
|
255
|
-
async ({ content }) => runTool(
|
|
202
|
+
async ({ content }) => runTool(["daily:append", `content=${content}`]),
|
|
256
203
|
);
|
|
257
204
|
|
|
258
205
|
server.tool(
|
|
259
206
|
"obsidian_read",
|
|
260
|
-
"Read a note by file name (wikilink-style) or exact path.",
|
|
207
|
+
"Read a note by file name (wikilink-style) or exact path.\n\nParameters:\n file (optional) — note name using wikilink resolution (e.g. 'My Note')\n path (optional) — exact vault-relative path (e.g. 'folder/My Note.md')\n One of file or path is required.\n\nExamples:\n obsidian_read({ file: \"Meeting Notes\" })\n obsidian_read({ path: \"Projects/todo.md\" })",
|
|
261
208
|
{
|
|
262
209
|
file: z.string().optional().describe("File name (wikilink resolution)"),
|
|
263
210
|
path: z.string().optional().describe("Exact file path"),
|
|
264
211
|
},
|
|
265
212
|
async ({ file, path }) => {
|
|
266
213
|
if (!file && !path) return text("Error: provide file= or path=");
|
|
267
|
-
const
|
|
268
|
-
|
|
214
|
+
const args = ["read"];
|
|
215
|
+
if (file) args.push(`file=${file}`);
|
|
216
|
+
if (path) args.push(`path=${path}`);
|
|
217
|
+
return runTool(args);
|
|
269
218
|
},
|
|
270
219
|
);
|
|
271
220
|
|
|
272
221
|
server.tool(
|
|
273
222
|
"obsidian_search",
|
|
274
|
-
"Full-text search across the vault with line context
|
|
223
|
+
"Full-text search across the vault with line context.\n\nParameters:\n query (required) — search terms, supports Obsidian query syntax\n path (optional) — restrict results to a folder path\n limit (optional) — max number of files to return\n\nExamples:\n obsidian_search({ query: \"meeting notes\" })\n obsidian_search({ query: \"project status\", path: \"Work/\", limit: 5 })",
|
|
275
224
|
{
|
|
276
225
|
query: z.string().describe("Search query"),
|
|
277
226
|
path: z.string().optional().describe("Limit to folder"),
|
|
278
227
|
limit: z.number().optional().describe("Max files to return"),
|
|
279
228
|
},
|
|
280
229
|
async ({ query, path, limit }) => {
|
|
281
|
-
|
|
282
|
-
if (path)
|
|
283
|
-
if (limit)
|
|
284
|
-
return runTool(
|
|
230
|
+
const args = ["search:context", `query=${query}`];
|
|
231
|
+
if (path) args.push(`path=${path}`);
|
|
232
|
+
if (limit) args.push(`limit=${limit}`);
|
|
233
|
+
return runTool(args);
|
|
285
234
|
},
|
|
286
235
|
);
|
|
287
236
|
|
|
288
237
|
server.tool(
|
|
289
238
|
"obsidian_tags",
|
|
290
|
-
"List tags in the vault with counts
|
|
239
|
+
"List tags in the vault with counts.\n\nParameters:\n sort (optional) — 'name' or 'count' (default: name)\n\nExamples:\n obsidian_tags({})\n obsidian_tags({ sort: \"count\" })",
|
|
291
240
|
{
|
|
292
241
|
sort: z.enum(["name", "count"]).optional().describe("Sort order"),
|
|
293
242
|
},
|
|
294
243
|
async ({ sort }) => {
|
|
295
|
-
|
|
296
|
-
if (sort)
|
|
297
|
-
return runTool(
|
|
244
|
+
const args = ["tags", "counts"];
|
|
245
|
+
if (sort) args.push(`sort=${sort}`);
|
|
246
|
+
return runTool(args);
|
|
298
247
|
},
|
|
299
248
|
);
|
|
300
249
|
|
|
301
250
|
server.tool(
|
|
302
251
|
"obsidian_tasks",
|
|
303
|
-
"List tasks
|
|
252
|
+
"List tasks from vault notes.\n\nParameters:\n daily (optional) — true to show only today's daily note tasks\n todo (optional) — true to show only incomplete tasks\n done (optional) — true to show only completed tasks\n path (optional) — filter by file path\n\nExamples:\n obsidian_tasks({ daily: true })\n obsidian_tasks({ todo: true, path: \"Projects/\" })",
|
|
304
253
|
{
|
|
305
254
|
daily: z.boolean().optional().describe("Show only daily note tasks"),
|
|
306
255
|
todo: z.boolean().optional().describe("Show incomplete tasks only"),
|
|
@@ -308,18 +257,18 @@ server.tool(
|
|
|
308
257
|
path: z.string().optional().describe("Filter by file path"),
|
|
309
258
|
},
|
|
310
259
|
async ({ daily, todo, done, path }) => {
|
|
311
|
-
|
|
312
|
-
if (daily)
|
|
313
|
-
if (todo)
|
|
314
|
-
if (done)
|
|
315
|
-
if (path)
|
|
316
|
-
return runTool(
|
|
260
|
+
const args = ["tasks"];
|
|
261
|
+
if (daily) args.push("daily");
|
|
262
|
+
if (todo) args.push("todo");
|
|
263
|
+
if (done) args.push("done");
|
|
264
|
+
if (path) args.push(`path=${path}`);
|
|
265
|
+
return runTool(args);
|
|
317
266
|
},
|
|
318
267
|
);
|
|
319
268
|
|
|
320
269
|
server.tool(
|
|
321
270
|
"obsidian_properties",
|
|
322
|
-
"List or read frontmatter properties
|
|
271
|
+
"List or read frontmatter properties.\n\nParameters:\n file (optional) — note name for wikilink resolution\n path (optional) — exact file path\n name (optional) — specific property name to read (requires file or path)\n\nExamples:\n obsidian_properties({}) — list all properties with counts\n obsidian_properties({ file: \"My Note\" }) — properties of a specific note\n obsidian_properties({ file: \"My Note\", name: \"status\" }) — read one property",
|
|
323
272
|
{
|
|
324
273
|
file: z.string().optional().describe("File name"),
|
|
325
274
|
path: z.string().optional().describe("File path"),
|
|
@@ -327,21 +276,22 @@ server.tool(
|
|
|
327
276
|
},
|
|
328
277
|
async ({ file, path, name }) => {
|
|
329
278
|
if (name && (file || path)) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
279
|
+
const args = ["property:read", `name=${name}`];
|
|
280
|
+
if (file) args.push(`file=${file}`);
|
|
281
|
+
if (path) args.push(`path=${path}`);
|
|
282
|
+
return runTool(args);
|
|
333
283
|
}
|
|
334
|
-
|
|
335
|
-
if (file)
|
|
336
|
-
if (path)
|
|
337
|
-
|
|
338
|
-
return runTool(
|
|
284
|
+
const args = ["properties"];
|
|
285
|
+
if (file) args.push(`file=${file}`);
|
|
286
|
+
if (path) args.push(`path=${path}`);
|
|
287
|
+
args.push("counts");
|
|
288
|
+
return runTool(args);
|
|
339
289
|
},
|
|
340
290
|
);
|
|
341
291
|
|
|
342
292
|
server.tool(
|
|
343
293
|
"obsidian_create",
|
|
344
|
-
"Create a new note.",
|
|
294
|
+
"Create a new note.\n\nParameters:\n name (optional) — file name for the new note\n path (optional) — vault-relative path\n content (optional) — initial markdown content\n template (optional) — template name to use\n\nExamples:\n obsidian_create({ name: \"Meeting 2026-04-03\", content: \"# Meeting Notes\\n\\n- Attendees: ...\" })\n obsidian_create({ path: \"Projects/new-idea.md\", template: \"project\" })",
|
|
345
295
|
{
|
|
346
296
|
name: z.string().optional().describe("File name"),
|
|
347
297
|
path: z.string().optional().describe("File path"),
|
|
@@ -349,18 +299,18 @@ server.tool(
|
|
|
349
299
|
template: z.string().optional().describe("Template to use"),
|
|
350
300
|
},
|
|
351
301
|
async ({ name, path, content, template }) => {
|
|
352
|
-
|
|
353
|
-
if (name)
|
|
354
|
-
if (path)
|
|
355
|
-
if (template)
|
|
356
|
-
if (content)
|
|
357
|
-
return runTool(
|
|
302
|
+
const args = ["create"];
|
|
303
|
+
if (name) args.push(`name=${name}`);
|
|
304
|
+
if (path) args.push(`path=${path}`);
|
|
305
|
+
if (template) args.push(`template=${template}`);
|
|
306
|
+
if (content) args.push(`content=${content}`);
|
|
307
|
+
return runTool(args);
|
|
358
308
|
},
|
|
359
309
|
);
|
|
360
310
|
|
|
361
311
|
server.tool(
|
|
362
312
|
"obsidian_property_set",
|
|
363
|
-
"Set a frontmatter property on a note.",
|
|
313
|
+
"Set a frontmatter property on a note.\n\nParameters:\n name (required) — property name\n value (required) — property value\n file (optional) — note name (wikilink resolution)\n path (optional) — exact file path\n One of file or path is required.\n\nExamples:\n obsidian_property_set({ name: \"status\", value: \"done\", file: \"My Task\" })\n obsidian_property_set({ name: \"tags\", value: \"project, active\", path: \"Work/todo.md\" })",
|
|
364
314
|
{
|
|
365
315
|
name: z.string().describe("Property name"),
|
|
366
316
|
value: z.string().describe("Property value"),
|
|
@@ -368,57 +318,103 @@ server.tool(
|
|
|
368
318
|
path: z.string().optional().describe("File path"),
|
|
369
319
|
},
|
|
370
320
|
async ({ name, value, file, path: filePath }) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
321
|
+
if (!file && !filePath) return text("Error: provide file= or path=");
|
|
322
|
+
const args = ["property:set", `name=${name}`, `value=${value}`];
|
|
323
|
+
if (file) args.push(`file=${file}`);
|
|
324
|
+
if (filePath) args.push(`path=${filePath}`);
|
|
325
|
+
return runTool(args);
|
|
374
326
|
},
|
|
375
327
|
);
|
|
376
328
|
|
|
377
329
|
server.tool(
|
|
378
330
|
"obsidian_backlinks",
|
|
379
|
-
"List backlinks to a note.",
|
|
331
|
+
"List backlinks to a note.\n\nParameters:\n file (optional) — note name (wikilink resolution)\n path (optional) — exact file path\n\nExamples:\n obsidian_backlinks({ file: \"Project Plan\" })\n obsidian_backlinks({ path: \"Ideas/brainstorm.md\" })",
|
|
380
332
|
{
|
|
381
333
|
file: z.string().optional().describe("File name"),
|
|
382
334
|
path: z.string().optional().describe("File path"),
|
|
383
335
|
},
|
|
384
336
|
async ({ file, path }) => {
|
|
385
|
-
const
|
|
386
|
-
|
|
337
|
+
const args = ["backlinks"];
|
|
338
|
+
if (file) args.push(`file=${file}`);
|
|
339
|
+
if (path) args.push(`path=${path}`);
|
|
340
|
+
args.push("counts");
|
|
341
|
+
return runTool(args);
|
|
387
342
|
},
|
|
388
343
|
);
|
|
389
344
|
|
|
390
345
|
server.tool(
|
|
391
346
|
"obsidian_files",
|
|
392
|
-
"List files in the vault or a specific folder.",
|
|
347
|
+
"List files in the vault or a specific folder.\n\nParameters:\n folder (optional) — filter by folder path\n ext (optional) — filter by file extension (e.g. 'md', 'canvas')\n\nExamples:\n obsidian_files({})\n obsidian_files({ folder: \"Projects/\", ext: \"md\" })",
|
|
393
348
|
{
|
|
394
349
|
folder: z.string().optional().describe("Filter by folder path"),
|
|
395
350
|
ext: z.string().optional().describe("Filter by extension"),
|
|
396
351
|
},
|
|
397
352
|
async ({ folder, ext }) => {
|
|
398
|
-
|
|
399
|
-
if (folder)
|
|
400
|
-
if (ext)
|
|
401
|
-
return runTool(
|
|
353
|
+
const args = ["files"];
|
|
354
|
+
if (folder) args.push(`folder=${folder}`);
|
|
355
|
+
if (ext) args.push(`ext=${ext}`);
|
|
356
|
+
return runTool(args);
|
|
402
357
|
},
|
|
403
358
|
);
|
|
404
359
|
|
|
405
360
|
server.tool(
|
|
406
361
|
"obsidian_recents",
|
|
407
|
-
"List recently opened files.",
|
|
362
|
+
"List recently opened files.\n\nReturns the most recently opened files in the vault, ordered by last access time.",
|
|
408
363
|
{},
|
|
409
364
|
async () => runTool("recents"),
|
|
410
365
|
);
|
|
411
366
|
|
|
367
|
+
server.tool(
|
|
368
|
+
"obsidian_help",
|
|
369
|
+
"Get Obsidian reference documentation on a topic.\n\nParameters:\n topic (required) — one of: cli, markdown, bases, canvas\n\nExamples:\n obsidian_help({ topic: \"markdown\" }) — wikilinks, embeds, callouts, properties, tags\n obsidian_help({ topic: \"bases\" }) — Bases YAML schema, filters, formulas, views\n obsidian_help({ topic: \"canvas\" }) — JSON Canvas nodes, edges, colors, layout\n obsidian_help({ topic: \"cli\" }) — CLI command syntax and parameter patterns",
|
|
370
|
+
{ topic: z.enum(["cli", "markdown", "bases", "canvas"]).describe("Reference topic") },
|
|
371
|
+
async ({ topic }) => ({
|
|
372
|
+
content: [{ type: "text", text: promptContent[`obsidian-${topic}`] }],
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// ---- MCP Prompts -----------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
const promptMeta = {
|
|
379
|
+
"obsidian-cli": {
|
|
380
|
+
title: "Obsidian CLI Reference",
|
|
381
|
+
description: "CLI usage patterns, parameter syntax, and command examples for the Obsidian CLI. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
382
|
+
},
|
|
383
|
+
"obsidian-markdown": {
|
|
384
|
+
title: "Obsidian Flavored Markdown Reference",
|
|
385
|
+
description: "Wikilinks, embeds, callouts, properties, tags, and other Obsidian-specific markdown syntax. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
386
|
+
},
|
|
387
|
+
"obsidian-bases": {
|
|
388
|
+
title: "Obsidian Bases Reference",
|
|
389
|
+
description: "Bases syntax for database-like views: filters, formulas, view types, and summaries. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
390
|
+
},
|
|
391
|
+
"obsidian-canvas": {
|
|
392
|
+
title: "JSON Canvas Reference",
|
|
393
|
+
description: "JSON Canvas format for visual canvases: node types, edges, groups, and layout. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
for (const [name, content] of Object.entries(promptContent)) {
|
|
398
|
+
const meta = promptMeta[name];
|
|
399
|
+
server.registerPrompt(
|
|
400
|
+
name,
|
|
401
|
+
{ title: meta.title, description: meta.description },
|
|
402
|
+
() => ({
|
|
403
|
+
messages: [{ role: "user", content: { type: "text", text: content } }],
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
412
408
|
// ---- Start ---------------------------------------------------------------
|
|
413
409
|
|
|
414
410
|
async function main() {
|
|
415
|
-
const
|
|
411
|
+
const running = await checkObsidianRunning();
|
|
416
412
|
if (!running) {
|
|
417
413
|
console.error("Error: Obsidian is not running. Please open Obsidian and try again.");
|
|
418
414
|
process.exit(1);
|
|
419
415
|
}
|
|
420
416
|
const transport = new StdioServerTransport();
|
|
421
|
-
console.error(
|
|
417
|
+
console.error("obsidian-mcp server running on stdio");
|
|
422
418
|
await server.connect(transport);
|
|
423
419
|
}
|
|
424
420
|
|