Skip to main content

mcpr_integrations/store/query/
sessions.rs

1//! Query: `mcpr proxy sessions <proxy>` — list MCP sessions with client info.
2
3use rusqlite::params;
4use serde::Serialize;
5
6use super::QueryEngine;
7
8/// How long since last activity before a session is considered inactive.
9/// Used by the `--active` filter and the `is_active` field.
10const ACTIVE_SESSION_THRESHOLD_MS: i64 = 5 * 60 * 1000; // 5 minutes
11
12/// Filter parameters for the sessions query.
13pub struct SessionsParams {
14    /// Proxy name to filter by (None = all proxies).
15    pub proxy: Option<String>,
16    /// Only sessions started after this unix ms timestamp.
17    pub since_ts: i64,
18    /// Maximum number of rows to return.
19    pub limit: i64,
20    /// Only show active sessions (seen within the active threshold).
21    pub active_only: bool,
22    /// Filter by client name (e.g., "claude-desktop").
23    pub client: Option<String>,
24}
25
26/// A single session row.
27#[derive(Debug, Clone, Serialize)]
28pub struct SessionRow {
29    pub session_id: String,
30    pub client_name: Option<String>,
31    pub client_version: Option<String>,
32    pub client_platform: Option<String>,
33    pub started_at: i64,
34    pub last_seen_at: i64,
35    pub ended_at: Option<i64>,
36    pub total_calls: i64,
37    pub total_errors: i64,
38    pub is_active: bool,
39}
40
41impl QueryEngine {
42    /// List sessions for a proxy, most recently seen first.
43    pub fn sessions(&self, params: &SessionsParams) -> Result<Vec<SessionRow>, rusqlite::Error> {
44        let now_ms = chrono::Utc::now().timestamp_millis();
45        let active_threshold = now_ms - ACTIVE_SESSION_THRESHOLD_MS;
46
47        let sql = "
48            SELECT
49                session_id, client_name, client_version, client_platform,
50                started_at, last_seen_at, ended_at, total_calls, total_errors,
51                (ended_at IS NULL AND last_seen_at > ?1) AS is_active
52            FROM sessions
53            WHERE (?2 IS NULL OR proxy = ?2)
54              AND (?3 IS NULL OR client_name = ?3)
55              AND (?4 = 0 OR (ended_at IS NULL AND last_seen_at > ?1))
56              AND started_at >= ?5
57            ORDER BY last_seen_at DESC
58            LIMIT ?6
59        ";
60
61        let active_flag: i64 = if params.active_only { 1 } else { 0 };
62
63        let mut stmt = self.conn().prepare(sql)?;
64        let rows = stmt.query_map(
65            params![
66                active_threshold,
67                params.proxy,
68                params.client,
69                active_flag,
70                params.since_ts,
71                params.limit,
72            ],
73            |row| {
74                Ok(SessionRow {
75                    session_id: row.get(0)?,
76                    client_name: row.get(1)?,
77                    client_version: row.get(2)?,
78                    client_platform: row.get(3)?,
79                    started_at: row.get(4)?,
80                    last_seen_at: row.get(5)?,
81                    ended_at: row.get(6)?,
82                    total_calls: row.get(7)?,
83                    total_errors: row.get(8)?,
84                    is_active: row.get(9)?,
85                })
86            },
87        )?;
88
89        rows.collect()
90    }
91}