Add MCP bridge local verification harness (TELE-WP-0002)

Introduce pytest smoke tests, run/verify scripts, and Makefile targets so
the bridge can be developed and validated without a full cluster deploy.
Document the local workflow and agent quickstart in README.
This commit is contained in:
2026-06-24 18:18:00 +02:00
parent f061364951
commit 8f2584c1a0
10 changed files with 218 additions and 11 deletions

View File

@@ -6,17 +6,16 @@
## Dev Commands
```bash
# MCP bridge — local (no cluster)
make bridge-install # once
make bridge-test # pytest smoke tests
make bridge-run # uvicorn on :8080
# Deploy observability stack (from repo root)
cd ansible && ansible-playbook -i inventories/local.ini playbook.yml
# MCP bridge (local)
cd mcp-telemetry-bridge
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8080
# Smoke (requires cluster access)
kubectl get pods -n monitoring
kubectl port-forward -n mcp svc/mcp-telemetry-bridge 8080:80
curl http://localhost:8080/healthz
curl http://localhost:8080/mcp/schema | jq .
make bridge-smoke # pytest + live curl against :8080
```

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
*.egg-info/

15
Makefile Normal file
View File

@@ -0,0 +1,15 @@
.PHONY: bridge-install bridge-run bridge-test bridge-verify bridge-smoke
bridge-install:
cd mcp-telemetry-bridge && python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements-dev.txt
bridge-run:
cd mcp-telemetry-bridge && ./scripts/run-local.sh
bridge-test:
cd mcp-telemetry-bridge && ./scripts/verify-local.sh
bridge-verify: bridge-test
bridge-smoke:
cd mcp-telemetry-bridge && RUN_LIVE=1 ./scripts/verify-local.sh

View File

@@ -15,7 +15,68 @@ TeleMcp deploys a standard observability stack onto a Linux Kubernetes host via
| **OpenTelemetry Collector** | `observability` | Optional OTLP fan-out to Prometheus and Loki |
| **mcp-telemetry-bridge** | `mcp` | FastAPI service exposing MCP resources, tools, and prompts |
## Quick Start
## Local development (no cluster)
Work on the MCP bridge without deploying the full observability stack.
### Install and verify
```bash
make bridge-install # venv + deps (once)
make bridge-test # pytest smoke: /healthz, /mcp/schema, /mcp/resource
```
Or from `mcp-telemetry-bridge/`:
```bash
./scripts/verify-local.sh
```
### Run locally
```bash
make bridge-run
# or: cd mcp-telemetry-bridge && ./scripts/run-local.sh
```
With the server up, optional live HTTP checks:
```bash
make bridge-smoke
# or: RUN_LIVE=1 ./mcp-telemetry-bridge/scripts/verify-local.sh
```
Manual curls:
```bash
curl http://127.0.0.1:8080/healthz
curl http://127.0.0.1:8080/mcp/schema | jq .
curl "http://127.0.0.1:8080/mcp/resource?uri=res://dashboards/top-pods-by-cpu.promql"
```
Tool calls use `POST /tools/<name>` with a JSON body (Prometheus/Loki/K8s backends are only reachable in-cluster).
### Agent quickstart
When changing the bridge, agents should:
1. Run `make bridge-test` after edits — fast, no cluster needed.
2. Introspect `GET /mcp/schema` for the current tools, resources, and prompts.
3. Call tools via `POST /tools/<tool-name>` (e.g. `POST /tools/promql.query` with `{"expr":"up"}`).
4. Fetch saved queries via `GET /mcp/resource?uri=<uri>`.
Expected smoke-test surface:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/healthz` | GET | Liveness |
| `/mcp/schema` | GET | MCP catalog (tools, resources, prompts) |
| `/mcp/resource` | GET | Saved PromQL/LogQL query by URI |
| `/tools/*` | POST | Execute a tool (needs in-cluster backends) |
---
## Quick Start (full cluster deploy)
### 0) Prereqs
@@ -75,6 +136,8 @@ tele-mcp/
values/ # Chart values for monitoring, logging, OTel
mcp-telemetry-bridge/ # Bridge Helm chart
mcp-telemetry-bridge/ # FastAPI bridge application
scripts/ # run-local.sh, verify-local.sh
tests/ # pytest smoke tests
environments/ # Per-environment overrides
wiki/ # Extended project and design docs
```

View File

@@ -0,0 +1,2 @@
-r requirements.txt
pytest==8.3.3

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Run the MCP bridge locally (no Kubernetes required).
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
PORT="${PORT:-8080}"
HOST="${HOST:-127.0.0.1}"
if [[ ! -d .venv ]]; then
python3 -m venv .venv
fi
# shellcheck disable=SC1091
source .venv/bin/activate
pip install -q -r requirements.txt
echo "Starting MCP bridge at http://${HOST}:${PORT}"
echo "Health: curl http://${HOST}:${PORT}/healthz"
echo "Schema: curl http://${HOST}:${PORT}/mcp/schema | jq ."
exec uvicorn app.main:app --reload --host "$HOST" --port "$PORT"

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Local verification harness: pytest smoke tests + optional live HTTP checks.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
RUN_LIVE="${RUN_LIVE:-0}"
if [[ ! -d .venv ]]; then
python3 -m venv .venv
fi
# shellcheck disable=SC1091
source .venv/bin/activate
pip install -q -r requirements-dev.txt
echo "==> Running pytest smoke tests"
pytest -q
if [[ "$RUN_LIVE" == "1" ]]; then
echo "==> Live HTTP smoke against ${BASE_URL}"
curl -fsS "${BASE_URL}/healthz" | python3 -m json.tool
curl -fsS "${BASE_URL}/mcp/schema" | python3 -c "
import json, sys
schema = json.load(sys.stdin)
assert 'tools' in schema and 'resources' in schema and 'prompts' in schema
print(f\"tools={len(schema['tools'])} resources={len(schema['resources'])} prompts={len(schema['prompts'])}\")
"
echo "Live smoke passed."
else
echo "Skipping live HTTP checks (set RUN_LIVE=1 to curl a running server)."
fi
echo "Local verification complete."

View File

View File

@@ -0,0 +1,62 @@
"""Smoke tests for the MCP bridge HTTP surface (no cluster required)."""
from fastapi.testclient import TestClient
from app.main import PROMPTS, RESOURCES, TOOLS, app
client = TestClient(app)
EXPECTED_TOOL_NAMES = {
"promql.query",
"loki.query",
"k8s.get",
"k8s.events",
"inventory.snapshot",
}
def test_healthz_returns_ok():
response = client.get("/healthz")
assert response.status_code == 200
body = response.json()
assert body["status"] == "ok"
assert isinstance(body["ts"], int)
def test_mcp_schema_exposes_resources_tools_and_prompts():
response = client.get("/mcp/schema")
assert response.status_code == 200
body = response.json()
assert "resources" in body
assert "tools" in body
assert "prompts" in body
assert len(body["resources"]) == len(RESOURCES)
assert len(body["tools"]) == len(TOOLS)
assert len(body["prompts"]) == len(PROMPTS)
tool_names = {tool["name"] for tool in body["tools"]}
assert tool_names == EXPECTED_TOOL_NAMES
for tool in body["tools"]:
assert "inputSchema" in tool
assert tool["inputSchema"]["type"] == "object"
def test_mcp_resource_returns_saved_query():
uri = "res://dashboards/top-pods-by-cpu.promql"
response = client.get("/mcp/resource", params={"uri": uri})
assert response.status_code == 200
body = response.json()
assert body["uri"] == uri
assert body["mimeType"] == "text/plain"
assert "container_cpu_usage_seconds_total" in body["content"]
def test_mcp_resource_unknown_uri():
response = client.get("/mcp/resource", params={"uri": "res://does-not-exist"})
assert response.status_code == 200
body = response.json()
assert body["error"] == "not found"
assert body["uri"] == "res://does-not-exist"

View File

@@ -4,11 +4,11 @@ type: workplan
title: "MCP bridge local verification loop"
domain: infotech
repo: tele-mcp
status: ready
status: finished
owner: codex
topic_slug: custodian
created: "2026-06-22"
updated: "2026-06-22"
updated: "2026-06-24"
state_hub_workstream_id: "c617a044-bf57-4671-9afd-0112e7f462fd"
---
@@ -20,9 +20,12 @@ Harden the local dev/test loop for `mcp-telemetry-bridge` independent of full cl
```task
id: TELE-WP-0002-T01
status: todo
status: done
priority: high
state_hub_task_id: "7bad3ebf-f42c-4069-9c13-28bcf231f6f9"
```
Result 2026-06-24: Added pytest smoke tests, run/verify scripts, Makefile targets,
and README local-dev + agent quickstart sections.
Add documented local run path, health/schema smoke tests, and agent-oriented quickstart in README.