obsidian_batch_get_files
Retrieve content and metadata from multiple Obsidian notes simultaneously to analyze or process vault information in batches.
Instructions
Return the contents and metadata of one or more notes (.md files) in your vault.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filepaths | Yes | List of file paths to read |
Implementation Reference
- The BatchGetFilesToolHandler class implements the obsidian_batch_get_files tool. It defines the tool schema via get_tool_description and executes the tool logic in run_tool by calling the API to fetch file contents and metadata, then formats the response.class BatchGetFilesToolHandler(ToolHandler): def __init__(self): super().__init__(TOOL_BATCH_GET_FILES) def get_tool_description(self): return Tool( name=self.name, description="Return the contents and metadata of one or more notes (.md files) in your vault.", inputSchema={ "type": "object", "properties": { "filepaths": { "type": "array", "items": { "type": "string", "description": "Path to a file (relative to your vault root)", "format": "path" }, "description": "List of file paths to read" }, }, "required": ["filepaths"] } ) def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]: if "filepaths" not in args: raise RuntimeError("filepaths argument missing in arguments") results = api.get_batch_file_contents(args["filepaths"]) response_parts = [] # For each file, add its metadata/info JSON followed by its content for result in results: if "error" not in result: # Add metadata and note info as JSON metadata_json = { "filepath": result["filepath"], "note_info": result["note_info"] } response_parts.append( TextContent( type="text", text=f"## File: {result['filepath']}\n\n### Metadata & Info\n```json\n{json.dumps(metadata_json, indent=2)}\n```" ) ) # Add the actual content as readable markdown response_parts.append( TextContent( type="text", text=f"### Content Below:\n\n{result['content']}" ) ) else: # Handle error case response_parts.append( TextContent( type="text", text=f"## File: {result['filepath']}\n\n### Error\n{result['error']}" ) ) return response_parts
- src/mcp_obsidian_advanced/server.py:26-43 (registration)TOOL_MAPPING dictionary maps the tool name 'obsidian_batch_get_files' (via constant) to its handler class. Used in register_tools() to instantiate and register the tool with the MCP server.TOOL_MAPPING = { tools.TOOL_LIST_FILES_IN_DIR: tools.ListFilesInDirToolHandler, tools.TOOL_SIMPLE_SEARCH: tools.SearchToolHandler, tools.TOOL_PATCH_CONTENT: tools.PatchContentToolHandler, tools.TOOL_PUT_CONTENT: tools.PutContentToolHandler, tools.TOOL_APPEND_CONTENT: tools.AppendContentToolHandler, tools.TOOL_DELETE_FILE: tools.DeleteFileToolHandler, tools.TOOL_COMPLEX_SEARCH: tools.ComplexSearchToolHandler, tools.TOOL_BATCH_GET_FILES: tools.BatchGetFilesToolHandler, tools.TOOL_PERIODIC_NOTES: tools.PeriodicNotesToolHandler, tools.TOOL_RECENT_PERIODIC_NOTES: tools.RecentPeriodicNotesToolHandler, tools.TOOL_RECENT_CHANGES: tools.RecentChangesToolHandler, tools.TOOL_UNDERSTAND_VAULT: tools.UnderstandVaultToolHandler, tools.TOOL_GET_ACTIVE_NOTE: tools.GetActiveNoteToolHandler, tools.TOOL_OPEN_FILES: tools.OpenFilesToolHandler, tools.TOOL_LIST_COMMANDS: tools.ListCommandsToolHandler, tools.TOOL_EXECUTE_COMMANDS: tools.ExecuteCommandsToolHandler, }
- Core helper method in Obsidian API class that implements batch retrieval of file contents and comprehensive metadata (tags, frontmatter, file stats, links, backlinks) using REST API and obsidiantools. Called directly by the tool handler.def get_batch_file_contents(self, filepaths: list[str]) -> list: """Get contents and info of multiple files using enhanced API approach. Args: filepaths: List of .md note file paths to read. ONLY .md files are supported. Returns: List of objects containing file metadata, connections, tags, front matter, file info, and content """ results = [] for filepath in filepaths: try: # Get content using the existing method content = self.get_file_contents(filepath) # Get enhanced metadata using REST API (similar to active note approach) url = f"{self.get_base_url()}/vault/{filepath}" headers = self._get_headers() | {'Accept': 'application/vnd.olrapi.note+json'} def call_fn(): response = requests.get(url, headers=headers, verify=self.verify_ssl, timeout=self.timeout) response.raise_for_status() return response.json() file_data = self._safe_call(call_fn) # Extract data from the API response api_frontmatter = file_data.get('frontmatter', {}) api_stat = file_data.get('stat', {}) api_tags = file_data.get('tags', []) # Get additional note info using obsidiantools for connections/links try: note_info = self.get_note_info(filepath) # Replace the tags from get_note_info with the more accurate API tags note_info['metadata']['tags'] = api_tags # Also update frontmatter with API data for accuracy note_info['metadata']['front_matter'] = api_frontmatter # Update file info with API stat data note_info['metadata']['file_info'].update({ 'ctime': api_stat.get('ctime'), 'mtime': api_stat.get('mtime'), 'size': api_stat.get('size') }) except Exception as note_info_error: # Fallback: create basic note info from API data note_info = { 'metadata': { 'tags': api_tags, 'front_matter': api_frontmatter, 'file_info': { 'rel_filepath': filepath, 'ctime': api_stat.get('ctime'), 'mtime': api_stat.get('mtime'), 'size': api_stat.get('size') }, 'counts': { 'n_backlinks': 0, 'n_wikilinks': 0, 'n_embedded_files': 0, 'n_tags': len(api_tags) } }, 'connections': { 'direct_links': [], 'backlinks': [], 'non_existent_links': [] } } results.append({ "filepath": filepath, "note_info": note_info, "content": content }) except Exception as e: # Add error message but continue processing other files results.append({ "filepath": filepath, "error": f"Error reading file: {str(e)}" }) return results
- src/mcp_obsidian_advanced/server.py:95-108 (registration)The register_tools function iterates over selected tools (including obsidian_batch_get_files if not filtered out), instantiates the handler using TOOL_MAPPING, and adds it to tool_handlers for use in @app.list_tools and @app.call_tool.def register_tools(): """Register the selected tools with the server.""" tools_to_include = parse_include_tools() registered_count = 0 for tool_name in tools_to_include: if tool_name in TOOL_MAPPING: handler_class = TOOL_MAPPING[tool_name] handler_instance = handler_class() add_tool_handler(handler_instance) registered_count += 1 logger.debug(f"Registered tool: {tool_name}") logger.info(f"Successfully registered {registered_count} tools")