feat: Phase 3 - Discovery Endpoints (stat and exists tools)
- Add stat tool for detailed file/folder metadata - Add exists tool for fast path existence checking - Add StatResult and ExistsResult type definitions - Implement stat() and exists() methods in VaultTools - Register both tools in ToolRegistry with complete schemas - Update version to 2.1.0 - Update ROADMAP.md to mark Phase 3 complete - Update CHANGELOG.md with Phase 3 release notes - Add IMPLEMENTATION_NOTES_PHASE3.md documentation - Clean up old phase documentation files
This commit is contained in:
@@ -116,6 +116,34 @@ export class ToolRegistry {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "stat",
|
||||
description: "Get detailed metadata for a file or folder at a specific path. Returns existence status, kind (file or directory), and full metadata including size, dates, etc. Use this to check if a path exists and get its properties. More detailed than exists() but slightly slower. Returns structured JSON with path, exists boolean, kind, and metadata object.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Vault-relative path to check (e.g., 'folder/note.md' or 'projects'). Can be a file or folder. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
|
||||
}
|
||||
},
|
||||
required: ["path"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "exists",
|
||||
description: "Quickly check if a file or folder exists at a specific path. Returns existence status and kind (file or directory) without fetching full metadata. Faster than stat() when you only need to verify existence. Use this before operations that require a path to exist. Returns structured JSON with path, exists boolean, and optional kind.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "Vault-relative path to check (e.g., 'folder/note.md' or 'projects'). Can be a file or folder. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
|
||||
}
|
||||
},
|
||||
required: ["path"]
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -137,6 +165,10 @@ export class ToolRegistry {
|
||||
return await this.vaultTools.getVaultInfo();
|
||||
case "list_notes":
|
||||
return await this.vaultTools.listNotes(args.path);
|
||||
case "stat":
|
||||
return await this.vaultTools.stat(args.path);
|
||||
case "exists":
|
||||
return await this.vaultTools.exists(args.path);
|
||||
default:
|
||||
return {
|
||||
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { App, TFile, TFolder } from 'obsidian';
|
||||
import { CallToolResult, FileMetadata, DirectoryMetadata, VaultInfo, SearchResult, SearchMatch } from '../types/mcp-types';
|
||||
import { CallToolResult, FileMetadata, DirectoryMetadata, VaultInfo, SearchResult, SearchMatch, StatResult, ExistsResult } from '../types/mcp-types';
|
||||
import { PathUtils } from '../utils/path-utils';
|
||||
import { ErrorMessages } from '../utils/error-messages';
|
||||
|
||||
@@ -239,4 +239,119 @@ export class VaultTools {
|
||||
modified: modified
|
||||
};
|
||||
}
|
||||
|
||||
// Phase 3: Discovery Endpoints
|
||||
async stat(path: string): Promise<CallToolResult> {
|
||||
// Validate path
|
||||
if (!PathUtils.isValidVaultPath(path)) {
|
||||
return {
|
||||
content: [{ type: "text", text: ErrorMessages.invalidPath(path) }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
// Normalize the path
|
||||
const normalizedPath = PathUtils.normalizePath(path);
|
||||
|
||||
// Check if it's a file
|
||||
const file = PathUtils.resolveFile(this.app, normalizedPath);
|
||||
if (file) {
|
||||
const result: StatResult = {
|
||||
path: normalizedPath,
|
||||
exists: true,
|
||||
kind: "file",
|
||||
metadata: this.createFileMetadata(file)
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Check if it's a folder
|
||||
const folder = PathUtils.resolveFolder(this.app, normalizedPath);
|
||||
if (folder) {
|
||||
const result: StatResult = {
|
||||
path: normalizedPath,
|
||||
exists: true,
|
||||
kind: "directory",
|
||||
metadata: this.createDirectoryMetadata(folder)
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Path doesn't exist
|
||||
const result: StatResult = {
|
||||
path: normalizedPath,
|
||||
exists: false
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
async exists(path: string): Promise<CallToolResult> {
|
||||
// Validate path
|
||||
if (!PathUtils.isValidVaultPath(path)) {
|
||||
return {
|
||||
content: [{ type: "text", text: ErrorMessages.invalidPath(path) }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
// Normalize the path
|
||||
const normalizedPath = PathUtils.normalizePath(path);
|
||||
|
||||
// Check if it's a file
|
||||
if (PathUtils.fileExists(this.app, normalizedPath)) {
|
||||
const result: ExistsResult = {
|
||||
path: normalizedPath,
|
||||
exists: true,
|
||||
kind: "file"
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Check if it's a folder
|
||||
if (PathUtils.folderExists(this.app, normalizedPath)) {
|
||||
const result: ExistsResult = {
|
||||
path: normalizedPath,
|
||||
exists: true,
|
||||
kind: "directory"
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Path doesn't exist
|
||||
const result: ExistsResult = {
|
||||
path: normalizedPath,
|
||||
exists: false
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,3 +107,17 @@ export interface SearchResult {
|
||||
filesSearched: number;
|
||||
filesWithMatches: number;
|
||||
}
|
||||
|
||||
// Phase 3: Discovery Endpoint Types
|
||||
export interface StatResult {
|
||||
path: string;
|
||||
exists: boolean;
|
||||
kind?: ItemKind;
|
||||
metadata?: FileMetadata | DirectoryMetadata;
|
||||
}
|
||||
|
||||
export interface ExistsResult {
|
||||
path: string;
|
||||
exists: boolean;
|
||||
kind?: ItemKind;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user