Skip to main content
Glama

get_touch_stats

Retrieve aggregate keyboard and mouse activity statistics for the last 1 to 24 hours, including keystroke counts, mouse clicks, scrolls, idle gaps, and active typing minutes. Use for activity reports, idle detection, or fatigue tracking.

Instructions

Return aggregate keyboard and mouse activity stats over the last N hours.

Returns counts: keystrokes (count only — no contents), mouse clicks, scrolls, idle gaps, active-typing minutes.

USE WHEN: producing activity reports, idle detection, or fatigue tracking.

BEHAVIOR: pure read. Privacy guarantee as in get_recent_touch_events.

PARAMETERS: hours: lookback window. Range 1-24. Default 1.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hoursNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The actual implementation of get_touch_stats — queries the SQLite activity DB for keystroke count, average WPM, backspaces, mouse clicks, scrolls, and corrections over a configurable hours window.
    @mcp_app.tool()
    def get_touch_stats(hours: float = 8.0) -> str:
        """Get typing and mouse usage statistics over the last N hours.
    
        Returns keystroke count, average WPM, click/scroll counts, and correction count.
    
        Args:
            hours: How many hours back to analyze (default 8).
        """
        hours = max(0.1, min(hours, 168.0))  # cap at 1 week
    
        conn = _get_db()
        if not conn:
            return "No activity database found."
    
        try:
            cutoff = time.time() - (hours * 3600)
    
            # Keyboard stats
            key_rows = conn.execute(
                "SELECT payload FROM events WHERE modality = 'keys' "
                "AND event_type = 'typing_burst' AND timestamp > ?",
                (cutoff,),
            ).fetchall()
    
            total_chars = 0
            total_bursts = len(key_rows)
            wpms = []
            total_backspaces = 0
            for row in key_rows:
                p = json.loads(row["payload"])
                total_chars += p.get("char_count", 0)
                total_backspaces += p.get("backspace_count", 0)
                wpm = p.get("wpm", 0)
                if wpm > 0:
                    wpms.append(wpm)
    
            avg_wpm = sum(wpms) / len(wpms) if wpms else 0
    
            # Mouse stats
            clicks = conn.execute(
                "SELECT COUNT(*) as c FROM events WHERE modality = 'flow' "
                "AND event_type = 'click' AND timestamp > ?",
                (cutoff,),
            ).fetchone()["c"]
    
            scrolls = conn.execute(
                "SELECT COUNT(*) as c FROM events WHERE modality = 'flow' "
                "AND event_type = 'scroll' AND timestamp > ?",
                (cutoff,),
            ).fetchone()["c"]
    
            # Corrections
            corrections = conn.execute(
                "SELECT COUNT(*) as c FROM events WHERE modality = 'keys' "
                "AND event_type = 'correction_detected' AND timestamp > ?",
                (cutoff,),
            ).fetchone()["c"]
    
            conn.close()
    
            lines = [
                f"=== Touch Stats (last {hours:.0f} hours) ===\n",
                f"Typing bursts: {total_bursts}",
                f"Total characters: {total_chars}",
                f"Average WPM: {avg_wpm:.0f}",
                f"Total backspaces: {total_backspaces}",
                f"Mouse clicks: {clicks}",
                f"Mouse scrolls: {scrolls}",
                f"Corrections detected: {corrections}",
            ]
            return "\n".join(lines)
        except Exception as e:
            return f"Error calculating stats: {e}"
  • Function signature and docstring for get_touch_stats; takes hours (float, default 8.0) and returns a string with typing speed, click counts, correction rate.
    @mcp_app.tool()
    def get_touch_stats(hours: float = 8.0) -> str:
        """Get typing and mouse usage statistics over the last N hours.
    
        Returns keystroke count, average WPM, click/scroll counts, and correction count.
    
        Args:
            hours: How many hours back to analyze (default 8).
        """
  • Glama registry stub registration of get_touch_stats via @mcp_app.tool() decorator (returns a stub message telling users to connect to the local daemon).
    @mcp_app.tool()
    def get_touch_stats(hours: int = 1) -> str:
        """Return aggregate keyboard and mouse activity stats over the last N hours.
    
        Returns counts: keystrokes (count only — no contents), mouse clicks,
        scrolls, idle gaps, active-typing minutes.
    
        USE WHEN: producing activity reports, idle detection, or fatigue tracking.
    
        BEHAVIOR: pure read. Privacy guarantee as in get_recent_touch_events.
    
        PARAMETERS:
          hours: lookback window. Range 1-24. Default 1.
        """
        return _LOCAL_ONLY_MSG
  • The mcp_app instance and _get_db() helper used by get_touch_stats to connect to the SQLite activity database.
    mcp_app = FastMCP("ContextPulse Touch")
    
    _DB_PATH = ACTIVITY_DB_PATH
    
    
    def _get_db() -> sqlite3.Connection | None:
        if not _DB_PATH.exists():
            return None
        conn = sqlite3.connect(str(_DB_PATH), timeout=5)
        conn.row_factory = sqlite3.Row
        return conn
  • Health-check invocation of get_touch_stats with a 0.01-hour lookback to verify the tool works.
    ("touch", "get_recent_touch_events", touch.get_recent_touch_events, {"seconds": 1}),
    ("touch", "get_touch_stats",         touch.get_touch_stats,         {"hours": 0.01}),
    ("touch", "get_correction_history",  touch.get_correction_history,  {"limit": 1}),
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description states it is a 'pure read' and provides a privacy guarantee, which is sufficient for a non-destructive tool. It does not cover authentication or rate limits, but that is acceptable given the tool's simplicity.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and well-structured with clear sections (main description, returns, USE WHEN, BEHAVIOR, PARAMETERS). Every sentence adds value without repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's low complexity, the description covers the return values, parameter details, and behavioral aspects. The presence of an output schema further reduces the need to detail return format.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The only parameter 'hours' is explained with its meaning ('lookback window'), range (1-24), and default (1), adding significant value beyond the bare schema (which had no description).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns aggregate keyboard and mouse activity stats over a period, listing specific metrics. While it references a sibling tool for privacy, it does not explicitly distinguish when to use this tool versus get_recent_touch_events.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'USE WHEN' section explicitly lists use cases (activity reports, idle detection, fatigue tracking). However, it does not mention when not to use the tool or suggest alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ContextPulse/contextpulse'

If you have feedback or need assistance with the MCP directory API, please join our Discord server