uprof-mcp 0.1.1
MCP server for AMD uProf: ask questions about your code, run profiling, and get LLM-powered insights
License: MIT
Requires Python: >=3.10
Keywords: amd, cursor, mcp, model-context-protocol, profiling, uprof

# uProf MCP Server

An **MCP (Model Context Protocol) server** for **AMD uProf** that lets users ask questions about their code, run the right analysis, generate reports, and get **LLM-powered insights**.

## Features

1. **uProf documentation** – Read the [AMD uProf User Guide](https://docs.amd.com/r/en-US/57368-uProf-user-guide) via the `uprof://docs/user-guide` resource.
2. **Q&A about your code** – Ask the assistant to profile your binary and explain results.
3. **Correct analysis selection** – Server suggests configs (tbp, cpi, threading, mpi) from the user’s goal.
4. **Run uProf → report → LLM** – Tools run `AMDuProfCLI` collect/report and optionally send the report to an LLM for insights.

## Prerequisites

- **AMD uProf**: `AMDuProfCLI` must be found by the server. Set **`UPROF_CLI_PATH`** (env or `uprof.cli_path` in config) to the path to the binary or to a directory containing it, or have it on your `PATH`. See [Where we look for AMDuProfCLI](#where-we-look-for-amduprofcli) below.
- **Python 3.10+**
- For LLM insights: enable in the **common config file** (`llm.insights_enabled = true`) and set the API key in the environment (env var name is configurable via `llm.api_key_env`, default `OPENAI_API_KEY`). One install includes LLM support; config controls whether it is used.

### Read-only policy when you provide an app path

When you give the server an **app path** (e.g. `target_executable` or session/report paths):

- **No source or file modifications** – The server never modifies your source code or any files under your app or project path.
- **Databases opened read-only** – Session `.db` files (DuckDB/SQLite) are always opened in **read-only mode** (DuckDB: `read_only=True`; SQLite: `file:...?mode=ro`). Only read-only SELECT queries are allowed.
- **Files only read** – Report files (e.g. `report.csv`) and config are read with normal file reads; nothing is written to your paths.
- **Writes only to output dir** – HTML reports, insights JSON, and collect output are written only to the configured **output directory** (`UPROF_OUTPUT_DIR` / `uprof.output_dir`), never under your app path.

So profiling and analysis are **non-destructive** with respect to your application and its directory.

## Install

One install includes everything (uProf tools and LLM support). LLM is turned on via config, not a separate package.

```bash
pip install uprof-mcp
```

(From source: `cd uprof_mcp && pip install -e .`)

## Quick start after install (CLI)

The `uprof-mcp` command provides a small help workflow:

```bash
uprof-mcp --help
```

**1. Generate a default config file**

```bash
cd ~/your-project
uprof-mcp init
```

This creates **`uprof_mcp_config.ini`** in the current directory. Use `uprof-mcp init /path/to/dir` to create it elsewhere. Use `--force` to overwrite an existing file.

**2. Edit the config**

Open `uprof_mcp_config.ini` and set:

- **`[uprof]`** → **`cli_path`** = directory that contains `AMDuProfCLI` (or leave empty if it’s on `PATH`).
- **`[uprof]`** → **`output_dir`** = where to write profiling output (use an absolute path).

**3. See how to link Cursor**

```bash
uprof-mcp cursor-setup
```

This prints the steps to add the MCP server in Cursor and connect it. No need to run the server manually—Cursor starts it when needed.

**4. Run the server** (optional; Cursor usually starts it)

```bash
uprof-mcp
# or
uprof-mcp run
```

These steps are also in the repo: [Configure on a new machine](docs/CONFIGURE_NEW_MACHINE.md).

## Config file: what it is and where to put it

**What:** One file holds all server settings: paths to uProf, where to write reports, and optional LLM. You can use **INI** (`uprof_mcp_config.ini`) or **YAML** (`uprof_mcp_config.yaml`). You don’t have to create it—env vars work too—but if you do, start from the example (see below).

**Where the server looks (in order):**
1. **Path you set** – If you set **`UPROF_MCP_CONFIG`** (in Cursor’s MCP env or in the shell) to a file path, that file is used.
2. **Your project folder** – When you use Cursor, the server usually runs with your **project folder** as the current directory. So put **`uprof_mcp_config.ini`** (or **`uprof_mcp_config.yaml`**) there and it will be found automatically.
3. **Package directory** – If nothing is found above, the server looks next to the installed package (you normally don’t put your own config there).

**Recommendation:** Put **`uprof_mcp_config.ini`** in the **same folder as the project you open in Cursor** (e.g. `~/myproject/uprof_mcp_config.ini`). One file per project is fine; or one shared file and set **`UPROF_MCP_CONFIG`** to its path.

**What to put in the config (minimum):**
- **`[uprof]`** – `cli_path` = directory that contains `AMDuProfCLI` (or leave empty if it’s on `PATH`). `output_dir` = where to write profiling output and reports (use an absolute path).
- **`[mcp]`** – `transport = stdio` when using Cursor.
- **`[llm]`** (optional) – `insights_enabled = true` to use LLM insights; `api_key_env = OPENAI_API_KEY` (set that env var to your API key).

**Get the example:** Run **`uprof-mcp init`** (creates `uprof_mcp_config.ini` in the current directory), or copy from the package (see [Configure on a new machine](docs/CONFIGURE_NEW_MACHINE.md#3-create-the-config-file)) or from this repo: `uprof_mcp_config.example.ini`.

Full step-by-step for a new machine: [Configure on a new machine](docs/CONFIGURE_NEW_MACHINE.md).

## Run the MCP server

**Streamable HTTP** (e.g. for Cursor / MCP Inspector):

```bash
uv run python -m uprof_mcp.server
# or
uv run uprof-mcp
```

Then connect to `http://localhost:8000/mcp` in [MCP Inspector](https://github.com/modelcontextprotocol/inspector) or configure Cursor to use this server.

**Stdio**: set `UPROF_MCP_TRANSPORT=stdio` (or use a config file) and run the same module; Cursor can start the server as a subprocess.

### Hosting (production)

The server is ready to be hosted. All bind and transport options come from **config** (env or file):

- **Host/port**: `UPROF_MCP_HOST` (default `127.0.0.1`), `UPROF_MCP_PORT` (default `8000`). Use `0.0.0.0` to listen on all interfaces.
- **Common config file**: one file for all MCP server settings (uprof, mcp, llm, docs, context) — `uprof_mcp_config.ini` or `uprof_mcp_config.yaml` in the current or project directory, or path in `UPROF_MCP_CONFIG`. Copy from `uprof_mcp_config.example.ini`.

Example for a shared host:

```bash
export UPROF_MCP_HOST=0.0.0.0
export UPROF_MCP_PORT=8000
python -m uprof_mcp.server
```

Clients then connect to `http://<host>:8000/mcp` (or your configured path via `UPROF_MCP_STREAMABLE_HTTP_PATH`).

## Connect to Cursor

### Option A: Cursor starts the server (recommended)

1. **Install the package** (from this repo):
   ```bash
   cd /path/to/uprof_mcp
   pip install -e .
   ```

2. **Use the project MCP config**  
   This repo already has `.cursor/mcp.json`. Open this project in Cursor; it will use that config and start the uProf MCP server automatically.

   Or add the server in Cursor’s settings: **Settings → Features → MCP → Add new MCP server**, and use:
   - **Command:** `python`
   - **Args:** `-m`, `uprof_mcp.server`
   - **Env:** `UPROF_MCP_TRANSPORT` = `stdio`

3. **Restart Cursor** (or reload the window) so it picks up the MCP config.

4. In chat, you can ask things like “List uProf collect configs” or “Suggest a config for hotspots”; the assistant will use the uProf MCP tools.

### Option B: Run the server yourself (HTTP)

1. In a terminal, from this repo:
   ```bash
   pip install -e .
   python -m uprof_mcp.server
   ```
   Server runs at `http://127.0.0.1:8000/mcp`.

2. In Cursor, add an MCP server with **URL** `http://127.0.0.1:8000/mcp` and **Transport** `streamable-http`.

### Config file locations

- **Project:** `.cursor/mcp.json` in this repo (already set up).
- **User (all projects):** `~/.cursor/config/mcp.json` (Linux/macOS) or `%USERPROFILE%\.cursor\config\mcp.json` (Windows).

---

## Share with your team (install only — no source code)

**You do not share any source code.** Each teammate **installs the server** (`pip install uprof-mcp`), **adds it in Cursor** (Cursor then launches it automatically), and uses it in chat. Their code stays on their machine. **One install**; LLM is enabled via the **common config file** and the API key env (see step 5).

**Config:** One file (**`uprof_mcp_config.ini`** or **`.yaml`**) holds all settings. Put it in the **project folder** they open in Cursor (or set **`UPROF_MCP_CONFIG`** to its path). Minimum: set `uprof.cli_path` and `uprof.output_dir`. See [Config file: what it is and where to put it](#config-file-what-it-is-and-where-to-put-it).

### Steps to send your team

1. **Install the server** (one install; no separate LLM package)
   - **If published on PyPI:**  
     `pip install uprof-mcp`
   - **If you see “No matching distribution found”** (package not on PyPI yet), install from this repo:
     ```bash
     pip install "uprof-mcp @ git+https://github.com/YOUR_ORG/uprof_mcp.git"
     ```
     Replace `YOUR_ORG/uprof_mcp` with your actual repo URL (e.g. `amd/uprof_mcp`).
   - **Or from a local clone:**  
     `cd /path/to/uprof_mcp && pip install -e .`

   **Maintainers:** To add the package to PyPI so `pip install uprof-mcp` works for everyone, see [Publishing to PyPI](docs/PUBLISHING.md).

2. **Prerequisites on their machine**
   - Python 3.10+
   - **AMDuProfCLI** (AMD uProf CLI) installed and on `PATH`, or set `UPROF_CLI_PATH` to its directory.

3. **Link the server with Cursor (Cursor launches it — no manual launch)**
   - Open **Cursor → Settings → Features → MCP → Add new MCP server**
   - **Command:** `python` (or `python3`)
   - **Args:** `-m`, `uprof_mcp.server`
   - **Env:** `UPROF_MCP_TRANSPORT` = `stdio`
   - To use LLM insights, add the API key env (e.g. **`OPENAI_API_KEY`** = your key) in Cursor’s MCP env for this server, or set it in your shell.
   - Save and **restart Cursor** (or refresh MCP).

   Cursor will **start** the server automatically when it needs to call uProf tools. You don’t run `python -m uprof_mcp.server` in a terminal — adding the server in MCP settings is how you “launch and link” it.

4. **Use it**
   - In Cursor chat, ask e.g. “What’s the uProf MCP status?” or “Suggest a uProf config for hotspots.”
   - To profile their app: “Find bottlenecks in my app” and give the path to their local binary when asked (e.g. `./myapp` or `/path/to/their/binary`). Profiling runs entirely on their machine; no code or binary is sent anywhere.

5. **Optional — common config file:** All settings (uprof, mcp, llm, output dir, etc.) live in **one config file**. Copy `uprof_mcp_config.example.ini` to `uprof_mcp_config.ini` (or use `uprof_mcp_config.yaml`) and set:
   - **uprof:** `cli_path`, `output_dir`
   - **llm:** `insights_enabled = true` to enable LLM insights; `api_key_env = OPENAI_API_KEY` (or another env var name) for the API key. Set that env var in the environment or in Cursor’s MCP env.
   See [Config file locations](#config-file-locations) and [Environment variables](#environment-variables).

   **Full step-by-step for a new machine** (where to put the config, how to create it, what to edit): [Configure on a new machine](docs/CONFIGURE_NEW_MACHINE.md).

That’s it. Each person installs the tool and adds the server in Cursor; no source code sharing and no manual server launch.

---

### 1. How MCP servers are used (local, install — no cloning required)

MCP servers are **run locally** by each user: Cursor starts the server process on the user’s machine, and all tools (collect, report, insights) run there. There is no central hosted server.

You **do not need to clone this repo** to use it. The normal, extendable way is to **install** the package:

- **From PyPI** (when published):  
  `pip install uprof-mcp`
- **From this repo** (without cloning):  
  `pip install "uprof-mcp @ git+https://github.com/YOUR_ORG/uprof_mcp.git"`  
  (replace with the real repo URL)
- **Clone + editable install** is only needed if you’re developing or contributing:  
  `git clone ... && pip install -e .`

So: many users can use the same server by installing it on their machine; the code is shared via the repo (or PyPI), and each user runs their own instance with their own config.

### 2. How to configure this server in another user's Cursor

Another user (on their own machine) can use the uProf MCP server as follows:

1. **Install the package**  
   `pip install uprof-mcp` (from PyPI), or from git (see above). No need to clone unless they are developing.

2. **Install prerequisites on that machine**  
   - Python 3.10+  
   - (Recommended) **AMDuProfCLI** on that machine — set `UPROF_CLI_PATH` or `uprof.cli_path` in config to the directory containing `AMDuProfCLI`, or have it on `PATH`.

3. **Add the MCP server in Cursor**  
   - **Settings → Features → MCP → Add new MCP server**
   - **Command:** `python` (or `python3`)  
   - **Args:** `-m`, `uprof_mcp.server`  
   - **Env:** `UPROF_MCP_TRANSPORT` = `stdio`  
   - Optionally set `UPROF_MCP_CONFIG` to the path to their config file (e.g. `uprof_mcp_config.ini`).

4. **Restart Cursor** (or reload the window) so it picks up the new server.

5. **Optional:** Copy `uprof_mcp_config.example.ini` to `uprof_mcp_config.ini` on that machine and set `cli_path` / `output_dir` as needed.

The server runs as a **local process** started by Cursor (or manually). There is no “central” server; each user runs their own instance.

### 3. Does Cursor send the user's binary to your machine?

**No.** Cursor does **not** send the user's app binary to another machine.

When a user asks for bottlenecks, Cursor sends a **tool call** to the MCP server with a **path string** (e.g. `target_executable="/home/alice/myapp"`). The **MCP server process** runs on one machine and executes the tool there. So:

- **If the server runs on the same machine as the user** (typical): The path is valid on that machine; the server runs `AMDuProfCLI collect` with that path, so the **app runs locally** on the user's machine. No binary is transferred.
- **If the server ran on a different machine** (e.g. “your” machine with uProf): The path would be something like `/home/otheruser/app`, which **does not exist** on your machine. The collect would fail or point at the wrong file. So the design is **not** “user’s app on machine A, uProf on machine B”; the server must run where the app (and ideally uProf) are.

### 4. How does analysis work?

1. **User asks Cursor** (e.g. “Find bottlenecks in my app”).
2. **Cursor** calls the MCP tool (e.g. `uprof_profile_and_get_insights`) with `target_executable=/path/to/app` — a path on the **machine where the MCP server is running**.
3. **The MCP server** (on that same machine):
   - Optionally runs pre-analyze (top/vmstat).
   - Runs `AMDuProfCLI collect` with that path → the **app runs on that machine** and is profiled.
   - Runs `AMDuProfCLI report` on the collected data.
   - Parses the report and returns pain points, metrics, and suggestions as JSON (or LLM narrative if enabled).
4. **Cursor** shows the response to the user.

So: **profiling and analysis run entirely on the machine where the MCP server runs.** The user must run the uProf MCP server on the same machine that has their app (and preferably AMDuProfCLI). There is no built-in “send binary to server” or “remote profile” flow.

### How to check if Cursor is using the uProf MCP server

1. **Cursor Settings UI**  
   Go to **Cursor Settings → Features → MCP**. Your uProf server should appear in the list. If it’s connected:
   - There is no red indicator next to it.
   - Under that server you see **Available Tools** (e.g. `uprof_status`, `uprof_list_collect_configs`, …).  
   If you see “No tools found” or a red status, the server isn’t connected; try refreshing or restarting Cursor.

2. **Ask the AI to call a uProf tool**  
   In chat, ask: *“Call the uprof_status tool”* or *“What’s the status of the uProf MCP server?”*  
   If Cursor is using your server, the reply will mention **“AMD uProf MCP server is connected. Version 0.1.0.”** (or your configured server name). If the AI doesn’t call the tool or says it can’t find it, the MCP server likely isn’t connected.

3. **Try another tool**  
   Ask: *“List uProf collect configs”* or *“Run uprof_suggest_config for hotspots.”*  
   A real response (e.g. list of configs or “Suggested config: tbp”) means the server is being used.

4. **Restart / refresh**  
   After changing `.cursor/mcp.json` or MCP settings, fully restart Cursor (or use the refresh button next to the server in **Settings → Features → MCP**) so it reloads the config.

## MCP capabilities

### Resources

- **`uprof://docs/user-guide`** – AMD uProf CLI reference and profile-type suggestions (sourced from the [official docs](https://docs.amd.com/r/en-US/57368-uProf-user-guide)).
- **`uprof://context/question-to-commands`** – Mapping from user questions (e.g. “why slow?”, “branch mispredictions”, “thread contention”) to collect/report/translate command combinations. Use this context to choose the right `--config` and report flags for a given question.

### Tools

| Tool | Description |
|------|-------------|
| `uprof_status` | Server status and version; use to verify Cursor is connected to this MCP server. |
| `uprof_get_command_reference` | Command reference from the AMD uProf User Guide (collect/report options). |
| `uprof_get_combinations` | Config + collect + report combinations with “when helpful” descriptions. |
| `uprof_list_all_combinations` | All collect configs, report/translate options, and collect × report combination examples. |
| `uprof_suggest_commands_for_question` | Map a user question to suggested collect + report commands. |
| `uprof_ensure_installed` | Check or download AMDuProfCLI (from configured URL if missing). |
| `uprof_list_collect_configs` | List collect configs (tbp, cpi, etc.). |
| `uprof_list_pmu_events` | List PMU events. |
| `uprof_suggest_config` | Suggest config from goal: hotspots, cycles, threading, mpi. |
| `uprof_collect` | Run `AMDuProfCLI collect` with config, optional target, duration. |
| `uprof_report` | Generate report from a collect output directory. |
| `uprof_profile_and_report` | Collect + report in one step; returns report text. |
| `uprof_visualize_report` | Generate an informative HTML report from report text (pain points, how-to-fix, charts, tables). |
| `uprof_import_and_visualize` | Import a session directory or report.csv and generate the same informative HTML (no new collect). |
| `uprof_get_insights` | Send report text (and optional question) to LLM for insights. **Insights JSON is always saved** to the output folder (or `save_insights_to_dir`) so you can visualize later. |
| `uprof_profile_and_get_insights` | Full pipeline: collect → report → LLM insights. **Insights JSON is always saved** to the session directory or output folder for later visualization. |
| `uprof_session_db_list` | List .db files in a session directory (e.g. cpu.db, callstack.db). |
| `uprof_session_db_tables` | List tables in a session DB file. |
| `uprof_session_db_schema` | Get column names and types for each table. |
| `uprof_session_db_query` | Run a read-only SELECT query against a session DB (DuckDB or SQLite) to fetch data for more insights. |
| `uprof_session_db_context` | Get a summary of all DBs in the session (tables, row counts) for context. |

When insights are generated from a session folder, the payload includes **session_db_context** (tables and row counts from .db files) so the LLM can suggest querying the DB for deeper insights. Install DuckDB for full support: `pip install duckdb` or `pip install uprof-mcp[session-db]`.

| `uprof_pre_analyze` | Run basic Linux monitoring (top, vmstat, iostat) before choosing a collect config; returns suggested config and reason. |
| `uprof_profile_and_get_insights` (with `pre_analyze_first=true`) | Run pre-analyze, then profile with the suggested config and return insights (including why that config was chosen). |

### Pre-analyze overview and how the config is chosen

**Pre-analyze** runs when you use `uprof_pre_analyze` or `uprof_profile_and_get_insights` with `pre_analyze_first=true`. It:

1. Optionally **launches your app** (if `target_executable` is set), waits a short warmup, then runs **top**, **vmstat**, and optionally **iostat** for a few seconds.
2. **Parses** the output into a summary: load average, CPU (us/sy/id/wa), context switches (`vmstat` cs), runnable/blocked processes.
3. **Suggests a uProf collect config** using the user’s question and that summary. The suggestion and reason are included in the MCP result JSON and in the insights payload so the LLM can explain the choice.

**Conditions used to choose the config** (in order; first match wins):

| Condition | Chosen config | Reason |
|-----------|----------------|--------|
| User question mentions **cache, cache miss, memory, L1/L2/L3** | `memory` | Cache/memory analysis (IBS OP). |
| User question mentions **thread, mutex, lock, contention, wait** | `threading` | Threading and lock wait analysis. |
| User question mentions **branch, mispredict, branch prediction** | `branch` | Branch misprediction analysis. |
| User question mentions **cpi, ipc, cycles, pipeline** | `cpi` | CPI/IPC and instruction efficiency. |
| **High context switches** (vmstat `cs` above threshold, default 5000) and question/reason suggests threading | `threading` | Likely lock contention or many threads; use threading config. |
| **High iowait** (above high threshold, default 20%) | `tbp` | CPU hotspots first; I/O is system-wide. |
| **High user CPU** (above threshold, default 60%) | `assess` | Broad first-pass (IPC, CPI, branch, L1 DC). |
| **None of the above** | `assess` | Broad PMC overview; user can re-run with a specific config from the report. |

Thresholds (e.g. context-switch value, iowait %, user CPU %) are configurable in the **config file** under `uprof.pre_analyze` (see `uprof_mcp_config.example.ini`).

### Prompt

- **`uprof_analyze_my_code`** – Builds a prompt to analyze a given executable with uProf and get insights (target, goal, duration, user question).

## Getting Cursor’s LLM to translate JSON and suggest code fixes

When the MCP server returns **insights as JSON** (e.g. from `uprof_profile_and_get_insights` with LLM disabled), Cursor’s AI receives that JSON as the tool result. To get it to **translate** that JSON and **suggest code fixes** (instead of dumping raw JSON), you can:

1. **Ask in one sentence** so the AI both runs the tool and then interprets the result:
   - *“Profile [path/to/binary], translate the results into clear recommendations and suggest code fixes for the hot spots.”*
   - *“Run uProf on [binary] and give me pain points with concrete code fix suggestions and file:line to open.”*
   - *“Where does [binary] slow down? Use the uProf MCP tool, then translate the JSON you get back and suggest code changes for each pain point.”*

2. **Use the project Cursor rule**  
   This repo includes a rule (`.cursor/rules/uprof-translate-json-and-suggest-fixes.mdc`) that tells Cursor’s AI: when you receive uProf insights JSON from an MCP tool, translate it into a readable summary and suggest code fixes using `pain_points`, `code_suggestion`, and `how_to_fix`. With that rule enabled, the AI should do the translation and fix suggestions automatically after calling the tool.

3. **What’s in the JSON**  
   The tool result includes `instructions_for_llm` (how to present the data), `response_focus` (what the user is looking for), `pain_points`, and `answer_detail`. So the external LLM gets **tailored instructions** instead of a generic "answer everything" prompt.

4. **Where the JSON is stored**  
   Whenever you run an analysis (`uprof_get_insights`, `uprof_profile_and_get_insights`, or `uprof_export_insights_for_llm`), the MCP server **always writes the insights JSON** to the output folder (config: `output_dir` / `UPROF_OUTPUT_DIR`). For profile-and-get-insights it is written under the session directory as `insights_export.json`; otherwise as `insights_export_YYYYMMDD_HHMMSS.json`. You can use these files later with `uprof_import_and_visualize` or your own visualizer.

### Intent-based prompts (response_focus)

The MCP server returns **instructions_for_llm** and **response_focus** so the external LLM can match the user's intent instead of always giving the same full answer. Possible values:

| response_focus | User wants | What the LLM should emphasize |
|----------------|------------|--------------------------------|
| **report_only** | Just the report / summary / metrics | profile_meta, answer_detail, key numbers. No pain points or code fixes. |
| **understand** | Explain the report / what it means | answer_detail, questions_solutions; pain points for context but not code snippets. |
| **code_fix** | Code fix suggestions | pain_points with how_to_fix, code_suggestion snippets, source file:line. |
| **all** | Full answer (default) | Report summary + explanation + code fix suggestions. |

- **Inferred from user_question**: If the caller does not pass `response_focus`, the server infers it from the question (e.g. "explain" → understand, "suggest code fix" → code_fix, "just the report" → report_only).
- **Explicit override**: Tools `uprof_get_insights`, `uprof_profile_and_get_insights`, and `uprof_export_insights_for_llm` accept an optional **response_focus** parameter (`report_only` \| `understand` \| `code_fix` \| `all`) so the client can request a specific kind of answer.

## Example flows

1. **“What’s slow in my app?”**  
   Assistant uses `uprof_profile_and_get_insights` (or `uprof_profile_and_report` then `uprof_get_insights`) with `analysis_goal=hotspots` and your binary.

2. **“Explain this uProf report”**  
   User pastes or provides report; assistant calls `uprof_get_insights` with that text and the user’s question.

3. **“Which analysis should I use?”**  
   Assistant reads `uprof://docs/user-guide` and/or calls `uprof_suggest_config` and `uprof_list_collect_configs`.

4. **“Find branch mispredictions” / “Thread contention” / “Cache misses”**  
   Assistant uses the question-to-commands context (resource `uprof://context/question-to-commands` or tool `uprof_suggest_commands_for_question`) to map the question to a collect config and report flags, then runs `uprof_collect` and `uprof_report` (or `uprof_profile_and_report`) with that combination.

5. **“Show wait time / threading in a UI”**  
   **Option A – Collect new:** Run `uprof_profile_and_report`, then `uprof_visualize_report(report_text=<report>, problem_statement="...")` to get an HTML report with pain points and how-to-fix guidance.  
   **Option B – Import existing:** Call `uprof_import_and_visualize(session_dir=<path>)` or `uprof_import_and_visualize(report_file_path=<path/to/report.csv>)` to generate the same HTML from existing data without running collect again.

## Standalone Web UI (no chatbot)

A **standalone web UI** lets users analyze an app or import a report without using the chatbot.

**Install (with web dependencies):**

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

**Run the UI:**

```bash
uv run python -m uprof_mcp.web_app
# or
uv run uprof-ui
```

Then open **http://127.0.0.1:8001** in a browser (port 8001 to avoid clashing with the MCP server on 8000).

**What the UI does:**

1. **Run analysis** – Enter the **test app path** (executable) and a **problem statement** (e.g. “Where does my app spend wait time?”, “CPI analysis”). The backend picks the right collect config from the problem, runs `AMDuProfCLI collect` and `report`, then parses the report.
2. **Import report** – Upload an existing **report.csv** (from a previous uProf run). Optionally add a problem statement so the UI can answer it.
3. **Results** – The UI shows:
   - **Answer to your question** – Short, data-based answer (e.g. main wait object, top CPI function).
   - **Commands used / suggested** – The collect/report combination used or suggested for the problem.
   - **About these metrics** – What each metric means and how to use it (wait time, CPI, IPC, etc.).
   - **Report data** – Same charts and tables as the HTML visualizer (threading summary, wait objects, CPI by function, source map, etc.).

Config (e.g. `UPROF_CLI_PATH`) is read from the same config file and environment as the MCP server.

**Adding context (so Web UI and MCP chat use the same mappings):**  
The UI uses the same question→command mapping as the MCP server. If a problem statement doesn’t map to the right analysis, add context via config so **both** the Web UI and Cursor chat see it:

1. In the UI, open **“Server context & how to add mappings”** and click **“Load context from server”** to see the current mapping and instructions.
2. Edit your config file (`uprof_mcp_config.ini` or `uprof_mcp_config.yaml`; or the path in `UPROF_MCP_CONFIG`) and add under `context:` (YAML) or `[context]` (INI):

   ```yaml
   context:
     question_to_commands_extra:
       - keywords: ["my keyword", "another phrase"]
         config: threading   # or cpi, tbp, branch, memory, etc.
         report_flags: "-g --detail"
         translate_note: ""
         description: When to use this
   ```

3. Restart the Web UI (and MCP server if you use chat). The new keywords will be used when you run analysis from the UI or ask in chat.

## Report visualizer (informative HTML)

The MCP server can produce a **single-file HTML report** that helps you understand pain points and how to fix them, in addition to charts and tables.

**Two ways to get the HTML:**

1. **Collect new, then visualize**  
   `uprof_profile_and_report` → then `uprof_visualize_report(report_text=..., output_path=..., problem_statement="...")`.

2. **Import existing session or report.csv**  
   `uprof_import_and_visualize(session_dir=<path to collect output>)` or `uprof_import_and_visualize(report_file_path=<path to report.csv>)`. Use this when you already have data and want to skip collect.

3. **CLI script (no MCP)**  
   From the project root: `python run_test_report.py <target_executable> [--collect]` or `python run_test_report.py path/to/report.csv`. Uses the same config file; generates the same HTML.

**What the HTML includes:**

- **Answer to your question** (if `problem_statement` is provided) – Short, data-based answer (e.g. main wait object, top CPI function).
- **Pain points – how to fix** – Prioritized findings (e.g. main wait object, top CPI hotspot, thread with high wait %) with concrete **how-to-fix** steps and **code suggestions** (before/after or snippet) shown by default. Your source files are never modified; a copy is created only when you explicitly ask.
- **About these metrics** – What each metric means and how to use it (wait time, CPI, IPC, etc.).
- **Charts and tables** – Threading summary, wait objects, wait functions, thread wait time, CPI by function/source line, hot functions, source map (function → file:line).

No server required; open the HTML file in a browser (Chart.js is loaded from a CDN, data is embedded).

## Auto-download (Linux build)

If `AMDuProfCLI` is not on your system, the server can download the Linux build from AMD’s site. All URLs and paths are configurable (no hardcoding).

- **Downloads page (configurable):** default is [AMD uProf downloads](https://www.amd.com/en/developer/uprof.html#downloads). Set `UPROF_DOWNLOAD_PAGE_URL` to use another page.
- **Direct URL:** set `UPROF_DIRECT_DOWNLOAD_URL` to the Linux `.tar.bz2` URL to skip scraping the page.
- **Enable:** set `UPROF_AUTO_DOWNLOAD=true` (or `auto_download: true` in config). When any tool needs the CLI and it’s missing, the server will download and extract to `UPROF_INSTALL_DIR` (default `~/.local/share/uprof_mcp`).
- **Tool:** use `uprof_ensure_installed` to check or trigger the download explicitly.

### Where we look for AMDuProfCLI

The server looks for `AMDuProfCLI` in this order:

1. **`UPROF_CLI_PATH`** (env) or **`uprof.cli_path`** in the config file — path to the binary or to a directory containing it (we search that directory for `AMDuProfCLI`).
2. **`PATH`** — `shutil.which("AMDuProfCLI")`.
3. **Previous install dir** — if we previously auto-downloaded, we look in the install directory.
4. **`UPROF_CLI_FALLBACK_PATH`** (env) or **`uprof.cli_fallback_path`** in the config file — used when not found above and auto-download is disabled (or after a failed download).

**Config file:** The **common config file** (`uprof_mcp_config.ini` or `uprof_mcp_config.yaml`) holds all MCP server settings. Copy from `uprof_mcp_config.example.ini`. Set `uprof.cli_path` or `uprof.cli_fallback_path` to your uProf path. The file is loaded when you set `UPROF_MCP_CONFIG` to its path, or when you run the server from the project root (we try `uprof_mcp_config.ini` then `uprof_mcp_config.yaml` automatically). If the CLI is not found, **provide the path** via **`UPROF_CLI_PATH`** or **`uprof.cli_path`** (or **`uprof.cli_fallback_path`**) in the common config file. **pyproject.toml** is for package metadata only; runtime settings live in the common config file or in env.

### Why the Linux build can’t be auto-downloaded from the AMD URL

On the [AMD uProf downloads page](https://www.amd.com/en/developer/uprof.html#downloads), the link labeled **"AMDuProf_Linux_x64_5.2.606.tar.bz2"** (or similar) does **not** point directly to the file. When you click it you get an **EULA page**; only after you **accept** the EULA does the browser start the actual tarball download (the file may be `.tar.bz2` or `.tar.gz` depending on the build). The MCP server cannot accept the EULA programmatically, so a plain `GET` of that URL returns HTML (the license page), not the archive. Auto-download would then receive HTML and extraction would fail.

**Workarounds:**

1. **Set `UPROF_CLI_PATH`** to an existing uProf install (e.g. the directory containing `AMDuProfCLI` after you’ve extracted the tarball).
2. **Manual download:** open the AMD downloads page in a browser, click the Linux tarball link (e.g. **AMDuProf_Linux_x64_5.2.606.tar.bz2**), accept the EULA when it appears, then save and extract the downloaded file. Point `UPROF_CLI_PATH` at the directory that contains `AMDuProfCLI` (often inside a `bin` or versioned folder in the archive).
3. **Direct URL (if you have one):** if you obtain a direct link to the tarball (e.g. from the browser’s final download URL after accepting the EULA), set **`UPROF_DIRECT_DOWNLOAD_URL`** to that URL; auto-download will then fetch and extract it.

## Configuration: common config file

All MCP server runtime settings (uprof, mcp, llm, docs, context, paths, timeouts, pre-analyze, etc.) live in **one common config file** — no separate configs. Use either INI or YAML:

| What | Role |
|------|------|
| **`uprof_mcp_config.ini`** or **`uprof_mcp_config.yaml`** | The common config file you edit. Copy from `uprof_mcp_config.example.ini`. Auto-discovered in the current or project directory, or set **`UPROF_MCP_CONFIG`** to its path. |
| **`config.py`** | Code that loads this file and env vars (no need to edit unless adding new settings). |
| **`pyproject.toml`** | Package metadata only (name, version, dependencies, entry points). Not used for MCP runtime config. |

Supported format: **INI** (e.g. `uprof_mcp_config.ini`) or **YAML** (e.g. `uprof_mcp_config.yaml`). Environment variables (UPROF_*) override values from the file.

## Environment variables

- **`UPROF_MCP_CONFIG`** – Path to config file (optional). Use one INI or YAML file for all MCP server settings.
- **`UPROF_MCP_HOST`** – Bind host (default `127.0.0.1`; use `0.0.0.0` to host publicly).
- **`UPROF_MCP_PORT`** – Bind port (default `8000`).
- **`UPROF_MCP_TRANSPORT`** – `streamable-http` (default) or `stdio`.
- **`UPROF_MCP_STREAMABLE_HTTP_PATH`** – URL path for MCP (default `/mcp`).
- **`UPROF_CLI_PATH`** – Path to `AMDuProfCLI` if not on `PATH`.
- **`UPROF_DOWNLOAD_PAGE_URL`** – URL of the AMD uProf downloads page (default: `https://www.amd.com/en/developer/uprof.html#downloads`). Used to find the Linux build link when auto-downloading.
- **`UPROF_DIRECT_DOWNLOAD_URL`** – Optional direct URL to the Linux tarball (`.tar.bz2` or `.tar.gz`). If set, used instead of scraping the downloads page.
- **`UPROF_AUTO_DOWNLOAD`** – If `true`, the server will download the Linux build from the configured URL when `AMDuProfCLI` is not found.
- **`UPROF_INSTALL_DIR`** – Directory to extract the downloaded build (default: `~/.local/share/uprof_mcp` or `$XDG_DATA_HOME/uprof_mcp`).
- **`UPROF_OUTPUT_DIR`** – Directory for collect output and report HTML. **Collect sessions are written here** (not to /tmp). Each visualization writes to `UPROF_OUTPUT_DIR/uprof_report_YYYYMMDD_HHMMSS.html` so runs are not overridden. Set this to an absolute path (e.g. `/path/to/uprof_mcp/output`) if you want a fixed location. Default: `output` folder inside the uprof_mcp project.
- **`UPROF_LLM_INSIGHTS_ENABLED`** – Set to `true` to enable LLM insights (or set `llm.insights_enabled = true` in the common config file).
- **`UPROF_LLM_API_KEY_ENV`** – Env var name for the API key (default `OPENAI_API_KEY`). The key itself must be set in that env var (or in config as `llm.api_key_env` to use a different name).

## License

MIT.