@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 ADDED
@@ -0,0 +1,93 @@
1
+ # Obsidian MCP Server
2
+
3
+ `obsidian-mcp-server`는 [Model Context Protocol(MCP)](https://modelcontextprotocol.io/docs/getting-started/intro)을 구현한 서버로, 로컬 Obsidian vault의 문서들을 AI 에이전트나 외부 애플리케이션에서 쉽게 탐색하고 관리할 수 있도록 강력한 도구 API를 제공합니다.
4
+
5
+ Obsidian Vault를 이용해 AI가 활용 가능한 지식 베이스(Knowledge Base)로 확장하여 사용할 수 있게끔 하고 문서 검색, 요약, 정리와 같은 부가적인 작업을 자동화하여 사용자가 핵심적인 "글쓰기 활동"에만 집중할 수 있는 환경을 구축하고자 제작했습니다.
6
+
7
+ ## 핵심 아키텍처
8
+
9
+ 본 서버는 `VaultManager`와 `Indexer`를 중심으로 구축되어 대규모 Vault에서도 높은 성능과 메모리 효율성을 보장합니다.
10
+
11
+ - **`Indexer` 기반 검색**: 서버 시작 시 가벼운 역 인덱스(Inverted Index)를 생성하여 키워드 검색 시 거의 즉각적인 결과를 반환합니다(O(1)). 전체 파일 내용을 메모리에 상주시키지 않아 메모리 사용량을 최소화합니다.
12
+ - **`VaultManager`**: Vault 내의 모든 문서를 효율적으로 관리하며, 파일 시스템과 상호작용하여 문서의 생성, 수정, 삭제를 처리합니다.
13
+
14
+ ## 주요 기능
15
+
16
+ - **고급 문서 탐색**: `vault` 도구를 통해 키워드 검색, 전체 목록 조회, 특정 문서 읽기, 통계 분석 등 다양한 탐색 기능을 제공합니다.
17
+ - **AI 기반 속성 생성**: `generate_property` 도구는 문서 본문을 분석하여 `title`, `tags`, `summary` 등 적절한 frontmatter 속성을 자동으로 생성합니다.
18
+ - **안전한 속성 업데이트**: `write_property` 도구를 사용하여 생성된 속성을 기존 frontmatter와 병합하여 파일에 안전하게 기록합니다.
19
+ - **첨부 파일 자동 정리**: `organize_attachments` 도구는 문서와 연결된 첨부 파일(예: 이미지)을 자동으로 감지하여 문서 제목에 맞는 폴더로 이동시키고 링크를 업데이트합니다.
20
+ - **통합 워크플로우**: `create_document_with_properties`와 같은 도구를 통해 문서 분석부터 속성 생성, 파일 업데이트까지의 전체 과정을 단일 명령으로 실행합니다.
21
+ - **신뢰성 및 테스트**: `vitest`를 사용한 End-to-End 테스트와 GitHub Actions 기반의 CI/CD 파이프라인을 통해 서버의 안정성과 각 도구 API의 응답 스키마를 검증합니다.
22
+
23
+ ## 도구 API
24
+
25
+ `obsidian-mcp-server`는 MCP 클라이언트를 통해 호출할 수 있는 다음과 같은 도구들을 제공합니다.
26
+
27
+ ### `vault`
28
+
29
+ Vault 내 문서를 탐색하고 분석하는 핵심 도구입니다. `action` 파라미터를 통해 다양한 기능을 수행할 수 있습니다.
30
+
31
+ - **`list_all`**: Vault 내 모든 문서의 목록과 메타데이터를 반환합니다.
32
+ - **`search`**: 키워드를 기반으로 문서 제목, 내용, 태그를 검색합니다.
33
+ - **`read`**: 특정 파일의 내용을 읽고 frontmatter와 본문을 반환합니다.
34
+ - **`stats`**: Vault 내 모든 문서의 통계(단어, 글자 수 등)를 제공합니다.
35
+
36
+ ### `generate_property`
37
+
38
+ 문서 경로(`filePath`)를 입력받아 해당 문서의 내용을 분석하고, AI가 추천하는 frontmatter 속성을 생성하여 반환합니다.
39
+
40
+ ### `write_property`
41
+
42
+ 파일 경로(`filePath`)와 JSON 형식의 속성(`properties`)을 입력받아, 해당 파일의 frontmatter를 업데이트합니다.
43
+
44
+ ### `create_document_with_properties`
45
+
46
+ 문서 분석, 속성 생성, 파일 업데이트의 전 과정을 한 번에 처리하는 통합 도구입니다.
47
+
48
+ ### `organize_attachments`
49
+
50
+ 키워드로 문서를 찾아 해당 문서에 연결된 모든 첨부 파일을 `images/{문서 제목}` 폴더로 이동시키고, 문서 내의 링크를 자동으로 업데이트합니다.
51
+
52
+ ## 시작하기
53
+
54
+ 1. **저장소 복제 및 의존성 설치**:
55
+
56
+ ```bash
57
+ git clone https://github.com/sunub/obsidian-mcp-server.git
58
+ cd obsidian-mcp-server
59
+ npm install
60
+ ```
61
+
62
+ 2. **환경 변수 설정**:
63
+ 프로젝트 루트에 `.env` 파일을 생성하고 Obsidian vault의 절대 경로를 지정합니다.
64
+
65
+ ```
66
+ VAULT_DIR_PATH=/path/to/your/obsidian/vault
67
+ ```
68
+
69
+ 3. **프로젝트 빌드**:
70
+
71
+ ```bash
72
+ npm run build
73
+ ```
74
+
75
+ 4. **서버 실행**:
76
+ ```bash
77
+ node build/index.js
78
+ ```
79
+ 이제 MCP 클라이언트에서 서버에 연결하여 도구를 사용할 수 있습니다.
80
+
81
+ ## 개발
82
+
83
+ ### 테스트 실행
84
+
85
+ `vitest`를 사용하여 End-to-End 테스트를 실행할 수 있습니다.
86
+
87
+ ```bash
88
+ npm test
89
+ ```
90
+
91
+ ### CI/CD
92
+
93
+ 이 프로젝트는 GitHub Actions를 사용하여 CI/CD 파이프라인을 구축했습니다. `main` 브랜치에 push 또는 pull request가 발생하면 자동으로 빌드와 테스트가 수행됩니다.
package/build/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import createMcpServer from "./server.js";
3
+ export default function smitheryEntryPoint() {
4
+ const server = createMcpServer();
5
+ return server.server;
6
+ }
7
+ async function main() {
8
+ try {
9
+ const server = createMcpServer();
10
+ const transport = new StdioServerTransport();
11
+ await server.connect(transport);
12
+ }
13
+ catch (error) {
14
+ console.error("Failed to start MCP server:", error);
15
+ process.exit(1);
16
+ }
17
+ }
18
+ main().catch((error) => {
19
+ console.error("main() 함수에서 치명적인 오류가 발생했습니다:", error);
20
+ process.exit(1);
21
+ });
@@ -0,0 +1,31 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import pkg from "../package.json" with { type: "json" };
3
+ import tools from "./tools/index.js";
4
+ export default function createMcpServer() {
5
+ const mcpServer = new McpServer({
6
+ version: pkg.version,
7
+ name: "obsidian-mcp-server",
8
+ title: "Obsidian MCP Server",
9
+ }, {
10
+ capabilities: {
11
+ logging: {},
12
+ tools: { listChanged: false },
13
+ },
14
+ instructions: `
15
+ This server provides access to Obsidian vault documents and related tools.
16
+
17
+ Available tools:
18
+ - obsidian_content_getter: Search, read, and analyze vault documents
19
+
20
+ Available resources:
21
+ - docs://{filename}: Read specific documents from the vault
22
+
23
+ Environment requirements:
24
+ - VAULT_DIR_PATH: Path to your Obsidian vault directory
25
+ `,
26
+ });
27
+ for (const tool of Object.values(tools)) {
28
+ tool.register(mcpServer);
29
+ }
30
+ return mcpServer;
31
+ }
@@ -0,0 +1,145 @@
1
+ import { getGlobalVaultManager } from "../../utils/getVaultManager.js";
2
+ import { getParsedVaultPath } from "../../utils/parseVaultPath.js";
3
+ import { execute as writePropertyExecute } from "../write_property/index.js";
4
+ import { createDocumentWithPropertiesParamsSchema, } from "./params.js";
5
+ export const name = "create_document_with_properties";
6
+ export const annotations = {
7
+ title: "Create Document with Properties",
8
+ openWorldHint: true,
9
+ };
10
+ export const description = `
11
+ Initiates an integrated workflow to read a document, guide an AI to generate properties, and then write those properties to a file.
12
+
13
+ This tool acts as a workflow manager for an AI agent. It reads the content of a specified document and returns a structured, multi-step plan. The AI agent must follow this plan by first calling the 'generate_obsidian_property' tool to get the document's content for analysis, and then, after generating the properties, calling the 'write_obsidian_property' tool to save them.
14
+
15
+ Use this tool to start the end-to-end process of enriching a document with AI-generated metadata.
16
+ `;
17
+ export const register = (mcpServer) => {
18
+ mcpServer.registerTool(name, {
19
+ title: annotations.title || name,
20
+ description: description,
21
+ inputSchema: createDocumentWithPropertiesParamsSchema.shape,
22
+ annotations: annotations,
23
+ }, execute);
24
+ };
25
+ export const execute = async (params) => {
26
+ const vaultDirPath = getParsedVaultPath();
27
+ if (!vaultDirPath) {
28
+ return {
29
+ isError: true,
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: JSON.stringify({
34
+ error: "VAULT_DIR_PATH environment variable is not set",
35
+ solution: "Set VAULT_DIR_PATH to your Obsidian vault directory",
36
+ }, null, 2),
37
+ },
38
+ ],
39
+ };
40
+ }
41
+ let vaultManager = null;
42
+ try {
43
+ vaultManager = getGlobalVaultManager();
44
+ }
45
+ catch (e) {
46
+ return {
47
+ isError: true,
48
+ content: [
49
+ { type: "text", text: JSON.stringify({ error: e.message }) },
50
+ ],
51
+ };
52
+ }
53
+ try {
54
+ const targetPath = params.outputPath || params.sourcePath;
55
+ if (params.aiGeneratedProperties) {
56
+ const writeResult = await writePropertyExecute({
57
+ filePath: targetPath,
58
+ properties: params.aiGeneratedProperties,
59
+ quiet: params.quiet || false,
60
+ });
61
+ return writeResult;
62
+ }
63
+ const document = await vaultManager.getDocumentInfo(params.sourcePath);
64
+ if (document === null) {
65
+ return {
66
+ isError: true,
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: JSON.stringify({
71
+ error: `Source document not found: ${params.sourcePath}`,
72
+ suggestion: "Verify the file path and ensure the file exists. You can use the 'obsidian_vault' tool with the 'list_all' action to see all available files.",
73
+ }, null, 2),
74
+ },
75
+ ],
76
+ };
77
+ }
78
+ const instructionForAI = {
79
+ ai_prompt: "Your task is to analyze the provided text content and create a JSON object of document properties based on the requested schema. After creating the JSON object, you MUST call the 'create_document_with_properties' tool again, passing back the original parameters along with the new 'aiGeneratedProperties' parameter containing your generated JSON.",
80
+ purpose: "Analyze document content and return a structured JSON object of properties.",
81
+ content_to_analyze: {
82
+ source_path: params.sourcePath,
83
+ content_preview: document.content.substring(0, 2000) +
84
+ (document.content.length > 2000 ? "..." : ""),
85
+ },
86
+ // AI가 다시 호출해야 할 다음 작업에 대한 명확한 명세
87
+ next_action_required: {
88
+ tool_to_call: name, // 바로 이 도구('create_document_with_properties')를 다시 호출
89
+ parameters_to_add: {
90
+ sourcePath: params.sourcePath,
91
+ outputPath: params.outputPath,
92
+ overwrite: params.overwrite,
93
+ quiet: params.quiet,
94
+ aiGeneratedProperties: "<- Insert your generated JSON object here.",
95
+ },
96
+ example_of_next_call: {
97
+ tool_name: name,
98
+ parameters: {
99
+ sourcePath: params.sourcePath,
100
+ aiGeneratedProperties: {
101
+ title: "Serverless 환경에서 I/O 처리 최적화 경험기",
102
+ date: "2025-04-03",
103
+ tags: ["serverless", "optimization"],
104
+ summary: "Promise.all, Worker를 벤치마크하며 서버리스 환경에서의 I/O 처리 최적화 경험기를 공유합니다.",
105
+ slug: "serverless-io-optimization",
106
+ category: "code",
107
+ completed: true,
108
+ },
109
+ },
110
+ },
111
+ },
112
+ };
113
+ return {
114
+ isError: false,
115
+ content: [
116
+ {
117
+ type: "text",
118
+ text: JSON.stringify(instructionForAI, null, 2),
119
+ },
120
+ ],
121
+ };
122
+ }
123
+ catch (error) {
124
+ return {
125
+ isError: true,
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: JSON.stringify({
130
+ error: `Failed to create workflow instruction: ${error instanceof Error ? error.message : String(error)}`,
131
+ params: params,
132
+ }, null, 2),
133
+ },
134
+ ],
135
+ };
136
+ }
137
+ };
138
+ export default {
139
+ name,
140
+ description,
141
+ annotations,
142
+ inputSchema: createDocumentWithPropertiesParamsSchema.shape,
143
+ execute,
144
+ register,
145
+ };
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { obsidianPropertyOutputSchema } from "../generate_property/params.js";
3
+ // input properties
4
+ const sourcePath = z
5
+ .string()
6
+ .describe('The path to the source markdown file to read and analyze (e.g., "draft/my-article.md")');
7
+ const outputPath = z
8
+ .string()
9
+ .optional()
10
+ .describe("The path where the processed file with properties will be saved. If not provided, the source file will be updated in place.");
11
+ const overwrite = z
12
+ .boolean()
13
+ .default(false)
14
+ .describe("If set to true, existing properties will be overwritten by the AI-generated content. Default: false.");
15
+ const aiGeneratedProperties = obsidianPropertyOutputSchema
16
+ .optional()
17
+ .describe("AI-generated properties based on content analysis. If provided, these will be used instead of internal analysis.");
18
+ const quiet = z
19
+ .boolean()
20
+ .optional()
21
+ .default(false)
22
+ .describe("If true, the final write operation will return a minimal success message.");
23
+ // input schema
24
+ export const createDocumentWithPropertiesParamsSchema = z
25
+ .object({
26
+ sourcePath: sourcePath,
27
+ outputPath: outputPath,
28
+ overwrite: overwrite.optional(),
29
+ aiGeneratedProperties: aiGeneratedProperties.optional(),
30
+ quiet: quiet,
31
+ })
32
+ .describe("Parameters for creating or updating a document with automatically generated properties");
@@ -0,0 +1,124 @@
1
+ import { getGlobalVaultManager } from "../../utils/getVaultManager.js";
2
+ import { obsidianPropertyQueryParamsSchema, } from "./params.js";
3
+ export const name = "generate_property";
4
+ export const annotations = {
5
+ title: "Obsidian Property Writer",
6
+ openWorldHint: true,
7
+ };
8
+ export const description = `
9
+ Analyzes the content of a specified Obsidian Markdown file to automatically generate the most suitable properties (frontmatter) and updates the file directly.
10
+
11
+ Use Cases:
12
+
13
+ - After Completing a Draft: Use when the body of the text is complete, and you want to generate all properties at once.
14
+ - Updating Information: Use when you want to update existing properties with more accurate information reflecting the latest content.
15
+ - Completing Missing Info: Use when you want to automatically add missing properties like tags or a summary to a document that only has a title.
16
+
17
+ Parameters:
18
+
19
+ filename: The name or path of the file to analyze and add properties to (e.g., "my-first-post.md").
20
+ overwrite: If set to true, existing properties will be overwritten by the AI-generated content. Default: false.
21
+
22
+ Generated Properties:
23
+
24
+ The AI analyzes the context of the content to generate the following properties:
25
+
26
+ - aliases: An array of alternative names or synonyms based on the content.
27
+ - title: A title that best represents the core topic of the document.
28
+ - tags: An array of tags extracted from the core keywords of the content (e.g., [AI, Obsidian, productivity]).
29
+ - summary: A one to two-sentence summary of the entire document.
30
+ - slug: A hyphenated-string suitable for URLs, containing the core keywords from the content.
31
+ - date: The event date or creation date inferred from the content (in ISO 8601 format).
32
+ - completed: A boolean (true or false) indicating whether the content is considered a final version.
33
+
34
+ Return Value:
35
+
36
+ Upon success, returns a JSON object containing a success message that includes the modified filename.
37
+ { "status": "success", "message": "Successfully updated properties for my-first-post.md" }
38
+
39
+ Requirements:
40
+
41
+ The user's absolute path to the Obsidian vault must be correctly set in an environment variable.
42
+ `;
43
+ export const register = (mcpServer) => {
44
+ mcpServer.registerTool(name, {
45
+ title: annotations.title || name,
46
+ description: description,
47
+ inputSchema: obsidianPropertyQueryParamsSchema.shape,
48
+ annotations: annotations,
49
+ }, execute);
50
+ };
51
+ export const execute = async (params) => {
52
+ const response = { content: [], isError: false };
53
+ let vaultManager = null;
54
+ try {
55
+ vaultManager = getGlobalVaultManager();
56
+ }
57
+ catch (e) {
58
+ return {
59
+ isError: true,
60
+ content: [
61
+ { type: "text", text: JSON.stringify({ error: e.message }) },
62
+ ],
63
+ };
64
+ }
65
+ try {
66
+ const document = await vaultManager.getDocumentInfo(params.filename);
67
+ if (document === null) {
68
+ response.content.push({
69
+ type: "text",
70
+ text: JSON.stringify({
71
+ error: `Document not found: ${params.filename}`,
72
+ suggestion: "Use 'list_all' action to see available documents",
73
+ searched_filename: params.filename,
74
+ }, null, 2),
75
+ });
76
+ return response;
77
+ }
78
+ const documentData = {
79
+ filename: params.filename,
80
+ content_preview: `${document.content.substring(0, 300).replace(/\s+/g, " ")}...`,
81
+ instructions: {
82
+ purpose: "Generate or update the document's frontmatter properties based on its content.",
83
+ usage: "Analyze the provided content_preview. If more detail is needed to generate accurate properties, you MUST first call the 'obsidian_vault' tool with the 'read' action to get the full document content.",
84
+ content_type: "markdown",
85
+ overwrite: params.overwrite || false,
86
+ output_format: "Return a JSON object with the following structure",
87
+ schema: {
88
+ title: "string - Title representing the core topic",
89
+ tags: "string[] - Array of relevant tags from content keywords",
90
+ summary: "string - 1-2 sentence summary of the document",
91
+ slug: "string - URL-friendly hyphenated identifier",
92
+ date: "string - ISO 8601 date format",
93
+ completed: "boolean - Whether content is finalized",
94
+ aliases: "string[] - (Optional) Alternative names or synonyms",
95
+ category: "string - (Optional) Document category",
96
+ },
97
+ },
98
+ };
99
+ response.content.push({
100
+ type: "text",
101
+ text: JSON.stringify(documentData, null, 2),
102
+ });
103
+ }
104
+ catch (error) {
105
+ response.isError = true;
106
+ response.content.push({
107
+ type: "text",
108
+ text: JSON.stringify({
109
+ error: error.message,
110
+ action: params,
111
+ solution: "Ensure the VAULT_DIR_PATH environment variable is set to your Obsidian vault directory and the filename is correct.",
112
+ }, null, 2),
113
+ });
114
+ }
115
+ return response;
116
+ };
117
+ export default {
118
+ name,
119
+ description,
120
+ annotations,
121
+ inputSchema: obsidianPropertyQueryParamsSchema.shape,
122
+ execute,
123
+ register,
124
+ };
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ // input properties
3
+ const obsidianGenerateInputFilename = z
4
+ .string()
5
+ .describe('The name or path of the file to analyze and add properties to (e.g., "my-first-post.md")');
6
+ const obsidianGenerateInputOverwrite = z
7
+ .boolean()
8
+ .default(true)
9
+ .describe("If set to true, existing properties will be overwritten by the AI-generated content. Default: false.");
10
+ // input schema
11
+ export const obsidianPropertyQueryParamsSchema = z
12
+ .object({
13
+ filename: obsidianGenerateInputFilename,
14
+ overwrite: obsidianGenerateInputOverwrite.optional(),
15
+ })
16
+ .describe("Parameters for generating or updating Obsidian document properties");
17
+ // output properties
18
+ export const obsidianCssClassesProperty = z
19
+ .array(z.string())
20
+ .describe("List of CSS classes associated with the document");
21
+ export const obsidianTagsProperty = z
22
+ .array(z.string())
23
+ .describe("List of tags associated with the document");
24
+ export const obsidianTitleProperty = z
25
+ .string()
26
+ .describe("Title of the document");
27
+ export const obsidianDateProperty = z
28
+ .string()
29
+ .describe("Creation date of the document in ISO 8601 format");
30
+ export const obsidianSummaryProperty = z
31
+ .string()
32
+ .describe("Brief summary or abstract of the document");
33
+ export const obsidianSlugProperty = z
34
+ .string()
35
+ .describe("URL-friendly identifier for the document");
36
+ export const obsidianCategoryProperty = z
37
+ .string()
38
+ .describe("Category or classification of the document");
39
+ export const obsidianCompletedProperty = z
40
+ .boolean()
41
+ .describe("Indicates whether a task or item is completed");
42
+ // output schema
43
+ export const obsidianPropertyOutputSchema = z
44
+ .object({
45
+ cssclasses: obsidianCssClassesProperty.optional(),
46
+ tags: obsidianTagsProperty.optional(),
47
+ title: obsidianTitleProperty.optional(),
48
+ date: obsidianDateProperty.optional(),
49
+ summary: obsidianSummaryProperty.optional(),
50
+ slug: obsidianSlugProperty.optional(),
51
+ category: obsidianCategoryProperty.optional(),
52
+ completed: obsidianCompletedProperty.optional(),
53
+ })
54
+ .describe("Extracted properties from the Obsidian document content");
@@ -0,0 +1,12 @@
1
+ import CreateDocumentWithPropertyTool from "./create_document_with_properties/index.js";
2
+ import GenerateObsidianVaultPropertiesTool from "./generate_property/index.js";
3
+ import OrganizeAttachmentsTool from "./organize_attachments/index.js";
4
+ import ObsidianVaultTool from "./vault/index.js";
5
+ import WriteObsidianPropertyTool from "./write_property/index.js";
6
+ export default {
7
+ ObsidianVaultTool,
8
+ GenerateObsidianVaultPropertiesTool,
9
+ WriteObsidianPropertyTool,
10
+ CreateDocumentWithPropertyTool,
11
+ OrganizeAttachmentsTool,
12
+ };
@@ -0,0 +1,110 @@
1
+ import { getGlobalVaultManager } from "../../utils/getVaultManager.js";
2
+ import { getParsedVaultPath } from "../../utils/parseVaultPath.js";
3
+ import { organizeAttachmentsParamsSchema, } from "./params.js";
4
+ import { genreateOrganizationTasks } from "./utils.js";
5
+ export const name = "organize_attachments";
6
+ export const annotations = {
7
+ title: "Organize Attachments",
8
+ openWorldHint: true,
9
+ };
10
+ export const description = `
11
+ Scans a specified markdown file for linked images (or other attachments),
12
+ moves them to a dedicated folder named after the document's title,
13
+ and updates the links within the markdown file automatically.
14
+
15
+ Use Cases:
16
+ - When a post is finalized and you want to clean up all associated images into a neat folder.
17
+ - To automatically organize attachments for better vault management.
18
+
19
+ Example Workflow:
20
+ 1. Specify 'my-awesome-post.md' as the fileName.
21
+ 2. The tool finds the 'title' property in the frontmatter (e.g., "My Awesome Post").
22
+ 3. It finds all image links like ![[my-image.png]].
23
+ 4. It creates a folder at '{vault}/images/My Awesome Post/'.
24
+ 5. It moves 'my-image.png' into that new folder.
25
+ 6. It updates the link in the markdown file to ![[images/My Awesome Post/my-image.png]].
26
+ `;
27
+ export const register = (mcpServer) => {
28
+ mcpServer.registerTool(name, {
29
+ title: annotations.title || name,
30
+ description: description,
31
+ inputSchema: organizeAttachmentsParamsSchema.shape,
32
+ annotations: annotations,
33
+ }, execute);
34
+ };
35
+ export const execute = async (params) => {
36
+ const vaultDirPath = getParsedVaultPath();
37
+ if (!vaultDirPath) {
38
+ return {
39
+ isError: true,
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: JSON.stringify({ error: "VAULT_DIR_PATH is not set" }),
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ let vaultManager = null;
49
+ try {
50
+ vaultManager = getGlobalVaultManager();
51
+ }
52
+ catch (e) {
53
+ return {
54
+ isError: true,
55
+ content: [
56
+ { type: "text", text: JSON.stringify({ error: e.message }) },
57
+ ],
58
+ };
59
+ }
60
+ try {
61
+ await vaultManager.initialize();
62
+ const documents = await vaultManager.searchDocuments(params.keyword);
63
+ if (documents.length === 0) {
64
+ return {
65
+ isError: true,
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: JSON.stringify({
70
+ error: `No document found for keyword: ${params.keyword}`,
71
+ }),
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ const organizationTasks = genreateOrganizationTasks(documents, vaultDirPath);
77
+ const results = await Promise.all(organizationTasks.map((task) => task()));
78
+ return {
79
+ isError: false,
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: JSON.stringify({
84
+ summary: `Processed ${documents.length} document(s).`,
85
+ details: results,
86
+ }, null, 2),
87
+ },
88
+ ],
89
+ };
90
+ }
91
+ catch (error) {
92
+ return {
93
+ isError: true,
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: JSON.stringify({ error: error.message }, null, 2),
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ };
103
+ export default {
104
+ name,
105
+ description,
106
+ annotations,
107
+ inputSchema: organizeAttachmentsParamsSchema.shape,
108
+ execute,
109
+ register,
110
+ };