# TiDB Observability MCP Server

MCP (Model Context Protocol) server for TiDB observability. It provides tools for Prometheus metrics, TopSQL, Loki logs, and Delta Lake data access.

## Quick Start (User Guide)

### Prerequisites
The tool uses a GitHub Personal Access Token to authenticate and obtain necessary observability platform permissions.

1.  Visit the [GitHub Token Creation Page](https://github.com/settings/tokens/new).
![GitHub Token Example](../docs/images/github-token.png)

2.  Enable these scopes:
   * `read:user`
   * `read:org`
3.  Generate the token and save it. You will be prompted to input this token when you run the tool for the first time.

### 1. Install dependencies

```bash
pip install -e "."
```

### 2. Register this MCP server in your agent config

```json
{
  "mcpServers": {
    "tidb-o11y": {
      "command": "o11y-mcp-server",
      "args": [],
      "env": {
        "AUTH_SERVICE_URL": "https://www.ds.us-west-2.aws.observability.tidbcloud.com",
        "GITHUB_TOKEN": "ghp_xxx"
      }
    }
  }
}
```

**Note: Some agents might not recognize the configuration above, prompt agent itself to install the mcp config.**

For OpenCode Users, add following to your `.opencode/opencode.json`

```
"mcp": {
    "tidb-o11y": {
      "type": "local",
      "command": ["o11y-mcp-server"],
      "enabled": true,
      "environment": {
        "AUTH_SERVICE_URL": "https://www.ds.us-west-2.aws.observability.tidbcloud.com",
        "GITHUB_TOKEN": "ghp_xxx"
      }
    }
  }
```

Then fully restart your MCP client app/CLI.

## Supported Tools

> Current runtime list reflects tools imported by `server.py` and `src/tools/__init__.py`.
> `get_metric_metadata` exists in source but is currently disabled in server registration.

| Category | Tool | Purpose | Key Parameters |
|---|---|---|---|
| Prometheus | `list_metrics` | Discover metric names (`/api/v1/label/__name__/values`) | `cluster_id`, `env`, `deploy_type`, `match`, `start`, `end`, `limit` |
| Prometheus | `list_metric_labels` | List label names (`/api/v1/labels`) | `cluster_id`, `env`, `deploy_type`, `match`, `start`, `end`, `limit` |
| Prometheus | `list_metric_label_values` | List values for one label (`/api/v1/label/<label>/values`) | `cluster_id`, `env`, `deploy_type`, `label`, `match`, `start`, `end`, `limit` |
| Prometheus | `query_instant` | Run instant PromQL query (`/api/v1/query`) | `query`, `cluster_id`, `env`, `deploy_type`, `time` |
| Prometheus | `query_range` | Run range PromQL query (`/api/v1/query_range`) | `query`, `start`, `end`, `step`, `cluster_id`, `env`, `deploy_type` |
| TopSQL | `query_topsql` | Rank SQL by CPU time with TopSQL metrics | `cluster_id`, `env`, `deploy_type`, `start`, `end`, `instance_type`, `limit` |
| Loki | `list_loki_labels` | List Loki label names | `cluster_id`, `env`, `deploy_type`, `start`, `end`, `query` |
| Loki | `get_loki_label_values` | List values for one Loki label | `label_name`, `cluster_id`, `env`, `deploy_type`, `start`, `end`, `query` |
| Loki | `list_loki_series` | List Loki series metadata | `match`, `cluster_id`, `env`, `deploy_type`, `start`, `end` |
| Loki | `query_loki_instant` | Run instant LogQL query | `query`, `cluster_id`, `env`, `deploy_type`, `time`, `limit`, `direction` |
| Loki | `query_loki_range` | Run range LogQL query | `query`, `start`, `end`, `cluster_id`, `env`, `deploy_type`, `limit`, `direction`, `step` |
| Delta Lake | `get_o11y_delta_path` | Resolve O11Y Delta table paths | `cluster_id`, `env`, `deploy_type` |
| Delta Lake | `read_delta` | Query Delta Lake table with DuckDB | `table_path`, `env`, `deploy_type`, `cluster_id`, `query`, `limit` |

Delta Lake tools support AWS, Alicloud, and GCP cloud clusters (plus `local` for file paths).

### Delta Lake provider behavior

- `get_o11y_delta_path`
  - Resolves provider/region from GS cluster metadata.
  - Returns `s3://...` for AWS/Alicloud clusters.
  - Returns `gs://...` for GCP clusters.
- `read_delta`
  - AWS/Alicloud: reads Delta tables via DuckDB `delta_scan` with S3-compatible credentials from `/client/auth`.
  - GCP: reads parquet files from GCS using OAuth `access_token` from `/client/auth`, then queries them locally with DuckDB.
  - GCP requests must include exactly one date partition predicate in SQL, for example:
    `WHERE date='20260318'`.
  - For GCP local parquet execution, the date predicate is used only for partition download
    and is removed from the SQL before DuckDB execution.
  - GCP parquet files are cached on local disk by `(bucket, table prefix, date)` and reused by later calls.
    The tool response includes `local_cache_path` for client-side reuse/visibility.
  - GCP local query view is deduplicated (`SELECT DISTINCT *`) to avoid duplicate parquet rows.

This GCP parquet path is intentional: current GCP auth payload is token-based (no S3-compatible key/secret tuple).

## Configuration

Required:
- `AUTH_SERVICE_URL`
- `GITHUB_TOKEN`

Optional:

```bash
export MCP_SERVER_LOG_LEVEL="INFO"   # DEBUG|INFO|WARNING|ERROR
```

Auth is server-owned: on startup the server bootstraps and caches backend auth. MCP callers do not send GitHub/JWT tokens per tool call.

### Optional `.env`

```bash
cp .env.example .env
source .env
```

Note: environment variables must be loaded by your shell/runner; copying `.env` alone is not sufficient.

## Usage Notes

### Recommended call flow (Prometheus)

1. `list_metrics`
2. `list_metric_labels`
3. `list_metric_label_values`
4. `query_instant` / `query_range`

### Inspector troubleshooting

For startup/auth/GS resolution debugging, see [`docs/MCP_INSPECTOR_DEBUG_GUIDE.md`](docs/MCP_INSPECTOR_DEBUG_GUIDE.md).

### HTTP mode (example client only)

```bash
fastmcp run server.py:mcp --host 0.0.0.0 --port 8000
```

Then `test/test_client.py` can connect to `http://localhost:8000/mcp`.

## Project Structure

```
src/
├── tools/           # MCP tool implementations
├── models/          # Pydantic models for validation
├── services/        # Auth, Prometheus, Loki, DuckDB orchestration
└── utils/           # Shared utilities (caches, config, redaction, formatting)

test/                # Executable spec
```

## Development

### Setup

```bash
pip install -e ".[dev]"
```

### Verify locally

```bash
# quick runtime export check
python -c "import server; print('mcp_exported', hasattr(server, 'mcp'))"

# lint + format
ruff check src/ test/ server.py
ruff format src/ test/ server.py

# tests
pytest
```

Expected runtime export output includes:
- `mcp_exported True`

## License

[Add your license here]
