unity-mcp/docs/guides/REMOTE_SERVER_AUTH.md

262 lines
9.7 KiB
Markdown
Raw Permalink Normal View History

Remote server auth (#644) * Disable the gloabl default to first session when hosting remotely * Remove calls to /plugin/sessions The newer /api/instances covers that data, and we want to remove these "expose all" endpoints * Disable CLI routes when running in remote hosted mode * Update server README * feat: add API key authentication support for remote-hosted HTTP transport - Add API key field to connection UI (visible only in HTTP Remote mode) - Add "Get API Key" and "Clear" buttons with login URL retrieval - Include X-API-Key header in WebSocket connections when configured - Add API key to CLI commands (mcp add, claude mcp add) when set - Update config.json generation to include headers with API key - Add API key validation service with caching and configurable endpoints - Add /api/auth/login-url endpoint * feat: add environment variable support for HTTP remote hosted mode - Add UNITY_MCP_HTTP_REMOTE_HOSTED environment variable as alternative to --http-remote-hosted flag - Accept "true", "1", or "yes" values (case-insensitive) - Update CLI help text to document environment variable option * feat: add user isolation enforcement for remote-hosted mode session listing - Raise ValueError when list_sessions() called without user_id in remote-hosted mode - Add comprehensive integration tests for multi-user session isolation - Add unit tests for PluginRegistry user-scoped session filtering - Verify cross-user isolation with same project hash - Test unity_instances resource and set_active_instance user filtering * feat: add comprehensive integration tests for API key authentication - Add ApiKeyService tests covering validation, caching, retries, and singleton lifecycle - Add startup config validation tests for remote-hosted mode requirements - Test cache hit/miss scenarios, TTL expiration, and manual invalidation - Test transient failure handling (5xx, timeouts, connection errors) with retry logic - Test service token header injection and empty key fast-path validation - Test startup validation requiring * test: add autouse fixture to restore config state after startup validation tests Ensures test isolation for config-dependent integration tests * feat: skip user_id resolution in non-remote-hosted mode Prevents unnecessary API key validation when not in remote-hosted mode * test: add missing mock attributes to instance routing tests - Add client_id to test context mock in set_active_instance test - Add get_state mock to context in global instance routing test * Fix broken telemetry test * Add comprehensive API key authentication documentation - Add user guide covering configuration, setup, and troubleshooting - Add architecture reference documenting internal design and request flows * Add remote-hosted mode and API key authentication documentation to server README * Update reference doc for Docker Hub * Specify exception being caught * Ensure caplog handler cleanup in telemetry queue worker test * Use NoUnitySessionError instead of RuntimeError in session isolation test * Remove unusued monkeypatch arg * Use more obviously fake API keys * Reject connections when ApiKeyService is not initialized in remote-hosted mode - Validate that user_id is present after successful key validation - Expand transient error detection to include timeout and service errors - Use consistent 1013 status code for retryable auth failures * Accept "on" for UNITY_MCP_HTTP_REMOTE_HOSTED env var Consistent with repo * Invalidate cached login URL when HTTP base URL changes * Pass API key as parameter instead of reading from EditorPrefs in RegisterWithCapturedValues * Cache API key in field instead of reading from EditorPrefs on each reconnection * Align markdown table formatting in remote server auth documentation * Minor fixes * security: Sanitize API key values in shell commands and fix minor issues Add SanitizeShellHeaderValue() method to escape special shell characters (", \, `, $, !) in API keys before including them in shell command arguments. Apply sanitization to all three locations where API keys are embedded in shell commands (two in RegisterWithCapturedValues, one in GetManualInstructions). Also fix deprecated passwordCharacter property (now maskChar) and improve exception logging in _resolve_user_id_from_request * Consolidate duplicate instance selection error messages into InstanceSelectionRequiredError class Add InstanceSelectionRequiredError exception class with centralized error messages (_SELECTION_REQUIRED and _MULTIPLE_INSTANCES). Replace 4 duplicate RuntimeError raises with new exception type. Update tests to catch InstanceSelectionRequiredError instead of RuntimeError. * Replace hardcoded "X-API-Key" strings with AuthConstants.ApiKeyHeader constant across C# and Python codebases Add AuthConstants class in C# and API_KEY_HEADER constant in Python to centralize the API key header name definition. Update all 8 locations where "X-API-Key" was hardcoded (4 in C#, 4 in Python) to use the new constants instead. * Fix imports * Filter session listing by user_id in all code paths to prevent cross-user session access Remove conditional logic that only filtered sessions by user_id in remote-hosted mode. Now all session listings are filtered by user_id regardless of hosting mode, ensuring users can only see and interact with their own sessions. * Consolidate get_session_id_by_hash methods into single method with optional user_id parameter Merge get_session_id_by_hash and get_session_id_by_user_hash into a single method that accepts an optional user_id parameter. Update all call sites to use the unified method signature with user_id as the second parameter. Update tests and documentation to reflect the simplified API. * Add environment variable support for project-scoped-tools flag [skip ci] Support UNITY_MCP_PROJECT_SCOPED_TOOLS environment variable as alternative to --project-scoped-tools command line flag. Accept "true", "1", "yes", or "on" as truthy values (case-insensitive). Update help text to document the environment variable option. * Fix Python tests * Update validation logic to only require API key validation URL when both http_remote_hosted is enabled AND transport mode is "http", preventing false validation errors in stdio mode. * Update Server/src/main.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Refactor HTTP transport configuration to support separate local and remote URLs Split HTTP transport into HttpLocal and HttpRemote modes with separate EditorPrefs storage (HttpBaseUrl and HttpRemoteBaseUrl). Add HttpEndpointUtility methods to get/save local and remote URLs independently, and introduce IsRemoteScope() and GetCurrentServerTransport() helpers to centralize 3-way transport determination (Stdio/Http/HttpRemote). Update all client configuration code to distinguish between local and remote HTTP * Only include API key headers in HTTP/WebSocket configuration when in remote-hosted mode Update all locations where API key headers are added to HTTP/WebSocket configurations to check HttpEndpointUtility.IsRemoteScope() or serverTransport == HttpRemote before including the API key. This prevents local HTTP mode from unnecessarily including API key headers in shell commands, config JSON, and WebSocket connections. * Hide Manual Server Launch foldout when not in HTTP Local mode * Fix failing test * Improve error messaging and API key validation for HTTP Remote transport Add detailed error messages to WebSocket connection failures that guide users to check server URL, server status, and API key validity. Store error state in TransportState for propagation to UI. Disable "Start Session" button when HTTP Remote mode is selected without an API key, with tooltip explaining requirement. Display error dialog on connection failure with specific error message from transport state. Update connection * Add missing .meta file * Store transport mode in ServerConfig instead of environment variable * Add autouse fixture to restore global config state between tests Add restore_global_config fixture in conftest.py that automatically saves and restores global config attributes and UNITY_MCP_TRANSPORT environment variable between tests. Update integration tests to use monkeypatch.setattr on config.transport_mode instead of monkeypatch.setenv to prevent test pollution and ensure clean state isolation. * Fix startup * Replace _current_transport() calls with direct config.transport_mode access * Minor cleanup * Add integration tests for HTTP transport authentication behavior Verify that HTTP local mode allows requests without user_id while HTTP remote-hosted mode rejects them with auth_required error. * Add smoke tests for transport routing paths across HTTP local, HTTP remote, and stdio modes Verify that HTTP local routes through PluginHub without user_id, HTTP remote routes through PluginHub with user_id, and stdio calls legacy send function with instance_id. Each test uses monkeypatch to configure transport mode and mock appropriate transport layer functions. --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-31 06:39:21 +08:00
# Remote Server API Key Authentication
When running the MCP for Unity server as a shared remote service, API key authentication ensures that only authorized users can access the server and that each user's Unity sessions are isolated from one another.
This guide covers how to configure, deploy, and use the feature.
## Prerequisites
### External Auth Service
You need an external HTTP endpoint that validates API keys. The server delegates all key validation to this endpoint rather than managing keys itself.
The endpoint must:
- Accept `POST` requests with a JSON body: `{"api_key": "<key>"}`
- Return a JSON response indicating validity and the associated user identity
- Be reachable from the MCP server over the network
See [Validation Contract](#validation-contract) for the full request/response specification.
### Transport Mode
API key authentication is only available when running with HTTP transport (`--transport http`). It has no effect in stdio mode.
## Server Configuration
### CLI Arguments
| Argument | Environment Variable | Default | Description |
| -------- | -------------------- | ------- | ----------- |
| `--http-remote-hosted` | `UNITY_MCP_HTTP_REMOTE_HOSTED` | `false` | Enable remote-hosted mode. Requires API key auth. |
| `--api-key-validation-url URL` | `UNITY_MCP_API_KEY_VALIDATION_URL` | None | External endpoint to validate API keys (required). |
| `--api-key-login-url URL` | `UNITY_MCP_API_KEY_LOGIN_URL` | None | URL where users can obtain or manage API keys. |
| `--api-key-cache-ttl SECONDS` | `UNITY_MCP_API_KEY_CACHE_TTL` | `300` | How long validated keys are cached (seconds). |
| `--api-key-service-token-header HEADER` | `UNITY_MCP_API_KEY_SERVICE_TOKEN_HEADER` | None | Header name for server-to-auth-service authentication. |
| `--api-key-service-token TOKEN` | `UNITY_MCP_API_KEY_SERVICE_TOKEN` | None | Token value sent to the auth service for server authentication. |
Environment variables take effect when the corresponding CLI argument is not provided. For boolean flags, set the env var to `true`, `1`, or `yes`.
### Startup Validation
The server validates its configuration at startup:
- If `--http-remote-hosted` is set but `--api-key-validation-url` is not provided (and the env var is also unset), the server logs an error and exits with code 1.
### Example
```bash
python -m src.main \
--transport http \
--http-host 0.0.0.0 \
--http-port 8080 \
--http-remote-hosted \
--api-key-validation-url https://auth.example.com/api/validate-key \
--api-key-login-url https://app.example.com/api-keys \
--api-key-cache-ttl 120
```
Or using environment variables:
```bash
export UNITY_MCP_TRANSPORT=http
export UNITY_MCP_HTTP_HOST=0.0.0.0
export UNITY_MCP_HTTP_PORT=8080
export UNITY_MCP_HTTP_REMOTE_HOSTED=true
export UNITY_MCP_API_KEY_VALIDATION_URL=https://auth.example.com/api/validate-key
export UNITY_MCP_API_KEY_LOGIN_URL=https://app.example.com/api-keys
python -m src.main
```
### Service Token (Optional)
If your auth service requires the MCP server to authenticate itself (server-to-server auth), configure a service token:
```bash
--api-key-service-token-header X-Service-Token \
--api-key-service-token "your-server-secret"
```
This adds the specified header to every validation request sent to the auth endpoint.
We strongly recommend using this feature because it ensures that the entity requesting validation is the MCP server itself, not an imposter.
## Unity Plugin Setup
When connecting to a remote-hosted server, Unity users need to provide their API key:
1. Open the MCP for Unity window in the Unity Editor.
2. Select HTTP Remote as the connection mode.
3. Enter the API key in the API Key field. The key is stored in `EditorPrefs` (per-machine, not source-controlled).
4. Click **Get API Key** to open the login URL in a browser if you need a new key. This fetches the URL from the server's `/api/auth/login-url` endpoint.
The API key is a one-time entry per machine. It persists across Unity sessions until explicitly cleared.
## MCP Client Configuration
When an API key is configured, the Unity plugin's MCP client configurators automatically include the `X-API-Key` header in generated configuration files.
Example generated config for **Cursor** (`~/.cursor/mcp.json`):
```json
{
"mcpServers": {
"mcp-for-unity": {
"url": "http://remote-server:8080/mcp",
"headers": {
"X-API-Key": "<your-api-key>"
}
}
}
}
```
Example for **Claude Code** (CLI):
```bash
claude mcp add --transport http mcp-for-unity http://remote-server:8080/mcp \
--header "X-API-Key: <your-api-key>"
```
Similar header injection works for VS Code, Windsurf, Cline, and other supported MCP clients.
## Behaviour Changes in Remote-Hosted Mode
Enabling `--http-remote-hosted` changes several server behaviours compared to the default local mode:
### Authentication Enforcement
All MCP tool and resource calls require a valid API key. The `X-API-Key` header must be present on every HTTP request to the `/mcp` endpoint. If the key is missing or invalid, the middleware raises a `RuntimeError` that surfaces as an MCP error response.
### WebSocket Auth Gate
Unity plugins connecting via WebSocket (`/hub/plugin`) are validated during the handshake:
| Scenario | WebSocket Close Code | Reason |
| -------- | -------------------- | ------ |
| No API key header | `4401` | API key required |
| Invalid API key | `4403` | Invalid API key |
| Auth service unavailable | `1013` | Try again later |
| Valid API key | Connection accepted | user_id stored in connection state |
### Session Isolation
Each user can only see and interact with their own Unity instances. When User A calls `set_active_instance` or lists instances, they only see Unity editors that connected with User A's API key. User B's sessions are invisible to User A.
### Auto-Select Disabled
In local mode, the server automatically selects the sole connected Unity instance. In remote-hosted mode, this auto-selection is disabled. Users must explicitly call `set_active_instance` with a `Name@hash` from the `mcpforunity://instances` resource.
### CLI Routes Disabled
The following REST endpoints are disabled in remote-hosted mode to prevent unauthenticated access:
- `POST /api/command`
- `GET /api/instances`
- `GET /api/custom-tools`
### Endpoints Always Available
These endpoints remain accessible regardless of auth:
| Endpoint | Method | Purpose |
| -------- | ------ | ------- |
| `/health` | GET | Health check for load balancers and monitoring |
| `/api/auth/login-url` | GET | Returns the login URL for API key management |
## Validation Contract
### Request
```http
POST <api-key-validation-url>
Content-Type: application/json
{
"api_key": "<the-api-key>"
}
```
If a service token is configured, an additional header is sent:
```http
<service-token-header>: <service-token-value>
```
### Response (Valid Key)
```json
{
"valid": true,
"user_id": "user-abc-123",
"metadata": {}
}
```
- `valid` (bool, required): Must be `true`.
- `user_id` (string, required): Stable identifier for the user. Used for session isolation.
- `metadata` (object, optional): Arbitrary metadata stored alongside the validation result.
### Response (Invalid Key)
```json
{
"valid": false,
"error": "API key expired"
}
```
- `valid` (bool, required): Must be `false`.
- `error` (string, optional): Human-readable reason.
### Response (HTTP 401)
A `401` status code is also treated as an invalid key (no body parsing required).
### Timeouts and Retries
- Request timeout: 5 seconds
- Retries: 1 (with 100ms backoff)
- Failure mode: deny by default (treated as invalid on any error)
Transient failures (5xx, timeouts, network errors) are **not cached**, so subsequent requests will retry the auth service.
## Error Reference
| Context | Condition | Response |
| ------- | --------- | -------- |
| MCP tool/resource | Missing API key (remote-hosted) | `RuntimeError` → MCP `isError: true` |
| MCP tool/resource | Invalid API key | `RuntimeError` → MCP `isError: true` |
| WebSocket connect | Missing API key | Close `4401` "API key required" |
| WebSocket connect | Invalid API key | Close `4403` "Invalid API key" |
| WebSocket connect | Auth service down | Close `1013` "Try again later" |
| `/api/auth/login-url` | Login URL not configured | HTTP `404` with admin guidance message |
| Server startup | Remote-hosted without validation URL | `SystemExit(1)` |
## Troubleshooting
### "API key authentication required" error on every tool call
The server is in remote-hosted mode but no API key is being sent. Ensure the MCP client configuration includes the `X-API-Key` header, or set it in the Unity plugin's connection settings.
### Server exits immediately with code 1
The `--http-remote-hosted` flag requires `--api-key-validation-url`. Provide the URL via CLI argument or `UNITY_MCP_API_KEY_VALIDATION_URL` environment variable.
### WebSocket connection closes with 4401
The Unity plugin is not sending an API key. Enter the key in the MCP for Unity window's connection settings.
### WebSocket connection closes with 1013
The external auth service is unreachable. Check network connectivity between the MCP server and the validation URL. The Unity plugin can retry the connection.
### User cannot see their Unity instance
Session isolation is active. The Unity editor and the MCP client must use API keys that resolve to the same `user_id`. Verify that the Unity plugin's WebSocket connection and the MCP client's HTTP requests use the same API key.
### Stale auth after key rotation
Validated keys are cached for `--api-key-cache-ttl` seconds (default: 300). After rotating or revoking a key, there is a delay equal to the TTL before the old key stops working. Lower the TTL for faster revocation at the cost of more frequent validation requests.