This document provides context for AI agents (LLMs, coding assistants) working on this project. It contains architecture decisions, conventions, and guidance for future improvements.
Repository: https://github.com/8bitgentleman/activitywatch-mcp-server
Purpose: Model Context Protocol (MCP) server that connects LLMs to ActivityWatch time tracking API
Language: Python 3.10+
Version: 2.0.0 (migrated from TypeScript)
Package Name: mcp-server-activitywatch
Entry Point: mcp-server-activitywatch command
┌─────────────────┐
│ MCP Client │ (Claude Desktop, OpenCode, Crush)
│ (LLM) │
└────────┬────────┘
│ MCP Protocol (stdio)
│
┌────────▼────────────────────────────────────────────┐
│ MCP Server (Python) │
│ ┌──────────────────────────────────────────────┐ │
│ │ server.py │ │
│ │ - Stdio transport │ │
│ │ - Tool registration │ │
│ │ - Request routing │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────────────┐ │
│ │ tools/ │ │
│ │ - list_buckets.py │ │
│ │ - run_query.py │ │
│ │ - get_events.py │ │
│ │ - get_settings.py │ │
│ │ - query_examples.py │ │
│ └──────────────────┬───────────────────────────┘ │
└───────────────────┬─┴────────────────────────────────┘
│ HTTP (httpx)
┌──────────▼──────────┐
│ ActivityWatch API │
│ localhost:5600 │
└─────────────────────┘
- Argument parsing for
--api-baseflag - Environment variable support (
AW_API_BASE) - Default:
http://localhost:5600/api/0 - Calls
serve()from server.py
- Initializes MCP server with stdio transport
- Registers 5 tools with decorators (
@server.list_tools(),@server.call_tool()) - Routes tool calls to appropriate handlers
- Prints startup banner to stderr
Each tool follows this pattern:
- Schema function: Returns JSON schema for parameters
- Handler function: Async function that takes
api_baseandarguments - Returns:
list[TextContent]with results - Error handling: Catches HTTP and network errors, returns helpful messages
- Test mode detection: Checks
PYTEST_CURRENT_TESTenv var to hide guidance text
1. LLM calls tool → 2. MCP Protocol → 3. server.py routes to handler →
4. Handler makes HTTP request → 5. ActivityWatch API responds →
6. Handler formats response → 7. Returns TextContent → 8. LLM receives result
- Tool names: Use hyphens (e.g.,
activitywatch-list-buckets) - Python modules: Use underscores (e.g.,
list_buckets.py) - Functions: Snake case (e.g.,
get_events_handler) - Classes: Pascal case (e.g.,
GetEventsArgs)
- All functions use type hints
- Use modern Python union syntax:
str | None(notOptional[str]) - Use
dict[str, Any]for flexible dictionaries - Use Pydantic
BaseModelfor parameter validation
All tools follow this pattern:
try:
# Parse arguments
args = ToolArgs(**arguments)
# Make API request
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0)
response.raise_for_status()
data = response.json()
# Format and return
return [TextContent(type="text", text=formatted_data)]
except httpx.HTTPStatusError as error:
# Handle HTTP errors (4xx, 5xx)
# Include status code and response details
except httpx.RequestError as error:
# Handle network errors (connection refused, timeout)
# Provide troubleshooting guidance
except Exception as error:
# Catch-all for unexpected errors- Use pytest with pytest-asyncio
- Mock HTTP requests with pytest-httpx
- Test success cases AND error cases
- Use
conftest.pyto setPYTEST_CURRENT_TESTenv var (suppresses guidance text)
- Always use
urllib.parse.quote(value, safe="")for path parameters - Use
urllib.parse.urlencode()for query parameters - This prevents issues with special characters (slashes, spaces, etc.)
File: tools/list_buckets.py
Purpose: List all ActivityWatch buckets
Parameters:
type(optional): Filter by bucket type (case-insensitive)include_data(optional): Include bucket data in response
API Call: GET {api_base}/buckets
Returns: Formatted list of buckets with IDs, types, clients, hostnames
File: tools/run_query.py
Purpose: Execute AQL (ActivityWatch Query Language) queries
Parameters:
timeperiods: Array of time period strings (e.g.,["2024-10-28/2024-10-29"])query: Array with ONE string containing ALL query statements separated by semicolonsname(optional): Query name for caching
API Call: POST {api_base}/query/
Special handling:
- Converts single date strings to proper ISO format with time
- Formats timeperiods as
["2024-10-28T00:00:00+00:00/2024-10-29T00:00:00+00:00"]
Common pitfall: Users often split query statements into separate array elements. The tool expects ONE string with semicolons.
File: tools/get_events.py
Purpose: Get raw events from a specific bucket
Parameters:
bucket_id: Bucket identifier (required)start(optional): Start date/time (ISO format)end(optional): End date/time (ISO format)limit(optional): Max number of events
API Call: GET {api_base}/buckets/{bucket_id}/events?{params}
Special handling:
- 404 errors provide helpful message about using
list-bucketstool - Uses
arguments.get("bucket_id")in error handler to avoid unbound variable
File: tools/get_settings.py
Purpose: Retrieve ActivityWatch settings
Parameters:
key(optional): Specific settings key to retrieve
API Call:
- All settings:
GET {api_base}/settings - Specific key:
GET {api_base}/settings/{encoded_key}
Important: Uses urllib.parse.quote(key, safe="") to properly encode keys with special characters
File: tools/query_examples.py
Purpose: Provide query syntax reference
Parameters: None
Returns: Comprehensive examples of AQL queries with explanations
Three configuration methods (in order of precedence):
- CLI flag:
--api-base http://custom:9999/api/1 - Environment variable:
AW_API_BASE=http://custom:9999/api/1 - Default:
http://localhost:5600/api/0
All three methods are validated and working.
python -m venv .venv
source .venv/bin/activate
uv pip install -e ".[dev]"pytest tests/ -v # Run all tests
pytest tests/test_list_buckets.py # Run specific test file
pytest --cov=src/ # Run with coveragepyright src/Known issues:
reportUnusedFunctionfor decorated functions (false positive)- Some type inference issues in dynamic data (acceptable)
ruff check src/
ruff check --fix src/ # Auto-fix issuesKnown issues:
- E501 line length warnings in documentation strings (acceptable for readability)
# Test server startup
mcp-server-activitywatch
# Test with custom endpoint
mcp-server-activitywatch --api-base http://localhost:5600/api/0
# Test with MCP Inspector
npx @modelcontextprotocol/inspector uvx mcp-server-activitywatch-
Test with Real ActivityWatch Instance
- Currently all tests use mocked HTTP requests
- Need integration tests against actual ActivityWatch server
- Consider adding
tests/integration/directory - Use
pytest.mark.integrationto separate from unit tests
-
Add Get Events Tests
- File
tests/test_get_events.pydoesn't exist yet - Need tests for: success case, filtering, limit, errors
- Follow pattern from
test_list_buckets.py
- File
-
Publish to PyPI
- Package is ready but not published
- Would enable
uvx mcp-server-activitywatchwithout local installation - Need PyPI account and credentials
- Update README with PyPI installation instructions
-
Add Query Validation
- AQL queries can be syntactically invalid
- Consider adding basic validation before sending to API
- Could improve error messages for common mistakes
-
Add Caching Layer
- Repeated queries with same parameters could be cached
- Would reduce load on ActivityWatch API
- Consider TTL-based cache invalidation
-
Add Bucket Autocomplete
- When
get-eventsgets invalid bucket_id, suggest similar buckets - Could use fuzzy matching on available buckets
- When
-
Improve Time Period Parsing
- Current implementation in
run_query.pyis basic - Could support relative dates ("today", "yesterday", "last week")
- Could validate date ranges
- Current implementation in
-
Add More Query Examples
query_examples.pyhas basic examples- Could add more advanced patterns (nested queries, filters, etc.)
- Could categorize examples by use case
-
Add Logging
- Currently no logging infrastructure
- Could help with debugging production issues
- Consider structured logging (JSON format)
-
Add Rate Limiting
- Protect ActivityWatch API from too many requests
- Could implement simple token bucket algorithm
-
Add Response Streaming
- For large query results, could stream response
- MCP supports streaming responses
- Would improve UX for large datasets
-
Add Health Check Tool
- New tool:
activitywatch-health-check - Verify ActivityWatch server is accessible
- Return version info, status, available watchers
- New tool:
- ✅ URI encoding for settings keys with special characters (fixed in
get_settings.py) - ✅ All 15 tests passing
- ✅ Possible unbound variable in
get_events.pyerror handler (fixed)
⚠️ Line length violations in documentation strings (E501 - acceptable)⚠️ Pyright reports unused functions that are used via decorators (false positive)⚠️ No integration tests against real ActivityWatch instance
- Query format confusion (documented extensively in README and examples tool)
Date: 2024-02-10
Reason: Better Python ecosystem integration, simpler deployment via UVX
- Complete rewrite in Python
- httpx instead of axios
- Pydantic instead of Zod
- pytest instead of Jest
- Async/await patterns similar to TypeScript
- Tool naming: underscores → hyphens
- All 5 tools with identical functionality
- API endpoints and parameters
- Error handling patterns
- Response formatting
- Tool names now use hyphens (e.g.,
activitywatch-list-bucketsinstead ofactivitywatch_list_buckets) - Installation method changed (npm → uvx/pip)
- Configuration file format unchanged (same JSON for MCP clients)
- Create
tools/new_tool.py - Implement schema function and async handler
- Register in
server.py(two places: list_tools and call_tool) - Create
tests/test_new_tool.pywith success and error cases - Update README with tool documentation
- Update this AGENTS.md with implementation details
- Write a failing test first
- Fix the implementation
- Verify all tests pass
- Check with pyright and ruff
- Update documentation if behavior changes
- Ensure all tests pass before starting
- Make small, incremental changes
- Run tests after each change
- Keep test coverage at 100%
- Update this file if architecture changes
- Forgetting to URL-encode path parameters → Use
quote(value, safe="") - Not handling network errors → Always catch
httpx.RequestError - Including line numbers in oldString when editing → Strip them first
- Forgetting to update both list_tools AND call_tool in server.py
- Not checking PYTEST_CURRENT_TEST before adding guidance text
- MCP SDK: https://github.com/modelcontextprotocol/python-sdk
- ActivityWatch API: https://docs.activitywatch.net/en/latest/api.html
- httpx: https://www.python-httpx.org/
- Pydantic: https://docs.pydantic.dev/
- ActivityWatch: https://github.com/ActivityWatch/activitywatch
- MCP Servers: https://github.com/modelcontextprotocol/servers
- Other MCP implementations: https://glama.ai/mcp/servers
Repository Owner: 8bitgentleman
Repository: https://github.com/8bitgentleman/activitywatch-mcp-server
Issues: https://github.com/8bitgentleman/activitywatch-mcp-server/issues
Last Updated: 2024-02-10
Version: 2.0.0
For questions or updates, create an issue in the repository.