github-pr-context-mcp 0.2.5__py3-none-any.whl
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.
- analytics/__init__.py +3 -0
- analytics/usage_metrics.py +185 -0
- app/__init__.py +3 -0
- app/mcp_app.py +928 -0
- auth/__init__.py +3 -0
- auth/gmail_identity.py +236 -0
- entrypoints/deployed/server.py +34 -0
- entrypoints/local/server.py +273 -0
- fetcher/__init__.py +3 -0
- fetcher/client.py +131 -0
- fetcher/queries.py +67 -0
- fetcher/transform.py +55 -0
- github_pr_context_mcp-0.2.5.dist-info/METADATA +192 -0
- github_pr_context_mcp-0.2.5.dist-info/RECORD +25 -0
- github_pr_context_mcp-0.2.5.dist-info/WHEEL +5 -0
- github_pr_context_mcp-0.2.5.dist-info/entry_points.txt +2 -0
- github_pr_context_mcp-0.2.5.dist-info/licenses/LICENSE +21 -0
- github_pr_context_mcp-0.2.5.dist-info/top_level.txt +7 -0
- inference/__init__.py +3 -0
- inference/providers.py +296 -0
- inference/review.py +175 -0
- storage/__init__.py +19 -0
- storage/document_builder.py +74 -0
- storage/encoder.py +35 -0
- storage/vector_store.py +270 -0
analytics/__init__.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import sqlite3
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
def _utc_now() -> datetime:
|
|
10
|
+
return datetime.now(timezone.utc)
|
|
11
|
+
|
|
12
|
+
class UsageMetricsStore:
|
|
13
|
+
"""Persist and summarize anonymous usage metrics for hosted MCP deployments backed by thread-safe SQLite."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, file_path: str):
|
|
16
|
+
# Swap existing json suffixes to .db without breaking integrations
|
|
17
|
+
p = Path(file_path)
|
|
18
|
+
if p.suffix == '.json':
|
|
19
|
+
self._path = p.with_suffix('.db')
|
|
20
|
+
else:
|
|
21
|
+
self._path = p
|
|
22
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
self._init_db()
|
|
24
|
+
|
|
25
|
+
def _get_conn(self) -> sqlite3.Connection:
|
|
26
|
+
# isolation_level=None enables autocommit for simple operations
|
|
27
|
+
# check_same_thread=False allows sharing across async workers
|
|
28
|
+
return sqlite3.connect(str(self._path), isolation_level=None, check_same_thread=False)
|
|
29
|
+
|
|
30
|
+
def _init_db(self):
|
|
31
|
+
with self._get_conn() as conn:
|
|
32
|
+
conn.execute('''
|
|
33
|
+
CREATE TABLE IF NOT EXISTS global_stats (
|
|
34
|
+
key TEXT PRIMARY KEY,
|
|
35
|
+
value INTEGER DEFAULT 0
|
|
36
|
+
)
|
|
37
|
+
''')
|
|
38
|
+
conn.execute('''
|
|
39
|
+
CREATE TABLE IF NOT EXISTS system_config (
|
|
40
|
+
key TEXT PRIMARY KEY,
|
|
41
|
+
value TEXT
|
|
42
|
+
)
|
|
43
|
+
''')
|
|
44
|
+
conn.execute("INSERT OR IGNORE INTO system_config (key, value) VALUES ('tracked_since', ?)", (_utc_now().isoformat(),))
|
|
45
|
+
|
|
46
|
+
conn.execute('''
|
|
47
|
+
CREATE TABLE IF NOT EXISTS tools (
|
|
48
|
+
name TEXT PRIMARY KEY,
|
|
49
|
+
calls INTEGER DEFAULT 0
|
|
50
|
+
)
|
|
51
|
+
''')
|
|
52
|
+
conn.execute('''
|
|
53
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
54
|
+
user_hash TEXT PRIMARY KEY,
|
|
55
|
+
first_seen TEXT,
|
|
56
|
+
last_seen TEXT,
|
|
57
|
+
events INTEGER DEFAULT 0
|
|
58
|
+
)
|
|
59
|
+
''')
|
|
60
|
+
conn.execute('''
|
|
61
|
+
CREATE TABLE IF NOT EXISTS daily_tool_calls (
|
|
62
|
+
day TEXT PRIMARY KEY,
|
|
63
|
+
calls INTEGER DEFAULT 0
|
|
64
|
+
)
|
|
65
|
+
''')
|
|
66
|
+
conn.execute('''
|
|
67
|
+
CREATE TABLE IF NOT EXISTS daily_users (
|
|
68
|
+
day TEXT,
|
|
69
|
+
user_hash TEXT,
|
|
70
|
+
calls INTEGER DEFAULT 0,
|
|
71
|
+
PRIMARY KEY (day, user_hash)
|
|
72
|
+
)
|
|
73
|
+
''')
|
|
74
|
+
conn.execute('''
|
|
75
|
+
CREATE TABLE IF NOT EXISTS pings (
|
|
76
|
+
mode TEXT,
|
|
77
|
+
user_hash TEXT,
|
|
78
|
+
first_seen TEXT,
|
|
79
|
+
last_seen TEXT,
|
|
80
|
+
count INTEGER DEFAULT 0,
|
|
81
|
+
PRIMARY KEY (mode, user_hash)
|
|
82
|
+
)
|
|
83
|
+
''')
|
|
84
|
+
conn.execute('''
|
|
85
|
+
CREATE TABLE IF NOT EXISTS github_daily_clones (
|
|
86
|
+
timestamp TEXT PRIMARY KEY,
|
|
87
|
+
count INTEGER DEFAULT 0,
|
|
88
|
+
uniques INTEGER DEFAULT 0
|
|
89
|
+
)
|
|
90
|
+
''')
|
|
91
|
+
|
|
92
|
+
def _hash_user(self, user_id: str) -> str:
|
|
93
|
+
return hashlib.sha256(user_id.encode("utf-8")).hexdigest()[:16]
|
|
94
|
+
|
|
95
|
+
def record_event(self, user_id: str, tool_name: str) -> None:
|
|
96
|
+
user_hash = self._hash_user(user_id)
|
|
97
|
+
now_str = _utc_now().isoformat()
|
|
98
|
+
day = _utc_now().date().isoformat()
|
|
99
|
+
|
|
100
|
+
with self._get_conn() as conn:
|
|
101
|
+
conn.execute("INSERT INTO global_stats (key, value) VALUES ('total_tool_calls', 1) ON CONFLICT(key) DO UPDATE SET value = value + 1")
|
|
102
|
+
conn.execute("INSERT INTO tools (name, calls) VALUES (?, 1) ON CONFLICT(name) DO UPDATE SET calls = calls + 1", (tool_name,))
|
|
103
|
+
conn.execute("INSERT INTO users (user_hash, first_seen, last_seen, events) VALUES (?, ?, ?, 1) ON CONFLICT(user_hash) DO UPDATE SET last_seen = ?, events = events + 1", (user_hash, now_str, now_str, now_str))
|
|
104
|
+
conn.execute("INSERT INTO daily_tool_calls (day, calls) VALUES (?, 1) ON CONFLICT(day) DO UPDATE SET calls = calls + 1", (day,))
|
|
105
|
+
conn.execute("INSERT INTO daily_users (day, user_hash, calls) VALUES (?, ?, 1) ON CONFLICT(day, user_hash) DO UPDATE SET calls = calls + 1", (day, user_hash))
|
|
106
|
+
|
|
107
|
+
def record_ping(self, anonymous_id: str, mode: str) -> None:
|
|
108
|
+
ALLOWED_MODES = {"render", "uvx", "pipx", "local", "unknown"}
|
|
109
|
+
safe_mode = mode if mode in ALLOWED_MODES else "unknown"
|
|
110
|
+
user_hash = self._hash_user(anonymous_id)
|
|
111
|
+
now_str = _utc_now().isoformat()
|
|
112
|
+
|
|
113
|
+
with self._get_conn() as conn:
|
|
114
|
+
conn.execute("INSERT INTO pings (mode, user_hash, first_seen, last_seen, count) VALUES (?, ?, ?, ?, 1) ON CONFLICT(mode, user_hash) DO UPDATE SET last_seen = ?, count = count + 1", (safe_mode, user_hash, now_str, now_str, now_str))
|
|
115
|
+
|
|
116
|
+
def update_github_clones(self, clones_data: list[dict]) -> None:
|
|
117
|
+
"""Update the persistent record of daily github clones fetched from the traffic API."""
|
|
118
|
+
with self._get_conn() as conn:
|
|
119
|
+
for day in clones_data:
|
|
120
|
+
ts = day.get("timestamp")
|
|
121
|
+
count = day.get("count", 0)
|
|
122
|
+
uniques = day.get("uniques", 0)
|
|
123
|
+
if ts:
|
|
124
|
+
conn.execute(
|
|
125
|
+
"INSERT INTO github_daily_clones (timestamp, count, uniques) VALUES (?, ?, ?) "
|
|
126
|
+
"ON CONFLICT(timestamp) DO UPDATE SET count = MAX(count, ?), uniques = MAX(uniques, ?)",
|
|
127
|
+
(ts, count, uniques, count, uniques)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def update_github_downloads(self, count: int) -> None:
|
|
131
|
+
"""Update the persistent record of GitHub release downloads."""
|
|
132
|
+
with self._get_conn() as conn:
|
|
133
|
+
conn.execute("INSERT INTO global_stats (key, value) VALUES ('github_downloads', ?) ON CONFLICT(key) DO UPDATE SET value = ?", (count, count))
|
|
134
|
+
|
|
135
|
+
def summary(self, last_days: int = 30) -> dict[str, Any]:
|
|
136
|
+
last_days = max(1, min(last_days, 365))
|
|
137
|
+
|
|
138
|
+
with self._get_conn() as conn:
|
|
139
|
+
tracked_since = conn.execute("SELECT value FROM system_config WHERE key = 'tracked_since'").fetchone()
|
|
140
|
+
tracked_since_val = tracked_since[0] if tracked_since else None
|
|
141
|
+
|
|
142
|
+
total_calls = conn.execute("SELECT value FROM global_stats WHERE key = 'total_tool_calls'").fetchone()
|
|
143
|
+
total_calls_val = total_calls[0] if total_calls else 0
|
|
144
|
+
|
|
145
|
+
tools_cursor = conn.execute("SELECT name, calls FROM tools ORDER BY calls DESC")
|
|
146
|
+
top_tools = [{"tool": row[0], "calls": row[1]} for row in tools_cursor]
|
|
147
|
+
|
|
148
|
+
daily_series = []
|
|
149
|
+
days_cursor = conn.execute("SELECT day, calls FROM daily_tool_calls ORDER BY day DESC LIMIT ?", (last_days,))
|
|
150
|
+
days_data = days_cursor.fetchall()
|
|
151
|
+
|
|
152
|
+
for day, calls in reversed(days_data):
|
|
153
|
+
users_count = conn.execute("SELECT COUNT(DISTINCT user_hash) FROM daily_users WHERE day = ?", (day,)).fetchone()[0]
|
|
154
|
+
daily_series.append({
|
|
155
|
+
"date": day,
|
|
156
|
+
"tool_calls": calls,
|
|
157
|
+
"unique_users": users_count
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
users_by_mode: dict[str, int] = {}
|
|
161
|
+
modes_cursor = conn.execute("SELECT mode, COUNT(DISTINCT user_hash) FROM pings GROUP BY mode")
|
|
162
|
+
for mode, count in modes_cursor:
|
|
163
|
+
users_by_mode[mode] = count
|
|
164
|
+
|
|
165
|
+
total_auth_users = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
|
166
|
+
if "render" not in users_by_mode or users_by_mode["render"] < total_auth_users:
|
|
167
|
+
users_by_mode["render"] = total_auth_users
|
|
168
|
+
|
|
169
|
+
github_clones = conn.execute("SELECT SUM(uniques) FROM github_daily_clones").fetchone()
|
|
170
|
+
github_clones_val = github_clones[0] if github_clones and github_clones[0] else 0
|
|
171
|
+
|
|
172
|
+
github_downloads = conn.execute("SELECT value FROM global_stats WHERE key = 'github_downloads'").fetchone()
|
|
173
|
+
github_downloads_val = github_downloads[0] if github_downloads else 0
|
|
174
|
+
|
|
175
|
+
total_ping_users = conn.execute("SELECT COUNT(DISTINCT user_hash) FROM pings").fetchone()[0]
|
|
176
|
+
total_unique = total_ping_users + total_auth_users + github_clones_val + github_downloads_val
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"tracked_since": tracked_since_val,
|
|
180
|
+
"total_tool_calls": total_calls_val,
|
|
181
|
+
"total_unique_users": total_unique,
|
|
182
|
+
"users_by_mode": users_by_mode,
|
|
183
|
+
"top_tools": top_tools,
|
|
184
|
+
"daily": daily_series,
|
|
185
|
+
}
|
app/__init__.py
ADDED