MEMORY.md•8.43 kB
# Project Memory: Keyboard Shortcuts MCP Server
## Project Overview
An MCP (Model Context Protocol) server that provides keyboard shortcuts for operating systems, desktop environments, and applications. Built to support Claude's computer use feature by providing context-aware keyboard shortcut information.
## Core Design Philosophy
**KISS (Keep It Simple, Stupid)** - This is intentionally a relay server, not a complex search engine. It filters data by OS/desktop/app and sends everything to Claude Opus for intelligent natural language matching.
## Key Architectural Decisions
### 1. LLM-First Approach
**Decision**: Use Claude Opus (claude-opus-4-20250514) for ALL query resolution, not procedural searching.
**Rationale**:
- User explicitly stated: "I don't even know how to write this procedurally - and don't want to discuss options atm"
- Simpler implementation - just filter and relay to Opus
- Better natural language understanding
- Future-proof as Opus improves
**Implementation**: Two-stage pipeline:
1. **Filter phase** (`data-loader.ts`): Fast in-memory filtering by os/desktop/application
2. **LLM resolution** (`opus-client.ts`): Opus intelligently matches shortcuts from filtered data
### 2. Optional Desktop Parameter
**Decision**: `desktop` parameter is optional in `get_shortcuts` tool
**Rationale**: CLI tools (tmux, vim, ssh) have the same shortcuts regardless of desktop environment
**Data Model**:
- Desktop shortcuts: `{os: "ubuntu", desktop: "gnome", application: null}`
- CLI tools: `{os: "ubuntu", desktop: null, application: "tmux"}`
- Desktop apps: `{os: "ubuntu", desktop: null, application: "firefox"}` (may vary by desktop in future)
### 3. In-Memory Data Storage
**Decision**: Load all JSON files at startup, no database
**Rationale**:
- Only ~30 files currently
- Fast filtering performance
- Simple architecture
- Designed for future SQLite migration (JSON → SQLite is straightforward)
### 4. Data Organization
**Structure**:
```
data/
└── {os}/
├── desktops/{desktop}/ # Desktop environment shortcuts
├── apps/{category}/ # Application shortcuts
└── tools/ # CLI tools
```
**Why this structure?**
- Clear separation of concerns
- Desktop-independent tools in separate location
- Easy to add new OS/desktop/apps
- Mirrors real-world categorization
### 5. Test Architecture
**Decision**: Use `pool: 'forks'` in vitest config
**Problem**: Anthropic SDK keeps HTTP connections alive (connection pooling), preventing tests from completing
**Solution**: Run test files in separate processes - connections clean up when process exits
**Key Learning**: HTTP client libraries with connection pooling can cause vitest to hang waiting for event loop to drain
## Technology Stack
### Core Dependencies
- **Node.js**: 20.x LTS
- **TypeScript**: 5.9.3
- **Package Manager**: pnpm 10.14.0
- **MCP SDK**: @modelcontextprotocol/sdk ^1.19.1
- **Anthropic SDK**: @anthropic-ai/sdk ^0.65.0
- **Validation**: zod ^4.1.11
### Dev Dependencies
- **Testing**: vitest ^3.2.4
- **Runtime**: tsx ^4.20.6 (for dev mode)
## Data Format
### JSON Schema
```json
{
"os": "ubuntu",
"desktop": "gnome" | null,
"application": "firefox" | null,
"file": "firefox",
"categories": [
{
"name": "Tab Management",
"shortcuts": [
{"keys": "Ctrl + T", "description": "New tab"}
]
}
]
}
```
**Field Meanings**:
- `desktop: null` - Works on any desktop OR is a CLI tool
- `application: null` - Desktop environment shortcut (not app-specific)
- `file` - Filename without extension (for reference)
## Important Implementation Details
### Environment Variables
- `ANTHROPIC_API_KEY` - Required for Opus API access
- Set in MCP client configuration or shell environment
### MCP Tool: get_shortcuts
**Parameters**:
- `os` (required): Operating system
- `query` (required): Natural language query
- `desktop` (optional): Desktop environment
- `application` (optional): Specific application
**Example Queries**:
- `{os: "ubuntu", application: "tmux", query: "split pane vertically"}`
- `{os: "ubuntu", desktop: "gnome", query: "tile window left"}`
### Data Conversion Pipeline
1. Create markdown in `docs/` with format: `- \`keys\` - description`
2. Run: `node scripts/convert-md-to-json.js`
3. Place generated JSON in appropriate `data/{os}/` subdirectory
4. Rebuild: `pnpm build`
## Testing Strategy
### Test Categories
1. **Unit tests** (mocked): Fast, isolated, no external dependencies
- `opus-client.test.ts` - 6 tests with mocked Anthropic SDK
- `data-loader.test.ts` - 7 tests with temporary test data directory
2. **Integration tests** (real): Slower, validates real behavior
- `opus-client.integration.test.ts` - 3 tests with live Opus API
- `data-loader.integration.test.ts` - 7 tests with real `data/` directory
### Test Infrastructure
- **vitest config**: `pool: 'forks'` to isolate test processes
- **Timeout**: 15 seconds (adequate for API calls)
- **Total**: 23 tests (22 reliably pass, 1 occasionally flaky due to network timing)
### Known Issues
- First Opus integration test occasionally times out (network variability)
- Not a code issue - just real API calls being slower sometimes
## User Preferences & Constraints
### Working Style
- **User is the designer, I am the coder**: User makes architectural decisions, I implement
- **Ask before major changes**: Don't make design decisions autonomously
- **KISS principle**: User explicitly values simplicity over complexity
### Key User Quotes
- "this is not you just going nuts writing code - I'm the designer - you are the coder ok?"
- "KISS and do not overcomplicate things"
- "I don't even know how to write this procedurally - and don't want to discuss options atm"
## Future Considerations
### Planned Expansion
- macOS support (data structure already supports it)
- Windows support (data structure already supports it)
- More desktop environments (KDE, XFCE, etc.)
### Migration Path to SQLite
Current JSON structure maps cleanly to relational schema:
```sql
CREATE TABLE shortcuts (
id INTEGER PRIMARY KEY,
os TEXT NOT NULL,
desktop TEXT,
application TEXT,
file TEXT NOT NULL
);
CREATE TABLE categories (
id INTEGER PRIMARY KEY,
shortcut_id INTEGER,
name TEXT NOT NULL
);
CREATE TABLE shortcut_keys (
id INTEGER PRIMARY KEY,
category_id INTEGER,
keys TEXT NOT NULL,
description TEXT NOT NULL
);
```
### Not Planned
- Procedural search implementation (intentionally avoided)
- Complex query language (Opus handles this)
- Local caching beyond in-memory (keep it simple)
## Commands Reference
```bash
# Development
pnpm dev # Watch mode with auto-reload
pnpm build # Compile TypeScript
pnpm start # Run production server
# Testing
pnpm test # Run all tests
pnpm test:ui # Run tests with UI
pnpm test:coverage # Generate coverage report
# Data Management
node scripts/convert-md-to-json.js # Convert markdown to JSON
```
## MCP Integration Example
```json
{
"mcpServers": {
"keyboard-shortcuts": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"],
"env": {
"ANTHROPIC_API_KEY": "sk-ant-..."
}
}
}
}
```
## Critical Files
- `src/index.ts` - MCP server entry point, tool registration
- `src/opus-client.ts` - Anthropic SDK wrapper, query handling
- `src/data-loader.ts` - JSON file loading and filtering
- `src/types.ts` - TypeScript type definitions
- `vitest.config.ts` - Test configuration (note: pool:'forks')
- `CLAUDE.md` - Project documentation for Claude Code
## Lessons Learned
### Vitest + HTTP Clients
**Problem**: Tests with HTTP clients (like Anthropic SDK) hang in vitest
**Root Cause**: Connection pooling keeps sockets alive, vitest waits for event loop
**Solution**: Use `pool: 'forks'` to run tests in separate processes
**Key Insight**: The fix is NOT to increase timeouts - that's treating symptoms
### Data Loader Testing
**Problem**: `loadShortcutData()` had hardcoded path, couldn't be tested in isolation
**Solution**: Added optional `customDataDir` parameter for testing
**Pattern**: Make functions testable by parameterizing dependencies
### Opus Integration
**Best Practice**: One fresh `OpusClient` instance per test to avoid connection reuse issues
**Reality**: Tests pass reliably with `pool: 'forks'`, occasional network delays are expected