Skip to main content
Glama
Fromzy1

Branch Diagnostics MCP

by Fromzy1
README.md
# Branch Diagnostics MCP

**An MCP server that turns a vague network complaint into a disciplined, AI-driven investigation.**

> _"The branch office is slow."_ → a structured triage that pinpoints **which hop** is to blame —
> DNS, TCP, TLS, the server, or the link — and says what to do about it.

This server gives an AI assistant a set of tools to investigate service and network problems the way
a seasoned engineer would: not by guessing, but by walking a deliberate funnel of evidence over
**cURL timing metrics**.

---

## The problem (why this exists)

When a user reports _"the app is slow"_ or _"the branch can't connect,"_ the complaint is vague but
the cost is real: time-to-resolution. Good triage is **slow, inconsistent, and locked in the heads of
a few senior engineers** — everyone else gathers the wrong data, reads it the wrong way, and escalates.

The expertise that makes triage fast is actually quite structured: _for this kind of symptom, look at
these specific signals, in this order, and here's what "bad" looks like._ That structure can be
**encoded once and handed to an AI assistant** — so anyone, at any hour, runs the same rigorous
investigation. That's what this project does.

> **What's MCP?** The [Model Context Protocol](https://modelcontextprotocol.io) is an open standard
> (introduced by Anthropic in late 2024) for giving AI assistants real tools and data through a
> uniform interface. An MCP **server** like this one exposes capabilities; any MCP **client** (Claude
> Desktop, IDEs, agents) can use them. This project was first built in **June 2025**, in MCP's earliest
> months — see [Evolution](#evolution) below.

## What it does — the funnel

A single coherent workflow, each step a tool the assistant can call:

```mermaid
flowchart LR
    S["Symptom<br/>(free text)"] --> C["1 · Categorize<br/>diagnostic_categorize"]
    C --> M["2 · Pick metrics<br/>find_metrics"]
    M --> D["3 · Collect data<br/>get_data_metrics"]
    D --> A["4 · Analyse<br/>analyse"]
    A --> R["Severity + anomalies<br/>+ recommendations"]
```

The assistant is also given a **guidance prompt** that teaches it how to run the funnel, and two
**resources** it can browse: the catalog of diagnostic categories and the catalog of metrics.

---

## Architecture & design decisions

### Why cURL metrics are the right signal

`CURLINFO_*` values are [libcurl](https://curl.se/libcurl/)'s per-request **timing and outcome
breakdown** — the same data you can see with `curl -w`. Every HTTP request passes through ordered
phases, and libcurl reports a cumulative timestamp at each one. The power is in the **differences
between adjacent phases**: each gap isolates one stage of the request, so a single slow request tells
you _exactly which hop_ is at fault.

| Phase gap | cURL metric math | What it isolates |
|---|---|---|
| DNS resolution | `NAMELOOKUP_TIME` | Name servers / resolver |
| TCP connect | `CONNECT_TIME − NAMELOOKUP_TIME` | Network path, routing, latency |
| TLS handshake | `PRETRANSFER_TIME − CONNECT_TIME` | Certificates, TLS negotiation |
| Server think-time (TTFB) | `STARTTRANSFER_TIME − PRETRANSFER_TIME` | The application / backend |
| Content download | `TOTAL_TIME − STARTTRANSFER_TIME` | Throughput, payload size, link |

Alongside timing, **outcome** metrics (`RESPONSE_CODE`, `SSL_VERIFYRESULT`, `OS_ERRNO`,
`NUM_CONNECTS`, …) catch failures rather than slowness. Together they cover the two questions every
triage starts with: _is it slow, or is it broken — and where?_

### The MCP surface

**Tools** (all read-only, annotated as such):

| Tool | Funnel step | In → Out |
|---|---|---|
| `diagnostic_categorize` | 1 · classify | `symptom` → best category + confidence + all scores |
| `find_metrics` | 2 · select | `symptom, category` → the metrics to collect, each with why |
| `get_data_metrics` | 3 · collect | `metrics, location` → current values, rolling stats, thresholds |
| `analyse` | 4 · evaluate | `symptom, category, data` → severity, anomalies, recommendations |

**Resources** (browsable JSON, with parameterized lookups):
`branch://categories`, `branch://categories/{name}`, `branch://metrics`, `branch://metrics/{name}`.

**Prompt:** `branch_diagnostics_guidance` — reusable system guidance that teaches a client to drive
the funnel (the diagnostic methodology, not just the tool list).

### Design decisions worth calling out

- **Structured, typed tool output.** Tools return typed dataclasses, so the server emits machine-
  readable `structuredContent` with an auto-generated `outputSchema` — clients get data, not prose to
  re-parse. (The original prototype returned hand-formatted Markdown; this is the meaningful upgrade.)
- **A registered guidance prompt.** The diagnostic methodology ships _with_ the server as a first-class
  MCP prompt, instead of living in a comment.
- **Read-only by contract.** Every tool is annotated `readOnlyHint`, so clients know it's safe to call.
- **A pluggable data layer.** `MetricsDataSource` is isolated behind one seam. It simulates realistic
  data today (so the server runs out of the box); a real backend drops in without touching any
  diagnostic logic — see [Going to production](#simulated-data--going-to-production).
- **Vendor-neutral by design.** Pure libcurl + observability vocabulary; nothing tied to any product.

### Worked example

Driving the funnel for _"branch office VPN connectivity problems"_ (actual server output):

```text
1 · diagnostic_categorize("branch office vpn connectivity problems")
      → recommended_category: "Branch Office Issue"  (confidence 3)

2 · find_metrics("branch office vpn connectivity problems", "Branch Office Issue")
      → CURLINFO_NAMELOOKUP_TIME, CURLINFO_CONNECT_TIME, CURLINFO_LOCAL_IP,
        CURLINFO_PRIMARY_IP, CURLINFO_TOTAL_TIME   (each with a relevance note)

3 · get_data_metrics([...], "branch-paris-01")
      → { "CURLINFO_CONNECT_TIME": { current: 0.125, threshold_warning: 0.5, ... }, ... }
        simulated: true

4 · analyse("branch office vpn connectivity problems", "Branch Office Issue", <data>)
      → overall_severity: "NORMAL"
        analysis_summary: "No significant anomalies detected ..."
        next_steps: [ "Monitor the identified metrics over time ...", ... ]
```

Feed `analyse` data where, say, `CURLINFO_CONNECT_TIME` exceeds its critical threshold and the verdict
flips to `CRITICAL` with a targeted recommendation — the network hop, not the server, is implicated.

---

## Install & run

Requires Python ≥ 3.13 and [`uv`](https://docs.astral.sh/uv/).

```bash
uv venv
uv pip install -e .
```

Run it (stdio is the default transport, ideal for local MCP clients):

```bash
uv run python branch_diagnostics_server.py
# or via the FastMCP CLI:
uv run fastmcp run branch_diagnostics_server.py
```

Run it over **Streamable HTTP** instead:

```bash
MCP_HTTP=1 uv run python branch_diagnostics_server.py   # serves on http://127.0.0.1:8000/mcp
```

Register it with an MCP client (e.g. Claude Desktop) by adding to the client's config:

```jsonc
{
  "mcpServers": {
    "branch-diagnostics": {
      "command": "uv",
      "args": ["run", "python", "branch_diagnostics_server.py"],
      "cwd": "/path/to/branch_mcp_v2"
    }
  }
}
```

Smoke-test the whole funnel in-memory (no network):

```bash
uv run python tests/smoke_test.py
```

## Simulated data — going to production

`get_data_metrics` returns **simulated** values by default (the response carries `simulated: true`), so
the server is useful immediately. The data layer is deliberately isolated in a single class,
`MetricsDataSource`. To go live, implement one that reads real measurements — from a synthetic-probe /
active-test result store, a time-series database, or an observability backend — and the four tools, the
analysis, and the schemas all keep working unchanged.

## Evolution

This is the **2026 modernized successor** to a prototype I built in **June 2025**, during MCP's first
months: **[`branch_MCP`](https://github.com/Fromzy1/branch_MCP)** (its commit history dates the work).
The diagnostic idea held up; the platform moved on. **v2** brings it current:

| | v1 (Jun 2025) | v2 (2026) |
|---|---|---|
| Framework | FastMCP 2.8 (now EOL) | **FastMCP 3.4** |
| Tool output | hand-formatted Markdown strings | **typed, structured `outputSchema`** |
| Guidance prompt | a dead variable, never registered | **a registered MCP prompt** |
| Tool metadata | none | **read-only annotations** |
| Resources | two flat JSON blobs | **+ parameterized templates** |
| Data layer | inline simulator | **pluggable `MetricsDataSource` seam** |

Taken together, the pair is a small, honest record of spotting a protocol early, shipping a real
solution to a real triage problem, and keeping the craft current as the ecosystem matured.

## License

MIT.

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

Each tool represents a distinct step in the diagnostic funnel: categorize, find metrics, get data, and analyse. Their purposes are clearly separated with no overlap.

Naming Consistency2/5

Naming is inconsistent: three tools follow a verb_noun pattern (diagnostic_categorize, find_metrics, get_data_metrics) but 'diagnostic_categorize' anomalously includes a prefix, and 'analyse' is just a verb without a noun, breaking the pattern.

Tool Count4/5

With 4 tools, the server is appropriately scoped for a focused diagnostic pipeline. Each tool serves a clear purpose without redundancy.

Completeness4/5

The four tools cover the entire diagnostic funnel from symptom categorization to analysis and recommendations. Minor gaps like a reset or overview tool are not needed for the core workflow.

Maintenance

ActivityInactive
ResponsivenessNo issues