Branch Diagnostics MCP
# 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
Scored across 4 tools
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 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.
With 4 tools, the server is appropriately scoped for a focused diagnostic pipeline. Each tool serves a clear purpose without redundancy.
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.