mcp-trace-inspector 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_trace_inspector-0.3.0/.github/workflows/ci.yml +22 -0
- mcp_trace_inspector-0.3.0/.github/workflows/publish-pypi.yml +43 -0
- mcp_trace_inspector-0.3.0/.gitignore +13 -0
- mcp_trace_inspector-0.3.0/CONTRIBUTING.md +19 -0
- mcp_trace_inspector-0.3.0/LICENSE +21 -0
- mcp_trace_inspector-0.3.0/PKG-INFO +157 -0
- mcp_trace_inspector-0.3.0/README.md +135 -0
- mcp_trace_inspector-0.3.0/docs/user-guide.md +204 -0
- mcp_trace_inspector-0.3.0/examples/example_trace.jsonl +15 -0
- mcp_trace_inspector-0.3.0/pyproject.toml +45 -0
- mcp_trace_inspector-0.3.0/server.json +23 -0
- mcp_trace_inspector-0.3.0/src/agenthawk/__init__.py +7 -0
- mcp_trace_inspector-0.3.0/src/agenthawk/__main__.py +4 -0
- mcp_trace_inspector-0.3.0/src/agenthawk/core.py +2 -0
- mcp_trace_inspector-0.3.0/src/agenthawk/server.py +6 -0
- mcp_trace_inspector-0.3.0/src/mcp_trace/__init__.py +41 -0
- mcp_trace_inspector-0.3.0/src/mcp_trace/__main__.py +6 -0
- mcp_trace_inspector-0.3.0/src/mcp_trace/core.py +528 -0
- mcp_trace_inspector-0.3.0/src/mcp_trace/server.py +163 -0
- mcp_trace_inspector-0.3.0/tests/conftest.py +6 -0
- mcp_trace_inspector-0.3.0/tests/fixtures/demo_trace.jsonl +15 -0
- mcp_trace_inspector-0.3.0/tests/test_core.py +168 -0
- mcp_trace_inspector-0.3.0/tests/test_server.py +115 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install -e ".[dev]"
|
|
21
|
+
- name: Test
|
|
22
|
+
run: pytest -q
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: publish-pypi
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
release:
|
|
6
|
+
types: [published]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment: pypi
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- name: Build distributions
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade build
|
|
24
|
+
python -m build
|
|
25
|
+
- name: Publish to PyPI with trusted publishing
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
27
|
+
|
|
28
|
+
publish-mcp:
|
|
29
|
+
needs: publish
|
|
30
|
+
if: github.event_name == 'release'
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
with:
|
|
35
|
+
ref: ${{ github.event.release.tag_name }}
|
|
36
|
+
- name: Install mcp-publisher
|
|
37
|
+
run: |
|
|
38
|
+
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
39
|
+
chmod +x mcp-publisher
|
|
40
|
+
- name: Authenticate to MCP Registry
|
|
41
|
+
run: ./mcp-publisher login github-oidc
|
|
42
|
+
- name: Publish server metadata
|
|
43
|
+
run: ./mcp-publisher publish
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Contributing to mcp-trace-inspector
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv venv
|
|
7
|
+
uv pip install -e ".[dev]"
|
|
8
|
+
pytest -q
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The core query engine is dependency-light and should remain pure. Server tests exercise the MCP 2.x protocol adapter as well as the core functions.
|
|
12
|
+
|
|
13
|
+
## Pull requests
|
|
14
|
+
|
|
15
|
+
- Add a deterministic fixture for new query behavior.
|
|
16
|
+
- Test the tool through `MCPServer.call_tool`, not only by calling the underlying function.
|
|
17
|
+
- Keep tool descriptions phrased as when-to-use prompts.
|
|
18
|
+
- Preserve trace-id prefix support and compact output defaults.
|
|
19
|
+
- Update the README's tool table and limitations when behavior changes.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhishek Ash
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-trace-inspector
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Agent observability MCP server for querying OpenTelemetry traces, failures, tool health, security evidence, and regressions.
|
|
5
|
+
Project-URL: Homepage, https://github.com/abhishekash/mcp-trace-inspector
|
|
6
|
+
Project-URL: Repository, https://github.com/abhishekash/mcp-trace-inspector
|
|
7
|
+
Project-URL: Issues, https://github.com/abhishekash/mcp-trace-inspector/issues
|
|
8
|
+
Author-email: Abhishek Ash <ash.abhishek@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-evals,ai-agents,mcp,model-context-protocol,observability,opentelemetry
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: mcp>=2.0.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# mcp-trace-inspector
|
|
24
|
+
|
|
25
|
+
<!-- mcp-name: io.github.abhishekash/mcp-trace-inspector -->
|
|
26
|
+
|
|
27
|
+
[](https://github.com/abhishekash/mcp-trace-inspector/actions/workflows/ci.yml) [](LICENSE)
|
|
28
|
+
|
|
29
|
+
mcp-trace-inspector is a small, local-first stdio MCP server for querying JSONL
|
|
30
|
+
OpenTelemetry spans emitted by agent runs. It exposes focused queries for
|
|
31
|
+
run summaries, tool failures, approvals, activity, and comparisons instead of
|
|
32
|
+
requiring an application-specific dashboard.
|
|
33
|
+
|
|
34
|
+
It pairs with [agent-harness](https://github.com/abhishekash/agent-harness),
|
|
35
|
+
but the reader is format-simple: any JSONL of OTel-shaped spans works. The
|
|
36
|
+
project's concrete origin was a shell-boundary regression: the harness blocked
|
|
37
|
+
`cat ../outside.txt`, but the old trace recorded only that `run_shell` was
|
|
38
|
+
called, not the returned diagnostic. mcp-trace-inspector and the harness now preserve and
|
|
39
|
+
query that bounded error.
|
|
40
|
+
|
|
41
|
+
## AI-native use cases
|
|
42
|
+
|
|
43
|
+
| Question during an agent run | MCP tool | Evidence returned |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| "Why did yesterday's run stall?" | `list_runs` → `slowest_spans` | Run IDs and the longest model or tool spans. |
|
|
46
|
+
| "Did the agent act after I denied the write?" | `approval_log` → `span_tree` | The recorded decision and subsequent execution path. |
|
|
47
|
+
| "How many model tokens did this run use?" | `token_usage` → `span_tree` | Run-level usage totals and the span tree for context. |
|
|
48
|
+
| "Which tool is failing or timing out?" | `failure_report` / `tool_stats` | Redacted diagnostics, error rates, denials, and p95 latency. |
|
|
49
|
+
| "Who/what was allowed to act?" | `security_audit` | Caller, tenant, server provenance, risk, and missing-evidence findings. |
|
|
50
|
+
| "What is happening in the run right now?" | `recent_activity` | Cursor-based polling of newly written spans. |
|
|
51
|
+
| "Did this model/version regress?" | `compare_runs` | Cost, latency, tokens, tool calls, and failure deltas. |
|
|
52
|
+
|
|
53
|
+
The tools read stored traces and can poll files being written, but they do not push events or infer intent from the final answer. Start with the [installation and user guide](docs/user-guide.md), then see the [real trace fixture](examples/example_trace.jsonl) and the [tool descriptions](src/mcp_trace/server.py) for the query contract.
|
|
54
|
+
|
|
55
|
+
## Install & run
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uvx mcp-trace-inspector --trace-dir ./traces
|
|
59
|
+
# or, for local development:
|
|
60
|
+
git clone https://github.com/abhishekash/mcp-trace-inspector
|
|
61
|
+
cd mcp-trace-inspector && uv pip install -e .
|
|
62
|
+
mcp-trace-inspector --trace-dir ./traces
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Client configuration
|
|
66
|
+
|
|
67
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"agent-traces": {
|
|
73
|
+
"command": "uvx",
|
|
74
|
+
"args": ["mcp-trace-inspector", "--trace-dir", "/path/to/traces"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**pi** (`~/.pi/agent/settings.json`):
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"agent-traces": {
|
|
86
|
+
"command": "uvx",
|
|
87
|
+
"args": ["mcp-trace-inspector", "--trace-dir", "/path/to/traces"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**agent-harness** (mounted as gated tools):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
harness run "Why was my last run slow?" --mcp "uvx mcp-trace-inspector --trace-dir ./traces"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Tools
|
|
100
|
+
|
|
101
|
+
| Tool | Use it when |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `list_runs` | Starting out — recent runs with task, model, duration, cost, decision counts |
|
|
104
|
+
| `run_summary` | One run at a glance (accepts trace-id prefix) |
|
|
105
|
+
| `span_tree` | "What did the agent actually do?" — nested shape of the run |
|
|
106
|
+
| `slowest_spans` | "Why was it slow?" — top-k spans by duration |
|
|
107
|
+
| `approval_log` | HITL audit — every approve/deny/**edit**, who decided, and *the rationale* |
|
|
108
|
+
| `token_usage` | Cost questions — aggregated across runs or per-run |
|
|
109
|
+
| `search_spans` | Find spans by tool name, file path, "denied", … |
|
|
110
|
+
| `failure_report` | Find actionable, bounded, redacted errors and failed tool calls. |
|
|
111
|
+
| `tool_stats` | Rank tools by volume, error rate, denials, and latency. |
|
|
112
|
+
| `security_audit` | Audit caller identity, tenant, server provenance, risk, and approval evidence. |
|
|
113
|
+
| `recent_activity` | Poll new spans with a cursor while a run is active. |
|
|
114
|
+
| `compare_runs` | Compare selected traces for regressions across models or versions. |
|
|
115
|
+
|
|
116
|
+
Tool descriptions are written as prompts (when-to-use, not just what-it-does) — descriptions are the interface for agent-called tools.
|
|
117
|
+
|
|
118
|
+
## Example session (real fixture trace)
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
> list_runs
|
|
122
|
+
[{ "trace_id": "f920798dd255…", "task": "Summarize the workspace's notes…",
|
|
123
|
+
"tool_calls": 4, "human_decisions": 2, "stopped_reason": "completed" }]
|
|
124
|
+
|
|
125
|
+
> approval_log
|
|
126
|
+
[{ "tool": "write_file", "decision": "approve", "approver": "auto", … },
|
|
127
|
+
{ "tool": "run_shell", "decision": "approve", "approver": "auto", … }]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Design
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
traces/*.jsonl ──▶ mcp_trace.core (pure query functions, zero deps)
|
|
134
|
+
│
|
|
135
|
+
mcp_trace.server (thin MCPServer adapter, mcp 2.x)
|
|
136
|
+
│
|
|
137
|
+
stdio (NDJSON JSON-RPC)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **core/server split**: all logic is pure functions over parsed spans; the MCP layer only parses args and JSON-encodes results. Tests hit both layers.
|
|
141
|
+
- **trace_id prefixes**: agents fumble full 32-char hex ids; every tool accepts prefixes.
|
|
142
|
+
- **bounded output**: diagnostics are truncated and obvious credentials are redacted before query results leave the server.
|
|
143
|
+
- **honest cost totals**: `token_usage` returns `cost_known: false` and a null cost when any producer marks pricing as unavailable; unknown pricing is never presented as `$0`.
|
|
144
|
+
- **cursor polling**: `recent_activity` makes the snapshot reader useful while a run is still writing spans.
|
|
145
|
+
- The demo fixture ([`examples/example_trace.jsonl`](examples/example_trace.jsonl)) is a *real* agent-harness run, not hand-written.
|
|
146
|
+
|
|
147
|
+
## Honest limitations
|
|
148
|
+
|
|
149
|
+
- stdio transport only (Streamable HTTP plus authenticated remote access is the next transport boundary)
|
|
150
|
+
- live polling re-reads snapshots; it is not a push subscription
|
|
151
|
+
- security audit can only report identity/provenance that the trace producer records
|
|
152
|
+
- read-only tools; trace mutation (annotations) is roadmap
|
|
153
|
+
- The Python query and server modules are available as `mcp_trace`; older import and command aliases remain for existing clients.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# mcp-trace-inspector
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.abhishekash/mcp-trace-inspector -->
|
|
4
|
+
|
|
5
|
+
[](https://github.com/abhishekash/mcp-trace-inspector/actions/workflows/ci.yml) [](LICENSE)
|
|
6
|
+
|
|
7
|
+
mcp-trace-inspector is a small, local-first stdio MCP server for querying JSONL
|
|
8
|
+
OpenTelemetry spans emitted by agent runs. It exposes focused queries for
|
|
9
|
+
run summaries, tool failures, approvals, activity, and comparisons instead of
|
|
10
|
+
requiring an application-specific dashboard.
|
|
11
|
+
|
|
12
|
+
It pairs with [agent-harness](https://github.com/abhishekash/agent-harness),
|
|
13
|
+
but the reader is format-simple: any JSONL of OTel-shaped spans works. The
|
|
14
|
+
project's concrete origin was a shell-boundary regression: the harness blocked
|
|
15
|
+
`cat ../outside.txt`, but the old trace recorded only that `run_shell` was
|
|
16
|
+
called, not the returned diagnostic. mcp-trace-inspector and the harness now preserve and
|
|
17
|
+
query that bounded error.
|
|
18
|
+
|
|
19
|
+
## AI-native use cases
|
|
20
|
+
|
|
21
|
+
| Question during an agent run | MCP tool | Evidence returned |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| "Why did yesterday's run stall?" | `list_runs` → `slowest_spans` | Run IDs and the longest model or tool spans. |
|
|
24
|
+
| "Did the agent act after I denied the write?" | `approval_log` → `span_tree` | The recorded decision and subsequent execution path. |
|
|
25
|
+
| "How many model tokens did this run use?" | `token_usage` → `span_tree` | Run-level usage totals and the span tree for context. |
|
|
26
|
+
| "Which tool is failing or timing out?" | `failure_report` / `tool_stats` | Redacted diagnostics, error rates, denials, and p95 latency. |
|
|
27
|
+
| "Who/what was allowed to act?" | `security_audit` | Caller, tenant, server provenance, risk, and missing-evidence findings. |
|
|
28
|
+
| "What is happening in the run right now?" | `recent_activity` | Cursor-based polling of newly written spans. |
|
|
29
|
+
| "Did this model/version regress?" | `compare_runs` | Cost, latency, tokens, tool calls, and failure deltas. |
|
|
30
|
+
|
|
31
|
+
The tools read stored traces and can poll files being written, but they do not push events or infer intent from the final answer. Start with the [installation and user guide](docs/user-guide.md), then see the [real trace fixture](examples/example_trace.jsonl) and the [tool descriptions](src/mcp_trace/server.py) for the query contract.
|
|
32
|
+
|
|
33
|
+
## Install & run
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uvx mcp-trace-inspector --trace-dir ./traces
|
|
37
|
+
# or, for local development:
|
|
38
|
+
git clone https://github.com/abhishekash/mcp-trace-inspector
|
|
39
|
+
cd mcp-trace-inspector && uv pip install -e .
|
|
40
|
+
mcp-trace-inspector --trace-dir ./traces
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Client configuration
|
|
44
|
+
|
|
45
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"agent-traces": {
|
|
51
|
+
"command": "uvx",
|
|
52
|
+
"args": ["mcp-trace-inspector", "--trace-dir", "/path/to/traces"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**pi** (`~/.pi/agent/settings.json`):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"agent-traces": {
|
|
64
|
+
"command": "uvx",
|
|
65
|
+
"args": ["mcp-trace-inspector", "--trace-dir", "/path/to/traces"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**agent-harness** (mounted as gated tools):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
harness run "Why was my last run slow?" --mcp "uvx mcp-trace-inspector --trace-dir ./traces"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Tools
|
|
78
|
+
|
|
79
|
+
| Tool | Use it when |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `list_runs` | Starting out — recent runs with task, model, duration, cost, decision counts |
|
|
82
|
+
| `run_summary` | One run at a glance (accepts trace-id prefix) |
|
|
83
|
+
| `span_tree` | "What did the agent actually do?" — nested shape of the run |
|
|
84
|
+
| `slowest_spans` | "Why was it slow?" — top-k spans by duration |
|
|
85
|
+
| `approval_log` | HITL audit — every approve/deny/**edit**, who decided, and *the rationale* |
|
|
86
|
+
| `token_usage` | Cost questions — aggregated across runs or per-run |
|
|
87
|
+
| `search_spans` | Find spans by tool name, file path, "denied", … |
|
|
88
|
+
| `failure_report` | Find actionable, bounded, redacted errors and failed tool calls. |
|
|
89
|
+
| `tool_stats` | Rank tools by volume, error rate, denials, and latency. |
|
|
90
|
+
| `security_audit` | Audit caller identity, tenant, server provenance, risk, and approval evidence. |
|
|
91
|
+
| `recent_activity` | Poll new spans with a cursor while a run is active. |
|
|
92
|
+
| `compare_runs` | Compare selected traces for regressions across models or versions. |
|
|
93
|
+
|
|
94
|
+
Tool descriptions are written as prompts (when-to-use, not just what-it-does) — descriptions are the interface for agent-called tools.
|
|
95
|
+
|
|
96
|
+
## Example session (real fixture trace)
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
> list_runs
|
|
100
|
+
[{ "trace_id": "f920798dd255…", "task": "Summarize the workspace's notes…",
|
|
101
|
+
"tool_calls": 4, "human_decisions": 2, "stopped_reason": "completed" }]
|
|
102
|
+
|
|
103
|
+
> approval_log
|
|
104
|
+
[{ "tool": "write_file", "decision": "approve", "approver": "auto", … },
|
|
105
|
+
{ "tool": "run_shell", "decision": "approve", "approver": "auto", … }]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Design
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
traces/*.jsonl ──▶ mcp_trace.core (pure query functions, zero deps)
|
|
112
|
+
│
|
|
113
|
+
mcp_trace.server (thin MCPServer adapter, mcp 2.x)
|
|
114
|
+
│
|
|
115
|
+
stdio (NDJSON JSON-RPC)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- **core/server split**: all logic is pure functions over parsed spans; the MCP layer only parses args and JSON-encodes results. Tests hit both layers.
|
|
119
|
+
- **trace_id prefixes**: agents fumble full 32-char hex ids; every tool accepts prefixes.
|
|
120
|
+
- **bounded output**: diagnostics are truncated and obvious credentials are redacted before query results leave the server.
|
|
121
|
+
- **honest cost totals**: `token_usage` returns `cost_known: false` and a null cost when any producer marks pricing as unavailable; unknown pricing is never presented as `$0`.
|
|
122
|
+
- **cursor polling**: `recent_activity` makes the snapshot reader useful while a run is still writing spans.
|
|
123
|
+
- The demo fixture ([`examples/example_trace.jsonl`](examples/example_trace.jsonl)) is a *real* agent-harness run, not hand-written.
|
|
124
|
+
|
|
125
|
+
## Honest limitations
|
|
126
|
+
|
|
127
|
+
- stdio transport only (Streamable HTTP plus authenticated remote access is the next transport boundary)
|
|
128
|
+
- live polling re-reads snapshots; it is not a push subscription
|
|
129
|
+
- security audit can only report identity/provenance that the trace producer records
|
|
130
|
+
- read-only tools; trace mutation (annotations) is roadmap
|
|
131
|
+
- The Python query and server modules are available as `mcp_trace`; older import and command aliases remain for existing clients.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# mcp-trace-inspector user guide
|
|
2
|
+
|
|
3
|
+
mcp-trace-inspector lets an MCP client ask questions about an agent's recorded traces.
|
|
4
|
+
Point it at a directory of JSONL span records, connect it to an MCP client, and
|
|
5
|
+
ask about a run's tool calls, failures, approvals, timing, or token usage.
|
|
6
|
+
|
|
7
|
+
## What it helps with
|
|
8
|
+
|
|
9
|
+
Use mcp-trace-inspector when you want to:
|
|
10
|
+
|
|
11
|
+
- Find a slow model or tool call without reading every trace line.
|
|
12
|
+
- See recorded tool failures and bounded diagnostic messages.
|
|
13
|
+
- Review approval decisions, including denials and edited calls.
|
|
14
|
+
- Compare recorded latency, tokens, tool calls, and failures across runs.
|
|
15
|
+
- Keep traces on your own machine and query them from an MCP-capable client.
|
|
16
|
+
|
|
17
|
+
mcp-trace-inspector is a read-only trace viewer. It does not run, change, retry, or stop
|
|
18
|
+
the agent being observed. Live activity is returned by polling the trace files;
|
|
19
|
+
there is no push stream. Results describe evidence in the traces. Missing
|
|
20
|
+
identity or approval fields mean the producer did not record that evidence;
|
|
21
|
+
they do not prove a person or policy authorized an action.
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Python 3.11 or newer.
|
|
26
|
+
- An MCP client that can launch a local stdio server.
|
|
27
|
+
- JSONL traces in the project's OTel-shaped span format. The reader expects each
|
|
28
|
+
line to contain a span with `trace_id`, `span_id`, `name`,
|
|
29
|
+
`start_unix_nano`, `end_unix_nano`, `attributes`, and, where available,
|
|
30
|
+
`parent_span_id`, `status`, and `events`. Tool, model, and approval details
|
|
31
|
+
depend on the attributes your trace producer records.
|
|
32
|
+
|
|
33
|
+
The bundled example trace works immediately. Other producers may need an
|
|
34
|
+
export adapter to map their OTel export format into these JSONL span records.
|
|
35
|
+
|
|
36
|
+
## Install and start
|
|
37
|
+
|
|
38
|
+
The simplest option is `uvx`, which runs the published package without a
|
|
39
|
+
project checkout:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx --from mcp-trace-inspector==0.3.0 mcp-trace-inspector --trace-dir /absolute/path/to/traces
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The server speaks MCP over standard input and output, so this command waits for
|
|
46
|
+
an MCP client. Its startup message goes to standard error. To use a different
|
|
47
|
+
trace directory, change `/absolute/path/to/traces` to the directory containing
|
|
48
|
+
your `.jsonl` files. Relative paths are resolved from the server's working
|
|
49
|
+
directory.
|
|
50
|
+
|
|
51
|
+
For a local development install:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/abhishekash/mcp-trace-inspector.git
|
|
55
|
+
cd mcp-trace-inspector
|
|
56
|
+
uv pip install -e .
|
|
57
|
+
mcp-trace-inspector --trace-dir /absolute/path/to/traces
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can also set `MCP_TRACE_DIR` and omit `--trace-dir`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
MCP_TRACE_DIR=/absolute/path/to/traces uvx --from mcp-trace-inspector==0.3.0 mcp-trace-inspector
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The server is local and read-only. Connecting an MCP client gives that client
|
|
67
|
+
access to trace-derived information, including task text and recorded tool
|
|
68
|
+
diagnostics. Choose a trace directory appropriate for the client that will
|
|
69
|
+
query it.
|
|
70
|
+
|
|
71
|
+
## Connect an MCP client
|
|
72
|
+
|
|
73
|
+
Configure the client to launch `uvx` with the package and trace directory.
|
|
74
|
+
Restart or reload its MCP connections after saving the configuration.
|
|
75
|
+
|
|
76
|
+
For clients using the `mcpServers` JSON format, the entry looks like this:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"mcp-trace-inspector": {
|
|
82
|
+
"command": "uvx",
|
|
83
|
+
"args": [
|
|
84
|
+
"--from",
|
|
85
|
+
"mcp-trace-inspector==0.3.0",
|
|
86
|
+
"mcp-trace-inspector",
|
|
87
|
+
"--trace-dir",
|
|
88
|
+
"/absolute/path/to/traces"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use the configuration file and field names documented by your MCP client. For
|
|
96
|
+
example, clients can place MCP server entries in user-level or project-level
|
|
97
|
+
settings. Use an absolute trace path so the server does not depend on the
|
|
98
|
+
client's working directory.
|
|
99
|
+
|
|
100
|
+
With the mcp-trace-inspector repository checked out locally, run the server directly:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"mcp-trace-inspector": {
|
|
106
|
+
"command": "/absolute/path/to/mcp-trace-inspector/.venv/bin/mcp-trace-inspector",
|
|
107
|
+
"args": ["--trace-dir", "/absolute/path/to/traces"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
On Windows, use the executable path from your virtual environment, commonly
|
|
114
|
+
`C:\\path\\to\\mcp-trace-inspector\\.venv\\Scripts\\mcp-trace-inspector.exe`.
|
|
115
|
+
|
|
116
|
+
## First questions to ask
|
|
117
|
+
|
|
118
|
+
Start with `list_runs` to find a recent trace. It returns a shortened trace ID;
|
|
119
|
+
the other tools accept ID prefixes.
|
|
120
|
+
|
|
121
|
+
Try these prompts in your MCP client:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
List the recent runs and tell me which one had a failure.
|
|
125
|
+
For trace <prefix>, show the tool failures and their diagnostics.
|
|
126
|
+
For trace <prefix>, show each approval decision and why it was made.
|
|
127
|
+
For trace <prefix>, what were the three slowest spans?
|
|
128
|
+
Compare traces <prefix-a> and <prefix-b>. Did tokens, latency, or failures change?
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The model chooses which tools to call. Ask for the trace IDs and returned
|
|
132
|
+
evidence in its answer so you can check the result against the source data.
|
|
133
|
+
|
|
134
|
+
## Tool reference
|
|
135
|
+
|
|
136
|
+
| Tool | What it returns | Useful question |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `list_runs` | Recent run summaries with shortened trace IDs | “What ran recently?” |
|
|
139
|
+
| `run_summary` | Task, model, duration, calls, tokens, cost, outcome | “What happened in this run?” |
|
|
140
|
+
| `span_tree` | Nested spans, durations, selected attributes, event names | “What was the execution sequence?” |
|
|
141
|
+
| `slowest_spans` | Longest spans across runs or one trace | “Where did this run spend time?” |
|
|
142
|
+
| `approval_log` | Decision, approver, rationale, and whether an edit was recorded | “What did the reviewer approve or deny?” |
|
|
143
|
+
| `token_usage` | Token totals and cost when producer pricing is known | “How much model usage did these runs record?” |
|
|
144
|
+
| `search_spans` | Spans whose names or attributes contain a search phrase | “Find calls involving `write_file`.” |
|
|
145
|
+
| `failure_report` | Bounded, redacted errors and failed calls | “Why did this tool fail?” |
|
|
146
|
+
| `tool_stats` | Call volume, errors, denials, and latency by tool | “Which integration has the most errors?” |
|
|
147
|
+
| `security_audit` | Recorded identity, tenant, server, risk, and approval evidence | “Which provenance fields were recorded?” |
|
|
148
|
+
| `recent_activity` | Newly written spans after a polling cursor | “What new spans appeared?” |
|
|
149
|
+
| `compare_runs` | Per-run metrics and deltas from the first trace | “What changed between these runs?” |
|
|
150
|
+
|
|
151
|
+
## Connect agent-harness
|
|
152
|
+
|
|
153
|
+
mcp-trace-inspector can run as a stdio MCP server mounted by `agent-harness`:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
harness run "Inspect the last run and explain its failure" \
|
|
157
|
+
--mcp "uvx --from mcp-trace-inspector==0.3.0 mcp-trace-inspector --trace-dir ./traces" \
|
|
158
|
+
--approve cli
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The harness treats mounted MCP tools as execute-risk by default, so its
|
|
162
|
+
interactive approval mode asks before invoking them. Read the harness's MCP
|
|
163
|
+
and approval documentation for its current policy options.
|
|
164
|
+
|
|
165
|
+
## Troubleshooting
|
|
166
|
+
|
|
167
|
+
**The client says the server is unavailable.** Confirm that `uvx` is installed,
|
|
168
|
+
the package can be fetched, and the configured trace path exists. Reload the
|
|
169
|
+
client's MCP configuration and check its server logs; the server writes its
|
|
170
|
+
startup message to standard error.
|
|
171
|
+
|
|
172
|
+
**No runs appear.** Check that the path points to a directory
|
|
173
|
+
containing `.jsonl` files. The server searches subdirectories. The included
|
|
174
|
+
example is at `examples/example_trace.jsonl` in the repository.
|
|
175
|
+
|
|
176
|
+
**Runs appear, but model or approval details are missing.** The server only
|
|
177
|
+
reports fields present in the trace. Check whether your producer exports model
|
|
178
|
+
names, token counts, cost-known status, tool call spans, and approval events.
|
|
179
|
+
Missing fields are not inferred from the final answer.
|
|
180
|
+
|
|
181
|
+
**The JSONL is rejected.** Each line must be a JSON object representing one
|
|
182
|
+
span with the required trace ID, span ID, name, and start and end timestamps.
|
|
183
|
+
Ask your tracing exporter to produce one span object per line.
|
|
184
|
+
|
|
185
|
+
## Privacy and limits
|
|
186
|
+
|
|
187
|
+
mcp-trace-inspector processes the files in the configured directory and returns selected
|
|
188
|
+
trace fields to the connected MCP client. Failure messages are bounded and
|
|
189
|
+
credential patterns are redacted, but redaction is not a guarantee that every
|
|
190
|
+
private value is removed. Review trace contents and client access before using
|
|
191
|
+
it with sensitive data.
|
|
192
|
+
|
|
193
|
+
The current server uses local stdio transport. It polls stored files for new
|
|
194
|
+
spans rather than receiving push events. `security_audit` summarizes recorded
|
|
195
|
+
identity and provenance; it does not independently verify them. Cost totals
|
|
196
|
+
are unknown when the trace producer does not provide reliable pricing.
|
|
197
|
+
|
|
198
|
+
## Example data
|
|
199
|
+
|
|
200
|
+
Try mcp-trace-inspector against the checked-in
|
|
201
|
+
[example trace](../examples/example_trace.jsonl). It contains a run emitted by
|
|
202
|
+
`agent-harness` and can be queried using the tools above.
|
|
203
|
+
|
|
204
|
+
[Back to the README](../README.md)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "047527cfe5a06b79", "parent_span_id": "b3870190725e6c0a", "name": "llm.complete", "start_unix_nano": 1791446657381364374, "end_unix_nano": 1791446657381395204, "status": "UNSET", "attributes": {"llm.model": "scripted", "llm.usage.input_tokens": 92, "llm.usage.output_tokens": 9, "llm.cost_usd": 0.0}, "events": []}
|
|
2
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "cf9ec887533385eb", "parent_span_id": "b3870190725e6c0a", "name": "tool.call", "start_unix_nano": 1791446657381557076, "end_unix_nano": 1791446657381753320, "status": "UNSET", "attributes": {"tool.name": "list_dir", "tool.risk": "read", "tool.call_id": "call_1d4b88534934", "tool.duration_ms": 0.17095799557864666}, "events": []}
|
|
3
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "b3870190725e6c0a", "parent_span_id": "aec12b5c51ce2580", "name": "agent.step", "start_unix_nano": 1791446657381336939, "end_unix_nano": 1791446657381830109, "status": "UNSET", "attributes": {"step.index": 1}, "events": []}
|
|
4
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "3a1883f304c6810a", "parent_span_id": "060258fcbbc7b4b2", "name": "llm.complete", "start_unix_nano": 1791446657381913540, "end_unix_nano": 1791446657381938066, "status": "UNSET", "attributes": {"llm.model": "scripted", "llm.usage.input_tokens": 94, "llm.usage.output_tokens": 9, "llm.cost_usd": 0.0}, "events": []}
|
|
5
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "ec96e325ccf2812b", "parent_span_id": "060258fcbbc7b4b2", "name": "tool.call", "start_unix_nano": 1791446657382001356, "end_unix_nano": 1791446657382097431, "status": "UNSET", "attributes": {"tool.name": "read_file", "tool.risk": "read", "tool.call_id": "call_e67e16d926b0", "tool.duration_ms": 0.08226605132222176}, "events": []}
|
|
6
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "060258fcbbc7b4b2", "parent_span_id": "aec12b5c51ce2580", "name": "agent.step", "start_unix_nano": 1791446657381892888, "end_unix_nano": 1791446657382146922, "status": "UNSET", "attributes": {"step.index": 2}, "events": []}
|
|
7
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "b07c48a63a82f767", "parent_span_id": "0bd568f46e01fc65", "name": "llm.complete", "start_unix_nano": 1791446657382220834, "end_unix_nano": 1791446657382241746, "status": "UNSET", "attributes": {"llm.model": "scripted", "llm.usage.input_tokens": 125, "llm.usage.output_tokens": 9, "llm.cost_usd": 0.0}, "events": []}
|
|
8
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "d6bc66fcba6ba958", "parent_span_id": "0bd568f46e01fc65", "name": "tool.call", "start_unix_nano": 1791446657382294447, "end_unix_nano": 1791446657382449645, "status": "UNSET", "attributes": {"tool.name": "write_file", "tool.risk": "write", "tool.call_id": "call_8a5a534020b7", "hitl.required": true, "tool.duration_ms": 0.11282693594694138}, "events": [{"name": "hitl.decision", "time_unix_nano": 1791446657382326440, "attributes": {"hitl.decision": "approve", "hitl.approver": "auto", "hitl.rationale": "auto-approved (unattended run)"}}]}
|
|
9
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "0bd568f46e01fc65", "parent_span_id": "aec12b5c51ce2580", "name": "agent.step", "start_unix_nano": 1791446657382193796, "end_unix_nano": 1791446657382508737, "status": "UNSET", "attributes": {"step.index": 3}, "events": []}
|
|
10
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "70d5591a02be91d6", "parent_span_id": "e83a45284010e5c7", "name": "llm.complete", "start_unix_nano": 1791446657382582514, "end_unix_nano": 1791446657382605733, "status": "UNSET", "attributes": {"llm.model": "scripted", "llm.usage.input_tokens": 132, "llm.usage.output_tokens": 9, "llm.cost_usd": 0.0}, "events": []}
|
|
11
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "0f176ff0f2ed3f5f", "parent_span_id": "e83a45284010e5c7", "name": "tool.call", "start_unix_nano": 1791446657382663037, "end_unix_nano": 1791446657384990883, "status": "UNSET", "attributes": {"tool.name": "run_shell", "tool.risk": "execute", "tool.call_id": "call_9a19cc1b4ead", "hitl.required": true, "tool.duration_ms": 2.2949520498514175}, "events": [{"name": "hitl.decision", "time_unix_nano": 1791446657382685591, "attributes": {"hitl.decision": "approve", "hitl.approver": "auto", "hitl.rationale": "auto-approved (unattended run)"}}]}
|
|
12
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "e83a45284010e5c7", "parent_span_id": "aec12b5c51ce2580", "name": "agent.step", "start_unix_nano": 1791446657382563796, "end_unix_nano": 1791446657385069850, "status": "UNSET", "attributes": {"step.index": 4}, "events": []}
|
|
13
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "7e5dff5291ab7ee6", "parent_span_id": "7523732e099104c7", "name": "llm.complete", "start_unix_nano": 1791446657385145680, "end_unix_nano": 1791446657385168372, "status": "UNSET", "attributes": {"llm.model": "scripted", "llm.usage.input_tokens": 196, "llm.usage.output_tokens": 14, "llm.cost_usd": 0.0}, "events": []}
|
|
14
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "7523732e099104c7", "parent_span_id": "aec12b5c51ce2580", "name": "agent.step", "start_unix_nano": 1791446657385127184, "end_unix_nano": 1791446657385204759, "status": "UNSET", "attributes": {"step.index": 5}, "events": []}
|
|
15
|
+
{"trace_id": "f920798dd2558fcb84f9977736432a8b", "span_id": "aec12b5c51ce2580", "parent_span_id": null, "name": "agent.run", "start_unix_nano": 1791446657381269974, "end_unix_nano": 1791446657385251046, "status": "UNSET", "attributes": {"agent.task": "Summarize the workspace's notes into SUMMARY.md, then verify it with ls", "agent.max_steps": 12, "agent.tools": ["list_dir", "read_file", "run_shell", "write_file"], "agent.skills": [], "llm.model": "scripted", "agent.steps": 5, "agent.stopped_reason": "completed", "agent.usage.input_tokens": 639, "agent.usage.output_tokens": 50, "agent.cost_usd": 0.0, "agent.approvals": 2}, "events": []}
|