Skip to content

feat: standardize expected operation durations for async polling #29

Description

@markballew

Problem

MCP tools and CLI wrappers frequently invoke long-running infrastructure operations (BMC reboots, MAAS deploys, firmware updates, etc.). Today, poll_with_progress defaults to timeout_s=600 and each caller picks its own timeout — often a guess. Agents calling these tools have no way to know how long an operation should take, leading to:

  • Premature timeouts (agent gives up too early)
  • Wasted idle time (agent waits far longer than needed before declaring failure)
  • No feedback loop — historical duration data is never captured or reused

For context, real-world operations vary wildly: a Supermicro BMC reboot is ~5 min, a vanilla MAAS deploy is ~20 min, a firmware flash is ~15 min. Each downstream MCP knows its own operations — mcp-common does not and should not.

What mcp-common should provide

mcp-common is a library — it doesn't know about redfish, MAAS, or weka. It should provide primitives and a reference architecture that downstream MCPs use to define, store, report, and consume their own operation timing data.

1. Timing catalog primitives

A typed data structure that any MCP can instantiate to register its operations and their expected durations:

from mcp_common.timing import OperationTiming, TimingCatalog

# Each downstream MCP defines its own catalog
catalog = TimingCatalog([
    OperationTiming(
        operation_id="power_cycle",
        description="Full BMC power cycle via Redfish",
        expected_s=300,
        timeout_s=600,
        tags=["power"],
    ),
    OperationTiming(
        operation_id="firmware_update",
        description="BMC firmware flash",
        expected_s=900,
        timeout_s=1800,
        tags=["firmware"],
    ),
])

mcp-common defines OperationTiming, TimingCatalog, and the lookup interface. Each MCP populates its own.

2. Integration with poll_with_progress

Allow callers to pass an operation_id that resolves timeout/interval from a catalog instead of raw numbers:

# Today — caller guesses
result = await poll_with_progress(ctx, check_fn, "status", states, timeout_s=1200)

# Proposed — catalog-driven
result = await poll_with_progress(ctx, check_fn, "status", states, catalog=catalog, operation_id="deploy")

If both timeout_s and operation_id are given, explicit timeout_s wins (escape hatch). The catalog just provides smart defaults.

3. Structured timing telemetry on poll completion

When poll_with_progress completes, emit a structured log event with:

{
  "log_channel": "transcript",
  "event": "poll_complete",
  "operation_id": "deploy",
  "expected_s": 1200,
  "actual_s": 1087,
  "timed_out": false,
  "ok": true
}

This uses the existing log_transcript_event / mcp_log_transcript infrastructure from #17. Downstream teams can mine these logs to refine their catalog values over time.

4. Catalog storage format

mcp-common should define a canonical file format (TOML section, or a Python dict) and a loader, so downstream MCPs can store timing data however they prefer:

Storage pattern Who does it Notes
Python dict in code Downstream MCP Simplest, versioned with the code
timing.toml file Downstream MCP Separates data from code, human-editable
mcp-plugin.toml section Downstream MCP Co-located with existing plugin metadata
Dynamic / DB-backed Downstream MCP (future) For teams that want to update without deploys

mcp-common provides the loader interface; each MCP chooses its own backend.

Design questions to resolve

  1. Should OperationTiming include percentiles (p50, p95)? Useful for progress reporting ("you're past the 95th percentile, something may be wrong") but adds complexity before we have real data.

  2. How do downstream MCPs collect actual durations?

    • Transcript log mining (passive, retrospective)
    • Explicit telemetry from poll_with_progress (proposed above)
    • Agent self-report during runs
    • Observability pipeline / OTel spans (future)
  3. Should agents be able to query the catalog? A dedicated tool (get_expected_duration) or just bake it into tool descriptions / progress messages?

  4. How does the agent use this? The agent needs to set block_until_ms when calling MCP tools. Options:

    • Progress messages include ETA so the agent can calibrate its own polling
    • Tool response metadata includes expected_duration_s
    • Agent reads the catalog before invoking (extra round-trip)

Scope for mcp-common

  • Define OperationTiming dataclass and TimingCatalog with lookup
  • Optional catalog + operation_id params on poll_with_progress
  • Emit structured timing telemetry on poll completion
  • Define canonical TOML schema for downstream timing.toml files
  • Loader that reads timing data from TOML or Python dict
  • Documentation: reference architecture guide for downstream MCPs
  • Example: seed a sample timing.toml in docs/examples

Not in scope for mcp-common: actual operation timing values, specific MCP domain knowledge, or centralized catalogs of all operations.

Related

References

  • Argo Workflows estimated-duration annotations (per-step, defined by the workflow author)
  • Temporal Nexus dynamic config for operation timeouts
  • AWS Systems Manager timeoutSeconds per automation action
  • Prefect SLA duration thresholds with severity levels

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions