Files
2026-07-13 12:49:17 +08:00

72 lines
4.0 KiB
Markdown

---
id: python-layers
slug: /architecture/python-layers
title: Three-Layer Python Design
sidebar_label: Three-Layer Python Design
description: The Python server has three independent surfaces — MCP tools, CLI commands, and resources — that all funnel into the same C# Editor handlers.
---
# Three-Layer Python Design
The Python server (`Server/src/`) exposes three distinct surfaces. They look similar but serve different consumers and are **not auto-generated from each other**.
| Layer | Where | Framework | Consumer | Transport to Unity |
|---|---|---|---|---|
| **MCP Tools** | `Server/src/services/tools/` | FastMCP (`@mcp_for_unity_tool`) | AI assistants via MCP | WebSocket (`send_with_unity_instance`) |
| **CLI Commands** | `Server/src/cli/commands/` | Click (`@click.command`) | Developers in a terminal | HTTP (`run_command`) |
| **Resources** | `Server/src/services/resources/` | FastMCP (`@mcp_for_unity_resource`) | AI assistants, read-only | WebSocket |
Both MCP tools and CLI commands eventually call the same C# `HandleCommand` methods inside `MCPForUnity/Editor/Tools/`. Resources are read-only — they observe state without mutating it.
## Why three layers, not one
Each surface has a different shape of consumer:
- **MCP tools** need rich type annotations (`Annotated[Type, "description"]`) because they're handed to an LLM. The descriptions are the prompt the LLM reads.
- **CLI commands** need composable flags, shell-friendly defaults, and graceful error messages. Click gives those for free.
- **Resources** need to be cheap to call repeatedly because the LLM polls them. They use a lighter decorator and skip the routing middleware.
Trying to autogenerate one layer from another erodes the ergonomics of all three. The cost is keeping the three in sync — which is mostly a discipline problem solved by domain symmetry.
## Domain symmetry
When you add a new domain (say, `manage_navigation`), you write **three** files:
```
Server/src/services/tools/manage_navigation.py # @mcp_for_unity_tool
Server/src/cli/commands/navigation.py # @click.command
MCPForUnity/Editor/Tools/ManageNavigation.cs # [McpForUnityTool]
```
The Python tool and CLI command both invoke the C# handler — they just take different paths to it.
## Tool registration
Tools are auto-discovered by walking `Server/src/services/tools/`. Each `.py` file with `@mcp_for_unity_tool`-decorated functions is imported at server startup; the decorator side-effects populate a global registry (`services.registry`). The registry is also what `tools/generate_docs_reference.py` reads to emit the [tool reference](/reference/tools).
A tool's `group` parameter controls per-session visibility — see [Tool Groups](/guides/tool-groups). `group=None` means the tool is always visible (server meta-tools like `set_active_instance` and `manage_tools`).
## Where the layers diverge from "just call the C# handler"
- **MCP tools** add parameter normalization (camelCase → snake_case via `ParamNormalizerMiddleware`), telemetry, and per-session routing.
- **CLI commands** add `@handle_unity_errors` for terminal-friendly stack traces, and synchronous wrappers around the async core.
- **Resources** skip middleware entirely — they're meant to be hot-path.
## Server entry point
`Server/src/main.py` (~935 lines) is the orchestrator:
1. Builds the FastMCP server
2. Calls `register_all_tools(mcp)` — auto-discovery
3. Calls `register_all_resources(mcp)` — same pattern, different decorator
4. Sets up the WebSocket hub for HTTP transport
5. Configures middleware (telemetry, normalization, instance routing)
6. Starts the transport (`http`/`stdio` from `core.config`)
## Where to read more
- Tool/CLI handler shape: `Server/src/services/tools/manage_material.py` is a canonical example
- Registry: `Server/src/services/registry/tool_registry.py` (~130 LOC, the single source the docs reference generator reads)
- Transport: `Server/src/transport/` — plugin hub (`plugin_hub.py`), websocket client, legacy stdio bridge
- C# side: `MCPForUnity/Editor/Tools/ManageMaterial.cs` is the C# half of `manage_material`