Skip to content
6 changes: 5 additions & 1 deletion SPECS/ARCHIVE/INDEX.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# mcpbridge-wrapper Tasks Archive

**Last Updated:** 2026-02-15 (P12-T4)
**Last Updated:** 2026-02-16 (P12-T2)

## Archived Tasks

Expand Down Expand Up @@ -97,6 +97,7 @@
| P12-T1 | [P12-T1_Add_MCP_Client_Identification/](P12-T1_Add_MCP_Client_Identification/) | 2026-02-15 | PASS |
| P12-T3 | [P12-T3_Add_Error_Classification_and_Categorization/](P12-T3_Add_Error_Classification_and_Categorization/) | 2026-02-15 | PASS |
| P12-T4 | [P12-T4_Add_documentation_about_data_storage/](P12-T4_Add_documentation_about_data_storage/) | 2026-02-15 | PASS |
| P12-T2 | [P12-T2_Add_Tool_Parameter_Frequency_Analysis/](P12-T2_Add_Tool_Parameter_Frequency_Analysis/) | 2026-02-16 | PASS |

## Historical Artifacts

Expand Down Expand Up @@ -158,6 +159,7 @@
| [REVIEW_P12-T1_mcp_client_identification.md](P12-T1_Add_MCP_Client_Identification/REVIEW_P12-T1_mcp_client_identification.md) | Review report for P12-T1 |
| [REVIEW_P12-T3_error_classification_categorization.md](P12-T3_Add_Error_Classification_and_Categorization/REVIEW_P12-T3_error_classification_categorization.md) | Review report for P12-T3 |
| [REVIEW_P12-T4_data_storage_documentation.md](P12-T4_Add_documentation_about_data_storage/REVIEW_P12-T4_data_storage_documentation.md) | Review report for P12-T4 |
| [REVIEW_P12-T2_param_frequency_analysis.md](P12-T2_Add_Tool_Parameter_Frequency_Analysis/REVIEW_P12-T2_param_frequency_analysis.md) | Review report for P12-T2 |

## Archive Log

Expand Down Expand Up @@ -269,3 +271,5 @@
| 2026-02-15 | P12-T3 | Archived REVIEW_P12-T3_error_classification_categorization report |
| 2026-02-15 | P12-T4 | Archived Add_documentation_about_data_storage (PASS) |
| 2026-02-15 | P12-T4 | Archived REVIEW_P12-T4_data_storage_documentation report |
| 2026-02-16 | P12-T2 | Archived Add_Tool_Parameter_Frequency_Analysis (PASS) |
| 2026-02-16 | P12-T2 | Archived REVIEW_P12-T2_param_frequency_analysis report |
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# PRD: P12-T2 — Add Tool Parameter Frequency Analysis

**Status:** In Progress
**Branch:** feature/P12-T2-tool-parameter-frequency-analysis
**Date:** 2026-02-16

---

## 1. Overview

Add optional capture and aggregation of tool call parameter **key names** (not values) to support pattern analysis. This enables operators to see which parameter combinations are most commonly used per tool without exposing sensitive argument values.

---

## 2. Deliverables

| File | Change |
|------|--------|
| `src/mcpbridge_wrapper/webui/config.py` | Add `capture_params: bool` flag (default `False`) under the `"metrics"` key |
| `src/mcpbridge_wrapper/webui/metrics.py` | Add `record_param_keys(tool_name, param_keys)` method and `get_param_patterns(tool_name)` aggregation |
| `src/mcpbridge_wrapper/webui/shared_metrics.py` | Add `param_patterns` table; `record_param_keys()` and `get_param_patterns()` methods |
| `src/mcpbridge_wrapper/webui/server.py` | Add `GET /api/analytics/param-patterns?tool=<name>` endpoint |
| `src/mcpbridge_wrapper/__main__.py` | Extract `params.arguments` key names when `capture_params=True` and call `metrics.record_param_keys()` |
| `src/mcpbridge_wrapper/webui/static/dashboard.js` | Add expandable param pattern row in tool latency table |
| `tests/unit/webui/test_metrics.py` | New test class covering param pattern recording and retrieval |
| `tests/unit/webui/test_shared_metrics.py` | New tests for SQLite param pattern storage and `get_param_patterns` |

---

## 3. Acceptance Criteria

- [ ] `capture_params: true` in config enables parameter key capture
- [ ] Only argument key names are stored (not values) by default
- [ ] `GET /api/analytics/param-patterns?tool=XcodeGrep` returns ranked param combos
- [ ] Dashboard shows expandable param pattern info per tool in the latency table
- [ ] Default behavior (`capture_params: false`) is unchanged — no extra capture overhead
- [ ] Tests cover param extraction, aggregation, and API response format

---

## 4. Design Details

### 4.1 Config Change

Add `"capture_params": False` under the `"metrics"` section in `_DEFAULTS`. Add `capture_params` property to `WebUIConfig`.

### 4.2 Param Key Extraction

In `__main__.py` `on_request` callback: after extracting `tool_name`, if `config.capture_params` is enabled, extract `sorted(req.params.arguments.keys())` from the `MCPRequest`. The sorted tuple of key names forms a "param signature" (e.g. `("path", "pattern", "tabIdentifier")`).

### 4.3 In-Memory Metrics (MetricsCollector)

Add:
- `_param_patterns: Dict[str, Dict[Tuple[str, ...], int]]` — maps `tool_name → {signature → count}`
- `record_param_keys(tool_name: str, param_keys: List[str]) -> None` — increments counter for sorted key tuple
- `get_param_patterns(tool_name: str, top_n: int = 10) -> List[Dict[str, Any]]` — returns sorted list of `{keys: [...], count: N}` dicts

`reset()` must also clear `_param_patterns`.

### 4.4 SQLite Storage (SharedMetricsStore)

New table `param_patterns`:
```sql
CREATE TABLE IF NOT EXISTS param_patterns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tool_name TEXT NOT NULL,
param_signature TEXT NOT NULL, -- JSON array of sorted key names
count INTEGER NOT NULL DEFAULT 1,
UNIQUE(tool_name, param_signature)
);
```

- `record_param_keys(tool_name, param_keys)`: upsert (INSERT OR REPLACE) incrementing `count`.
- `get_param_patterns(tool_name, top_n=10)`: SELECT ordered by `count DESC LIMIT top_n`.

### 4.5 API Endpoint

```
GET /api/analytics/param-patterns?tool=<name>&top_n=10
```

Response:
```json
{
"tool": "XcodeGrep",
"patterns": [
{"keys": ["path", "pattern"], "count": 42},
{"keys": ["path", "pattern", "tabIdentifier"], "count": 17}
]
}
```

### 4.6 Dashboard JS

In the tool latency table, add an expandable chevron button per row. On click, fetch `/api/analytics/param-patterns?tool=<toolName>` and render a sub-row listing the top param combos (formatted as `pattern, path × 42`).

---

## 5. Dependencies

- P12-T1 (MCP client identification) — ✅ completed
- `MCPRequest.params.arguments` already available in `schemas.py`
- Existing `on_request` callback in `__main__.py`

---

## 6. Non-Goals

- Do not store argument values (only key names)
- Do not add persistent storage for param patterns in the audit CSV
- Do not add a config UI for the flag; JSON config file is sufficient

---

## 7. Test Plan

### MetricsCollector
- `test_record_param_keys_basic`: record two calls with same keys → count = 2
- `test_record_param_keys_different_signatures`: two different key combos tracked separately
- `test_get_param_patterns_sorted_by_count`: returns highest-count first
- `test_get_param_patterns_top_n`: respects limit
- `test_get_param_patterns_unknown_tool`: returns empty list
- `test_reset_clears_param_patterns`

### SharedMetricsStore
- `test_record_param_keys_upserts_count`
- `test_get_param_patterns_returns_ranked_list`
- `test_get_param_patterns_unknown_tool_empty`

### Server
- `test_param_patterns_endpoint_returns_tool_patterns`
- `test_param_patterns_endpoint_unknown_tool_empty`
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Validation Report: P12-T2 — Add Tool Parameter Frequency Analysis

**Date:** 2026-02-16
**Branch:** feature/P12-T2-tool-parameter-frequency-analysis
**Verdict:** PASS

---

## Quality Gates

| Gate | Result | Details |
|------|--------|---------|
| `pytest` | ✅ PASS | 457 passed, 5 skipped |
| `ruff check src/` | ✅ PASS | No issues |
| `mypy src/` | ✅ PASS | No issues in 13 source files |
| `pytest --cov` | ✅ PASS | Coverage: 95.95% (≥ 90%) |

---

## Acceptance Criteria

| Criterion | Status |
|-----------|--------|
| `capture_params: true` enables parameter key capture | ✅ Implemented — `WebUIConfig.capture_params` property, `on_request` reads flag |
| Only argument key names are stored (not values) | ✅ Only `req.params.arguments.keys()` captured, values never touched |
| `GET /api/analytics/param-patterns?tool=<name>` returns ranked combos | ✅ Endpoint added to `server.py` |
| Dashboard shows expandable param pattern info per tool | ✅ Chevron buttons + fetch in `dashboard.js`, CSS in `dashboard.css` |
| Default behavior unchanged (flag off) | ✅ `capture_params` defaults to `False`; no capture overhead when off |
| Tests cover param extraction, aggregation, API response | ✅ 8 tests in `test_metrics.py`, 7 in `test_shared_metrics.py`, 4 in `test_server.py`, 2 in `test_config.py` |

---

## Files Changed

| File | Change |
|------|--------|
| `src/mcpbridge_wrapper/webui/config.py` | Added `capture_params` default and property |
| `src/mcpbridge_wrapper/webui/metrics.py` | Added `_param_patterns` dict, `record_param_keys()`, `get_param_patterns()`, updated `reset()` |
| `src/mcpbridge_wrapper/webui/shared_metrics.py` | Added `import json`, `param_patterns` table, `record_param_keys()`, `get_param_patterns()`, updated `reset()` |
| `src/mcpbridge_wrapper/webui/server.py` | Added `GET /api/analytics/param-patterns` endpoint |
| `src/mcpbridge_wrapper/__main__.py` | Added `config = None` initializer; param key extraction in `on_request` |
| `src/mcpbridge_wrapper/webui/static/dashboard.js` | Rewrote `updateLatencyTable` with expandable param pattern rows + `fetchParamPatterns()` |
| `src/mcpbridge_wrapper/webui/static/dashboard.css` | Added styles for `.param-toggle-btn`, `.param-detail-row`, `.param-patterns-table` |
| `tests/unit/webui/test_metrics.py` | Added `TestParamPatterns` class (8 tests) |
| `tests/unit/webui/test_shared_metrics.py` | Added 6 param pattern tests |
| `tests/unit/webui/test_server.py` | Added `TestParamPatternsEndpoint` class (4 tests) |
| `tests/unit/webui/test_config.py` | Added 2 tests for `capture_params` config flag |

---

## Notes

- `SharedMetricsStore.record_param_keys` uses SQLite UPSERT (`ON CONFLICT DO UPDATE`) for atomic count increment across processes.
- `MetricsCollector.record_param_keys` uses a nested dict for in-memory single-process use.
- The `updateLatencyTable` function now builds DOM elements directly instead of innerHTML string concatenation for the expand-row listener pattern.
- When `capture_params` is disabled (default), the `on_request` path reaches the `if` check but immediately short-circuits — no argument key extraction occurs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## REVIEW REPORT — P12-T2: Tool Parameter Frequency Analysis

**Scope:** origin/main..HEAD (4 commits)
**Files:** 12 modified, 2 created (archive artifacts)
**Date:** 2026-02-16

---

### Summary Verdict

- [x] Approve

---

### Critical Issues

None.

---

### Secondary Issues

**[Low] `on_request` catch-all `except Exception: pass` swallows param capture errors silently**

In `__main__.py`, the entire `on_request` body is wrapped in a bare `except Exception: pass`. This is pre-existing, not introduced by this task, and is intentional to prevent metrics logic from crashing the bridge. However the new `record_param_keys` call inherits this behavior — any bug in param key extraction would be silently suppressed. This is acceptable given the non-critical nature of the feature, but worth noting.

**[Low] `SharedMetricsStore.record_param_keys` opens a new transaction per call**

For high-frequency tool calls, each `record_param_keys` invocation commits a separate SQLite transaction. This is consistent with the existing `record_request`/`record_response` pattern and does not change behavior, but at very high call rates (>1000 req/s) SQLite write contention could increase. Acceptable for the use case.

**[Nit] Dashboard event listener is re-registered on every `updateLatencyTable` call**

The `tbody.addEventListener("click", ...)` call inside `updateLatencyTable` adds a new click listener each time metrics update (every 1 second). This means multiple handlers accumulate on the `tbody` element. Since the tbody is replaced via `innerHTML = ""` / `appendChild` on each refresh, old listeners are detached but the outer `tbody` element persists — so multiple listeners stack. Functionally harmless (duplicate fetches for the same tool would simply overwrite the container) but slightly wasteful.

**Fix suggestion:** Move the event listener attachment to a one-time setup function or use event delegation on the parent table/section. Alternatively, track whether the listener is already attached via a `data-listener-attached` attribute.

---

### Architectural Notes

- The design correctly separates in-memory (`MetricsCollector`) and process-safe (`SharedMetricsStore`) implementations, consistent with the existing pattern.
- The `capture_params` flag is opt-in and defaults to `False` — safe for production use where argument values might be sensitive.
- The API endpoint (`/api/analytics/param-patterns`) is read-only and requires the same auth as other endpoints. Good.
- Param signatures are sorted before storage so `(pattern, path)` and `(path, pattern)` produce the same key — correct deduplication behavior.
- The SQLite UPSERT (`ON CONFLICT DO UPDATE SET count = count + 1`) is atomic and correct for multi-process use.

---

### Tests

- 8 new tests in `tests/unit/webui/test_metrics.py` (class `TestParamPatterns`)
- 6 new tests in `tests/unit/webui/test_shared_metrics.py`
- 4 new tests in `tests/unit/webui/test_server.py` (class `TestParamPatternsEndpoint`)
- 2 new tests in `tests/unit/webui/test_config.py`
- All 457 tests pass; coverage 95.95% (≥90%)
- Note: No test covers the `on_request` param key extraction path in `__main__.py` (pre-existing limitation of `__main__` test coverage). This is acceptable.

---

### Next Steps

- **FU-P12-T2-1 (Low):** Fix stacking event listeners in `updateLatencyTable` dashboard JS — move click handler attachment to a one-time setup.
- No blockers. Feature is complete and ready to merge.
5 changes: 3 additions & 2 deletions SPECS/INPROGRESS/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ The previously selected task has been archived.

## Recently Archived

- 2026-02-16 — P12-T2: Add Tool Parameter Frequency Analysis (PASS)
- 2026-02-15 — P12-T4: Add documentation about data storage (PASS)
- 2026-02-15 — P12-T3: Add Error Classification & Categorization (PASS)
- 2026-02-15 — P12-T1: Add MCP Client Identification (PASS)
- 2026-02-15 — P11-T4: Add Keyboard Shortcuts & Command Palette (PASS)
- 2026-02-15 — P11-T3: Add Dashboard Theme Toggle (Dark/Light) (PASS)

## Suggested Next Tasks

- P12-T2: Add Tool Parameter Frequency Analysis (P3, depends on P12-T1 ✅)
- P11-T1: Add Tool Call Detail Inspector (P2, depends on P10-T1 ✅)
- FU-P12-T2-1: Fix stacking click event listeners in `updateLatencyTable` (P3, depends on P12-T2 ✅)
- FU-P12-T1-1: Remove or document `MCPInitializeParams` in schemas (P3, depends on P12-T1 ✅)
- FU-P12-T1-2: Add code comment clarifying stdin-only client capture in `on_request` (P3, depends on P12-T1 ✅)
- FU-P12-T3-1: Document unused `error_message` parameter in `MetricsCollector.record_response` (P3, depends on P12-T3 ✅)
- FU-P12-T3-2: Add `error_code` column to audit CSV export (P3, depends on P12-T3 ✅)
- P11-T2: Add Session Timeline View (P3, depends on P11-T1)
16 changes: 15 additions & 1 deletion SPECS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1696,7 +1696,7 @@ Phase 9 Follow-up Backlog

---

#### P12-T2: Add Tool Parameter Frequency Analysis
#### P12-T2: Add Tool Parameter Frequency Analysis
- **Description:** Optionally capture and aggregate tool call parameter keys (not values by default) for pattern analysis. Config flag `capture_params: bool` (default off). On request capture, extract `params.arguments` key names. Store parameter key signatures per tool (e.g. `XcodeGrep(pattern, path, tabIdentifier)`). New API: `GET /api/analytics/param-patterns?tool=<name>` returns top-N parameter combinations. Dashboard: expandable section in latency table showing common param combos.
- **Priority:** P3
- **Dependencies:** P12-T1
Expand Down Expand Up @@ -1993,6 +1993,20 @@ Phase 9 Follow-up Backlog

---

#### FU-P12-T2-1: Fix stacking click event listeners in `updateLatencyTable`
- **Description:** The `tbody.addEventListener("click", ...)` call inside `updateLatencyTable()` in `dashboard.js` is re-registered on every metrics refresh (every 1 second), causing listeners to accumulate. Move the click handler to a one-time setup function called once during dashboard initialization, or guard with a `data-listener-attached` sentinel attribute on the tbody element.
- **Priority:** P3
- **Dependencies:** P12-T2
- **Parallelizable:** yes
- **Outputs/Artifacts:**
- Updated `src/mcpbridge_wrapper/webui/static/dashboard.js` — click handler moved out of `updateLatencyTable`
- **Acceptance Criteria:**
- [ ] Click handler on latency tbody is registered exactly once per page load
- [ ] Param pattern expand/collapse still works correctly after fix
- [ ] No regression in existing dashboard behavior

---

#### FU-P11-T1-1: Refactor `_FakeWebUIConfig` test stub to use `MagicMock(spec=WebUIConfig)`
- **Description:** The hand-rolled `_FakeWebUIConfig` class in `tests/unit/test_main.py` must be manually updated every time a new property is added to `WebUIConfig`. Refactor it to use `MagicMock(spec=WebUIConfig)` with only the properties needed by the test wired up, so the spec auto-enforces the real interface and future property additions do not break the test.
- **Priority:** P3
Expand Down
11 changes: 11 additions & 0 deletions src/mcpbridge_wrapper/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ def main() -> int:
return 2

# Initialize web UI components if enabled
config = None
metrics = None
audit = None

Expand Down Expand Up @@ -334,6 +335,16 @@ def on_request(line: str) -> None:
metrics.record_request(tool_name, request_id=request_id)
pending_requests[request_id] = (tool_name, start_time)

# Capture parameter key names when feature flag is enabled
if (
config is not None
and config.capture_params
and req.params is not None
and req.params.arguments is not None
):
param_keys = list(req.params.arguments.keys())
metrics.record_param_keys(tool_name, param_keys)

except Exception:
pass

Expand Down
6 changes: 6 additions & 0 deletions src/mcpbridge_wrapper/webui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"metrics": {
"window_seconds": 3600,
"max_datapoints": 3600,
"capture_params": False,
},
"audit": {
"enabled": True,
Expand Down Expand Up @@ -130,6 +131,11 @@ def metrics_max_datapoints(self) -> int:
"""Maximum metrics data points per time-series."""
return int(self._data["metrics"]["max_datapoints"])

@property
def capture_params(self) -> bool:
"""Whether to capture tool call parameter key names for pattern analysis."""
return bool(self._data["metrics"].get("capture_params", False))

@property
def audit_enabled(self) -> bool:
"""Whether audit logging is enabled."""
Expand Down
Loading