Skip to main content
Glama
dev-in-black

OpenProject MCP Server

by dev-in-black

get_work_package

Retrieve detailed information about a specific work package from OpenProject, including task details, assignees, and relationships.

Instructions

Get detailed information about a work package.

Args:
    work_package_id: Work package ID

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
work_package_idYes

Implementation Reference

  • The main handler function that fetches a work package by ID from the OpenProject API using OpenProjectClient and formats the response as a markdown string using the helper function.
    async def get_work_package(work_package_id: int) -> str:
        """Get detailed information about a work package.
    
        Args:
            work_package_id: Work package ID
    
        Returns:
            Formatted markdown string with work package details
        """
        client = OpenProjectClient()
    
        try:
            result = await client.get(f"work_packages/{work_package_id}")
            return _format_work_package_markdown(result)
        finally:
            await client.close()
  • Registration of the 'get_work_package' tool using the @mcp.tool() decorator in the FastMCP server. This thin wrapper delegates execution to the implementation in tools/work_packages.py.
    @mcp.tool()
    async def get_work_package(work_package_id: int):
        """Get detailed information about a work package.
    
        Args:
            work_package_id: Work package ID
        """
        return await work_packages.get_work_package(work_package_id=work_package_id)
  • Supporting helper function that takes a raw work package dictionary from the API and formats it into a comprehensive markdown report including all key fields like ID, subject, status, assignee, dates, costs, hierarchy, etc.
    def _format_work_package_markdown(wp: dict[str, Any]) -> str:
        """Format a work package as markdown.
    
        Args:
            wp: Work package object from OpenProject API
    
        Returns:
            Formatted markdown string
        """
        # Basic information
        wp_id = wp.get("id", "N/A")
        subject = wp.get("subject", "No subject")
        lock_version = wp.get("lockVersion", "N/A")
        created_at = wp.get("createdAt", "N/A")
        updated_at = wp.get("updatedAt", "N/A")
    
        # Description
        description_obj = wp.get("description", {})
        description = description_obj.get("raw", "") if description_obj else ""
    
        # Dates
        start_date = wp.get("startDate", "Not set")
        due_date = wp.get("dueDate", "Not set")
        estimated_time = wp.get("estimatedTime", "Not set")
    
        # Costs
        labor_costs = wp.get("laborCosts", "0.00")
        material_costs = wp.get("materialCosts", "0.00")
        overall_costs = wp.get("overallCosts", "0.00")
        spent_time = wp.get("spentTime", "PT0S")
    
        # Get embedded resources
        embedded = wp.get("_embedded", {})
    
        # Type
        type_obj = embedded.get("type", {})
        type_name = type_obj.get("name", "N/A")
        type_color = type_obj.get("color", "")
    
        # Status
        status_obj = embedded.get("status", {})
        status_name = status_obj.get("name", "N/A")
        status_color = status_obj.get("color", "")
    
        # Priority
        priority_obj = embedded.get("priority", {})
        priority_name = priority_obj.get("name", "N/A")
    
        # Project
        project_obj = embedded.get("project", {})
        project_name = project_obj.get("name", "N/A")
        project_id = project_obj.get("identifier", "N/A")
    
        # Author
        author_obj = embedded.get("author", {})
        author_name = author_obj.get("name", "Unknown")
    
        # Assignee (from _links)
        links = wp.get("_links", {})
        assignee_link = links.get("assignee", {})
        assignee_name = assignee_link.get("title") if assignee_link.get("href") else "Unassigned"
    
        # Parent
        parent_link = links.get("parent", {})
        parent_info = parent_link.get("title") if parent_link.get("href") else "None"
    
        # Ancestors
        ancestors = links.get("ancestors", [])
        ancestor_info = []
        for ancestor in ancestors:
            ancestor_info.append(ancestor.get("title", "Unknown"))
    
        # Children
        children = links.get("children", [])
        children_count = len(children) if children else 0
    
        # Attachments
        attachments_obj = embedded.get("attachments", {})
        attachments_count = attachments_obj.get("total", 0)
    
        # Relations
        relations_obj = embedded.get("relations", {})
        relations_count = relations_obj.get("total", 0)
    
        # Build markdown
        markdown = f"""# Work Package #{wp_id}: {subject}
    
    ## Basic Information
    - **Type:** {type_name}
    - **Status:** {status_name}
    - **Priority:** {priority_name}
    - **Project:** {project_name} (`{project_id}`)
    - **Lock Version:** {lock_version}
    
    ## People
    - **Author:** {author_name}
    - **Assignee:** {assignee_name}
    
    ## Dates & Schedule
    - **Start Date:** {start_date}
    - **Due Date:** {due_date}
    - **Estimated Time:** {estimated_time}
    - **Spent Time:** {spent_time}
    
    ## Costs
    - **Labor Costs:** {labor_costs}
    - **Material Costs:** {material_costs}
    - **Overall Costs:** {overall_costs}
    
    ## Hierarchy
    - **Parent:** {parent_info}"""
    
        if ancestor_info:
            markdown += "\n- **Ancestors:** " + " > ".join(ancestor_info)
    
        if children_count > 0:
            markdown += f"\n- **Children:** {children_count} child work package(s)"
    
        markdown += f"""
    
    ## Additional Info
    - **Attachments:** {attachments_count}
    - **Relations:** {relations_count}
    - **Created:** {created_at}
    - **Updated:** {updated_at}
    """
    
        if description:
            markdown += f"""
    ## Description
    ```
    {description}
    ```
    """
    
        return markdown
Behavior2/5

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

With no annotations provided, the description carries full burden but lacks behavioral details. It doesn't disclose whether this is a read-only operation, what permissions are required, error handling, or response format. 'Get detailed information' is vague about what 'detailed' entails.

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 concise with two sentences: one stating the purpose and another listing the parameter. It's front-loaded with the main function, though the parameter section could be integrated more smoothly.

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 the complexity (a read operation with no annotations or output schema), the description is incomplete. It doesn't explain what 'detailed information' includes, potential errors, or how this fits with sibling tools like 'get_work_package_activities', leaving significant gaps for an agent.

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?

The description adds minimal semantics beyond the input schema, which has 0% coverage. It specifies that 'work_package_id' is a 'Work package ID', but this is redundant with the schema's title. No format, constraints, or examples are provided, leaving gaps in understanding.

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 with a specific verb ('Get') and resource ('detailed information about a work package'), distinguishing it from siblings like 'list_work_packages' or 'get_work_package_activities'. However, it doesn't explicitly differentiate from 'get_work_package_schema', which might cause some ambiguity.

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 like 'list_work_packages' for multiple packages or 'get_work_package_schema' for schema details. It only states what the tool does, without context or 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/dev-in-black/openproject-mcp'

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