{
  "markdown": "# OTC Pricing MCP Server\n\n[![CI](https://github.com/seaser0/otc-pricing-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/seaser0/otc-pricing-mcp/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/seaser0/otc-pricing-mcp)](https://github.com/seaser0/otc-pricing-mcp/releases/latest)\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n[![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/)\n\nAn open-source **Model Context Protocol (MCP)** server for the **Open Telekom Cloud (OTC) Price Calculator API**.\n\nExpose OTC pricing data to Claude and other LLM clients with full observability (structured logging, Prometheus metrics, health checks).\n\n**Status**: v0.1.3 — STDIO + SSE transports, Kubernetes deployment, full observability\n\n---\n\n## What is MCP?\n\n**Model Context Protocol** is a standard that enables LLM applications (like Claude) to interact with external tools and data sources. This server supports two transports:\n\n| Transport | How it works | Best for |\n|-----------|-------------|----------|\n| **STDIO** | Claude launches the server as a subprocess; communication is over stdin/stdout | Local Claude Desktop, CLI tools |\n| **SSE** | Server-Sent Events over HTTP — Claude connects to a URL | Remote/hosted deployments, web clients |\n\nThis server gives Claude access to OTC pricing data and the user-manual / API-reference documentation through 9 specialized tools, on whichever transport you prefer.\n\n---\n\n## What Can You Do With This?\n\n**Example Use Cases:**\n- Ask Claude: _\"Find the cheapest ECS instance with 4 CPUs and 8GB RAM in eu-de\"_\n- Claude calls `find_compute_flavor` tool → gets pricing data → answers you\n- Ask: _\"Compare PAYG vs 12-month reserved pricing for S3 storage\"_\n- Claude calls `compare_billing_models` tool → does the analysis → shows savings\n\n---\n\n## Quick Start\n\n### 1. Install\n\n**Requirements**: Python 3.12+\n\n```bash\n# Clone the repository\ngit clone https://github.com/seaser0/otc-pricing-mcp.git\ncd otc-pricing-mcp\n\n# Install dependencies\nuv sync\n\n# Run the server\npython -m otc_pricing_mcp\n```\n\n**What You'll See:**\n```\n{\"event\": \"mcp_server_starting\", \"transports\": [\"stdio\", \"sse\"], \"port\": 8080, ...}\n{\"event\": \"mcp_server_ready\", \"status\": \"accepting_connections\", ...}\n```\n\nThe server now listens for MCP connections on **both** stdin/stdout and `http://localhost:8080/sse`.\n\n### 2. Connect Your MCP Client\n\n**Option A — STDIO (local, Claude Desktop)**\n\n```json\n{\n  \"mcpServers\": {\n    \"otc-pricing\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"otc_pricing_mcp\"],\n      \"env\": {\n        \"LOG_LEVEL\": \"INFO\",\n        \"METRICS_PORT\": \"8080\"\n      }\n    }\n  }\n}\n```\n\n**Option B — SSE (remote, Kubernetes)**\n\nPoint any MCP client that supports SSE transport at the hosted endpoint:\n\n```json\n{\n  \"mcpServers\": {\n    \"otc-pricing\": {\n      \"url\": \"https://mcp-otc-pricing.example.com/sse\"\n    }\n  }\n}\n```\n\nOr test locally while running the server:\n\n```bash\n# In a second terminal:\ncurl -N http://localhost:8080/sse\n# event: endpoint\n# data: /messages/?session_id=<uuid>\n```\n\n### 3. Start Using Tools\n\nOnce connected, Claude can call any of the 7 available tools. See the **Tools Reference** section below.\n\n---\n\n## Tools Reference\n\nThe server exposes **7 MCP tools** for different pricing queries:\n\n### 1. `list_services`\n**Purpose**: Get all available OTC services\n\n**Input**: None\n\n**Output**: List of service names and metadata\n\n**Example Claude usage:**\n```\n\"What OTC services are available for pricing?\"\n```\n\n---\n\n### 2. `list_regions`\n**Purpose**: Get available OTC regions\n\n**Input**: None\n\n**Output**: List of region codes (eu-de, eu-nl, eu-ch2, etc.)\n\n**Example Claude usage:**\n```\n\"What regions does OTC support?\"\n```\n\n---\n\n### 3. `get_service_schema`\n**Purpose**: Get filterable/returnable columns for a service\n\n**Input**:\n- `service` (string): Service name (e.g., \"ecs\", \"evs\", \"obs\", \"s3\", \"rds\")\n\n**Output**: Schema with filterable and returnable column names\n\n**Example Claude usage:**\n```\n\"What columns can I filter on for ECS pricing?\"\n```\n\n---\n\n### 4. `query_pricing`\n**Purpose**: Query pricing data with flexible filtering\n\n**Input**:\n- `services` (array): List of service names (e.g., [\"ecs\", \"evs\"])\n- `region` (string, optional): Filter by region (e.g., \"eu-de\")\n- `max_results` (integer, optional): Max results to return (default: 5000)\n\n**Output**: Pricing rows matching the filter\n\n**Example Claude usage:**\n```\n\"Show me ECS and EVS pricing in the eu-de region\"\n```\n\n---\n\n### 5. `find_compute_flavor`\n**Purpose**: Find compute (ECS) instances by vCPU/RAM/OS\n\n**Input**:\n- `v_cpu` (integer): Number of virtual CPUs\n- `ram_gb` (number): RAM in GiB\n- `os` (string, optional): Operating system (Linux, Windows, etc.)\n- `region` (string, optional): Region (default: eu-de)\n\n**Output**: Matching ECS instance types with pricing\n\n**Example Claude usage:**\n```\n\"Find a Linux ECS instance with 4 CPUs and 16GB RAM in eu-nl\"\n```\n\n---\n\n### 6. `estimate_monthly_cost`\n**Purpose**: Calculate monthly cost for multiple resources\n\n**Input**:\n- `items` (array): Resources with:\n  - `id` (string): Product ID (e.g., \"OTC_S3M1_LI\")\n  - `quantity` (number, optional): How many units (default: 1)\n  - `hours_per_month` (number, optional): Usage hours (default: 730)\n\n**Output**: Itemized costs with monthly total\n\n**Example Claude usage:**\n```\n\"Calculate monthly cost for 100GB S3 storage and an ECS instance\"\n```\n\n---\n\n### 7. `compare_billing_models`\n**Purpose**: Compare PAYG vs Reserved Instance pricing\n\n**Input**:\n- `product_id` (string): Product ID (e.g., \"OTC_S3M1_LI\")\n- `quantity` (number, optional): Quantity (default: 1)\n- `hours_per_month` (number, optional): Usage hours (default: 730)\n\n**Output**: Cost comparison for PAYG, 12mo, 24mo, 36mo reserved\n\n**Example Claude usage:**\n```\n\"Compare PAYG vs 12/24/36 month reserved pricing for ECS\"\n```\n\n### 8. `search_otc_docs`\n**Purpose**: Full-text search across the indexed OTC user manual and API reference\n\n**Input**:\n- `query` (string): Search terms (BM25-ranked, AND of tokens)\n- `scope` (string, optional): `public` | `swiss` | `both` (default: `both`)\n- `service` (string, optional): Restrict to one service repo (e.g. `elastic-cloud-server`)\n- `top_k` (integer, optional): 1-50, default 5\n\n**Output**: Ranked list of `{url, title, h2, h3, snippet, service, cloud, upstream_commit}` hits.\nThe index ships with the package and is rebuilt weekly from the upstream\n`opentelekomcloud-docs/<service>` Sphinx/RST repos (Apache-2.0); the runtime\nnever touches the Anubis-gated docs.otc.t-systems.com HTML.\n\n**Example Claude usage:**\n```\n\"Find the OTC docs page that explains S3-flavor ECS specifications\"\n```\n\n### 9. `get_otc_doc_section`\n**Purpose**: Fetch the body of one indexed documentation page (or one of its sections) as Markdown\n\n**Input**:\n- `url` (string): Canonical URL as returned by `search_otc_docs` (with or without `#anchor`)\n- `section` (string, optional): H2/H3 heading filter (case-insensitive substring)\n\n**Output**: `{url, title, sections: [{h2, h3, anchor, body}, ...], matched, ...}`\n\n**Example Claude usage:**\n```\n\"Show me the EVS Disk Types and Performance section\"\n```\n\n---\n\n## Configuration\n\n### Environment Variables\n\nControl the server behavior with environment variables:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `LOG_LEVEL` | `INFO` | Logging level: `DEBUG`, `INFO`, `WARNING`, `ERROR` |\n| `METRICS_PORT` | `8080` | Port for metrics/health endpoints |\n| `METRICS_HOST` | `0.0.0.0` | Bind address for the HTTP server (set to `127.0.0.1` for non-container runs) |\n| `OTC_PRICING_API_BASE` | `https://calculator.otc-service.com/en/open-telekom-price-api/` | OTC API endpoint |\n| `OTC_DOCS_DB` | (auto) | Override path to the docs FTS5 index (default: bundled `data/otc_docs.sqlite3`) |\n\n**Example**:\n```bash\nLOG_LEVEL=DEBUG METRICS_PORT=9090 python -m otc_pricing_mcp\n```\n\n---\n\n## Observability: Metrics & Logs\n\nThis server is built with **production-grade observability** so you can debug issues and monitor performance.\n\n### Structured Logging (JSON)\n\nEvery action is logged as JSON, making logs machine-readable for aggregation and analysis.\n\n**Start the server with DEBUG logging:**\n```bash\nLOG_LEVEL=DEBUG python -m otc_pricing_mcp 2>&1\n```\n\n**You'll see JSON logs like:**\n```json\n{\"timestamp\": \"2026-05-06T18:00:00.123456Z\", \"event\": \"tool_invocation_start\", \"tool\": \"query_pricing\", \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\", \"arguments\": {\"services\": [\"ecs\"]}}\n\n{\"timestamp\": \"2026-05-06T18:00:00.234567Z\", \"event\": \"upstream_request_start\", \"service\": \"ecs\", \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\"}\n\n{\"timestamp\": \"2026-05-06T18:00:00.345678Z\", \"event\": \"upstream_request_success\", \"service\": \"ecs\", \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\", \"status_code\": 200, \"duration_seconds\": 0.111, \"attempt\": 1, \"items_returned\": 42}\n\n{\"timestamp\": \"2026-05-06T18:00:00.456789Z\", \"event\": \"tool_invocation_success\", \"tool\": \"query_pricing\", \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\", \"duration_seconds\": 0.333}\n```\n\n**Key fields in every log:**\n- `timestamp`: When the event happened (ISO 8601)\n- `event`: What happened (tool_invocation_start, upstream_request_success, etc.)\n- `request_id`: Unique ID for this request (same across all related logs)\n- Custom fields depending on the event\n\n**Logs are printed to stderr**, so redirect to a file or log aggregator:\n```bash\npython -m otc_pricing_mcp 2>/var/log/otc-pricing-mcp.log\n```\n\n**Pipe to `jq` for pretty printing:**\n```bash\npython -m otc_pricing_mcp 2>&1 | jq .\n```\n\n### HTTP Endpoints (port 8080)\n\nThe uvicorn server exposes all endpoints on port 8080:\n\n| Path | Method | Description |\n|------|--------|-------------|\n| `/sse` | GET | MCP SSE transport — connect your MCP client here |\n| `/messages/` | POST | MCP SSE message handler (used internally by the client) |\n| `/healthz` | GET | Liveness probe — always 200 if the process is up |\n| `/readyz` | GET | Readiness probe — 200 when OTC API is reachable, 503 otherwise |\n| `/metrics` | GET | Prometheus metrics in text exposition format |\n\n**Health Checks:**\n```bash\n# Liveness check (always 200 if process is up)\ncurl http://localhost:8080/healthz\n# {\"status\": \"ok\", \"service\": \"otc-pricing-mcp\"}\n\n# Readiness check (verifies OTC API is reachable)\ncurl http://localhost:8080/readyz\n# {\"status\": \"ready\", \"upstream\": \"ok\", \"api_response_time\": 0.042}\n```\n\n**Prometheus Metrics:**\n```bash\ncurl http://localhost:8080/metrics\n```\n\nReturns Prometheus format metrics:\n```\n# HELP otc_pricing_mcp_requests_total Total MCP tool requests (success and failure)\n# TYPE otc_pricing_mcp_requests_total counter\notc_pricing_mcp_requests_total{status=\"success\",tool=\"query_pricing\"} 5.0\notc_pricing_mcp_requests_total{status=\"error\",tool=\"query_pricing\"} 1.0\n\n# HELP otc_pricing_mcp_request_duration_seconds MCP tool request duration in seconds\n# TYPE otc_pricing_mcp_request_duration_seconds histogram\notc_pricing_mcp_request_duration_seconds_bucket{le=\"0.005\",tool=\"query_pricing\"} 0.0\notc_pricing_mcp_request_duration_seconds_bucket{le=\"0.01\",tool=\"query_pricing\"} 1.0\n...\n\n# HELP otc_pricing_mcp_upstream_requests_total Total upstream OTC API requests (success and failure)\n# TYPE otc_pricing_mcp_upstream_requests_total counter\notc_pricing_mcp_upstream_requests_total{service=\"ecs\",status=\"success\"} 10.0\notc_pricing_mcp_upstream_requests_total{service=\"ecs\",status=\"error\"} 2.0\n...\n```\n\n**Available Metrics:**\n- `otc_pricing_mcp_requests_total{tool, status}`: Count of tool invocations\n- `otc_pricing_mcp_request_duration_seconds{tool}`: Tool execution time\n- `otc_pricing_mcp_upstream_requests_total{service, status}`: Count of API calls\n- `otc_pricing_mcp_upstream_duration_seconds{service}`: API call latency\n\n**Using Prometheus:**\n\nAdd to your `prometheus.yml`:\n```yaml\nscrape_configs:\n  - job_name: 'otc-pricing-mcp'\n    static_configs:\n      - targets: ['localhost:8080']\n```\n\nThen query in Prometheus:\n```\nrate(otc_pricing_mcp_requests_total[5m])  # Requests per second\nhistogram_quantile(0.95, otc_pricing_mcp_request_duration_seconds_bucket)  # p95 latency\n```\n\n---\n\n## Debugging Guide\n\n### Problem: Slow API Calls\n\n**Check the logs:**\n```bash\nLOG_LEVEL=DEBUG python -m otc_pricing_mcp 2>&1 | jq 'select(.event == \"upstream_request_success\") | {service, duration_seconds}'\n```\n\n**Check metrics:**\n```bash\ncurl http://localhost:8080/metrics | grep upstream_duration_seconds\n```\n\n### Problem: Tool Fails\n\n**Look for error logs:**\n```bash\nLOG_LEVEL=DEBUG python -m otc_pricing_mcp 2>&1 | jq 'select(.event == \"tool_invocation_error\")'\n```\n\n**Example error log:**\n```json\n{\n  \"event\": \"tool_invocation_error\",\n  \"tool\": \"query_pricing\",\n  \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"error\": \"list index out of range\",\n  \"error_type\": \"IndexError\",\n  \"duration_seconds\": 0.001,\n  \"exc_info\": true\n}\n```\n\n### Problem: OTC API Unreachable\n\n**Check readiness endpoint:**\n```bash\ncurl -v http://localhost:8080/readyz\n# HTTP/1.1 503 Service Unavailable\n# {\"status\": \"not_ready\", \"upstream\": \"unreachable\", \"error\": \"...\"}\n```\n\n**Check metrics:**\n```bash\ncurl http://localhost:8080/metrics | grep upstream_requests_total\n# Will show increased error counts\n```\n\n### Problem: Need Full Request Trace\n\n**Use request_id to trace a request:**\n```bash\n# Get the request_id from any log\nLOG_LEVEL=DEBUG python -m otc_pricing_mcp 2>&1 | jq 'select(.request_id == \"550e8400-e29b-41d4-a716-446655440000\")'\n```\n\nThis shows all logs for that request in order:\n1. tool_invocation_start\n2. upstream_request_start\n3. upstream_request_success (with items_returned)\n4. tool_invocation_success\n\n---\n\n## Running Locally (Development)\n\n### Setup\n\n```bash\n# Clone repo\ngit clone https://github.com/seaser0/otc-pricing-mcp.git\ncd otc-pricing-mcp\n\n# Install with dev dependencies\nuv sync\n\n# Run tests\nuv run pytest tests/ -v\n\n# Check code quality\nuv run ruff check src/\nuv run mypy src/ --strict\n```\n\n### Run in Development Mode\n\n```bash\n# With debug logging\nLOG_LEVEL=DEBUG python -m otc_pricing_mcp\n\n# In another terminal, test the endpoints\ncurl http://localhost:8080/healthz | jq .\ncurl http://localhost:8080/metrics\n```\n\n---\n\n## Running in Production (Docker)\n\n### Build Image\n\n```bash\ndocker build -t otc-pricing-mcp:latest .\n```\n\n### Run Container\n\n```bash\ndocker run \\\n  --name otc-pricing-mcp \\\n  -e LOG_LEVEL=INFO \\\n  -e METRICS_PORT=8080 \\\n  -p 8080:8080 \\\n  otc-pricing-mcp:latest\n```\n\n### Kubernetes Deployment\n\nSee `deploy/kubernetes/` for the full manifest set (Deployment, Service, Ingress, NetworkPolicy, ServiceMonitor, PodDisruptionBudget).\n\nWhen self-hosting on Kubernetes, connect remote clients to your ingress hostname:\n```\nhttps://mcp-otc-pricing.example.com/sse\n```\n\nKey features:\n- Non-root user, read-only root filesystem\n- Resource limits (100m–500m CPU, 128Mi–512Mi RAM)\n- Liveness probe: GET /healthz on port 8080\n- Readiness probe: GET /readyz on port 8080\n- NetworkPolicy: ingress from nginx controller only, egress to DNS + OTC API\n- ServiceMonitor for Prometheus scraping\n- Managed by ArgoCD with `selfHeal: true` and `prune: true`\n\n---\n\n## Architecture\n\n### Request Flow\n\n```\nClaude Client\n    │\n    ├─ STDIO transport (local)      ──┐\n    │  stdin/stdout                   │\n    │                                 ▼\n    └─ SSE transport (remote)      MCP Server (server.py)\n       GET  /sse                     - List tools\n       POST /messages/               - Route tool calls\n                                     - Log invocations\n                                     - Record metrics\n                                         │\n                                         ▼\n                                   HTTP Client (client.py)\n                                     - Build request\n                                     - Retry logic\n                                     - Parse response\n                                         │\n                                         ▼\n                                   OTC Price Calculator API\n```\n\nBoth transports share the same MCP Server instance and run concurrently in the same asyncio event loop.\n\n### Component Overview\n\n| Component | Purpose |\n|-----------|---------|\n| `__main__.py` | Entry point — runs STDIO + uvicorn SSE concurrently |\n| `server.py` | MCP server, routes tool calls, logs invocations |\n| `client.py` | HTTP client for OTC API, retry logic, API logging |\n| `tools/` | Tool implementations (discovery, pricing, estimation) |\n| `observability/http_server.py` | Starlette app — SSE transport + health/metrics routes |\n| `observability/` | Logging, Prometheus metrics, request context |\n| `models.py` | Data models (validated with Pydantic) |\n| `normalize.py` | Price parsing and formatting |\n\n---\n\n## Enhancement Ideas (Future Development)\n\nStories 0–9 are complete. The following are post-v1.0 enhancements:\n\n### Enhancement Ideas\n\n**Caching**\n- Cache pricing data for N seconds to reduce API load\n- Redis or in-memory cache option\n- Cache invalidation strategy\n\n**Advanced Querying**\n- More filtering options (e.g., price range, commitment period)\n- Sorting by price, CPU, RAM\n- Aggregations (min/max/avg pricing per service)\n\n**Cost Analysis Tools**\n- Historical pricing trends\n- Cost anomaly detection\n- Recommendation engine (right-sizing)\n\n**Multi-Cloud Support**\n- AWS pricing API integration\n- Azure pricing API integration\n- Cost comparison across clouds\n\n**User Preferences**\n- Save favorite services/regions\n- Custom pricing alerts\n- Budget tracking per project\n\n**Better Error Recovery**\n- Exponential backoff with jitter (vs fixed exponential)\n- Circuit breaker pattern\n- Fallback to cached data on API failure\n\n**Performance Optimizations**\n- Query result pagination\n- Database caching layer\n- Streaming responses for large datasets\n\n**Observability Enhancements**\n- Distributed tracing (OpenTelemetry)\n- Custom business metrics (cost calculated, queries per service)\n- Log aggregation integration (Loki, ELK)\n- Alert rules (Prometheus Alertmanager)\n\n**Testing Improvements**\n- Load testing (k6, Locust)\n- Chaos testing (failure scenarios)\n- Contract testing with OTC API\n\n**API Stability**\n- API versioning (v1, v2)\n- Deprecation policies\n- Backward compatibility guarantees\n\n---\n\n## Contributing\n\nWe welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for:\n- Development setup\n- Code style (ruff, mypy --strict)\n- Testing requirements (53+ tests with coverage)\n- Security scanning (bandit, cyclonedx-bom)\n- Commit message conventions\n\n**Quick PR Checklist:**\n- [ ] Tests pass: `uv run pytest tests/`\n- [ ] Linting passes: `uv run ruff check src/`\n- [ ] Type checking passes: `uv run mypy src/ --strict`\n- [ ] Security scan passes: `uv run bandit -r src/`\n- [ ] Meaningful commit message\n\n---\n\n## License\n\nApache License 2.0 — see [LICENSE](LICENSE) file.\n\n**Copyright**: seaser0 (s34s3r@gmail.com)\n\n---\n\n## Getting Help\n\n**Questions or Issues?**\n1. Check the [Debugging Guide](#debugging-guide) above\n2. Open a GitHub Issue: https://github.com/seaser0/otc-pricing-mcp/issues\n3. Check logs with: `LOG_LEVEL=DEBUG python -m otc_pricing_mcp 2>&1 | jq .`\n\n**Want to Report a Security Issue?**\nSee [SECURITY.md](SECURITY.md) for responsible disclosure.\n\n---\n\n## Project Status\n\n| Story | Feature | Status |\n|-------|---------|--------|\n| 0 | Project setup, API client, data models | ✅ Done |\n| 1 | Catalog discovery tools | ✅ Done |\n| 2 | Pricing query tools | ✅ Done |\n| 3 | Multi-service fan-out | ✅ Done |\n| 4 | Comprehensive testing | ✅ Done |\n| 5 | Security & container hardening | ✅ Done |\n| 6 | CI/CD pipeline (GHCR image, PyPI, SBOM, GitHub Release) | ✅ Done |\n| 7 | Observability (structured logging, Prometheus metrics, health probes) | ✅ Done |\n| 8 | ArgoCD deployment (Kubernetes, SSE transport, remote endpoint) | ✅ Done |\n| 9 | Open source documentation (README, server.json, community docs) | ✅ Done |\n\n---\n\n## Architecture Decisions\n\nSee [docs/](docs/) directory for detailed documentation:\n- `docs/ci-cd.md` — CI/CD workflow details\n- `docs/deployment.md` — Deployment guide\n- `docs/security.md` — Security features and considerations\n\n---\n<!-- mcp-name: io.github.seaser0/otc-pricing-mcp -->\n**Built with ❤️ by seaser0**\n\n*Last updated: 2026-05-07*\n",
  "bytes": 20317,
  "sha": "d6b7fbb818bdc184265286d7a2069a9f72064baabb89f8e48937471e3cc52da9",
  "repo_slug": "seaser0/otc-pricing-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_seaser0_otc_pricing_mcp_0d19d0ae/readme"
}