scope-analytics-mcp 0.1.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.
- scope_analytics_mcp-0.1.0/PKG-INFO +99 -0
- scope_analytics_mcp-0.1.0/README.md +87 -0
- scope_analytics_mcp-0.1.0/pyproject.toml +31 -0
- scope_analytics_mcp-0.1.0/scope_analytics_mcp.egg-info/PKG-INFO +99 -0
- scope_analytics_mcp-0.1.0/scope_analytics_mcp.egg-info/SOURCES.txt +22 -0
- scope_analytics_mcp-0.1.0/scope_analytics_mcp.egg-info/dependency_links.txt +1 -0
- scope_analytics_mcp-0.1.0/scope_analytics_mcp.egg-info/entry_points.txt +2 -0
- scope_analytics_mcp-0.1.0/scope_analytics_mcp.egg-info/requires.txt +6 -0
- scope_analytics_mcp-0.1.0/scope_analytics_mcp.egg-info/top_level.txt +1 -0
- scope_analytics_mcp-0.1.0/scope_mcp/__init__.py +5 -0
- scope_analytics_mcp-0.1.0/scope_mcp/__main__.py +3 -0
- scope_analytics_mcp-0.1.0/scope_mcp/client.py +166 -0
- scope_analytics_mcp-0.1.0/scope_mcp/config.py +41 -0
- scope_analytics_mcp-0.1.0/scope_mcp/detect.py +374 -0
- scope_analytics_mcp-0.1.0/scope_mcp/format.py +834 -0
- scope_analytics_mcp-0.1.0/scope_mcp/install.py +279 -0
- scope_analytics_mcp-0.1.0/scope_mcp/integrations.py +269 -0
- scope_analytics_mcp-0.1.0/scope_mcp/server.py +435 -0
- scope_analytics_mcp-0.1.0/setup.cfg +4 -0
- scope_analytics_mcp-0.1.0/tests/test_coverage_tools.py +467 -0
- scope_analytics_mcp-0.1.0/tests/test_detect_stack.py +247 -0
- scope_analytics_mcp-0.1.0/tests/test_install_tools.py +216 -0
- scope_analytics_mcp-0.1.0/tests/test_integrations_manifest.py +293 -0
- scope_analytics_mcp-0.1.0/tests/test_query_tools.py +436 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scope-analytics-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scope MCP server — install + query tools for AI coding agents (the Scope analytics connector)
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: mcp>=1.6
|
|
8
|
+
Requires-Dist: httpx>=0.24
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# Scope MCP server
|
|
14
|
+
|
|
15
|
+
<!-- mcp-name: io.github.wally827/scope-analytics-mcp -->
|
|
16
|
+
|
|
17
|
+
The unified Scope MCP server (PRODUCT.md §16) — one server an AI coding agent (Claude Code, Cursor,
|
|
18
|
+
ChatGPT, …) uses both to **install** Scope analytics in a project and to **query** it afterward. Scope
|
|
19
|
+
is the AI analyst for AI products; this is its programmatic surface.
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
### Install / setup (run once, at install time)
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `scope_detect_stack` | Scans the codebase (local, no key) and reports what Scope would auto-instrument for your stack — backend framework, LLM SDKs, frontend, deploy platform — plus honest non-coverage. |
|
|
28
|
+
| `scope_install_frontend` | The exact frontend-SDK install steps tailored to your stack (build-time injection or the one-line script tag), with your project's **public** key embedded. |
|
|
29
|
+
| `scope_install_backend` | The exact backend-SDK install steps for your stack — the zero-code `scope-run` path plus the code-based middleware (FastAPI/Flask/Django). Honest when a path isn't shipped yet (e.g. a Node backend). |
|
|
30
|
+
| `scope_coverage_report` | Honest, tenant-scoped **data-flow** census: which event types/sources are flowing, quarantine state, identity stitching, deploy metadata, and notes on gaps. |
|
|
31
|
+
| `scope_verify_installation` | Quick "are events flowing right now?" check, for right after install. |
|
|
32
|
+
|
|
33
|
+
The install tools **propose** code changes; your agent shows them to you and applies them on your
|
|
34
|
+
confirmation (detect-and-confirm). The Scope MCP never silently writes your files, and your **secret**
|
|
35
|
+
key is never echoed into a snippet — only a placeholder.
|
|
36
|
+
|
|
37
|
+
### Query (ongoing analysis)
|
|
38
|
+
|
|
39
|
+
| Tool | What it does |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `scope_ask` | Natural-language analytics question → the analyst's full reasoned answer (findings + recommendations). The catch-all; runs a real server-side agent loop (can take a minute). |
|
|
42
|
+
| `scope_get_stats` | Headline counts — total events, unique users, the per-type breakdown, and a day-by-day trend — over an optional date range / event type. Instant and deterministic; the quick "what are the numbers?" before deciding whether to dig in. |
|
|
43
|
+
| `scope_query_events` | The raw event feed (most recent first), optionally filtered by type/user. |
|
|
44
|
+
| `scope_get_session` | One session's (or user's) events stitched across frontend + backend + LLM, in time order, with a small census. |
|
|
45
|
+
| `scope_list_metrics` | The project's metric definitions — the analyst's recorded recipes ("how we computed it last time"). |
|
|
46
|
+
| `scope_get_metric` | One metric's full recipe + provenance. |
|
|
47
|
+
|
|
48
|
+
All query tools are **read-only** and tenant-scoped to your key's project. Use the deterministic
|
|
49
|
+
primitives for instant lookups you compose yourself; use `scope_ask` for open-ended "why/what/how".
|
|
50
|
+
|
|
51
|
+
## Configure (auth = your project SECRET key in the MCP config)
|
|
52
|
+
|
|
53
|
+
`env` keys:
|
|
54
|
+
- `SCOPE_API_KEY` — **required.** Your project's secret key (`sk_...`), from the Scope dashboard. (The
|
|
55
|
+
install tools also read your **public** key from the API; you don't configure it.)
|
|
56
|
+
- `SCOPE_API_BASE` — backend base URL (default: the Scope backend).
|
|
57
|
+
- `SCOPE_API_TIMEOUT` — request timeout seconds (default 90; the free-tier backend can cold-start, and
|
|
58
|
+
`scope_ask` runs a real agent loop, so its client floors the timeout at 180s).
|
|
59
|
+
|
|
60
|
+
### Claude Code (`.mcp.json` or `claude mcp add`)
|
|
61
|
+
|
|
62
|
+
Published as **`scope-analytics-mcp`** on PyPI — `uvx` fetches + runs it on demand (no manual install):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"scope": {
|
|
68
|
+
"command": "uvx",
|
|
69
|
+
"args": ["scope-analytics-mcp"],
|
|
70
|
+
"env": {
|
|
71
|
+
"SCOPE_API_KEY": "sk_...",
|
|
72
|
+
"SCOPE_API_BASE": "https://your-scope-backend"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Running from a local checkout** (development): swap the command for your venv python and the import
|
|
80
|
+
> name — `"command": "/path/to/mcp-server/venv/bin/python", "args": ["-m", "scope_mcp"]` (the import
|
|
81
|
+
> package stays `scope_mcp`; the PyPI/`uvx` name is `scope-analytics-mcp`).
|
|
82
|
+
|
|
83
|
+
Then ask your agent things like:
|
|
84
|
+
- *"Use Scope to install analytics in this project."* (→ detect → install → verify)
|
|
85
|
+
- *"What does Scope cover for my app?"* (→ coverage report)
|
|
86
|
+
- *"Ask Scope why signups dropped this week."* (→ the analyst)
|
|
87
|
+
- *"Show me what user `u_123` did in their last session."* (→ session)
|
|
88
|
+
|
|
89
|
+
## Develop
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python3.10 -m venv venv
|
|
93
|
+
venv/bin/pip install -e ".[dev]"
|
|
94
|
+
venv/bin/python -m pytest -q
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> Not yet shipped (Track C): the sharing tools (`scope_list_reports`, `scope_get_report`, …) and
|
|
98
|
+
> the cross-origin CORS opt-in action. (A multi-project `project_id` arg is intentionally *not*
|
|
99
|
+
> planned — configure one MCP server per project so each is cleanly tenant-isolated by its own key.)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Scope MCP server
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.wally827/scope-analytics-mcp -->
|
|
4
|
+
|
|
5
|
+
The unified Scope MCP server (PRODUCT.md §16) — one server an AI coding agent (Claude Code, Cursor,
|
|
6
|
+
ChatGPT, …) uses both to **install** Scope analytics in a project and to **query** it afterward. Scope
|
|
7
|
+
is the AI analyst for AI products; this is its programmatic surface.
|
|
8
|
+
|
|
9
|
+
## Tools
|
|
10
|
+
|
|
11
|
+
### Install / setup (run once, at install time)
|
|
12
|
+
|
|
13
|
+
| Tool | What it does |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `scope_detect_stack` | Scans the codebase (local, no key) and reports what Scope would auto-instrument for your stack — backend framework, LLM SDKs, frontend, deploy platform — plus honest non-coverage. |
|
|
16
|
+
| `scope_install_frontend` | The exact frontend-SDK install steps tailored to your stack (build-time injection or the one-line script tag), with your project's **public** key embedded. |
|
|
17
|
+
| `scope_install_backend` | The exact backend-SDK install steps for your stack — the zero-code `scope-run` path plus the code-based middleware (FastAPI/Flask/Django). Honest when a path isn't shipped yet (e.g. a Node backend). |
|
|
18
|
+
| `scope_coverage_report` | Honest, tenant-scoped **data-flow** census: which event types/sources are flowing, quarantine state, identity stitching, deploy metadata, and notes on gaps. |
|
|
19
|
+
| `scope_verify_installation` | Quick "are events flowing right now?" check, for right after install. |
|
|
20
|
+
|
|
21
|
+
The install tools **propose** code changes; your agent shows them to you and applies them on your
|
|
22
|
+
confirmation (detect-and-confirm). The Scope MCP never silently writes your files, and your **secret**
|
|
23
|
+
key is never echoed into a snippet — only a placeholder.
|
|
24
|
+
|
|
25
|
+
### Query (ongoing analysis)
|
|
26
|
+
|
|
27
|
+
| Tool | What it does |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `scope_ask` | Natural-language analytics question → the analyst's full reasoned answer (findings + recommendations). The catch-all; runs a real server-side agent loop (can take a minute). |
|
|
30
|
+
| `scope_get_stats` | Headline counts — total events, unique users, the per-type breakdown, and a day-by-day trend — over an optional date range / event type. Instant and deterministic; the quick "what are the numbers?" before deciding whether to dig in. |
|
|
31
|
+
| `scope_query_events` | The raw event feed (most recent first), optionally filtered by type/user. |
|
|
32
|
+
| `scope_get_session` | One session's (or user's) events stitched across frontend + backend + LLM, in time order, with a small census. |
|
|
33
|
+
| `scope_list_metrics` | The project's metric definitions — the analyst's recorded recipes ("how we computed it last time"). |
|
|
34
|
+
| `scope_get_metric` | One metric's full recipe + provenance. |
|
|
35
|
+
|
|
36
|
+
All query tools are **read-only** and tenant-scoped to your key's project. Use the deterministic
|
|
37
|
+
primitives for instant lookups you compose yourself; use `scope_ask` for open-ended "why/what/how".
|
|
38
|
+
|
|
39
|
+
## Configure (auth = your project SECRET key in the MCP config)
|
|
40
|
+
|
|
41
|
+
`env` keys:
|
|
42
|
+
- `SCOPE_API_KEY` — **required.** Your project's secret key (`sk_...`), from the Scope dashboard. (The
|
|
43
|
+
install tools also read your **public** key from the API; you don't configure it.)
|
|
44
|
+
- `SCOPE_API_BASE` — backend base URL (default: the Scope backend).
|
|
45
|
+
- `SCOPE_API_TIMEOUT` — request timeout seconds (default 90; the free-tier backend can cold-start, and
|
|
46
|
+
`scope_ask` runs a real agent loop, so its client floors the timeout at 180s).
|
|
47
|
+
|
|
48
|
+
### Claude Code (`.mcp.json` or `claude mcp add`)
|
|
49
|
+
|
|
50
|
+
Published as **`scope-analytics-mcp`** on PyPI — `uvx` fetches + runs it on demand (no manual install):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"scope": {
|
|
56
|
+
"command": "uvx",
|
|
57
|
+
"args": ["scope-analytics-mcp"],
|
|
58
|
+
"env": {
|
|
59
|
+
"SCOPE_API_KEY": "sk_...",
|
|
60
|
+
"SCOPE_API_BASE": "https://your-scope-backend"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> **Running from a local checkout** (development): swap the command for your venv python and the import
|
|
68
|
+
> name — `"command": "/path/to/mcp-server/venv/bin/python", "args": ["-m", "scope_mcp"]` (the import
|
|
69
|
+
> package stays `scope_mcp`; the PyPI/`uvx` name is `scope-analytics-mcp`).
|
|
70
|
+
|
|
71
|
+
Then ask your agent things like:
|
|
72
|
+
- *"Use Scope to install analytics in this project."* (→ detect → install → verify)
|
|
73
|
+
- *"What does Scope cover for my app?"* (→ coverage report)
|
|
74
|
+
- *"Ask Scope why signups dropped this week."* (→ the analyst)
|
|
75
|
+
- *"Show me what user `u_123` did in their last session."* (→ session)
|
|
76
|
+
|
|
77
|
+
## Develop
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python3.10 -m venv venv
|
|
81
|
+
venv/bin/pip install -e ".[dev]"
|
|
82
|
+
venv/bin/python -m pytest -q
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> Not yet shipped (Track C): the sharing tools (`scope_list_reports`, `scope_get_report`, …) and
|
|
86
|
+
> the cross-origin CORS opt-in action. (A multi-project `project_id` arg is intentionally *not*
|
|
87
|
+
> planned — configure one MCP server per project so each is cleanly tenant-isolated by its own key.)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scope-analytics-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Scope MCP server — install + query tools for AI coding agents (the Scope analytics connector)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"mcp>=1.6",
|
|
13
|
+
"httpx>=0.24",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=7.0.0",
|
|
19
|
+
"pytest-asyncio>=0.21.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# Console script matches the PyPI dist name so `uvx scope-analytics-mcp` runs it directly.
|
|
23
|
+
# (The import package stays `scope_mcp` — users never type it when launching via uvx.)
|
|
24
|
+
[project.scripts]
|
|
25
|
+
scope-analytics-mcp = "scope_mcp.server:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
include = ["scope_mcp*"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scope-analytics-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scope MCP server — install + query tools for AI coding agents (the Scope analytics connector)
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: mcp>=1.6
|
|
8
|
+
Requires-Dist: httpx>=0.24
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# Scope MCP server
|
|
14
|
+
|
|
15
|
+
<!-- mcp-name: io.github.wally827/scope-analytics-mcp -->
|
|
16
|
+
|
|
17
|
+
The unified Scope MCP server (PRODUCT.md §16) — one server an AI coding agent (Claude Code, Cursor,
|
|
18
|
+
ChatGPT, …) uses both to **install** Scope analytics in a project and to **query** it afterward. Scope
|
|
19
|
+
is the AI analyst for AI products; this is its programmatic surface.
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
### Install / setup (run once, at install time)
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `scope_detect_stack` | Scans the codebase (local, no key) and reports what Scope would auto-instrument for your stack — backend framework, LLM SDKs, frontend, deploy platform — plus honest non-coverage. |
|
|
28
|
+
| `scope_install_frontend` | The exact frontend-SDK install steps tailored to your stack (build-time injection or the one-line script tag), with your project's **public** key embedded. |
|
|
29
|
+
| `scope_install_backend` | The exact backend-SDK install steps for your stack — the zero-code `scope-run` path plus the code-based middleware (FastAPI/Flask/Django). Honest when a path isn't shipped yet (e.g. a Node backend). |
|
|
30
|
+
| `scope_coverage_report` | Honest, tenant-scoped **data-flow** census: which event types/sources are flowing, quarantine state, identity stitching, deploy metadata, and notes on gaps. |
|
|
31
|
+
| `scope_verify_installation` | Quick "are events flowing right now?" check, for right after install. |
|
|
32
|
+
|
|
33
|
+
The install tools **propose** code changes; your agent shows them to you and applies them on your
|
|
34
|
+
confirmation (detect-and-confirm). The Scope MCP never silently writes your files, and your **secret**
|
|
35
|
+
key is never echoed into a snippet — only a placeholder.
|
|
36
|
+
|
|
37
|
+
### Query (ongoing analysis)
|
|
38
|
+
|
|
39
|
+
| Tool | What it does |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `scope_ask` | Natural-language analytics question → the analyst's full reasoned answer (findings + recommendations). The catch-all; runs a real server-side agent loop (can take a minute). |
|
|
42
|
+
| `scope_get_stats` | Headline counts — total events, unique users, the per-type breakdown, and a day-by-day trend — over an optional date range / event type. Instant and deterministic; the quick "what are the numbers?" before deciding whether to dig in. |
|
|
43
|
+
| `scope_query_events` | The raw event feed (most recent first), optionally filtered by type/user. |
|
|
44
|
+
| `scope_get_session` | One session's (or user's) events stitched across frontend + backend + LLM, in time order, with a small census. |
|
|
45
|
+
| `scope_list_metrics` | The project's metric definitions — the analyst's recorded recipes ("how we computed it last time"). |
|
|
46
|
+
| `scope_get_metric` | One metric's full recipe + provenance. |
|
|
47
|
+
|
|
48
|
+
All query tools are **read-only** and tenant-scoped to your key's project. Use the deterministic
|
|
49
|
+
primitives for instant lookups you compose yourself; use `scope_ask` for open-ended "why/what/how".
|
|
50
|
+
|
|
51
|
+
## Configure (auth = your project SECRET key in the MCP config)
|
|
52
|
+
|
|
53
|
+
`env` keys:
|
|
54
|
+
- `SCOPE_API_KEY` — **required.** Your project's secret key (`sk_...`), from the Scope dashboard. (The
|
|
55
|
+
install tools also read your **public** key from the API; you don't configure it.)
|
|
56
|
+
- `SCOPE_API_BASE` — backend base URL (default: the Scope backend).
|
|
57
|
+
- `SCOPE_API_TIMEOUT` — request timeout seconds (default 90; the free-tier backend can cold-start, and
|
|
58
|
+
`scope_ask` runs a real agent loop, so its client floors the timeout at 180s).
|
|
59
|
+
|
|
60
|
+
### Claude Code (`.mcp.json` or `claude mcp add`)
|
|
61
|
+
|
|
62
|
+
Published as **`scope-analytics-mcp`** on PyPI — `uvx` fetches + runs it on demand (no manual install):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"scope": {
|
|
68
|
+
"command": "uvx",
|
|
69
|
+
"args": ["scope-analytics-mcp"],
|
|
70
|
+
"env": {
|
|
71
|
+
"SCOPE_API_KEY": "sk_...",
|
|
72
|
+
"SCOPE_API_BASE": "https://your-scope-backend"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Running from a local checkout** (development): swap the command for your venv python and the import
|
|
80
|
+
> name — `"command": "/path/to/mcp-server/venv/bin/python", "args": ["-m", "scope_mcp"]` (the import
|
|
81
|
+
> package stays `scope_mcp`; the PyPI/`uvx` name is `scope-analytics-mcp`).
|
|
82
|
+
|
|
83
|
+
Then ask your agent things like:
|
|
84
|
+
- *"Use Scope to install analytics in this project."* (→ detect → install → verify)
|
|
85
|
+
- *"What does Scope cover for my app?"* (→ coverage report)
|
|
86
|
+
- *"Ask Scope why signups dropped this week."* (→ the analyst)
|
|
87
|
+
- *"Show me what user `u_123` did in their last session."* (→ session)
|
|
88
|
+
|
|
89
|
+
## Develop
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python3.10 -m venv venv
|
|
93
|
+
venv/bin/pip install -e ".[dev]"
|
|
94
|
+
venv/bin/python -m pytest -q
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> Not yet shipped (Track C): the sharing tools (`scope_list_reports`, `scope_get_report`, …) and
|
|
98
|
+
> the cross-origin CORS opt-in action. (A multi-project `project_id` arg is intentionally *not*
|
|
99
|
+
> planned — configure one MCP server per project so each is cleanly tenant-isolated by its own key.)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
scope_analytics_mcp.egg-info/PKG-INFO
|
|
4
|
+
scope_analytics_mcp.egg-info/SOURCES.txt
|
|
5
|
+
scope_analytics_mcp.egg-info/dependency_links.txt
|
|
6
|
+
scope_analytics_mcp.egg-info/entry_points.txt
|
|
7
|
+
scope_analytics_mcp.egg-info/requires.txt
|
|
8
|
+
scope_analytics_mcp.egg-info/top_level.txt
|
|
9
|
+
scope_mcp/__init__.py
|
|
10
|
+
scope_mcp/__main__.py
|
|
11
|
+
scope_mcp/client.py
|
|
12
|
+
scope_mcp/config.py
|
|
13
|
+
scope_mcp/detect.py
|
|
14
|
+
scope_mcp/format.py
|
|
15
|
+
scope_mcp/install.py
|
|
16
|
+
scope_mcp/integrations.py
|
|
17
|
+
scope_mcp/server.py
|
|
18
|
+
tests/test_coverage_tools.py
|
|
19
|
+
tests/test_detect_stack.py
|
|
20
|
+
tests/test_install_tools.py
|
|
21
|
+
tests/test_integrations_manifest.py
|
|
22
|
+
tests/test_query_tools.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scope_mcp
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Thin async HTTP client around Scope's Developer API v1 (secret-key auth).
|
|
2
|
+
|
|
3
|
+
The MCP tools call this; it owns auth, error translation, and nothing else. Kept separate from the
|
|
4
|
+
server so it is trivially unit-testable with httpx's MockTransport (no network, no MCP runtime)."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .config import ScopeConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ScopeApiError(Exception):
|
|
15
|
+
"""A user-actionable failure talking to the Scope API (surfaced verbatim to the agent)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# The analyst tool (scope_ask) runs a real server-side LLM agent loop — far slower than the census
|
|
19
|
+
# endpoints. Give it a generous timeout floor (still overridable UPWARD via SCOPE_API_TIMEOUT).
|
|
20
|
+
ASK_TIMEOUT_SECONDS = 180.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ScopeApiClient:
|
|
24
|
+
def __init__(self, config: ScopeConfig, *, transport: Optional[httpx.AsyncBaseTransport] = None):
|
|
25
|
+
self._config = config
|
|
26
|
+
self._transport = transport # tests inject httpx.MockTransport
|
|
27
|
+
|
|
28
|
+
def _require_api_key(self) -> None:
|
|
29
|
+
if not self._config.api_key:
|
|
30
|
+
raise ScopeApiError(
|
|
31
|
+
"SCOPE_API_KEY is not set. Add your project's secret key (sk_...) to the MCP server's "
|
|
32
|
+
"env config."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def _parse(self, r: httpx.Response) -> Dict[str, Any]:
|
|
36
|
+
"""Shared response handling: secret-key/status translation + JSON parse. Used by every verb
|
|
37
|
+
so error messages stay identical across tools."""
|
|
38
|
+
if r.status_code == 401:
|
|
39
|
+
raise ScopeApiError(
|
|
40
|
+
"Authentication failed (401). SCOPE_API_KEY must be a valid project SECRET key (sk_...)."
|
|
41
|
+
)
|
|
42
|
+
if r.status_code == 403:
|
|
43
|
+
raise ScopeApiError(
|
|
44
|
+
"Forbidden (403). The Developer API needs a SECRET key (sk_...), not a public key (pk_...)."
|
|
45
|
+
)
|
|
46
|
+
if r.status_code >= 400:
|
|
47
|
+
detail = _safe_detail(r)
|
|
48
|
+
raise ScopeApiError(f"Scope API error {r.status_code}: {detail}")
|
|
49
|
+
try:
|
|
50
|
+
return r.json()
|
|
51
|
+
except ValueError:
|
|
52
|
+
raise ScopeApiError(f"Scope API returned a non-JSON response ({r.status_code}).")
|
|
53
|
+
|
|
54
|
+
async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
55
|
+
self._require_api_key()
|
|
56
|
+
url = f"{self._config.api_base}{path}"
|
|
57
|
+
headers = {"Authorization": f"Bearer {self._config.api_key}"}
|
|
58
|
+
clean = {k: v for k, v in (params or {}).items() if v is not None}
|
|
59
|
+
try:
|
|
60
|
+
async with httpx.AsyncClient(timeout=self._config.timeout, transport=self._transport) as c:
|
|
61
|
+
r = await c.get(url, headers=headers, params=clean)
|
|
62
|
+
except httpx.TimeoutException:
|
|
63
|
+
raise ScopeApiError(
|
|
64
|
+
f"Scope API timed out after {self._config.timeout:.0f}s calling {path}. The backend "
|
|
65
|
+
"may be cold-starting (free tier) — try again."
|
|
66
|
+
)
|
|
67
|
+
except httpx.HTTPError as e:
|
|
68
|
+
raise ScopeApiError(f"Could not reach the Scope API at {self._config.api_base}: {e}")
|
|
69
|
+
return self._parse(r)
|
|
70
|
+
|
|
71
|
+
async def _post(
|
|
72
|
+
self, path: str, json_body: Dict[str, Any], *, timeout: Optional[float] = None
|
|
73
|
+
) -> Dict[str, Any]:
|
|
74
|
+
self._require_api_key()
|
|
75
|
+
url = f"{self._config.api_base}{path}"
|
|
76
|
+
headers = {"Authorization": f"Bearer {self._config.api_key}"}
|
|
77
|
+
eff_timeout = timeout if timeout is not None else self._config.timeout
|
|
78
|
+
try:
|
|
79
|
+
async with httpx.AsyncClient(timeout=eff_timeout, transport=self._transport) as c:
|
|
80
|
+
r = await c.post(url, headers=headers, json=json_body)
|
|
81
|
+
except httpx.TimeoutException:
|
|
82
|
+
raise ScopeApiError(
|
|
83
|
+
f"Scope API timed out after {eff_timeout:.0f}s calling {path}. The analyst can take a "
|
|
84
|
+
"while on a complex question (and the free-tier backend may be cold-starting) — try "
|
|
85
|
+
"again, or raise SCOPE_API_TIMEOUT."
|
|
86
|
+
)
|
|
87
|
+
except httpx.HTTPError as e:
|
|
88
|
+
raise ScopeApiError(f"Could not reach the Scope API at {self._config.api_base}: {e}")
|
|
89
|
+
return self._parse(r)
|
|
90
|
+
|
|
91
|
+
async def coverage_report(self, *, window_hours: Optional[int] = None) -> Dict[str, Any]:
|
|
92
|
+
return await self._get("/api/v1/installation/coverage", {"window_hours": window_hours})
|
|
93
|
+
|
|
94
|
+
async def verify_installation(self, *, window_minutes: Optional[int] = None) -> Dict[str, Any]:
|
|
95
|
+
return await self._get("/api/v1/installation/verify", {"window_minutes": window_minutes})
|
|
96
|
+
|
|
97
|
+
async def query_events(
|
|
98
|
+
self,
|
|
99
|
+
*,
|
|
100
|
+
limit: Optional[int] = None,
|
|
101
|
+
event_type: Optional[str] = None,
|
|
102
|
+
user_id: Optional[str] = None,
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
return await self._get(
|
|
105
|
+
"/api/v1/analytics/events",
|
|
106
|
+
{"limit": limit, "event_type": event_type, "user_id": user_id},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def ask(self, query: str) -> Dict[str, Any]:
|
|
110
|
+
"""Ask the Scope analyst a natural-language question (POST /analytics/insights).
|
|
111
|
+
|
|
112
|
+
Runs the full agent server-side (a real LLM loop), so it's far slower than the census
|
|
113
|
+
endpoints — we give it a longer timeout floor (still overridable upward via SCOPE_API_TIMEOUT).
|
|
114
|
+
The endpoint runs the agent with user_present=False, so it is READ-ONLY (no durable writes)."""
|
|
115
|
+
timeout = max(self._config.timeout, ASK_TIMEOUT_SECONDS)
|
|
116
|
+
return await self._post("/api/v1/analytics/insights", {"query": query}, timeout=timeout)
|
|
117
|
+
|
|
118
|
+
async def get_session(
|
|
119
|
+
self,
|
|
120
|
+
*,
|
|
121
|
+
session_id: Optional[str] = None,
|
|
122
|
+
user_id: Optional[str] = None,
|
|
123
|
+
limit: Optional[int] = None,
|
|
124
|
+
) -> Dict[str, Any]:
|
|
125
|
+
"""Fetch one session's (or user's) events stitched across FE/BE/LLM, time-ascending."""
|
|
126
|
+
return await self._get(
|
|
127
|
+
"/api/v1/analytics/sessions",
|
|
128
|
+
{"session_id": session_id, "user_id": user_id, "limit": limit},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
async def list_metrics(self) -> Dict[str, Any]:
|
|
132
|
+
"""List the project's Metric Registry entries (names + one-line definitions)."""
|
|
133
|
+
return await self._get("/api/v1/analytics/metrics")
|
|
134
|
+
|
|
135
|
+
async def get_metric(self, name: str) -> Dict[str, Any]:
|
|
136
|
+
"""Fetch one metric's full recipe + provenance by exact name."""
|
|
137
|
+
return await self._get("/api/v1/analytics/metrics/detail", {"name": name})
|
|
138
|
+
|
|
139
|
+
async def get_stats(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
start_date: Optional[str] = None,
|
|
143
|
+
end_date: Optional[str] = None,
|
|
144
|
+
event_type: Optional[str] = None,
|
|
145
|
+
) -> Dict[str, Any]:
|
|
146
|
+
"""Fetch headline analytics counts + a daily trend (GET /analytics/stats). Dates are ISO
|
|
147
|
+
strings (e.g. "2026-05-01" or a full timestamp); the backend parses them."""
|
|
148
|
+
return await self._get(
|
|
149
|
+
"/api/v1/analytics/stats",
|
|
150
|
+
{"start_date": start_date, "end_date": end_date, "event_type": event_type},
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def get_project(self) -> Dict[str, Any]:
|
|
154
|
+
"""Fetch the authenticated project (incl. its public key; the secret is never returned).
|
|
155
|
+
Used by scope_install_frontend to embed the project's pk_ in the install snippet."""
|
|
156
|
+
return await self._get("/api/v1/projects/current")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _safe_detail(r: httpx.Response) -> str:
|
|
160
|
+
try:
|
|
161
|
+
body = r.json()
|
|
162
|
+
if isinstance(body, dict):
|
|
163
|
+
return str(body.get("error") or body.get("detail") or body)[:300]
|
|
164
|
+
except ValueError:
|
|
165
|
+
pass
|
|
166
|
+
return r.text[:300]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Configuration for the Scope MCP server (PRODUCT.md §16 auth model: API key in MCP config)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
# v1 default points at the verified staging backend (where the Coverage Report endpoints are live).
|
|
9
|
+
# Production users override via SCOPE_API_BASE. (Pre-launch: there is no canonical prod host yet.)
|
|
10
|
+
DEFAULT_API_BASE = "https://scopeai-backend-staging.onrender.com"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ScopeConfig:
|
|
15
|
+
"""Resolved from the MCP client's env block (the `env` map in the MCP config)."""
|
|
16
|
+
api_key: str
|
|
17
|
+
api_base: str
|
|
18
|
+
timeout: float
|
|
19
|
+
# Optional Scope DASHBOARD (frontend) base URL, e.g. https://app.scope.example. When set, the
|
|
20
|
+
# coverage tool can hand the user a clickable deep-link to approve pending domains (PRODUCT.md
|
|
21
|
+
# §16 — the MCP proposes; the user confirms in Scope's own UI). Optional because there is no
|
|
22
|
+
# canonical pre-launch dashboard host; when unset we fall back to an in-words instruction.
|
|
23
|
+
dashboard_url: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_env(cls) -> "ScopeConfig":
|
|
27
|
+
return cls(
|
|
28
|
+
api_key=os.getenv("SCOPE_API_KEY", "").strip(),
|
|
29
|
+
api_base=os.getenv("SCOPE_API_BASE", DEFAULT_API_BASE).strip().rstrip("/"),
|
|
30
|
+
timeout=_float_env("SCOPE_API_TIMEOUT", 90.0),
|
|
31
|
+
dashboard_url=(os.getenv("SCOPE_DASHBOARD_URL", "").strip().rstrip("/") or None),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _float_env(name: str, default: float) -> float:
|
|
36
|
+
"""Parse a float env var, falling back to default on a missing/garbage value (never crash at
|
|
37
|
+
import — a bad SCOPE_API_TIMEOUT shouldn't take the whole MCP server down)."""
|
|
38
|
+
try:
|
|
39
|
+
return float(os.getenv(name, default))
|
|
40
|
+
except (TypeError, ValueError):
|
|
41
|
+
return default
|