# Zoo Model Context Protocol (MCP) Server An [MCP server](https://modelcontextprotocol.io/docs/getting-started/intro) housing various Zoo built utilities ## Prerequisites 1. An API key for Zoo, get one [here](https://zoo.dev/account) 2. An environment variable `ZOO_API_TOKEN` set to your API key ```bash export ZOO_API_TOKEN="your_api_key_here" ``` ## Installation 1. [Ensure uv has been installed](https://docs.astral.sh/uv/getting-started/installation/) 2. [Create a uv environment](https://docs.astral.sh/uv/pip/environments/) ```bash uv venv ``` 3. [Activate your uv environment (Optional)](https://docs.astral.sh/uv/pip/environments/#using-a-virtual-environment) 4. Install the package from GitHub ```bash uv pip install git+ssh://git@github.com/KittyCAD/mcp.git ``` ## Running the Server The server can be started by using [uvx](https://docs.astral.sh/uv/guides/tools/#running-tools) ```bash uvx zoo-mcp ``` The server can be started locally by using uv and the zoo_mcp module ```bash uv run -m zoo_mcp ``` The server can also be run with the [mcp package](https://github.com/modelcontextprotocol/python-sdk) ```bash uv run mcp run src/zoo_mcp/server.py ``` ### Prebuilt binaries Each [GitHub release](https://github.com/KittyCAD/mcp/releases) also attaches standalone executables (built with PyInstaller) for Linux (`x86_64`, `arm64`), macOS (`arm64`, `x86_64`), and Windows (`x86_64`) — no Python toolchain required. Download the binary for your platform, set `ZOO_API_TOKEN`, and run it directly, e.g.: ```bash ZOO_API_TOKEN="your_api_key_here" ./zoo-mcp-linux-x86_64 ``` > The binaries are not code-signed, so macOS Gatekeeper and Windows SmartScreen may warn on first run. ## Integrations The server can be used as is by [running the server](#running-the-server) or importing directly into your python code. ```python from zoo_mcp.server import mcp mcp.run() ``` Individual tools can be used in your own python code as well. At Zoo we use zoo-mcp like this with ZooKeeper to save on resources. Instead of spinning up one MCP server per agent, each agent in a sense "embeds" the server in their own runtime. It has the additional benefit of preventing shared state. ```python from mcp.server.mcpserver import MCPServer from zoo_mcp.zoo_tools import ResultZooExecuteKcl, zoo_execute_kcl mcp = MCPServer(name="My Example Server") @mcp.tool() async def my_execute_kcl(kcl_code: str) -> ResultZooExecuteKcl: """ Example tool that uses the zoo_execute_kcl function from zoo_mcp.zoo_tools """ return await zoo_execute_kcl(kcl_code=kcl_code) ``` The server can be integrated with [Claude desktop](https://claude.ai/download) using the following command ```bash uv run mcp install src/zoo_mcp/server.py ``` The server can also be integrated with [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) using the following command ```bash claude mcp add --scope project "Zoo-MCP" uv -- --directory "$PWD"/src/zoo_mcp run server.py ``` The server can also be tested using the [MCP Inspector](https://modelcontextprotocol.io/legacy/tools/inspector#python) ```bash uv run mcp dev src/zoo_mcp/server.py ``` For running with [codex-cli](https://github.com/openai/codex) ```bash codex \ -c 'mcp_servers.zoo.command="uvx"' \ -c 'mcp_servers.zoo.args=["zoo-mcp"]' \ -c mcp_servers.zoo.env.ZOO_API_TOKEN="$ZOO_API_TOKEN" ``` You can also use the helper script included in this repo: ```bash ./codex-zoo.sh ``` The script prompts for a request, runs Codex with the Zoo MCP server, and saves a JSONL transcript (including token usage) to `codex-run-.jsonl`. ## Architecture Tools are defined in `src/zoo_mcp/*.py`, where they are then imported into `src/zoo_mcp/server.py` and tied to actual `@mcp.tool()` decorated functions. `src/zoo_mcp/zoo_tools.py` acts as a large toolset to interact with Zoo's KCL and engine facilities. This source file houses other utilities like `parse_unit` or `normalize_ext` (normalizing file extensions). Modeling scenes use explicit persistent sessions, with at most one session open per server process. Call `get_modeling_sessions` to recover its ID after a client reconnect, or call `start_modeling_session` when none exists. Populate the session with `execute_kcl`, `exec_kcl_project`, or `import_cad_file`; pass the same `session_id` to `snapshot` and modeling tools; then call `stop_modeling_session` when finished. As of 0.28.0, `execute_kcl` and `exec_kcl_project` run mock execution before real execution and return separate `mock_preflight` and `real_execution` objects. Each contains `status` (`succeeded`, `failed`, or `not_run`), `message`, and `diagnostics` grouped by severity. Stage messages are short summaries; the top-level `message` retains the full report for existing callers. Failed stages also expose `error_family`, including `ZooMCPTimeoutError` for session timeouts. Mock errors or an aborted mock execution return immediately with `ok: false` and `real_execution.status: "not_run"`. Mock warnings remain in `mock_preflight.diagnostics` even if real execution fails. The known `planeOf` mock-engine limitation is reported as a warning so the real engine can evaluate it; other mock errors still block execution. Session responses expose mock diagnostics; the engine does not return real-stage diagnostics for session execution. Path inputs capture the entrypoint, its transitive imports (including linked modules and glTF buffers), and `project.toml` once. Both stages use that copy without scanning unrelated files in the containing directory. Dependencies and symlink targets must stay inside the entrypoint's directory; external paths are rejected before file reads or execution. Transient local real-execution failures retain their bounded retries using the same copy without repeating mock execution. Diagnostics refer to the original source paths. Inline `kcl_code` accepts self-contained code and standard-library imports; filesystem imports require `kcl_path` so their dependencies can be captured within an explicit directory. `exec_kcl_project` now returns this structured result instead of a path string: check `ok`, then read `path_artifact_graph` on session success. The standalone `mock_execute_kcl` tool continues to return its existing boolean/message pair. ## Contributing Contributions are welcome! Please open an issue or submit a pull request on the [GitHub repository](https://github.com/KittyCAD/mcp) PRs will need to pass tests and linting before being merged. ### [ruff](https://docs.astral.sh/ruff/) is used for linting and formatting. ```bash uvx ruff check uvx ruff format ``` ### [ty](https://docs.astral.sh/ty/) is used for type checking. ```bash uvx ty check ``` ## Testing The server includes tests located in [`tests`](`tests`). To run the tests, use the following command: ```bash uv run pytest -n auto ```