Skip to main content
Glama
paulieb89

PyP6Xer MCP Server

pyp6xer_progress_summary

Read-onlyIdempotent

Summarize schedule progress with status breakdown, weighted percent complete, and milestone statistics to quickly assess project performance.

Instructions

Summarise schedule progress: status breakdown, percent complete, milestones.

Returns counts by status, weighted percent complete, and milestone statistics.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cache_keyNoCache key identifying the loaded XER file (set when calling pyp6xer_load_file)default
proj_idNoProject ID or short name; uses first project if omitted

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The main handler function for the pyp6xer_progress_summary tool. It retrieves the loaded XER file from cache, computes schedule progress metrics including status breakdown, percent complete (weighted by duration and simple average), milestone statistics, and cost summary.
    def pyp6xer_progress_summary(
        cache_key: Annotated[str, Field(description="Cache key identifying the loaded XER file (set when calling pyp6xer_load_file)")] = "default",
        proj_id: Annotated[str | None, Field(description="Project ID or short name; uses first project if omitted")] = None,
        ctx: Context = None,
    ) -> str:
        """Summarise schedule progress: status breakdown, percent complete, milestones.
    
        Returns counts by status, weighted percent complete, and milestone statistics.
        """
        xer = _get_xer(ctx, cache_key)
        proj = _get_project(xer, proj_id)
        tasks = proj.tasks if proj_id else list(xer.tasks.values())
    
        not_started = [t for t in tasks if t.status.is_not_started]
        in_progress = [t for t in tasks if t.status.is_in_progress]
        completed = [t for t in tasks if t.status.is_completed]
        milestones = [t for t in tasks if t.type.is_milestone]
    
        # Weighted percent complete by original duration
        total_dur = sum(t.original_duration for t in tasks if t.original_duration > 0)
        weighted_pct = (
            sum(t.percent_complete * t.original_duration for t in tasks if t.original_duration > 0)
            / total_dur
            if total_dur else 0
        )
    
        # Simple average
        simple_pct = sum(t.percent_complete for t in tasks) / len(tasks) if tasks else 0
    
        ms_completed = sum(1 for m in milestones if m.status.is_completed)
        ms_open = len(milestones) - ms_completed
    
        return json.dumps({
            "data_date": _fmt_date(proj.data_date),
            "project_finish": _fmt_date(proj.finish_date),
            "total_activities": len(tasks),
            "status_breakdown": {
                "not_started": len(not_started),
                "in_progress": len(in_progress),
                "completed": len(completed),
            },
            "percent_complete": {
                "weighted_by_duration": _fmt_pct(weighted_pct),
                "simple_average": _fmt_pct(simple_pct),
                "project_duration_pct": _fmt_pct(proj.duration_percent),
                "project_task_pct": _fmt_pct(proj.task_percent),
            },
            "milestones": {
                "total": len(milestones),
                "completed": ms_completed,
                "remaining": ms_open,
            },
            "cost_summary": {
                "budgeted": round(proj.budgeted_cost, 2),
                "actual": round(proj.actual_cost, 2),
                "remaining": round(proj.remaining_cost, 2),
            },
        }, indent=2)
  • server.py:1295-1295 (registration)
    The tool is registered via the @mcp.tool decorator on the pyp6xer_progress_summary function. FastMCP auto-discovers the tool from the decorated function. The annotations indicate it is read-only, non-destructive, and idempotent.
    @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False))
  • The _fmt_date helper is used to format datetime objects into YYYY-MM-DD strings for the progress summary output.
    def _fmt_date(dt: datetime | None) -> str:
        if dt is None:
            return ""
        return dt.strftime(DATE_FMT)
    
    
    def _fmt_pct(v: float) -> str:
        return f"{v * 100:.1f}%"
  • The _fmt_pct helper converts a float (0-1) to a formatted percentage string like '75.0%', used in the percent_complete output.
    def _fmt_pct(v: float) -> str:
        return f"{v * 100:.1f}%"
  • The _get_cache and _get_xer helper functions retrieve the cached XER file data that the progress_summary tool accesses.
    def _get_cache(ctx: Context, cache_key: str) -> dict:
        cache = ctx.lifespan_context["cache"]
        if cache_key not in cache:
            raise ValueError(
                f"No file loaded with key '{cache_key}'. "
                "Call pyp6xer_load_file first."
            )
        return cache[cache_key]
    
    
    def _get_xer(ctx: Context, cache_key: str) -> Xer:
        return _get_cache(ctx, cache_key)["xer"]
Behavior4/5

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

Annotations already indicate safe read-only operation. Description adds behavioral context by listing specific return values (counts by status, weighted percent complete, milestone statistics), which helps the agent understand what to expect.

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 extremely concise: two sentences, no wasted words. The key purpose and outputs are front-loaded.

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

Completeness4/5

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

Given the presence of an output schema and comprehensive annotations, the description provides sufficient context about what the tool returns. It could mention that it requires a loaded XER file, but that is implied by the cache_key parameter.

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 coverage is 100%, so the input schema already documents both parameters. The description does not add meaningful semantics beyond what is in the schema, achieving the baseline of 3.

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 tool's purpose: to summarize schedule progress. It specifies the three key outputs (status breakdown, percent complete, milestones), making it distinct from sibling tools like earned value or schedule health check.

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

Usage Guidelines3/5

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

No explicit guidance on when to use this tool vs alternatives. The description implies it's for a high-level summary of schedule progress, but doesn't direct when to choose this over similar tools like work package summary or schedule quality.

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/paulieb89/pyp6xer-mcp'

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