Skip to main content
Glama
ToKiDoO

Advanced Obsidian MCP Server

by ToKiDoO

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
NameRequiredDescriptionDefault
filepathsYesList 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
  • 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
  • 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")
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It discloses the tool reads files and returns content/metadata, but lacks details on permissions needed, error handling for non-existent files, rate limits, or return format. It adds basic behavioral context but leaves gaps for a read operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose ('Return the contents and metadata') without wasted words. Every part earns its place by specifying scope and resource.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations and no output schema, the description is minimally complete for a read tool with one parameter. It covers what the tool does but lacks details on output structure, error cases, or vault-specific constraints. Adequate but with clear gaps in behavioral context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents the single parameter 'filepaths'. The description adds no additional parameter semantics beyond implying multiple files can be specified, which is already clear from the schema's array type. Baseline 3 is appropriate as the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Return') and target resource ('contents and metadata of one or more notes (.md files) in your vault'), specifying both verb and resource. It distinguishes from siblings like obsidian_get_active_note (single active note) and obsidian_list_files_in_dir (list files without content).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for reading multiple notes by specifying 'one or more notes', providing clear context. However, it doesn't explicitly state when not to use it (e.g., vs. obsidian_simple_search for filtered content) or name alternatives, missing explicit exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ToKiDoO/mcp-obsidian-advanced'

If you have feedback or need assistance with the MCP directory API, please join our Discord server