Skip to main content
Glama

ctrltest-mcp - Flight-control regression lab for MCP agents

TL;DR: Evaluate PID and bio-inspired controllers against analytic or diffSPH/Foam-Agent data through MCP, logging overshoot, energy, and gust metrics automatically.

Table of contents

  1. What it provides

  2. Quickstart

  3. Run as a service

  4. Agent playbook

  5. Stretch ideas

  6. Accessibility & upkeep

  7. Contributing

Related MCP server: shewhart-mcp

What it provides

Scenario

Value

Analytic PID benchmarking

Run closed-form plant models and produce overshoot/settling/energy metrics without manual scripting.

High-fidelity scoring

Ingest logged data from Foam-Agent or diffSPH runs and fuse it into controller evaluations.

MCP integration

Expose the scoring API via STDIO/HTTP so ToolHive or other clients can automate gain tuning and generate continuous performance scorecards.

Quickstart

uv pip install "git+https://github.com/Three-Little-Birds/ctrltest-mcp.git"

Run a PID evaluation:

from ctrltest_mcp import (
    ControlAnalysisInput,
    ControlPlant,
    ControlSimulation,
    PIDGains,
    evaluate_control,
)

request = ControlAnalysisInput(
    plant=ControlPlant(natural_frequency_hz=3.2, damping_ratio=0.35),
    gains=PIDGains(kp=2.0, ki=0.5, kd=0.12),
    simulation=ControlSimulation(duration_s=3.0, sample_points=400),
    setpoint=0.2,
)
response = evaluate_control(request)
print(response.model_dump())

Typical outputs (analytic only):

{
  "overshoot": -0.034024863556091134,
  "ise": 0.008612387509182674,
  "settling_time": 3.0,
  "gust_detection_latency_ms": 0.8,
  "gust_detection_bandwidth_hz": 1200.0,
  "gust_rejection_pct": 0.396,
  "cpg_energy_baseline_j": 12.0,
  "cpg_energy_consumed_j": 7.8,
  "cpg_energy_reduction_pct": 0.35,
  "lyapunov_margin": 0.12,
  "moe_switch_penalty": 0.135,
  "moe_latency_ms": 12.72,
  "moe_energy_j": 3.9,
  "multi_modal_score": null,
  "extra_metrics": null,
  "metadata": {"solver": "analytic"}
}

The analytic plant example above clips settling_time at the requested simulation duration (duration_s=3.0). Increase the horizon if you need the loop to settle fully before computing that metric.

Run as a service

CLI (STDIO transport)

uvx ctrltest-mcp  # runs the MCP over stdio
# or just python -m ctrltest_mcp

Use python -m ctrltest_mcp --describe to print basic metadata without starting the server.

FastAPI (REST)

uv run uvicorn ctrltest_mcp.fastapi_app:create_app --factory --port 8005

python-sdk tool (STDIO / MCP)

from mcp.server.fastmcp import FastMCP
from ctrltest_mcp.tool import build_tool

mcp = FastMCP("ctrltest-mcp", "Flapping-wing control regression")
build_tool(mcp)

if __name__ == "__main__":
    mcp.run()

ToolHive smoke test

Run the integration script from your workspace root:

uvx --with 'mcp==1.20.0' python scripts/integration/run_ctrltest.py

The smoke test runs the analytic path by default. To exercise high-fidelity scoring, stage Foam-Agent archives under logs/foam_agent/ and diffSPH gradients under logs/diffsph/ before launching the script.

Agent playbook

  • Gust rejection - feed archived diffSPH gradients (diffsph_metrics) and Foam-Agent archives (paths returned by those services) to quantify adaptive CPG improvements.

  • Controller comparison - log analytics for multiple PID gains, export JSONL evidence, and visualise in Grafana.

  • Policy evaluation - integrate with RL or evolutionary algorithms; metrics are structured for automated scoring.

Stretch ideas

  1. Extend the adapter for PteraControls (planned once upstream Python bindings are published).

  2. Drive the MCP from scripts/fitness to populate nightly scorecards.

  3. Combine with migration-mcp to explore route-specific disturbance budgets.

Accessibility & upkeep

  • Run uv run pytest (tests mock diffSPH/Foam-Agent inputs and assert deterministic analytic results).

  • Keep metric schema changes documented—downstream dashboards rely on them.

Metric schema at a glance

Field

Units

Notes

overshoot

radians

peak response minus setpoint

ise

rad²·s

integral squared error

settling_time

seconds

first time error stays within tolerance

gust_detection_latency_ms

milliseconds

detector latency

gust_detection_bandwidth_hz

hertz

detector bandwidth

gust_rejection_pct

0–1

fraction of disturbance rejected

cpg_energy_baseline_j

joules

energy pre-adaptation

cpg_energy_consumed_j

joules

energy post-adaptation

cpg_energy_reduction_pct

0–1

energy reduction ratio

lyapunov_margin

unitless

stability margin

moe_switch_penalty

unitless

cost weight × switches

moe_latency_ms

milliseconds

latency budget after switching

moe_energy_j

joules

mix-of-experts energy draw

multi_modal_score

unitless

only when both diffSPH & Foam metrics are present

extra_metrics

varies

raw diffSPH/Foam metrics merged in

Example of fused high-fidelity metrics:

{
  "extra_metrics": {
    "force_gradient_norm": 0.87,
    "lift_drag_ratio": 18.4
  },
  "multi_modal_score": 0.047,
  "metadata": {"solver": "analytic"}
}

Contributing

  1. uv pip install --system -e .[dev]

  2. uv run ruff check . and uv run pytest

  3. Share sample metrics in PRs so reviewers can sanity-check improvements quickly.

MIT license - see LICENSE.

Available Tools

1 tool
ctrltest.analyze_pidC

Score PID gains for a flapping-wing plant. Provide plant dynamics and optional gradients/metadata. Returns key control metrics plus provenance. Example input: {"plant":{"natural_frequency_hz":4.2,"damping_ratio":0.45},"gains":{"kp":1.1,"ki":0.2,"kd":0.08},"diffsph_metrics":{"force_gradient_norm":1.9}}

ParametersJSON Schema
NameRequiredDescriptionDefault
requestYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
iseYes
metadataNo
overshootYes
moe_energy_jYes
extra_metricsNo
settling_timeYes
moe_latency_msYes
lyapunov_marginYes
multi_modal_scoreNo
gust_rejection_pctYes
moe_switch_penaltyYes
cpg_energy_baseline_jYes
cpg_energy_consumed_jYes
cpg_energy_reduction_pctYes
gust_detection_latency_msYes
gust_detection_bandwidth_hzYes

TDQS

C2.9/5.0
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions what the tool returns ('key control metrics plus provenance'), it doesn't describe important behavioral aspects like computational requirements, accuracy limitations, whether it's a simulation or real-time analysis, error conditions, or performance characteristics. The description is insufficient for a complex control analysis tool.

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 reasonably concise with two sentences plus an example. The first sentence states the purpose and required inputs, the second describes the output, and the example provides concrete illustration. However, the example could be more focused on illustrating the structure rather than specific values.

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 the complex parameter structure (1 top-level parameter with 10 nested properties across multiple objects), 0% schema description coverage, and no annotations, the description is incomplete. While an output schema exists (which helps), the description doesn't adequately explain the sophisticated control engineering concepts involved or the tool's operational context. The example helps but doesn't compensate for the missing conceptual explanation.

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 mentions 'plant dynamics and optional gradients/metadata' which aligns with the input schema's structure. However, with 0% schema description coverage, the description doesn't adequately explain the complex nested parameter structure (plant, gains, simulation, setpoint, gust_detector, adaptive_cpg, moe_router, diffsph_metrics, foam_metrics, prefer_high_fidelity). The example input shows some parameters but doesn't cover the full complexity.

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: 'Score PID gains for a flapping-wing plant' with specific verb ('Score') and resource ('PID gains'). It mentions providing 'plant dynamics and optional gradients/metadata' and returning 'key control metrics plus provenance'. However, without sibling tools, we cannot assess differentiation from alternatives.

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 mentions the tool's function but offers no context about prerequisites, typical use cases, or limitations. The example input shows what data to provide, but doesn't explain when this analysis would be appropriate.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 1 tool updatev1.0.0
    • Changedctrltest.analyze_pid2 fields changed
      • addedInput schema / $defs
        Added value: +{
        +  "AdaptiveCPGConfig": {
        +    "properties": {
        +      "energy_baseline_j": {
        +        "default": 12,
        +        "exclusiveMinimum": 0,
        +        "title": "Energy Baseline J",
        +        "type": "number"
        +      },
        +      "energy_reduction_pct": {
        +        "default": 0.35,
        +        "maximum": 0.95,
        +        "minimum": 0,
        +        "title": "Energy Reduction Pct",
        +        "type": "number"
        +      },
        +      "lyapunov_margin": {
        +        "default": 0.12,
        +        "minimum": 0,
        +        "title": "Lyapunov Margin",
        +        "type": "number"
        +      },
        +      "target_rejection_pct": {
        +        "default": 0.45,
        +        "maximum": 0.95,
        +        "minimum": 0,
        +        "title": "Target Rejection Pct",
        +        "type": "number"
        +      }
        +    },
        +    "title": "AdaptiveCPGConfig",
        +    "type": "object"
        +  },
        +  "ControlAnalysisInput": {
        +    "properties": {
        +      "adaptive_cpg": {
        +        "$ref": "#/$defs/AdaptiveCPGConfig"
        +      },
        +      "diffsph_metrics": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": true,
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "title": "Diffsph Metrics"
        +      },
        +      "foam_metrics": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": true,
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null,
        +        "title": "Foam Metrics"
        +      },
        +      "gains": {
        +        "$ref": "#/$defs/PIDGains"
        +      },
        +      "gust_detector": {
        +        "$ref": "#/$defs/GustDetectorConfig"
        +      },
        +      "moe_router": {
        +        "$ref": "#/$defs/MoERouterConfig"
        +      },
        +      "plant": {
        +        "$ref": "#/$defs/ControlPlant"
        +      },
        +      "prefer_high_fidelity": {
        +        "default": true,
        +        "description": "Attempt to use PteraControls when available before falling back to the analytic surrogate.",
        +        "title": "Prefer High Fidelity",
        +        "type": "boolean"
        +      },
        +      "setpoint": {
        +        "default": 0,
        +        "title": "Setpoint",
        +        "type": "number"
        +      },
        +      "simulation": {
        +        "$ref": "#/$defs/ControlSimulation"
        +      }
        +    },
        +    "required": [
        +      "plant",
        +      "gains"
        +    ],
        +    "title": "ControlAnalysisInput",
        +    "type": "object"
        +  },
        +  "ControlPlant": {
        +    "properties": {
        +      "damping_ratio": {
        +        "minimum": 0,
        +        "title": "Damping Ratio",
        +        "type": "number"
        +      },
        +      "natural_frequency_hz": {
        +        "exclusiveMinimum": 0,
        +        "title": "Natural Frequency Hz",
        +        "type": "number"
        +      },
        +      "settling_tolerance_rad": {
        +        "default": 0.02,
        +        "exclusiveMinimum": 0,
        +        "title": "Settling Tolerance Rad",
        +        "type": "number"
        +      },
        +      "trim_setpoint": {
        +        "default": 0,
        +        "title": "Trim Setpoint",
        +        "type": "number"
        +      }
        +    },
        +    "required": [
        +      "natural_frequency_hz",
        +      "damping_ratio"
        +    ],
        +    "title": "ControlPlant",
        +    "type": "object"
        +  },
        +  "ControlSimulation": {
        +    "properties": {
        +      "duration_s": {
        +        "default": 5,
        +        "exclusiveMinimum": 0,
        +        "title": "Duration S",
        +        "type": "number"
        +      },
        +      "sample_points": {
        +        "default": 500,
        +        "maximum": 5000,
        +        "minimum": 50,
        +        "title": "Sample Points",
        +        "type": "integer"
        +      }
        +    },
        +    "title": "ControlSimulation",
        +    "type": "object"
        +  },
        +  "GustDetectorConfig": {
        +    "properties": {
        +      "bandwidth_hz": {
        +        "default": 1200,
        +        "minimum": 10,
        +        "title": "Bandwidth Hz",
        +        "type": "number"
        +      },
        +      "latency_ms": {
        +        "default": 0.8,
        +        "minimum": 0.1,
        +        "title": "Latency Ms",
        +        "type": "number"
        +      },
        +      "sensitivity": {
        +        "default": 0.88,
        +        "maximum": 1,
        +        "minimum": 0,
        +        "title": "Sensitivity",
        +        "type": "number"
        +      }
        +    },
        +    "title": "GustDetectorConfig",
        +    "type": "object"
        +  },
        +  "MoERouterConfig": {
        +    "properties": {
        +      "energy_budget_j": {
        +        "default": 6,
        +        "exclusiveMinimum": 0,
        +        "title": "Energy Budget J",
        +        "type": "number"
        +      },
        +      "latency_budget_ms": {
        +        "default": 12,
        +        "exclusiveMinimum": 0,
        +        "title": "Latency Budget Ms",
        +        "type": "number"
        +      },
        +      "switch_cost_weight": {
        +        "default": 0.045,
        +        "minimum": 0,
        +        "title": "Switch Cost Weight",
        +        "type": "number"
        +      },
        +      "switch_events": {
        +        "default": 3,
        +        "minimum": 0,
        +        "title": "Switch Events",
        +        "type": "integer"
        +      }
        +    },
        +    "title": "MoERouterConfig",
        +    "type": "object"
        +  },
        +  "PIDGains": {
        +    "properties": {
        +      "kd": {
        +        "default": 0,
        +        "minimum": 0,
        +        "title": "Kd",
        +        "type": "number"
        +      },
        +      "ki": {
        +        "default": 0,
        +        "minimum": 0,
        +        "title": "Ki",
        +        "type": "number"
        +      },
        +      "kp": {
        +        "minimum": 0,
        +        "title": "Kp",
        +        "type": "number"
        +      }
        +    },
        +    "required": [
        +      "kp"
        +    ],
        +    "title": "PIDGains",
        +    "type": "object"
        +  }
        +}
      • addedInput schema / title
        Added value: +"analyzeArguments"
  2. 1 tool update
    • First observedctrltest.analyze_pid

TDQS

B3.1/5.0
Disambiguation5/5

With only one tool, there is no possibility of ambiguity or overlap between tools. The tool's purpose is clearly defined as analyzing PID gains for a specific type of plant, making it distinct by default.

Naming Consistency5/5

The single tool name follows a consistent pattern with a clear namespace prefix (ctrltest) and descriptive action (analyze_pid). With only one tool, naming consistency is inherently perfect as there are no other tools to compare against.

Tool Count2/5

A single tool is generally too few for most server purposes, as it limits functionality and forces agents to rely on one operation. While it might be appropriate for a highly specialized task, the server's name 'CtrlTest MCP Server' suggests a broader control testing domain where more tools would be expected for completeness.

Completeness2/5

The server appears focused on control system testing, but with only one analysis tool, there are significant gaps. Missing operations likely include tools for setting up tests, running simulations, comparing results, or managing test configurations, making the surface severely incomplete for the implied domain.

Maintenance

ActivityInactive
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A dual-track testing server that combines CLI test execution with Playwright-based browser testing and persistent SQLite logging. It enables automated test pipelines, Git integration, and evidence-based requirement generation to streamline the development lifecycle.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to perform statistical process control calculations using validated, deterministic tools such as control charts, capability analysis, and tolerance intervals.
    3
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Validated PNT-resilience simulator over MCP — SGP4/SDP4 orbit propagation, IAU reference frames, GNSS availability/DOP, GNSS/INS fusion, ARAIM integrity, and Allan deviations, with results validated against AIAA/IGS/SOFA/NIST reference data.
    6
    AGPL 3.0

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/Three-Little-Birds/ctrltest-mcp'

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