-
Notifications
You must be signed in to change notification settings - Fork 0
tests: unit and integration tests for the CLI #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # AGENTS.md | ||
|
|
||
| ## What this is | ||
|
|
||
| Jarvis is an MCP proxy that aggregates multiple MCP servers behind 2 synthetic tools (`search_tools` + `call_tool`). Python 3.11+, managed with **uv**. | ||
|
|
||
| ## Layout | ||
|
|
||
| ``` | ||
| src/jarvis/ # the package (6 modules) | ||
| __main__.py # CLI entrypoint — arg parsing, server startup | ||
| config.py # DATA_DIR, presets, config loading, OAuth wiring | ||
| proxy.py # builds FastMCP proxy (stdio vs HTTP client selection) | ||
| api.py # REST management API (runs on port+1) | ||
| probe.py # server/tool discovery | ||
| tui.py # Textual TUIs (mcp manager, auth manager) | ||
| tests/unit/ # pure unit tests | ||
| tests/integration/ # API endpoint + TUI tests | ||
| scripts/ # PyInstaller build scripts | ||
| macOs/ # Xcode project for the menu bar app | ||
| ``` | ||
|
|
||
| ## Commands | ||
|
|
||
| ```bash | ||
| # Install deps (no separate install step — uv handles it) | ||
| uv sync --group dev | ||
|
|
||
| # Run locally | ||
| uv run python -m jarvis --http 7070 | ||
|
|
||
| # Run all tests | ||
| uv run --group dev pytest tests | ||
|
|
||
| # Run a single test file or test | ||
| uv run --group dev pytest tests/unit/test_config.py | ||
| uv run --group dev pytest tests/unit/test_config.py::test_name -k test_name | ||
|
|
||
| # Build standalone binary (macOS arm64) | ||
| bash scripts/build_jarvis_binary.sh | ||
|
|
||
| # Build standalone binary (Linux x86_64) | ||
| bash scripts/build_jarvis_binary_linux.sh | ||
| ``` | ||
|
|
||
| ## Testing quirks | ||
|
|
||
| - **pytest-asyncio `auto` mode** is on (`asyncio_mode = "auto"` in pyproject.toml). Do not add `@pytest.mark.asyncio` to async tests. | ||
| - **`conftest.py` sets `JARVIS_DATA_DIR` at import time** before any jarvis module is imported. This isolates tests from `~/.jarvis`. If you add a new conftest or rearrange imports, preserve this ordering — the module-level `DATA_DIR` and `token_storage` in `config.py` bind once on first import. | ||
| - Use the `data_dir` fixture for per-test isolation. It monkeypatches `DATA_DIR`, `PRESETS_PATH`, and `token_storage` across `config`, `api`, and `probe` modules. | ||
| - Use the `servers_json` fixture when you need a pre-populated `servers.json` in the isolated data dir. | ||
|
|
||
| ## Architecture notes | ||
|
|
||
| - `config.py` resolves `DATA_DIR` and creates `token_storage` (DiskStore) **at module level**. The env var `JARVIS_DATA_DIR` overrides the default `~/.jarvis` — this is the only mechanism for test isolation. | ||
| - `proxy.py` chooses `StatefulProxyClient` (persistent subprocess) for stdio servers and `ProxyClient` (fresh connection) for HTTP/SSE. The stateful clients are pinned to `mcp._stateful_clients` to avoid GC. | ||
| - The hatchling build uses `packages = ["src/jarvis"]` — the wheel package is `jarvis`, not `jarvis_mcp`. | ||
|
|
||
| ## macOS app | ||
|
|
||
| The menu bar app (`macOs/Jarvis/`) is a Swift/Xcode project that embeds the PyInstaller binary. **Build order matters** — the binary must exist before Xcode can bundle it: | ||
|
|
||
| ```bash | ||
| # 1. Build the Python binary into the Xcode Resources dir | ||
| bash scripts/build_jarvis_binary.sh # → macOs/Jarvis/Jarvis/Resources/jarvis | ||
|
|
||
| # 2. Build the app | ||
| xcodebuild -project macOs/Jarvis/Jarvis.xcodeproj -scheme Jarvis -configuration Debug build | ||
| ``` | ||
| ## CI | ||
|
|
||
| - Every push/PR: pytest + binary builds (macOS arm64, Linux x86_64). | ||
| - No lint or typecheck step in CI. Ruff cache exists locally but there is no enforced config. | ||
| - Releases trigger on `v*` tags and produce binaries + a macOS `.dmg`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| AGENTS.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| """Proxy builder for Jarvis. | ||
|
|
||
| Replaces ``fastmcp.server.create_proxy(MCPConfig)`` with a builder that uses | ||
| ``StatefulProxyClient`` for stdio backends (persistent subprocess per frontend | ||
| session) and ``ProxyClient`` for HTTP/SSE backends (fresh connection per request). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from fastmcp.mcp_config import MCPConfig, StdioMCPServer | ||
| from fastmcp.server import FastMCP | ||
| from fastmcp.server.providers.proxy import ( | ||
| ProxyClient, | ||
| ProxyProvider, | ||
| StatefulProxyClient, | ||
| ) | ||
|
|
||
|
|
||
| def build_proxy(config: MCPConfig, name: str = "jarvis") -> FastMCP: | ||
| """Build a FastMCP proxy server from an MCPConfig. | ||
|
|
||
| For each server in *config*: | ||
| - stdio servers get a ``StatefulProxyClient`` with ``new_stateful`` as the | ||
| client factory, so the subprocess lives for the duration of each frontend | ||
| session rather than being respawned on every tool call. | ||
| - HTTP/SSE servers get a ``ProxyClient`` with ``new`` as the factory, | ||
| giving a fresh connection per request (stateless, correct for HTTP). | ||
|
|
||
| Args: | ||
| config: Validated MCPConfig with servers already configured | ||
| (OAuth injected, env vars expanded). | ||
| name: Name for the resulting FastMCP server. | ||
|
|
||
| Returns: | ||
| A ``FastMCP`` server with one ``ProxyProvider`` per backend, namespaced | ||
| by server name. | ||
| """ | ||
| mcp: FastMCP = FastMCP(name=name) | ||
| # Keep strong references to StatefulProxyClient instances so they are not | ||
| # garbage-collected while the server is alive (new_stateful reads _caches). | ||
| mcp._stateful_clients: list = [] # type: ignore[attr-defined] | ||
|
|
||
| for server_name, server in config.mcpServers.items(): | ||
| transport = server.to_transport() | ||
|
|
||
| if isinstance(server, StdioMCPServer): | ||
| client = StatefulProxyClient(transport) | ||
| mcp._stateful_clients.append(client) | ||
| factory = client.new_stateful | ||
| else: | ||
| client = ProxyClient(transport) | ||
| factory = client.new | ||
|
|
||
| mcp.add_provider(ProxyProvider(factory), namespace=server_name) | ||
|
|
||
| return mcp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.