obsidian-mcp-server 1.3.0 → 1.4.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/build/index.js +12 -3
- package/build/mcp/handlers.js +138 -0
- package/build/mcp/index.js +7 -0
- package/build/mcp/server.js +131 -0
- package/build/mcp/types.js +7 -0
- package/build/{obsidian.js → obsidian/client.js} +116 -96
- package/build/obsidian/errors.js +75 -0
- package/build/obsidian/index.js +7 -0
- package/build/obsidian/types.js +12 -0
- package/build/resources/index.js +15 -0
- package/build/{resources.js → resources/tags.js} +31 -5
- package/build/resources/types.js +5 -0
- package/build/tools/base.js +78 -0
- package/build/tools/files/content.js +171 -0
- package/build/tools/files/index.js +22 -0
- package/build/tools/files/list.js +133 -0
- package/build/tools/index.js +31 -0
- package/build/tools/properties/index.js +19 -0
- package/build/{properties.js → tools/properties/manager.js} +40 -8
- package/build/{propertyTools.js → tools/properties/tools.js} +22 -6
- package/build/{propertyTypes.js → tools/properties/types.js} +17 -5
- package/build/tools/search/complex.js +203 -0
- package/build/tools/search/index.js +20 -0
- package/build/tools/search/simple.js +127 -0
- package/build/utils/errors.js +59 -0
- package/build/utils/index.js +9 -0
- package/build/utils/logging.js +119 -0
- package/build/utils/rate-limiting.js +94 -0
- package/build/utils/tokenization.js +62 -0
- package/build/utils/validation.js +88 -0
- package/examples/README.md +48 -0
- package/examples/append-content.md +63 -0
- package/examples/complex-search.md +117 -0
- package/examples/find-in-file.md +94 -0
- package/examples/get-file-contents.md +72 -0
- package/examples/get-properties.md +89 -0
- package/examples/list-files-in-dir.md +55 -0
- package/examples/list-files-in-vault.md +53 -0
- package/examples/patch-content.md +60 -0
- package/examples/update-properties.md +126 -0
- package/package.json +1 -1
- package/src/index.ts +13 -3
- package/src/mcp/handlers.ts +183 -0
- package/src/mcp/index.ts +6 -0
- package/src/mcp/server.ts +162 -0
- package/src/mcp/types.ts +46 -0
- package/src/{obsidian.ts → obsidian/client.ts} +132 -125
- package/src/obsidian/errors.ts +105 -0
- package/src/obsidian/index.ts +6 -0
- package/src/obsidian/types.ts +124 -0
- package/src/resources/index.ts +17 -0
- package/src/{resources.ts → resources/tags.ts} +38 -6
- package/src/resources/types.ts +30 -0
- package/src/tools/base.ts +112 -0
- package/src/tools/files/content.ts +206 -0
- package/src/tools/files/index.ts +31 -0
- package/src/tools/files/list.ts +150 -0
- package/src/tools/index.ts +38 -0
- package/src/tools/properties/index.ts +21 -0
- package/src/{properties.ts → tools/properties/manager.ts} +41 -8
- package/src/{propertyTools.ts → tools/properties/tools.ts} +33 -7
- package/src/{propertyTypes.ts → tools/properties/types.ts} +29 -5
- package/src/tools/search/complex.ts +231 -0
- package/src/tools/search/index.ts +22 -0
- package/src/tools/search/simple.ts +147 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logging.ts +146 -0
- package/src/utils/rate-limiting.ts +114 -0
- package/src/utils/tokenization.ts +71 -0
- package/src/utils/validation.ts +95 -0
- package/build/server.js +0 -263
- package/build/tools.js +0 -845
- package/build/types.js +0 -37
- package/src/server.ts +0 -335
- package/src/tools.ts +0 -926
- package/src/types.ts +0 -184
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ObsidianError } from "../utils/errors.js";
|
|
2
|
+
import { tokenCounter } from "../utils/tokenization.js";
|
|
3
|
+
import { createLogger } from "../utils/logging.js";
|
|
4
|
+
// Create a logger for tool operations
|
|
5
|
+
const logger = createLogger('Tools');
|
|
6
|
+
/**
|
|
7
|
+
* Base class for all tool handlers with common functionality
|
|
8
|
+
*/
|
|
9
|
+
export class BaseToolHandler {
|
|
10
|
+
name;
|
|
11
|
+
client;
|
|
12
|
+
/**
|
|
13
|
+
* Create a new tool handler
|
|
14
|
+
* @param name The name of the tool
|
|
15
|
+
* @param client The ObsidianClient instance
|
|
16
|
+
*/
|
|
17
|
+
constructor(name, client) {
|
|
18
|
+
this.name = name;
|
|
19
|
+
this.client = client;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a standardized response from any content type
|
|
23
|
+
* @param content The content to format for response
|
|
24
|
+
* @returns An array of TextContent objects
|
|
25
|
+
*/
|
|
26
|
+
createResponse(content) {
|
|
27
|
+
let text;
|
|
28
|
+
// Handle different content types
|
|
29
|
+
if (typeof content === 'string') {
|
|
30
|
+
text = content;
|
|
31
|
+
}
|
|
32
|
+
else if (content instanceof Buffer) {
|
|
33
|
+
text = content.toString('utf-8');
|
|
34
|
+
}
|
|
35
|
+
else if (Array.isArray(content) && content.every(item => typeof item === 'string')) {
|
|
36
|
+
text = content.join('\n');
|
|
37
|
+
}
|
|
38
|
+
else if (content instanceof Error) {
|
|
39
|
+
text = `Error: ${content.message}\n${content.stack || ''}`;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
try {
|
|
43
|
+
text = JSON.stringify(content, null, 2);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
text = String(content);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Count tokens and truncate if necessary
|
|
50
|
+
const originalTokenCount = tokenCounter.countTokens(text);
|
|
51
|
+
const truncatedText = tokenCounter.truncateToTokenLimit(text);
|
|
52
|
+
const finalTokenCount = tokenCounter.countTokens(truncatedText);
|
|
53
|
+
if (originalTokenCount > finalTokenCount) {
|
|
54
|
+
logger.debug(`[${this.name}] Response truncated:`, `original tokens=${originalTokenCount}`, `truncated tokens=${finalTokenCount}`);
|
|
55
|
+
}
|
|
56
|
+
return [{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: truncatedText
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Standard error handling for tool execution
|
|
63
|
+
* @param error The error to handle
|
|
64
|
+
* @throws ObsidianError
|
|
65
|
+
*/
|
|
66
|
+
handleError(error) {
|
|
67
|
+
if (error instanceof ObsidianError) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
if (error instanceof Error) {
|
|
71
|
+
throw new ObsidianError(`Tool '${this.name}' execution failed: ${error.message}`, 50000, // Internal server error
|
|
72
|
+
{ originalError: error.stack });
|
|
73
|
+
}
|
|
74
|
+
throw new ObsidianError(`Tool '${this.name}' execution failed with unknown error`, 50000, // Internal server error
|
|
75
|
+
{ error });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { BaseToolHandler } from "../base.js";
|
|
2
|
+
import { createLogger } from "../../utils/logging.js";
|
|
3
|
+
// Create a logger for file content operations
|
|
4
|
+
const logger = createLogger('FileContentTools');
|
|
5
|
+
/**
|
|
6
|
+
* Tool names for file content operations
|
|
7
|
+
*/
|
|
8
|
+
export const FILE_CONTENT_TOOL_NAMES = {
|
|
9
|
+
GET_FILE_CONTENTS: "obsidian_get_file_contents",
|
|
10
|
+
APPEND_CONTENT: "obsidian_append_content",
|
|
11
|
+
PATCH_CONTENT: "obsidian_patch_content"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Tool handler for getting file contents
|
|
15
|
+
*/
|
|
16
|
+
export class GetFileContentsToolHandler extends BaseToolHandler {
|
|
17
|
+
constructor(client) {
|
|
18
|
+
super(FILE_CONTENT_TOOL_NAMES.GET_FILE_CONTENTS, client);
|
|
19
|
+
}
|
|
20
|
+
getToolDescription() {
|
|
21
|
+
return {
|
|
22
|
+
name: this.name,
|
|
23
|
+
description: "Return the content of a single file in your vault. Supports markdown files, text files, and other readable formats. Returns the raw content including any YAML frontmatter.",
|
|
24
|
+
examples: [
|
|
25
|
+
{
|
|
26
|
+
description: "Get content of a markdown note",
|
|
27
|
+
args: {
|
|
28
|
+
filepath: "Projects/research.md"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description: "Get content of a configuration file",
|
|
33
|
+
args: {
|
|
34
|
+
filepath: "configs/settings.yml"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
filepath: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Path to the relevant file (relative to your vault root).",
|
|
44
|
+
format: "path"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ["filepath"]
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async runTool(args) {
|
|
52
|
+
try {
|
|
53
|
+
logger.debug(`Getting contents of file: ${args.filepath}`);
|
|
54
|
+
const content = await this.client.getFileContents(args.filepath);
|
|
55
|
+
return this.createResponse(content);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return this.handleError(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Tool handler for appending content to a file
|
|
64
|
+
*/
|
|
65
|
+
export class AppendContentToolHandler extends BaseToolHandler {
|
|
66
|
+
constructor(client) {
|
|
67
|
+
super(FILE_CONTENT_TOOL_NAMES.APPEND_CONTENT, client);
|
|
68
|
+
}
|
|
69
|
+
getToolDescription() {
|
|
70
|
+
return {
|
|
71
|
+
name: this.name,
|
|
72
|
+
description: "Append content to a new or existing file in the vault.",
|
|
73
|
+
examples: [
|
|
74
|
+
{
|
|
75
|
+
description: "Append a new task",
|
|
76
|
+
args: {
|
|
77
|
+
filepath: "tasks.md",
|
|
78
|
+
content: "- [ ] New task to complete"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
description: "Append meeting notes",
|
|
83
|
+
args: {
|
|
84
|
+
filepath: "meetings/2025-01-23.md",
|
|
85
|
+
content: "## Meeting Notes\n\n- Discussed project timeline\n- Assigned tasks"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
filepath: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Path to the file (relative to vault root)",
|
|
95
|
+
format: "path"
|
|
96
|
+
},
|
|
97
|
+
content: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Content to append to the file"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
required: ["filepath", "content"]
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async runTool(args) {
|
|
107
|
+
try {
|
|
108
|
+
logger.debug(`Appending content to file: ${args.filepath}`);
|
|
109
|
+
await this.client.appendContent(args.filepath, args.content);
|
|
110
|
+
return this.createResponse({
|
|
111
|
+
message: `Successfully appended content to ${args.filepath}`,
|
|
112
|
+
success: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return this.handleError(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Tool handler for updating file content
|
|
122
|
+
*/
|
|
123
|
+
export class PatchContentToolHandler extends BaseToolHandler {
|
|
124
|
+
constructor(client) {
|
|
125
|
+
super(FILE_CONTENT_TOOL_NAMES.PATCH_CONTENT, client);
|
|
126
|
+
}
|
|
127
|
+
getToolDescription() {
|
|
128
|
+
return {
|
|
129
|
+
name: this.name,
|
|
130
|
+
description: "Update the entire content of an existing note or create a new one.",
|
|
131
|
+
examples: [
|
|
132
|
+
{
|
|
133
|
+
description: "Update a note's content",
|
|
134
|
+
args: {
|
|
135
|
+
filepath: "project.md",
|
|
136
|
+
content: "# Project Notes\n\nThis will replace the entire content of the note."
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
filepath: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Path to the file (relative to vault root)",
|
|
146
|
+
format: "path"
|
|
147
|
+
},
|
|
148
|
+
content: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "New content for the note (replaces existing content)"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
required: ["filepath", "content"]
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async runTool(args) {
|
|
158
|
+
try {
|
|
159
|
+
logger.debug(`Updating content of file: ${args.filepath}`);
|
|
160
|
+
await this.client.updateContent(args.filepath, args.content);
|
|
161
|
+
return this.createResponse({
|
|
162
|
+
message: `Successfully updated content in ${args.filepath}`,
|
|
163
|
+
success: true
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return this.handleError(error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File operation tools exports
|
|
3
|
+
*/
|
|
4
|
+
export * from './list.js';
|
|
5
|
+
export * from './content.js';
|
|
6
|
+
import { ListFilesInVaultToolHandler, ListFilesInDirToolHandler } from './list.js';
|
|
7
|
+
import { GetFileContentsToolHandler, AppendContentToolHandler, PatchContentToolHandler } from './content.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create all file-related tool handlers
|
|
10
|
+
* @param client The ObsidianClient instance
|
|
11
|
+
* @returns Array of file tool handlers
|
|
12
|
+
*/
|
|
13
|
+
export function createFileToolHandlers(client) {
|
|
14
|
+
return [
|
|
15
|
+
new ListFilesInVaultToolHandler(client),
|
|
16
|
+
new ListFilesInDirToolHandler(client),
|
|
17
|
+
new GetFileContentsToolHandler(client),
|
|
18
|
+
new AppendContentToolHandler(client),
|
|
19
|
+
new PatchContentToolHandler(client)
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { BaseToolHandler } from "../base.js";
|
|
2
|
+
import { createLogger } from "../../utils/logging.js";
|
|
3
|
+
// Create a logger for file operations
|
|
4
|
+
const logger = createLogger('FileTools');
|
|
5
|
+
/**
|
|
6
|
+
* Tool names for file listing operations
|
|
7
|
+
*/
|
|
8
|
+
export const FILE_TOOL_NAMES = {
|
|
9
|
+
LIST_FILES_IN_VAULT: "obsidian_list_files_in_vault",
|
|
10
|
+
LIST_FILES_IN_DIR: "obsidian_list_files_in_dir"
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Tool handler for listing all files in the vault
|
|
14
|
+
*/
|
|
15
|
+
export class ListFilesInVaultToolHandler extends BaseToolHandler {
|
|
16
|
+
constructor(client) {
|
|
17
|
+
super(FILE_TOOL_NAMES.LIST_FILES_IN_VAULT, client);
|
|
18
|
+
}
|
|
19
|
+
getToolDescription() {
|
|
20
|
+
return {
|
|
21
|
+
name: this.name,
|
|
22
|
+
description: "Lists all files and directories in the root directory of your Obsidian vault. Returns a hierarchical structure of files and folders, including metadata like file type.",
|
|
23
|
+
examples: [
|
|
24
|
+
{
|
|
25
|
+
description: "List all files in vault",
|
|
26
|
+
args: {}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
description: "Example response",
|
|
30
|
+
args: {},
|
|
31
|
+
response: [
|
|
32
|
+
{
|
|
33
|
+
"path": "Daily Notes",
|
|
34
|
+
"type": "folder",
|
|
35
|
+
"children": [
|
|
36
|
+
{ "path": "Daily Notes/2025-01-24.md", "type": "file" }
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"path": "Projects",
|
|
41
|
+
"type": "folder",
|
|
42
|
+
"children": [
|
|
43
|
+
{ "path": "Projects/MCP.md", "type": "file" }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {},
|
|
52
|
+
required: []
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async runTool() {
|
|
57
|
+
try {
|
|
58
|
+
logger.debug('Listing all files in vault');
|
|
59
|
+
const files = await this.client.listFilesInVault();
|
|
60
|
+
return this.createResponse(files);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return this.handleError(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Tool handler for listing files in a specific directory
|
|
69
|
+
*/
|
|
70
|
+
export class ListFilesInDirToolHandler extends BaseToolHandler {
|
|
71
|
+
constructor(client) {
|
|
72
|
+
super(FILE_TOOL_NAMES.LIST_FILES_IN_DIR, client);
|
|
73
|
+
}
|
|
74
|
+
getToolDescription() {
|
|
75
|
+
return {
|
|
76
|
+
name: this.name,
|
|
77
|
+
description: "Lists all files and directories that exist in a specific Obsidian directory. Returns a hierarchical structure showing files, folders, and their relationships. Useful for exploring vault organization and finding specific files.",
|
|
78
|
+
examples: [
|
|
79
|
+
{
|
|
80
|
+
description: "List files in Documents folder",
|
|
81
|
+
args: {
|
|
82
|
+
dirpath: "Documents"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
description: "Example response structure",
|
|
87
|
+
args: {
|
|
88
|
+
dirpath: "Projects"
|
|
89
|
+
},
|
|
90
|
+
response: [
|
|
91
|
+
{
|
|
92
|
+
"path": "Projects/Active",
|
|
93
|
+
"type": "folder",
|
|
94
|
+
"children": [
|
|
95
|
+
{ "path": "Projects/Active/ProjectA.md", "type": "file" },
|
|
96
|
+
{ "path": "Projects/Active/ProjectB.md", "type": "file" }
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"path": "Projects/Archive",
|
|
101
|
+
"type": "folder",
|
|
102
|
+
"children": [
|
|
103
|
+
{ "path": "Projects/Archive/OldProject.md", "type": "file" }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
dirpath: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "Path to list files from (relative to your vault root). Note that empty directories will not be returned.",
|
|
115
|
+
format: "path"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
required: ["dirpath"]
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async runTool(args) {
|
|
123
|
+
try {
|
|
124
|
+
logger.debug(`Listing files in directory: ${args.dirpath}`);
|
|
125
|
+
const files = await this.client.listFilesInDir(args.dirpath);
|
|
126
|
+
return this.createResponse(files);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return this.handleError(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createFileToolHandlers } from './files/index.js';
|
|
2
|
+
import { createSearchToolHandlers } from './search/index.js';
|
|
3
|
+
import { createPropertyToolHandlers } from './properties/index.js';
|
|
4
|
+
// Export the base handler and all submodules
|
|
5
|
+
export * from './base.js';
|
|
6
|
+
export * from './files/index.js';
|
|
7
|
+
export * from './search/index.js';
|
|
8
|
+
export * from './properties/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create all tool handlers
|
|
11
|
+
* @param client The ObsidianClient instance
|
|
12
|
+
* @returns Array of all tool handlers
|
|
13
|
+
*/
|
|
14
|
+
export function createToolHandlers(client) {
|
|
15
|
+
return [
|
|
16
|
+
...createFileToolHandlers(client),
|
|
17
|
+
...createSearchToolHandlers(client),
|
|
18
|
+
...createPropertyToolHandlers(client)
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get a tool handler map by name
|
|
23
|
+
* @param handlers Array of tool handlers
|
|
24
|
+
* @returns Map of tool names to handlers
|
|
25
|
+
*/
|
|
26
|
+
export function createToolHandlerMap(handlers) {
|
|
27
|
+
const toolHandlerMap = new Map();
|
|
28
|
+
handlers.forEach(handler => toolHandlerMap.set(handler.name, handler));
|
|
29
|
+
return toolHandlerMap;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Properties module exports
|
|
3
|
+
*/
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
export * from './manager.js';
|
|
6
|
+
export * from './tools.js';
|
|
7
|
+
import { GetPropertiesToolHandler, UpdatePropertiesToolHandler } from './tools.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create all property-related tool handlers
|
|
10
|
+
* @param client The ObsidianClient instance
|
|
11
|
+
* @returns Array of property tool handlers
|
|
12
|
+
*/
|
|
13
|
+
export function createPropertyToolHandlers(client) {
|
|
14
|
+
return [
|
|
15
|
+
new GetPropertiesToolHandler(client),
|
|
16
|
+
new UpdatePropertiesToolHandler(client)
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property manager for Obsidian notes
|
|
3
|
+
*/
|
|
1
4
|
import { parse, stringify } from 'yaml';
|
|
2
5
|
import { EOL } from 'os';
|
|
3
|
-
import {
|
|
6
|
+
import { createLogger } from '../../utils/logging.js';
|
|
7
|
+
import { ObsidianPropertiesSchema, PropertyUpdateSchema } from './types.js';
|
|
8
|
+
// Create a logger for property operations
|
|
9
|
+
const logger = createLogger('PropertyManager');
|
|
10
|
+
/**
|
|
11
|
+
* Manages YAML frontmatter properties in Obsidian notes
|
|
12
|
+
*/
|
|
4
13
|
export class PropertyManager {
|
|
5
14
|
client;
|
|
6
15
|
constructor(client) {
|
|
@@ -8,15 +17,18 @@ export class PropertyManager {
|
|
|
8
17
|
}
|
|
9
18
|
/**
|
|
10
19
|
* Parse YAML frontmatter from note content
|
|
20
|
+
* @param content The note content
|
|
21
|
+
* @returns Extracted properties
|
|
11
22
|
*/
|
|
12
23
|
parseProperties(content) {
|
|
13
24
|
try {
|
|
14
25
|
// Extract frontmatter between --- markers (handles both \n and \r\n)
|
|
15
|
-
const match = content.match(
|
|
26
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
16
27
|
if (!match) {
|
|
28
|
+
logger.debug('No frontmatter found in content');
|
|
17
29
|
return {};
|
|
18
30
|
}
|
|
19
|
-
const frontmatter = match[
|
|
31
|
+
const frontmatter = match[1];
|
|
20
32
|
const properties = parse(frontmatter);
|
|
21
33
|
// Handle tags - don't add # prefix in frontmatter
|
|
22
34
|
if (properties.tags && Array.isArray(properties.tags)) {
|
|
@@ -25,19 +37,21 @@ export class PropertyManager {
|
|
|
25
37
|
// Validate against schema
|
|
26
38
|
const result = ObsidianPropertiesSchema.safeParse(properties);
|
|
27
39
|
if (!result.success) {
|
|
28
|
-
|
|
40
|
+
logger.warn('Property validation warnings:', result.error);
|
|
29
41
|
// Return the properties with fixed tags
|
|
30
42
|
return properties;
|
|
31
43
|
}
|
|
32
44
|
return result.data;
|
|
33
45
|
}
|
|
34
46
|
catch (error) {
|
|
35
|
-
|
|
47
|
+
logger.error('Error parsing properties:', error);
|
|
36
48
|
return {};
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
/**
|
|
40
52
|
* Generate YAML frontmatter from properties
|
|
53
|
+
* @param properties The properties to convert to YAML
|
|
54
|
+
* @returns YAML frontmatter string
|
|
41
55
|
*/
|
|
42
56
|
generateProperties(properties) {
|
|
43
57
|
try {
|
|
@@ -48,12 +62,14 @@ export class PropertyManager {
|
|
|
48
62
|
return `---${EOL}${yaml}---${EOL}`;
|
|
49
63
|
}
|
|
50
64
|
catch (error) {
|
|
51
|
-
|
|
65
|
+
logger.error('Error generating properties:', error);
|
|
52
66
|
throw error;
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
/**
|
|
56
|
-
* Validate property values
|
|
70
|
+
* Validate property values against schema
|
|
71
|
+
* @param properties The properties to validate
|
|
72
|
+
* @returns Validation result
|
|
57
73
|
*/
|
|
58
74
|
validateProperties(properties) {
|
|
59
75
|
const result = PropertyUpdateSchema.safeParse(properties);
|
|
@@ -67,6 +83,10 @@ export class PropertyManager {
|
|
|
67
83
|
}
|
|
68
84
|
/**
|
|
69
85
|
* Merge new properties with existing ones
|
|
86
|
+
* @param existing The existing properties
|
|
87
|
+
* @param updates The new properties to merge
|
|
88
|
+
* @param replace Whether to replace arrays instead of merging them
|
|
89
|
+
* @returns The merged properties
|
|
70
90
|
*/
|
|
71
91
|
mergeProperties(existing, updates, replace = false) {
|
|
72
92
|
const merged = { ...existing };
|
|
@@ -99,9 +119,12 @@ export class PropertyManager {
|
|
|
99
119
|
}
|
|
100
120
|
/**
|
|
101
121
|
* Get properties from a note
|
|
122
|
+
* @param filepath Path to the note
|
|
123
|
+
* @returns The properties from the note
|
|
102
124
|
*/
|
|
103
125
|
async getProperties(filepath) {
|
|
104
126
|
try {
|
|
127
|
+
logger.debug(`Getting properties from file: ${filepath}`);
|
|
105
128
|
const content = await this.client.getFileContents(filepath);
|
|
106
129
|
const properties = this.parseProperties(content);
|
|
107
130
|
return {
|
|
@@ -111,6 +134,7 @@ export class PropertyManager {
|
|
|
111
134
|
};
|
|
112
135
|
}
|
|
113
136
|
catch (error) {
|
|
137
|
+
logger.error(`Failed to get properties from ${filepath}:`, error);
|
|
114
138
|
return {
|
|
115
139
|
success: false,
|
|
116
140
|
message: `Failed to get properties: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -120,12 +144,17 @@ export class PropertyManager {
|
|
|
120
144
|
}
|
|
121
145
|
/**
|
|
122
146
|
* Update properties of a note
|
|
147
|
+
* @param filepath Path to the note
|
|
148
|
+
* @param newProperties The new properties to apply
|
|
149
|
+
* @param replace Whether to replace arrays instead of merging them
|
|
150
|
+
* @returns The result of the update operation
|
|
123
151
|
*/
|
|
124
152
|
async updateProperties(filepath, newProperties, replace = false) {
|
|
125
153
|
try {
|
|
126
154
|
// Validate new properties
|
|
127
155
|
const validation = this.validateProperties(newProperties);
|
|
128
156
|
if (!validation.valid) {
|
|
157
|
+
logger.warn(`Invalid properties for ${filepath}:`, validation.errors);
|
|
129
158
|
return {
|
|
130
159
|
success: false,
|
|
131
160
|
message: 'Invalid properties',
|
|
@@ -133,6 +162,7 @@ export class PropertyManager {
|
|
|
133
162
|
};
|
|
134
163
|
}
|
|
135
164
|
// Get existing content and properties
|
|
165
|
+
logger.debug(`Updating properties for file: ${filepath}`);
|
|
136
166
|
const content = await this.client.getFileContents(filepath);
|
|
137
167
|
const existingProperties = this.parseProperties(content);
|
|
138
168
|
// Merge properties
|
|
@@ -144,6 +174,7 @@ export class PropertyManager {
|
|
|
144
174
|
const updatedContent = newFrontmatter + newContent;
|
|
145
175
|
// Update file
|
|
146
176
|
await this.client.updateContent(filepath, updatedContent);
|
|
177
|
+
logger.debug(`Successfully updated properties for ${filepath}`);
|
|
147
178
|
return {
|
|
148
179
|
success: true,
|
|
149
180
|
message: 'Properties updated successfully',
|
|
@@ -151,6 +182,7 @@ export class PropertyManager {
|
|
|
151
182
|
};
|
|
152
183
|
}
|
|
153
184
|
catch (error) {
|
|
185
|
+
logger.error(`Failed to update properties for ${filepath}:`, error);
|
|
154
186
|
return {
|
|
155
187
|
success: false,
|
|
156
188
|
message: `Failed to update properties: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -159,4 +191,4 @@ export class PropertyManager {
|
|
|
159
191
|
}
|
|
160
192
|
}
|
|
161
193
|
}
|
|
162
|
-
//# sourceMappingURL=
|
|
194
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { BaseToolHandler } from "
|
|
2
|
-
import { PropertyManager } from "./
|
|
3
|
-
|
|
1
|
+
import { BaseToolHandler } from "../base.js";
|
|
2
|
+
import { PropertyManager } from "./manager.js";
|
|
3
|
+
import { createLogger } from "../../utils/logging.js";
|
|
4
|
+
// Create a logger for property tools
|
|
5
|
+
const logger = createLogger('PropertyTools');
|
|
6
|
+
/**
|
|
7
|
+
* Tool names for property operations
|
|
8
|
+
*/
|
|
9
|
+
export const PROPERTY_TOOL_NAMES = {
|
|
4
10
|
GET_PROPERTIES: "obsidian_get_properties",
|
|
5
11
|
UPDATE_PROPERTIES: "obsidian_update_properties"
|
|
6
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Tool handler for getting properties from a note
|
|
15
|
+
*/
|
|
7
16
|
export class GetPropertiesToolHandler extends BaseToolHandler {
|
|
8
17
|
propertyManager;
|
|
9
18
|
constructor(client) {
|
|
10
|
-
super(
|
|
19
|
+
super(PROPERTY_TOOL_NAMES.GET_PROPERTIES, client);
|
|
11
20
|
this.propertyManager = new PropertyManager(client);
|
|
12
21
|
}
|
|
13
22
|
getToolDescription() {
|
|
@@ -43,18 +52,23 @@ export class GetPropertiesToolHandler extends BaseToolHandler {
|
|
|
43
52
|
}
|
|
44
53
|
async runTool(args) {
|
|
45
54
|
try {
|
|
55
|
+
logger.debug(`Getting properties from: ${args.filepath}`);
|
|
46
56
|
const result = await this.propertyManager.getProperties(args.filepath);
|
|
47
57
|
return this.createResponse(result);
|
|
48
58
|
}
|
|
49
59
|
catch (error) {
|
|
60
|
+
logger.error(`Error getting properties from ${args.filepath}:`, error);
|
|
50
61
|
return this.handleError(error);
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Tool handler for updating properties in a note
|
|
67
|
+
*/
|
|
54
68
|
export class UpdatePropertiesToolHandler extends BaseToolHandler {
|
|
55
69
|
propertyManager;
|
|
56
70
|
constructor(client) {
|
|
57
|
-
super(
|
|
71
|
+
super(PROPERTY_TOOL_NAMES.UPDATE_PROPERTIES, client);
|
|
58
72
|
this.propertyManager = new PropertyManager(client);
|
|
59
73
|
}
|
|
60
74
|
getToolDescription() {
|
|
@@ -164,12 +178,14 @@ export class UpdatePropertiesToolHandler extends BaseToolHandler {
|
|
|
164
178
|
}
|
|
165
179
|
async runTool(args) {
|
|
166
180
|
try {
|
|
181
|
+
logger.debug(`Updating properties for: ${args.filepath}`);
|
|
167
182
|
const result = await this.propertyManager.updateProperties(args.filepath, args.properties, args.replace);
|
|
168
183
|
return this.createResponse(result);
|
|
169
184
|
}
|
|
170
185
|
catch (error) {
|
|
186
|
+
logger.error(`Error updating properties for ${args.filepath}:`, error);
|
|
171
187
|
return this.handleError(error);
|
|
172
188
|
}
|
|
173
189
|
}
|
|
174
190
|
}
|
|
175
|
-
//# sourceMappingURL=
|
|
191
|
+
//# sourceMappingURL=tools.js.map
|