Skip to content

Commit 24f8c10

Browse files
jamesbrinkclaude
andcommitted
feat(docker): add pre-cache feature for faster startup
- Add --pre-cache command-line option to populate cache and exit - Update Dockerfile to automatically pre-cache during build - Document pre-cache feature in CLAUDE.md - Synced rule files with updated documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6c86806 commit 24f8c10

File tree

7 files changed

+178
-4
lines changed

7 files changed

+178
-4
lines changed

.cursorrules

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
2424
- **Tools**: Search, info, and statistics tools
2525
- **Utils**: Cross-platform helpers and cache management
2626
- **Server**: FastMCP server implementation
27+
- **Pre-Cache**: Command-line option to populate cache data during setup/build
2728

2829
### Implementation Guidelines
2930

@@ -50,6 +51,12 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
5051
- Strict null safety with defensive programming
5152
- Detailed error logging and user-friendly messages
5253
- Support wildcard searches and handle empty results
54+
- Cross-platform compatibility:
55+
- Use pathlib.Path for platform-agnostic path handling
56+
- Check sys.platform before using platform-specific features
57+
- Handle file operations with appropriate platform-specific adjustments
58+
- Use os.path.join() instead of string concatenation for paths
59+
- Ensure tests work consistently across Windows, macOS, and Linux
5360

5461
## API Reference
5562

@@ -96,6 +103,11 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
96103
- Integration tests: `@pytest.mark.integration`
97104
- Slow tests: `@pytest.mark.slow`
98105
- Async tests: `@pytest.mark.asyncio`
106+
- Cross-platform testing:
107+
- CI runs tests on Linux, macOS, and Windows
108+
- Linux and macOS tests use flake.nix for development environment
109+
- Windows tests use Python's venv with special dependencies (pywin32)
110+
- All tests must be platform-agnostic or include platform-specific handling
99111
- Run specific test categories:
100112
- Unit tests only: `nix run .#run-tests -- --unit`
101113
- Integration tests only: `nix run .#run-tests -- --integration`
@@ -110,6 +122,17 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
110122
- All tests must check for both default OS cache paths and test-specific paths
111123
- The gitignore file excludes `mcp_nixos_test_cache/` and `*test_cache*/` patterns
112124

125+
### Logging Tests
126+
- Prefer direct behavior verification over implementation details
127+
- When testing log level filtering:
128+
- Use `isEnabledFor()` assertions for level checks (platform-independent)
129+
- Use mock handlers with explicit level settings
130+
- Test both logger configuration and actual handler behavior
131+
- Avoid patching internal logging methods (`_log`) which vary by platform
132+
- Add clear error messages to assertions
133+
- Prevent test flakiness by avoiding sleep/timing dependencies
134+
- Use clean logger fixtures to prevent test interaction
135+
113136
### Dependency Management
114137
- Project uses `pyproject.toml` for dependency specification (PEP 621)
115138
- Core dependencies:
@@ -125,9 +148,15 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
125148
- Install: `pip install mcp-nixos`, `uv pip install mcp-nixos`, `uvx mcp-nixos`
126149
- Claude Code configuration: Add to `~/.config/claude/config.json`
127150
- Interactive shell: `python mcp_shell.py` (manual testing and tool exploration)
151+
- Docker deployment:
152+
- Standard use: `docker run --rm ghcr.io/utensils/mcp-nixos`
153+
- Docker image includes pre-cached data for immediate startup
154+
- Build with pre-cache: `docker build -t mcp-nixos .`
155+
- Deployed on Smithery.ai as a hosted service
128156
- Development:
129157
- Environment: `nix develop`
130158
- Run server: `run`
159+
- Pre-cache data: `python -m mcp_nixos --pre-cache`
131160
- Tests: `run-tests`, `run-tests --unit`, `run-tests --integration`
132161
- Code quality: `lint`, `typecheck`, `format`
133162
- Stats: `loc`

.goosehints

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
2424
- **Tools**: Search, info, and statistics tools
2525
- **Utils**: Cross-platform helpers and cache management
2626
- **Server**: FastMCP server implementation
27+
- **Pre-Cache**: Command-line option to populate cache data during setup/build
2728

2829
### Implementation Guidelines
2930

@@ -50,6 +51,12 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
5051
- Strict null safety with defensive programming
5152
- Detailed error logging and user-friendly messages
5253
- Support wildcard searches and handle empty results
54+
- Cross-platform compatibility:
55+
- Use pathlib.Path for platform-agnostic path handling
56+
- Check sys.platform before using platform-specific features
57+
- Handle file operations with appropriate platform-specific adjustments
58+
- Use os.path.join() instead of string concatenation for paths
59+
- Ensure tests work consistently across Windows, macOS, and Linux
5360

5461
## API Reference
5562

@@ -96,6 +103,11 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
96103
- Integration tests: `@pytest.mark.integration`
97104
- Slow tests: `@pytest.mark.slow`
98105
- Async tests: `@pytest.mark.asyncio`
106+
- Cross-platform testing:
107+
- CI runs tests on Linux, macOS, and Windows
108+
- Linux and macOS tests use flake.nix for development environment
109+
- Windows tests use Python's venv with special dependencies (pywin32)
110+
- All tests must be platform-agnostic or include platform-specific handling
99111
- Run specific test categories:
100112
- Unit tests only: `nix run .#run-tests -- --unit`
101113
- Integration tests only: `nix run .#run-tests -- --integration`
@@ -110,6 +122,17 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
110122
- All tests must check for both default OS cache paths and test-specific paths
111123
- The gitignore file excludes `mcp_nixos_test_cache/` and `*test_cache*/` patterns
112124

125+
### Logging Tests
126+
- Prefer direct behavior verification over implementation details
127+
- When testing log level filtering:
128+
- Use `isEnabledFor()` assertions for level checks (platform-independent)
129+
- Use mock handlers with explicit level settings
130+
- Test both logger configuration and actual handler behavior
131+
- Avoid patching internal logging methods (`_log`) which vary by platform
132+
- Add clear error messages to assertions
133+
- Prevent test flakiness by avoiding sleep/timing dependencies
134+
- Use clean logger fixtures to prevent test interaction
135+
113136
### Dependency Management
114137
- Project uses `pyproject.toml` for dependency specification (PEP 621)
115138
- Core dependencies:
@@ -125,9 +148,15 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
125148
- Install: `pip install mcp-nixos`, `uv pip install mcp-nixos`, `uvx mcp-nixos`
126149
- Claude Code configuration: Add to `~/.config/claude/config.json`
127150
- Interactive shell: `python mcp_shell.py` (manual testing and tool exploration)
151+
- Docker deployment:
152+
- Standard use: `docker run --rm ghcr.io/utensils/mcp-nixos`
153+
- Docker image includes pre-cached data for immediate startup
154+
- Build with pre-cache: `docker build -t mcp-nixos .`
155+
- Deployed on Smithery.ai as a hosted service
128156
- Development:
129157
- Environment: `nix develop`
130158
- Run server: `run`
159+
- Pre-cache data: `python -m mcp_nixos --pre-cache`
131160
- Tests: `run-tests`, `run-tests --unit`, `run-tests --integration`
132161
- Code quality: `lint`, `typecheck`, `format`
133162
- Stats: `loc`

.windsurfrules

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
2424
- **Tools**: Search, info, and statistics tools
2525
- **Utils**: Cross-platform helpers and cache management
2626
- **Server**: FastMCP server implementation
27+
- **Pre-Cache**: Command-line option to populate cache data during setup/build
2728

2829
### Implementation Guidelines
2930

@@ -50,6 +51,12 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
5051
- Strict null safety with defensive programming
5152
- Detailed error logging and user-friendly messages
5253
- Support wildcard searches and handle empty results
54+
- Cross-platform compatibility:
55+
- Use pathlib.Path for platform-agnostic path handling
56+
- Check sys.platform before using platform-specific features
57+
- Handle file operations with appropriate platform-specific adjustments
58+
- Use os.path.join() instead of string concatenation for paths
59+
- Ensure tests work consistently across Windows, macOS, and Linux
5360

5461
## API Reference
5562

@@ -96,6 +103,11 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
96103
- Integration tests: `@pytest.mark.integration`
97104
- Slow tests: `@pytest.mark.slow`
98105
- Async tests: `@pytest.mark.asyncio`
106+
- Cross-platform testing:
107+
- CI runs tests on Linux, macOS, and Windows
108+
- Linux and macOS tests use flake.nix for development environment
109+
- Windows tests use Python's venv with special dependencies (pywin32)
110+
- All tests must be platform-agnostic or include platform-specific handling
99111
- Run specific test categories:
100112
- Unit tests only: `nix run .#run-tests -- --unit`
101113
- Integration tests only: `nix run .#run-tests -- --integration`
@@ -110,6 +122,17 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
110122
- All tests must check for both default OS cache paths and test-specific paths
111123
- The gitignore file excludes `mcp_nixos_test_cache/` and `*test_cache*/` patterns
112124

125+
### Logging Tests
126+
- Prefer direct behavior verification over implementation details
127+
- When testing log level filtering:
128+
- Use `isEnabledFor()` assertions for level checks (platform-independent)
129+
- Use mock handlers with explicit level settings
130+
- Test both logger configuration and actual handler behavior
131+
- Avoid patching internal logging methods (`_log`) which vary by platform
132+
- Add clear error messages to assertions
133+
- Prevent test flakiness by avoiding sleep/timing dependencies
134+
- Use clean logger fixtures to prevent test interaction
135+
113136
### Dependency Management
114137
- Project uses `pyproject.toml` for dependency specification (PEP 621)
115138
- Core dependencies:
@@ -125,9 +148,15 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
125148
- Install: `pip install mcp-nixos`, `uv pip install mcp-nixos`, `uvx mcp-nixos`
126149
- Claude Code configuration: Add to `~/.config/claude/config.json`
127150
- Interactive shell: `python mcp_shell.py` (manual testing and tool exploration)
151+
- Docker deployment:
152+
- Standard use: `docker run --rm ghcr.io/utensils/mcp-nixos`
153+
- Docker image includes pre-cached data for immediate startup
154+
- Build with pre-cache: `docker build -t mcp-nixos .`
155+
- Deployed on Smithery.ai as a hosted service
128156
- Development:
129157
- Environment: `nix develop`
130158
- Run server: `run`
159+
- Pre-cache data: `python -m mcp_nixos --pre-cache`
131160
- Tests: `run-tests`, `run-tests --unit`, `run-tests --integration`
132161
- Code quality: `lint`, `typecheck`, `format`
133162
- Stats: `loc`

CLAUDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
2424
- **Tools**: Search, info, and statistics tools
2525
- **Utils**: Cross-platform helpers and cache management
2626
- **Server**: FastMCP server implementation
27+
- **Pre-Cache**: Command-line option to populate cache data during setup/build
2728

2829
### Implementation Guidelines
2930

@@ -147,9 +148,15 @@ Official repository: [https://github.com/utensils/mcp-nixos](https://github.com/
147148
- Install: `pip install mcp-nixos`, `uv pip install mcp-nixos`, `uvx mcp-nixos`
148149
- Claude Code configuration: Add to `~/.config/claude/config.json`
149150
- Interactive shell: `python mcp_shell.py` (manual testing and tool exploration)
151+
- Docker deployment:
152+
- Standard use: `docker run --rm ghcr.io/utensils/mcp-nixos`
153+
- Docker image includes pre-cached data for immediate startup
154+
- Build with pre-cache: `docker build -t mcp-nixos .`
155+
- Deployed on Smithery.ai as a hosted service
150156
- Development:
151157
- Environment: `nix develop`
152158
- Run server: `run`
159+
- Pre-cache data: `python -m mcp_nixos --pre-cache`
153160
- Tests: `run-tests`, `run-tests --unit`, `run-tests --integration`
154161
- Code quality: `lint`, `typecheck`, `format`
155162
- Stats: `loc`

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ COPY . .
1313
# Install the package and dependencies
1414
RUN pip install --no-cache-dir -e .
1515

16+
# Pre-cache data during build
17+
RUN echo "Running pre-cache to populate cache data..." && \
18+
python -m mcp_nixos --pre-cache
19+
1620
# Run the MCP server
1721
CMD ["python", "-m", "mcp_nixos"]

mcp_nixos/__main__.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,26 @@
66
framework to manage signal handling and graceful shutdown.
77
"""
88

9+
import argparse
910
import sys
11+
import os
1012

1113
# Import mcp from server
12-
from mcp_nixos.server import mcp, logger
13-
import os
14+
from mcp_nixos.server import mcp, logger, run_precache
15+
16+
17+
def parse_args():
18+
"""Parse command line arguments."""
19+
parser = argparse.ArgumentParser(description="MCP-NixOS server")
20+
parser.add_argument("--pre-cache", action="store_true", help="Run initialization to populate cache and then exit")
21+
return parser.parse_args()
1422

1523

1624
def main():
1725
"""Run the MCP-NixOS server."""
18-
# Check if we're running under Windsurf for specific debugging
26+
args = parse_args()
1927

28+
# Check if we're running under Windsurf for specific debugging
2029
windsurf_detected = False
2130
for env_var in os.environ:
2231
if "WINDSURF" in env_var.upper() or "WINDSURFER" in env_var.upper():
@@ -27,6 +36,12 @@ def main():
2736
logger.info("Running under Windsurf - monitoring for restart/refresh signals")
2837

2938
try:
39+
if args.pre_cache:
40+
logger.info("Running in pre-cache mode - will exit after caching completes")
41+
run_precache()
42+
logger.info("Pre-cache completed successfully")
43+
return 0
44+
3045
# Run the server (this is a blocking call)
3146
logger.info("Starting server main loop")
3247
mcp.run()
@@ -44,4 +59,4 @@ def main():
4459
# This is needed for the "mcp-nixos = "mcp_nixos.__main__:mcp.run" entry point in pyproject.toml
4560

4661
if __name__ == "__main__":
47-
main()
62+
sys.exit(main())

mcp_nixos/server.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,67 @@ async def async_with_timeout(coro_func, timeout_seconds=5.0, operation_name="ope
140140
return None
141141

142142

143+
async def run_precache_async():
144+
"""Run all initialization and cache population, then exit.
145+
146+
This function runs the same initialization steps as the server startup
147+
but waits for all caching operations to complete before returning.
148+
"""
149+
logger.info("Starting pre-cache initialization")
150+
151+
# Start loading Home Manager data
152+
logger.info("Loading Home Manager data...")
153+
home_manager_context.hm_client.load_in_background()
154+
155+
# Start loading Darwin data
156+
logger.info("Loading Darwin data...")
157+
try:
158+
await async_with_timeout(
159+
lambda: darwin_context.startup(), timeout_seconds=30.0, operation_name="Darwin context startup"
160+
)
161+
logger.info(f"Darwin context status: {darwin_context.status}")
162+
except Exception as e:
163+
logger.error(f"Error starting Darwin context: {e}")
164+
165+
# Wait for Home Manager client to complete loading
166+
# Use a polling approach since there's no explicit wait_for_loading method
167+
logger.info("Waiting for Home Manager data to complete loading...")
168+
max_wait_seconds = 120
169+
wait_interval = 2
170+
start_time = time.time()
171+
172+
while time.time() - start_time < max_wait_seconds:
173+
with home_manager_context.hm_client.loading_lock:
174+
if hasattr(home_manager_context.hm_client, "is_loaded") and home_manager_context.hm_client.is_loaded:
175+
logger.info("Home Manager data finished loading")
176+
break
177+
if (
178+
hasattr(home_manager_context.hm_client, "loading_error")
179+
and home_manager_context.hm_client.loading_error
180+
):
181+
logger.error(f"Home Manager loading failed: {home_manager_context.hm_client.loading_error}")
182+
break
183+
logger.debug(f"Home Manager data still loading, waiting {wait_interval}s...")
184+
await asyncio.sleep(wait_interval)
185+
else:
186+
logger.warning(f"Timed out after {max_wait_seconds}s waiting for Home Manager data to load")
187+
188+
logger.info("All initialization completed successfully")
189+
return True
190+
191+
192+
def run_precache():
193+
"""Run all initialization and cache population synchronously, then exit."""
194+
try:
195+
return asyncio.run(run_precache_async())
196+
except KeyboardInterrupt:
197+
logger.info("Pre-cache operation interrupted")
198+
return False
199+
except Exception as e:
200+
logger.error(f"Error during pre-cache: {e}", exc_info=True)
201+
return False
202+
203+
143204
# Define the lifespan context manager for app initialization
144205
@asynccontextmanager
145206
async def app_lifespan(mcp_server: FastMCP):

0 commit comments

Comments
 (0)