@sunub/obsidian-mcp-server 0.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 +93 -0
- package/build/index.js +21 -0
- package/build/server.js +31 -0
- package/build/tools/create_document_with_properties/index.js +145 -0
- package/build/tools/create_document_with_properties/params.js +32 -0
- package/build/tools/generate_property/index.js +124 -0
- package/build/tools/generate_property/params.js +54 -0
- package/build/tools/index.js +12 -0
- package/build/tools/organize_attachments/index.js +110 -0
- package/build/tools/organize_attachments/params.js +60 -0
- package/build/tools/organize_attachments/utils.js +65 -0
- package/build/tools/vault/index.js +146 -0
- package/build/tools/vault/params.js +123 -0
- package/build/tools/vault/types/list_all.js +52 -0
- package/build/tools/vault/types/read_specific.js +40 -0
- package/build/tools/vault/types/search.js +58 -0
- package/build/tools/vault/utils.js +163 -0
- package/build/tools/write_property/index.js +130 -0
- package/build/tools/write_property/params.js +51 -0
- package/build/utils/DirectoryWalker.js +45 -0
- package/build/utils/Indexer.js +139 -0
- package/build/utils/VaultManager.js +160 -0
- package/build/utils/getVaultManager.js +14 -0
- package/build/utils/isIterable.js +5 -0
- package/build/utils/isMatterTransformData.js +12 -0
- package/build/utils/parseVaultPath.js +18 -0
- package/build/utils/processor/LinkExtractor.js +30 -0
- package/build/utils/processor/MatterParser.js +19 -0
- package/build/utils/processor/types.js +48 -0
- package/build/utils/semaphore.js +23 -0
- package/package.json +57 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getParsedVaultPath } from "./parseVaultPath.js";
|
|
2
|
+
import { VaultManager } from "./VaultManager.js";
|
|
3
|
+
let instance = null;
|
|
4
|
+
export function getGlobalVaultManager() {
|
|
5
|
+
if (instance) {
|
|
6
|
+
return instance;
|
|
7
|
+
}
|
|
8
|
+
const vaultPath = getParsedVaultPath();
|
|
9
|
+
if (!vaultPath) {
|
|
10
|
+
throw new Error("VAULT_DIR_PATH environment variable is not set");
|
|
11
|
+
}
|
|
12
|
+
instance = new VaultManager(vaultPath, 20);
|
|
13
|
+
return instance;
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function isMatterTransformData(obj) {
|
|
2
|
+
return (typeof obj === "object" &&
|
|
3
|
+
obj !== null &&
|
|
4
|
+
"frontmatter" in obj &&
|
|
5
|
+
"contentLength" in obj &&
|
|
6
|
+
"hasContent" in obj &&
|
|
7
|
+
"content" in obj &&
|
|
8
|
+
typeof obj.frontmatter === "object" &&
|
|
9
|
+
typeof obj.contentLength === "number" &&
|
|
10
|
+
typeof obj.hasContent === "boolean" &&
|
|
11
|
+
typeof obj.content === "string");
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
dotenv.config({ debug: false });
|
|
4
|
+
export const VAULT_DIR_PATH = process.env.VAULT_DIR_PATH || "";
|
|
5
|
+
function isWindowsPath(path) {
|
|
6
|
+
return os.platform() === "win32" && /^[a-zA-Z]:\\/.test(path);
|
|
7
|
+
}
|
|
8
|
+
function parseWindosPathToLinux(path) {
|
|
9
|
+
if (!isWindowsPath(path)) {
|
|
10
|
+
throw new Error("Not a valid Windows path");
|
|
11
|
+
}
|
|
12
|
+
const driveLetter = path[0].toLowerCase();
|
|
13
|
+
const pathWithoutDrive = path.slice(2).replace(/\\/g, "/");
|
|
14
|
+
return `/mnt/${driveLetter}/${pathWithoutDrive}`;
|
|
15
|
+
}
|
|
16
|
+
export const getParsedVaultPath = () => isWindowsPath(VAULT_DIR_PATH)
|
|
17
|
+
? parseWindosPathToLinux(VAULT_DIR_PATH)
|
|
18
|
+
: VAULT_DIR_PATH;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function extractImageLinks(content) {
|
|
2
|
+
const imageLinks = [];
|
|
3
|
+
const wikiLinkRegex = /!\[\[([^\]]+)\]\]/g;
|
|
4
|
+
const markdownLinkRegex = /!\[.*?\]\((.*?)\)/g;
|
|
5
|
+
let match;
|
|
6
|
+
match = wikiLinkRegex.exec(content);
|
|
7
|
+
while (match !== null) {
|
|
8
|
+
imageLinks.push(match[1]);
|
|
9
|
+
match = wikiLinkRegex.exec(content);
|
|
10
|
+
}
|
|
11
|
+
match = markdownLinkRegex.exec(content);
|
|
12
|
+
while (match !== null) {
|
|
13
|
+
imageLinks.push(match[1]);
|
|
14
|
+
match = markdownLinkRegex.exec(content);
|
|
15
|
+
}
|
|
16
|
+
return imageLinks;
|
|
17
|
+
}
|
|
18
|
+
export function extractDocumentLinks(content) {
|
|
19
|
+
const docLinks = [];
|
|
20
|
+
const wikiLinkRegex = /(?<!!)\[\[([^\]]+)\]\]/g;
|
|
21
|
+
let match;
|
|
22
|
+
match = wikiLinkRegex.exec(content);
|
|
23
|
+
while (match !== null) {
|
|
24
|
+
// 링크에서 앨리어스(|)나 앵커(#) 부분을 제거하고 순수 파일 이름만 추출합니다.
|
|
25
|
+
const link = match[1].split(/\||#/)[0];
|
|
26
|
+
docLinks.push(link);
|
|
27
|
+
match = wikiLinkRegex.exec(content);
|
|
28
|
+
}
|
|
29
|
+
return docLinks;
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
import { FrontMatterSchema } from "./types.js";
|
|
3
|
+
export function parse(text) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = matter(text);
|
|
6
|
+
const frontmatter = FrontMatterSchema.parse(parsed.data);
|
|
7
|
+
return {
|
|
8
|
+
frontmatter,
|
|
9
|
+
content: parsed.content,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
console.warn("Frontmatter 파싱에 실패했습니다. 전체를 내용으로 간주합니다.");
|
|
14
|
+
return {
|
|
15
|
+
frontmatter: FrontMatterSchema.parse({}),
|
|
16
|
+
content: text,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const PostCategorySchema = z
|
|
3
|
+
.union([
|
|
4
|
+
z.literal("web"),
|
|
5
|
+
z.literal("algorithm"),
|
|
6
|
+
z.literal("cs"),
|
|
7
|
+
z.literal("code"),
|
|
8
|
+
])
|
|
9
|
+
.optional();
|
|
10
|
+
const DateStringSchema = z.union([z.string(), z.date()]).optional();
|
|
11
|
+
export const FrontMatterSchema = z.object({
|
|
12
|
+
title: z.string().optional(),
|
|
13
|
+
date: DateStringSchema,
|
|
14
|
+
tags: z.array(z.string()).optional(),
|
|
15
|
+
summary: z.string().optional(),
|
|
16
|
+
slug: z.string().optional(),
|
|
17
|
+
category: PostCategorySchema,
|
|
18
|
+
completed: z.boolean().optional(),
|
|
19
|
+
});
|
|
20
|
+
const CacheDataSchema = z.object({
|
|
21
|
+
content: z.string(),
|
|
22
|
+
data: FrontMatterSchema,
|
|
23
|
+
isEmpty: z.boolean(),
|
|
24
|
+
excerpt: z.string(),
|
|
25
|
+
cacheKey: z.string(),
|
|
26
|
+
category: PostCategorySchema,
|
|
27
|
+
date: DateStringSchema,
|
|
28
|
+
});
|
|
29
|
+
export const PostFrontMatterSchema = z.object({
|
|
30
|
+
frontmatter: FrontMatterSchema,
|
|
31
|
+
filePath: z.string(),
|
|
32
|
+
});
|
|
33
|
+
export const JsonPostFrontMatterSchema = z.object({
|
|
34
|
+
all: z.array(PostFrontMatterSchema),
|
|
35
|
+
web: z.array(PostFrontMatterSchema),
|
|
36
|
+
algorithm: z.array(PostFrontMatterSchema),
|
|
37
|
+
code: z.array(PostFrontMatterSchema),
|
|
38
|
+
cs: z.array(PostFrontMatterSchema),
|
|
39
|
+
});
|
|
40
|
+
export const DocumentIndexSchema = z.object({
|
|
41
|
+
filePath: z.string(),
|
|
42
|
+
frontmatter: FrontMatterSchema,
|
|
43
|
+
contentLength: z.number(),
|
|
44
|
+
imageLinks: z.array(z.string()),
|
|
45
|
+
documentLinks: z.array(z.string()),
|
|
46
|
+
});
|
|
47
|
+
export const DocumentIndexResponseSchema = z.array(DocumentIndexSchema);
|
|
48
|
+
export { CacheDataSchema, PostCategorySchema };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class Semaphore {
|
|
2
|
+
permits = 0;
|
|
3
|
+
waitingQueue = [];
|
|
4
|
+
constructor(permits) {
|
|
5
|
+
this.permits = permits;
|
|
6
|
+
}
|
|
7
|
+
async acquire() {
|
|
8
|
+
if (this.permits > 0) {
|
|
9
|
+
this.permits--;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
this.waitingQueue.push(resolve);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async release() {
|
|
17
|
+
const next = this.waitingQueue.shift();
|
|
18
|
+
if (next) {
|
|
19
|
+
this.permits++;
|
|
20
|
+
next();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sunub/obsidian-mcp-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A server for the Obsidian Model Context Protocol.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"obsidian",
|
|
7
|
+
"mcp",
|
|
8
|
+
"model context protocol"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": {
|
|
12
|
+
"obsidian-mcp-server": "build/index.js"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/sunub/obsidian-mcp-server",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/sunub/obsidian-mcp-server"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"build"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./build/index.js"
|
|
24
|
+
},
|
|
25
|
+
"module": "src/index.ts",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"smithery:dev": "npx @smithery/cli dev",
|
|
28
|
+
"smithery:build": "npx @smithery/cli build",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:watch": "vitest --watch",
|
|
31
|
+
"build": "tsc && tsc-alias && chmod 755 build/index.js",
|
|
32
|
+
"inspector": "npx -y @modelcontextprotocol/inspector node ./build/index.js",
|
|
33
|
+
"format": "npx biome format --write",
|
|
34
|
+
"lint": "npx biome lint --write",
|
|
35
|
+
"check": "npx biome check --write"
|
|
36
|
+
},
|
|
37
|
+
"author": "sunub",
|
|
38
|
+
"license": "ISC",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
41
|
+
"dotenv": "^17.2.2",
|
|
42
|
+
"gray-matter": "^4.0.3",
|
|
43
|
+
"zod": "^3.25.76"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@biomejs/biome": "2.3.8",
|
|
47
|
+
"@smithery/cli": "^1.2.31",
|
|
48
|
+
"@types/node": "^24.3.1",
|
|
49
|
+
"jsdom": "^27.0.0",
|
|
50
|
+
"prettier": "^3.6.2",
|
|
51
|
+
"tsc-alias": "^1.8.16",
|
|
52
|
+
"tsx": "^4.20.5",
|
|
53
|
+
"typescript": "^5.9.2",
|
|
54
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
55
|
+
"vitest": "^3.2.4"
|
|
56
|
+
}
|
|
57
|
+
}
|