Skip to main content
Glama
lethain

Library MCP

by lethain

get_by_tag

Retrieve blog content by tag from local Markdown files using the Library MCP server. Specify a tag and optional limit to filter and access relevant posts.

Instructions

Get blog content by its tag.

Args: tag: the tag associated with content limit: the number of results to include

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
tagYes
limitNo

Implementation Reference

  • main.py:428-441 (handler)
    MCP tool handler for 'get_by_tag' with @mcp.tool() decorator (registration). Includes schema via args and docstring. Executes core logic via content_manager and formats output.
    @mcp.tool()
    async def get_by_tag(tag: str, limit: int = 50) -> str:
        """Get blog content by its tag.
        
        Args:
            tag: the tag associated with content
            limit: the number of results to include
        """
        if content_manager is None:
            return "Content has not been loaded. Please ensure the server is properly initialized."
        
        matching_content = content_manager.get_by_tag(tag, limit)
        return format_content_for_output(matching_content)
  • Core helper method in HugoContentManager that implements the tag-based search logic: normalizes tags, finds exact/partial matches case-insensitively, sorts by date descending, applies limit.
    def get_by_tag(self, tag: str, limit: int = 50) -> List[ContentFile]:
        """Find all files with a given tag"""
        matches = []
        tag_lower = tag.lower()
        
        debug_print(f"Searching for tag: '{tag_lower}'")
        for file_path, content_file in self.path_to_content.items():
            raw_tags = content_file.meta.get('tags', [])
            tags = self._normalize_tags(raw_tags)
            
            # Debug
            if tags:
                debug_print(f"File: {os.path.basename(file_path)} - Tags: {tags}")
            
            # Check for exact tag match (case insensitive)
            if any(tag_lower == t.lower() for t in tags):
                debug_print(f"Found exact tag match in {os.path.basename(file_path)}")
                matches.append(content_file)
                continue
            
            # Check if the tag is part of a tag
            for t in tags:
                if tag_lower in t.lower():
                    debug_print(f"Found partial tag match in {os.path.basename(file_path)}: '{t}'")
                    matches.append(content_file)
                    break
        
        debug_print(f"Found {len(matches)} files with tag '{tag}'")
        
        # Sort by date (most recent first)
        def get_sort_key(content_file):
            date = content_file.date
            if date is None:
                return datetime.min
            # Make date naive if it has timezone info
            if hasattr(date, 'tzinfo') and date.tzinfo is not None:
                date = date.replace(tzinfo=None)
            return date
            
        matches.sort(key=get_sort_key, reverse=True)
        
        return matches[:limit]
  • Helper function to format the list of ContentFile objects into a readable string output for the tool response.
    def format_content_for_output(content_files: List[ContentFile]) -> str:
        """Format the content files for output"""
        if not content_files:
            return "No matching content found."
        
        result = []
        
        for i, file in enumerate(content_files):
            result.append(f"File: {file.path}")
            result.append("Metadata:")
            for key, value in file.meta.items():
                result.append(f"  {key}: {value}")
            
            # Include the full content
            result.append("Content:")
            result.append(file.data.strip())
            
            # Add separator between entries, but not after the last one
            if i < len(content_files) - 1:
                result.append("-" * 50)
        
        return "\n".join(result)
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the tool 'Get[s] blog content' but doesn't clarify what 'blog content' includes (e.g., full posts, summaries, metadata), whether results are paginated or sorted, or if there are rate limits or authentication requirements. For a read operation with zero annotation coverage, this leaves significant gaps in understanding the tool's behavior.

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

Conciseness4/5

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

The description is appropriately sized and front-loaded: the first sentence states the core purpose, followed by a brief 'Args' section. There's no wasted text, though the structure could be slightly improved by integrating parameter details more seamlessly rather than a separate list.

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

Completeness2/5

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

Given 2 parameters, 0% schema coverage, no annotations, and no output schema, the description is incomplete. It doesn't explain what 'blog content' entails as output, how results are formatted, or error conditions. For a tool with multiple siblings and no structured support, more context is needed to guide effective use.

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 0%, so the description must compensate. It adds basic semantics for both parameters: 'tag' is 'the tag associated with content' and 'limit' is 'the number of results to include'. This clarifies purpose but lacks details like tag format (e.g., case sensitivity) or limit constraints (e.g., max value). With 2 parameters and low schema coverage, this provides marginal value beyond the schema's structure.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Get blog content by its tag' specifies the verb ('Get') and resource ('blog content'), and distinguishes it from siblings like 'get_by_date_range' or 'get_by_slug_or_url' by focusing on tag-based retrieval. However, it doesn't explicitly differentiate from 'search_tags' or 'list_all_tags', which are related but distinct operations.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when to prefer 'get_by_tag' over 'get_by_text' for content retrieval, nor does it specify prerequisites or exclusions. The agent must infer usage from the tool name and sibling names alone.

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/lethain/library-mcp'

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