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:
2
mcp-telemetry-bridge/requirements-dev.txt
Normal file
2
mcp-telemetry-bridge/requirements-dev.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
-r requirements.txt
|
||||
pytest==8.3.3
|
||||
22
mcp-telemetry-bridge/scripts/run-local.sh
Executable file
22
mcp-telemetry-bridge/scripts/run-local.sh
Executable 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"
|
||||
36
mcp-telemetry-bridge/scripts/verify-local.sh
Executable file
36
mcp-telemetry-bridge/scripts/verify-local.sh
Executable 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."
|
||||
0
mcp-telemetry-bridge/tests/__init__.py
Normal file
0
mcp-telemetry-bridge/tests/__init__.py
Normal file
62
mcp-telemetry-bridge/tests/test_smoke.py
Normal file
62
mcp-telemetry-bridge/tests/test_smoke.py
Normal 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"
|
||||
Reference in New Issue
Block a user